Valo Documentation
首頁
開發設置
專案結構
登入前單元
登入後單元
聊天對話單元
聊天系統
群組系統
認證
部署
故障排除
首頁
開發設置
專案結構
登入前單元
登入後單元
聊天對話單元
聊天系統
群組系統
認證
部署
故障排除
  • 專案文檔

    • 開發環境設置指南
    • 專案資料夾結構說明
    • - 聊天功能核心
    • - 群組目錄
    • 身份驗證
    • 登入前的單元
    • 登入後的單元
    • 聊天對話單元
    • 部署指南
    • 常見問題排解
    • 專案故事
    • 最初的那些痛

專案資料夾結構說明

lib/
├── 1. core/               # 核心邏輯 - 應用啟動與生命週期管理
├── 2. config/             # 全域設定 - 色彩、主題、事件匯流排
├── 3. localization/       # 多語言 - 國際化翻譯資源
├── 4. presentation/       # 頁面層 - 路由、佈局、畫面組件
│   ├── layout/            # 佈局組件 - 應用程式整體佈局結構
│   ├── router/            # 路由系統 - Auto Route 路由管理與守衛
│   └── screens/           # 畫面組件 - 各功能頁面的具體實現
├── 5. services/           # 服務層 - 外部系統交互核心
├── 6. modal/              # 純資料模型 - API響應結構定義
├── 7. modalView/          # UI資料模型 - 畫面狀態與顯示邏輯
├── 8. adapters/           # 資料轉換層 - SDK與UI格式橋接
├── 9. utils/              # 工具函數 - 通用輔助功能
├── 10. components/        # 基礎UI組件 - 純視覺元件,無業務邏輯
└── 11. widgets/           # 業務組件 - 高內聚功能組件 (專案特色)

1. core/ - 核心應用

包裝用途?

"讓複雜度向下沈淪,高維護性事務向上揭露(開放)"

  • 在開發過程中,有一段時間在整理 main.dart 程式碼當時非常凌亂,其實改善做法只是把 100 行的程式,依照職責盡量分類抽象,包裝複雜度,向上(開發者)提供高維護情況與事件,才造就目前的 main.dart。
  • CoreApplication: 應用程式啟動流程封裝,包含所有 init 動作

    新套件引入

    如果引入新套件,且需要 init 相關動作,到 CoreApplication::initLibrary 這邊添加、調整。

  • CoreApplicationHooks: 應用程式生命週期事件處理 (背景/前台切換等)

    Application Hooks

    裝置生命週期集中區,可自定義邏輯:

    • whenResumed - 應用程式從背景回到前台(重新連接聊天、更新資料)
    • whenHidden - 應用程式進入背景時(登出聊天連接)
    • whenInactive - 應用程式變為非活躍狀態
    • whenPaused - 應用程式暫停時
    • whenDetached - 應用程式完全關閉時
    • whenDeepLink - 處理深度連結(忘記密碼、群組分享等) :::

2. config/ - Valo 的共用設定

  • env.dart: Valo 的專案變數,依照不同的環境,有不同的設定

    • .env.dev、.ent.test, env.prod
    • AGORA_APP_ID: AGORA APP KEY,有兩個不同環境(正式、測試)。
    • API_URL: 前台 API 網址。
    • TERMS_URL: 隱私權政策網址。
    • GROUP_SHARE_URL: 加入群組打開 APP 用的網址。
  • style.dart:

    • ValoColor: Valo 的色彩集中區,對照 Figma 的 color。

      Color 提醒

      盡量直接使用 primary 、 neutral...這類設計師定義的色名。

      (之前定義很多 component 的 color,發現後來很常忘記名稱、調整修改上要想很久。)

    • ValoIcons: Valo 的 ICON 集中區

      ICON 提醒

      盡量直接使用 SvgPicture,從 Figma 下載 SVG 引入到專案用 SvgPictue

      (過往尚未引入 SvgPicture 套件時,都必須將 Svg 轉成 font icon,過程很麻煩還會有 Icon 壓壞的情況,引入 SvgPicture 未來新 Icon 盡量直接使用 Svg 就好,之前的 IconData 就封存吧!)

    • ValoSize、ValoShadow、ValoWidget...就不細說,都是共用的外觀設定集中。

  • valo.dat: 共用抽象設定,目前主要 topic_layout.dart 的抽象設定。

    • ValoTopic: 大廳的底部導覽,有三頁。
      • 依照 ValoTopicEntity抽象設定出新對象,下方導覽會自動多一區。
    • ValoHeaderActions: 目前大廳 header 右上,會有兩個 '加入好友'、'加入群組'。
      • 能添加出新的 ValoHeaderActions 在 ValoTopicEntity::getHeaders 添加新的 ValoHeaderActions。

3. localization/ - 多語言

使用 easy_localization 套件,支援語言中文(zh.json)、英文(en.json)、越南文(vi.json)

  • 在 Dart 中使用:

    // 基本用法
    Text('common.error.login_fail'.tr())
    // 帶參數的翻譯
    Text('common.error.login_fail'.tr(namedArgs: {'name': '使用者名稱'}))
    // context.tr (如果使用端有立馬切換語系,畫面會立即響應)
    Text(context.tr('common.error.login_fail', namedArgs: {'name': '使用者名稱'}))
    

    提醒

    通常 bottom sheet、popup,這種一次性的功能/頁面,使用簡單的 ''.tr 即可。

    • 因為使用情境沒機會讓它切換語系。

    但如果該頁是,比較共用的 screen 區,盡量使用 context.tr,因為有可能被切換語系,

    • Example: settings_screen.dart 有語系的 item,當頁的文字需要 context.tr 立即響應切換。

    • Example: login_screen.dart 左上有語系切換,當頁的文字需要 context.tr 立即響應切換。 :::


4. presentation/ - 核心展示

處理應用程式的頁面展示邏輯,是用戶直接互動的界面層。

4-1. router/ - Valo 頁面路由,基於 Auto Route 路由管理套件。

  • app_router.dart:

    • 所有頁面路由,每個 route 都會對應到 /screen/*。
    • app_router.gr.dart: Auto Route 自動生成的檔案。

      如何快速添加新的頁面?

      1. 定義好新的 new_xxx_screen.dart, 頭掛上 @RoutePage
      2. 在 app_router.dart 的底部 import '...new_xxx_screen.dart';
      3. Terminal 執行 flutter packages pub run build_runner build --delete-conflicting-outputs
      4. 完成

      還有很多功能, ex: 子路由、導覽式路由...等,到 Auto Route 了解。

  • 路由守衛系統:

    • conversation_guard.dart: 對話頁面-進入前守衛。
      • 在進入對話前,有些前置作業需要先做?不然容易在進入後,發生其他錯誤。
        • 對話進入前,先確認 Agora SDK 是否有該則,沒有先創建對話資料。
        • 對話進入前,都標示為已讀。
        • 群組對話進入前,先啟動取得群組成員的匿名資料。
    • topic_guard.dart: 大廳頁面(topic_screen.dart)-進入前守衛。
      • 該守衛做的事情,進入大廳前
        • Valo共用資料湖全部重新更新。
        • 開始監聽 Agora 新消息,間接更新Valo共用資料湖。
      • 通常會觸發該守衛的時機為以下:
        • 登入成功後,準備進入 topic_contacts.screen。
        • APP 重新開啟,自動登入成功,準備進入 topic_contacts.screen。

4-2. layout/ - 佈局樣式 共用型組件。

  • app_layout.dart: 提供給進入大廳前 topic_screen.dart的佈局外觀。
    • Example: 登入、忘記密碼、註冊,大多都有一個共同點是,右上要掛著語言切換。
  • topic_layout.dart: 提供進入大廳後 topic_screen.dart 的佈局外觀。
    • Example: 三大主題,底部導覽 Navbar,header 右邊 title + 左邊-搜尋好友&創建群組。
  • setting_layout.dart: 提供給設定 底下的子功能路由的佈局外觀。
    • Example: 他們都有個共通點, header 右方-back,底藍色,不要 Navbar 導覽。

4-3. screens/ - 各項功能畫面

  • welcome_screen.dart: 歡迎引導頁面。

    • 全新 APP 會進來此。
    • 曾登入過,但過非常長時間(30 日)未使用 APP,重新打開會被導回此。
    • 相關業務流程: 詳見 登入前業務單元 - 歡迎業務單元
  • login_screen.dart: 登入主頁面

    • 相關業務流程: 詳見 登入前業務單元 - 登入業務單元
  • topic/: 大廳 使用 嵌套路由 (Nested Routes)

    • contacts/: 聯絡人 (兩種分類,對應兩種 friends, groups)
    • chats/: 聊天 (三種分類,對應三種 all, friends, groups)
    • settings/: 設定
  • conversation/: 從 topic/chats 聊天列表,點入某一個聊天後的 對話聊天模式

    • 核心對話功能:
      • conversation_screen.dart: 核心對話畫面 (聊天訊息列表與輸入區)
    • 好友對話相關:
      • conversation_friend_info_screen.dart: 好友對話資訊頁 (好友資料、設定)
    • 群組對話相關:
      • conversation_group_info_screen.dart: 群組對話資訊頁 (群組資料、成員列表)
      • conversation_group_settings_screen.dart: 群組對話設定頁 (個人匿名編輯、管理員的管理功能)
    • 共用功能:
      • conversation_info_files_screen.dart: 對話檔案列表 (對話歷史檔案清單)
      • conversation_info_media_screen.dart: 對話媒體列表 (對話歷史圖片、影片清單)
      • conversation_info_links_screen.dart: 對話連結列表 (查看歷史連結)
  • common/: 共用頁面。 (注意:這裡的元件,並沒有使用 @RoutePage)

    • Example: 對話中有圖片、檔案、影片訊息,點擊後需要 全螢幕模式 的功能畫面。
  • forgot/: 忘記密碼流程頁面

    • forgot_password_email_screen.dart: 忘記密碼 - 輸入電子郵件頁面
    • forgot_passowrd_email_verify_screen.dart: 忘記密碼 - 驗證碼驗證頁
    • 相關業務流程: 詳見 登入前業務單元 - 忘記密碼業務單元
  • register/: 註冊相關頁面群組

    • register_screen.dart: 註冊 - 註冊佈局樣式-第一步填寫表單,第二部驗證碼驗證。
    • register_form_screen.dart: 註冊 - 表單填寫頁面
    • register_verify_screen.dart: 註冊 - 電子郵件驗證頁
    • 相關業務流程: 詳見 登入前業務單元 - 註冊業務單元

5. services/ - 功能服務層

  • ChatService: Agora Chat SDK 使用的方法,這邊多墊一層,因為有些繁瑣事情,會將複雜度再包裝進來。

    為什麼多包裝這些靜態方法?

    Agora SDK 原生呼叫的方法 fetchContacs fetchGroups fetchtConverstaion getMessages...等,有一個特性是,只讓開發者 20 筆分頁式的取得方法,但某些情況,我希望直接取得所有的 Groups 回來,因為無時無刻都有機會會有新的 Converstaion 過來,我如果沒有將所有的 Groups 先 ready,曾經就一直有空白的 Bug 出現。

    • ex: ChatService::fetchAllMessages AgoraFetchService 有意識的刻意包裝,給業務層方便。

    所以這邊我都會墊一層 while loop search 包裝成一個方法,讓其他畫面元件使用。

    瑕疵

    不過有些 method 確實好像不用包裝,只用短短的一行 (= . =|||) 早期開發上的過度設計(ex:ChatService::getConversationsByFriends這類的情況)

  • ApiService: REST API 客戶端,所有前台 API 調用

    • API 其實集中在 api_client.dart 定義中, ApiService 其實只是一個使用介面(getInstance)

      如何添加新的前台 API?

      1. 參考過往 api_client.dart 的定義,API 類型、Response、Request

      2. 更新 api_client.g.dart,執行

        flutter packages pub run build_runner build --delete-conflicting-output

      如何使用?

      1. async function
        try {
          // refreshTokenResult 已經是查完的 Result
          ApiServicefinal refreshTokenResult = await ApiService.getInstance.refreshToken();
        } on DioException catch(ex) {
          // DioException 錯誤處理
        } catch(ex) {
          // 未知的錯誤處理
        }
      
      1. Promise
        ApiService.getInstance.refreshToken()
          .then(response {
            // 成功後做什麼
          })
          .catch((ex) {
          // DioException 錯誤處理
          }, ex => ex is DioException)
          .catch((ex) {
          // 未知的錯誤處理
          })
      
      

      細節

      ApiService::_initApiClient 我偷將後端需要的 JWT Auth Token,這邊偷定義進去 interceptor(攔截者),未來如果有什麼需要共同定義的可以從這邊下手。

  • AuthService: Valo 的雙重認證業務 (Agora + API JWT),詳情 認證架構文檔

    提示

    • 主要處理 登入、自動記憶登入、refresh token、即將到期...等,這些是在表面上的意義,但實際每件事情,可能會牽扯 2~3 件服務

      ex:登入頁的 LoginButton,點擊下去時,會先跟

      1. login api 請求
      2. agora client login
      3. 記憶帳戶方便下次自動登入
      4. 取得個人資料

      而有這些流程事務複雜度,而不希望暴露在畫面、元件層,會造成閱讀負擔。

  • MediaService: Valo 設備媒體的操作服務

    • 打開 文件區、媒體區、相機模式、驗證檔案類型、大小 等的服務方法。

    注意

    這邊大概是本專案最凌亂、最難搞的一部分。

    1. 打開文件區、下載檔案到 文件區
    • IOS 有統一的規範比較少問題。
    • Android,新舊版的沒有一個統一的共用文件路徑,
      • 至今目前我還是不太清楚新舊版,在幾版以上&以下,該用哪個文件目錄。
      • 嘗試部分套件 file_selector、file_picker...等,都無法共同解決方案,未來這段需要花一些時間,深入嘗試多方媒體套件。
  • AppBadgeManager: APP 數字徽章管理

    • IOS 沒問題
    • Android 大部分平台沒有支援數字徽章,除了很少部分的機型。

6. modal/ - 資料模型

  • user.dart:

    • 該設備登入的使用者、角色類別(Guest、Member、Admin),目前大部分只用到 Member。
  • user_profile.dart:

    • user.dart 的延伸 Profile 資料,ex: Member 需要 agora fetchProfile
  • contact.dart:

    • 跟 contacts 這邊有關的資料模型,ex: 好友(Friend)、群組(Group)、搜尋好友(SearchMember)、
  • chat.dart: 聊天相關資料模型

    • ValoConversationType - 聊天對話類別 (Friend, Group)
    • ValoConversationMessage
      • 一則 Valo 聊天訊息,多了一些方法封裝
    • CreateGroupFormData - 創建群組的資料

備註

chat.dart ValoConversationMessage 後來因為在整合 valo_chat.dart 時,已經有新的方式實作,ValoConversationMessage的使用上,變得有點雞肋,而目前是在 adapters/ 這邊實作轉換,當時實驗新版的 valo_chat.dart,為了要銜接 新舊版本的 chat 資料模型,而才使用 adapter 去做中間轉換。

contact.dart 跟 chat.dart 其實有在想是不是整合一起就好? CreateGroupFormData 放在 chat.dart 內好像也怪怪的


7. modalView/ - Valo UI 資料湖

其實就是 Provider 的資料模型、資料業務,Valo資料湖 共用所有資料狀態。

  • 在開發過程中,遇到非常多 資料不同步 Bug、功能場景的資料依賴,這種情況而設計,例如:
    • 資料重複創建問題: 如果不使用 Provider 共用
      • 開發者容易遺忘最源頭的資料源 是否為同一個創立,造成以下
      • 各 Widget 組件會各自創建獨立的資料物件,造成記憶體浪費且資料不一致
    • 狀態同步困難: A 組件修改了好友資料,B 組件無法即時得知變更,導致顯示過時資料

因此統一的資料湖,讓所有組件共享同一份資料,主確保資料一致性。

注意

modalView 是我最早期開發的目錄名稱,目前意義上可能已經不太合適,當時想使用 mvvc 的概念,不過經過後面的開發改善,目前以 Provider 共用資料的方式,開發狀況才比較穩定下來。

8. adapters/ - 資料轉換層

  • ChatUIAdapter: 將 Agora ChatMessage 轉換為 flutter_chat_ui 所需的 Message 格式
    • 用途: 解決不同資料格式間的相容性問題,讓 UI 層無需了解 Agora SDK 的內部結構

9. utils/ - 工具函數

提供與業務邏輯無關的通用工具函數,特點是 職責單一、低異動,大部分都有包裝 單元測試,如果有異動方法,建議要跑過單元測試。

核心工具檔案

  • time_util.dart: 處理 timestamp、DateTime、格式化時間文字的工具。
  • toast_util.dart: 顯示通知的工具。
  • bottom_sheet_util.dart: 顯示 由下滑上的內容 的工具。
  • popup_util.dart: 顯示彈窗工具。
  • notice_util.dart: 舊的彈窗的工具 (遺棄中,減少此工具的使用,toast_util 替代它)

資料處理工具

  • text_util.dart: 文字處理和格式化工具
  • clipboard_util.dart: 剪貼簿操作工具
  • secure_storage_util.dart: 安全儲存工具
    • ex: 登入成功時,保存用戶資訊與 Token,需要使用 secure_storage

擴展功能

  • agora_extension.dart: Agora SDK 相關擴展方法
  • context_extension.dart: Flutter Context 擴展方法
  • throw_util.dart: 錯誤處理和異常工具

備註

throw_util 、 context_extension 目前比較少使用

  1. throw_util: 是一個包裝錯誤處理的工具,專案中時常寫 try catch,但在 catch 中很容易無處理,也沒辦法集中管理錯誤處理,為了這件事情而生,但忙著其他需求後來沒什麼使用。

10. components/ - 通用元件

提供無業務邏輯的純 UI 組件,專注視覺展示,可在整個專案中重複使用。

buttons/ - 按鈕組件

  • action_button.dart: 一般任務按鈕組件(無載入狀態)
  • worker_button.dart: 異步任務 按鈕 (有載入狀態)
  • countdown_worker_button.dart: 倒數計時按鈕 (驗證碼場景)

inputs/ - 輸入框組件

  • valo_text_field.dart: Valo 文字輸入框
  • valo_password_field.dart: 密碼輸入框 (驗證規則已內涵在裡頭)
  • valo_phone_field.dart: 電話號碼輸入框
  • email_text_field.dart: 電子郵件輸入框

popup/ & sheet/ - 彈窗組件

  • base_popup.dart: 基礎彈窗組件
  • base_bottom_sheet.dart: 基礎底部彈出組件
    • BaseBottomSheet: 搭配 BottomSheetUtil::show,BaseBottomSheet 是提供一個底表原型,開放 header, content 參數。
    • 內建常用的 Header 樣式:
      • BaseBottomSheetHeader: 自己客製右中左的 Widget
      • OnlyCloseBottomSheetHeader: 右邊無東西、中間標題、左邊關閉
      • DoneCloseBottomSheetHeader: 右邊無東西、中間標題、左邊 Done

11. widgets/ - 業務&功能組件

依照 業務類別 切分的目錄群。

add_friend/ - 好友添加功能

  • add_friend_button.dart: 添加好友按鈕組件
  • open_search_member_bottom_sheet_button.dart: 打開搜尋成員底部表單按鈕
  • search_member_bottom_sheet.dart: 搜尋成員底部表單

auth/ - 認證相關組件

  • auth_register_prompt.dart: 註冊提示組件
  • create_account_prompt.dart: 創建帳號提示
  • login_bottom_section.dart: 登入頁底部區塊
  • 相關業務流程: 詳見 登入前業務單元

chat/ - 聊天功能核心

基於 flutter_chat_ui 的完整聊天系統,支援多種訊息類型、回覆機制、搜尋功能等。

詳細架構請參閱 聊天系統架構文檔。

  • message/ - 訊息組件群: 各種訊息類型與功能組件
    • reply/ - 回覆子系統: 完整的回覆訊息機制
  • search/ - 聊天搜尋: 編輯器與標題列搜尋功能
  • 聊天核心組件: 主要聊天組件、編輯器、狀態管理等

common/ - 通用業務組件

  • base_loading_view.dart: 基礎載入視圖
  • settings_list_tile.dart: 設定列表項目
  • upload_image_bottom_sheet.dart: 圖片上傳底部表單

contacts/ - 聯絡人功能

  • contacts_category.dart: 聯絡人分類標籤
  • contacts_friend_item.dart: 好友項目顯示
  • contacts_header.dart: 聯絡人頁面標題

conversation/ - 對話管理

  • appbar/ - 對話頁標題列:
    • conversation_appbar.dart: 對話標題列
    • conversation2_appbar.dart: 對話標題列變體
  • conversation_board_item.dart: 對話列表項目
  • conversation_pin_slidable_button.dart: 對話置頂滑動按鈕

create_group/ - 群組創建流程

  • buttons/ - 創建按鈕群:
    • create_group_button.dart: 創建群組按鈕
    • create_group_bottom_sheet_button.dart: 底部表單觸發按鈕
  • sheets/ - 創建流程表單:
    • create_group_step_1.dart: 步驟 1 - 選擇成員
    • create_group_step_2.dart: 步驟 2 - 群組資訊
    • create_group_step_3.dart: 步驟 3 - 完成創建

friend/ - 好友功能

  • buttons/ - 好友操作按鈕:
    • friend_chat_button.dart: 開始聊天按鈕
    • friend_block_button.dart: 封鎖好友按鈕
    • friend_mute_button.dart: 靜音好友按鈕
  • friend_avatar.dart: 好友頭像組件
  • friend_display_name.dart: 好友顯示名稱
  • friend_profile_card.dart: 好友資料卡片
  • sheets/friend_profile_bottom_sheet.dart: 好友資料底部表單

group/ - 群組功能

完整的匿名群聊系統,支援角色管理、匿名身份、即時通訊等功能。

詳細架構請參閱 群組系統架構文檔。

  • avatars/ - 群組頭像系統: 群組與成員頭像顯示
  • buttons/ - 群組操作按鈕: 聊天、加入、管理等功能按鈕
  • settings/ - 群組設定項目: 管理員、編輯、刪除等設定
  • sections/ - 群組區塊: 匿名資料設定區塊
  • sheets/ - 群組底部表單: 資料查看、邀請等表單
  • popups/ - 群組彈窗: 分享、離開確認彈窗
  • 群組核心組件: 顯示名稱、資料卡片、邀請功能等

language/ - 語言切換

  • language_selector.dart: 語言選擇器
  • language_sheet.dart: 語言選擇底部表單

login/ - 登入功能

  • login_form.dart: 登入表單組件
  • 相關業務流程: 詳見 登入前業務單元 - 登入業務單元

register/ - 註冊功能

  • register_button.dart: 註冊按鈕
  • register_confirm_button.dart: 註冊確認按鈕
  • register_verify_code_input.dart: 驗證碼輸入框
  • 相關業務流程: 詳見 登入前業務單元 - 註冊業務單元

topic/ - 主題頁面組件

  • valo_topic_appbar.dart: 主題頁標題列
  • valo_topic_navigation.dart: 主題頁導航
  • valo_topic_tabbar.dart: 主題頁標籤欄

user/ - 使用者功能

  • profile/ - 使用者資料:
    • user_profile_nickname_item.dart: 暱稱編輯項目
    • user_profile_birthday_item.dart: 生日編輯項目
  • user_avatar.dart: 使用者頭像
  • user_profile.dart: 使用者資料組件
  • delete_user_button.dart: 刪除帳號按鈕

welcome/ - 歡迎頁組件

  • welcome_background.dart: 歡迎頁背景
  • welcome_login_section.dart: 歡迎頁登入區塊
  • 相關業務流程: 詳見 登入前業務單元 - 歡迎業務單元

根目錄組件

  • change_password_bottom_sheet.dart: 修改密碼底部表單
  • delete_account_bottom_sheet.dart: 刪除帳號底部表單
  • logout_popup.dart: 登出確認彈窗
  • valo_empty_view.dart: 空狀態顯示組件
  • valo_empty_navigation.dart: 空導航組件
最後更新: 2025/8/26 下午2:59
貢獻者: boheng
Prev
開發環境設置指南
Next
- 聊天功能核心