# 画面設計書 7-サーバー選択画面

## 概要

本ドキュメントは、InvenTree Flutterアプリケーションのサーバー選択画面（InvenTreeSelectServerWidget）の画面設計を定義するものである。

### 本画面の処理概要

サーバー選択画面は複数のInvenTreeサーバープロファイルを管理し、接続先サーバーの選択・追加・編集・削除を行う機能を提供する。

**業務上の目的・背景**：企業では開発環境、テスト環境、本番環境など複数のInvenTreeサーバーを運用することがある。本画面は複数のサーバープロファイルを管理し、用途に応じて接続先を切り替える機能を提供する。また、認証トークンの管理により、一度ログインした環境への再接続を簡易化する。

**画面へのアクセス方法**：設定画面の「サーバー」項目をタップ、またはホーム画面のサーバーアイコンをタップ。

**主要な操作・処理内容**：
1. プロファイル一覧の表示（名前、サーバーURL、認証状態）
2. プロファイルの選択と接続（タップで選択、トークンありなら自動接続）
3. プロファイルの新規作成（+アイコンタップ）
4. プロファイルの編集（長押しメニューから「編集」）
5. プロファイルからのログアウト（長押しメニューから「ログアウト」でトークン削除）
6. プロファイルの削除（長押しメニューから「削除」で確認後削除）
7. トークン未取得プロファイル選択時のログイン画面遷移

**画面遷移**：
- 遷移元：設定画面、ホーム画面
- 遷移先：プロファイル編集画面、ログイン画面、ホーム画面（接続成功時に戻る）

**権限による表示制御**：本画面は全ユーザーがアクセス可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | ユーザープロファイル管理 | 主機能 | サーバープロファイルの一覧表示・選択・削除 |
| 1 | サーバー接続管理 | 補助機能 | プロファイル選択時のサーバー接続処理 |
| 3 | ログイン機能 | 遷移先機能 | トークン未取得プロファイル選択時にログイン画面へ遷移 |

## 画面種別

一覧・管理画面

## URL/ルーティング

- Flutter Widget：`InvenTreeSelectServerWidget`（StatefulWidget）
- 状態クラス：`_InvenTreeSelectServerState`

## 入出力項目

| 項目名 | 入力/出力 | データ型 | 必須 | 説明 |
|--------|----------|----------|------|------|
| なし | - | - | - | 本画面は入力パラメータを持たない |

## 表示項目

| 項目名 | データ型 | 説明 | 取得元 |
|--------|----------|------|--------|
| プロファイル名 | String | プロファイルの表示名 | profile.name |
| サーバーURL | String | InvenTreeサーバーのURL | profile.server |
| 認証状態アイコン | Icon | トークン有無を示すアイコン（user_check/user_cancel） | profile.hasToken |
| 選択状態 | Color | 選択中プロファイルの背景色 | profile.selected |
| 接続状態アイコン | Icon | 接続済み（緑）、接続中（スピナー）、未接続（赤） | _getProfileIcon() |
| プロファイルなしメッセージ | Text | プロファイルが0件の場合のメッセージ | L10().profileNone |

## イベント仕様

### 1-プロファイル選択（タップ）

プロファイルをタップした際にサーバー接続を試行する。

**処理フロー**：
1. ユーザーがプロファイルをタップ
2. `_selectProfile()`メソッドが呼び出される
3. `InvenTreeAPI().disconnectFromServer()`で現在の接続を切断
4. `UserProfileDBManager().selectProfile(key)`でプロファイルを選択
5. `getProfileByKey(key)`でプロファイル情報を再取得
6. トークンがない場合（!prf.hasToken）:
   - `Navigator.push()`で`InvenTreeLoginWidget(profile)`へ遷移
   - ログイン成功後にサーバー接続を試行
7. トークンがある場合:
   - `InvenTreeAPI().connectToServer(prf)`で接続
8. `_reload()`で一覧を更新

### 2-プロファイル長押し（オプションメニュー）

プロファイルを長押しした際にオプションメニューダイアログを表示する。

**処理フロー**：
1. ユーザーがプロファイルを長押し
2. `OneContext().showDialog()`でSimpleDialogを表示
3. メニュー項目：
   - 「接続」: `_selectProfile()`を呼び出し
   - 「編集」: `_editProfile()`を呼び出し
   - 「ログアウト」: `_logoutProfile()`を呼び出し
   - 「削除」: 確認ダイアログ後に`_deleteProfile()`を呼び出し

### 3-プロファイル新規作成

AppBarの+アイコンをタップした際にプロファイル編集画面を新規作成モードで開く。

**処理フロー**：
1. ユーザーが+アイコンをタップ
2. `_editProfile(context, createNew: true)`が呼び出される
3. `Navigator.push()`で`ProfileEditWidget(null)`へ遷移
4. 編集画面から戻った際に`_reload()`で一覧を更新

### 4-プロファイル編集

長押しメニューの「編集」を選択した際にプロファイル編集画面を編集モードで開く。

**処理フロー**：
1. ユーザーが「編集」を選択
2. ダイアログを閉じる
3. `_editProfile(context, userProfile: profile)`が呼び出される
4. `Navigator.push()`で`ProfileEditWidget(profile)`へ遷移
5. 編集画面から戻った際に`_reload()`で一覧を更新

### 5-プロファイルログアウト

長押しメニューの「ログアウト」を選択した際に認証トークンを削除する。

**処理フロー**：
1. ユーザーが「ログアウト」を選択
2. ダイアログを閉じる
3. `_logoutProfile(context, userProfile: profile)`が呼び出される
4. `profile.token = ""`でトークンをクリア
5. `UserProfileDBManager().updateProfile(profile)`でプロファイルを更新
6. `InvenTreeAPI().disconnectFromServer()`で接続を切断
7. `_reload()`で一覧を更新

### 6-プロファイル削除

長押しメニューの「削除」を選択した際に確認後プロファイルを削除する。

**処理フロー**：
1. ユーザーが「削除」を選択
2. ダイアログを閉じる
3. `confirmationDialog()`で削除確認ダイアログを表示
4. ユーザーが確認した場合、`_deleteProfile(profile)`が呼び出される
5. `UserProfileDBManager().deleteProfile(profile)`でプロファイルを削除
6. 削除したプロファイルが現在接続中の場合、`InvenTreeAPI().disconnectFromServer()`
7. `_reload()`で一覧を更新

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示時 | profiles（ローカル） | SELECT | プロファイル一覧の取得 |
| プロファイル選択 | profiles（ローカル） | UPDATE | selectedフィールドの更新 |
| ログアウト | profiles（ローカル） | UPDATE | tokenフィールドをクリア |
| プロファイル削除 | profiles（ローカル） | DELETE | プロファイルレコードの削除 |

### テーブル別更新項目詳細

#### profiles（ローカルDB - Sembast）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | 全件取得 | getAllProfiles() |
| UPDATE | selected | profile.key | selectProfile(key) |
| UPDATE | token | "" | ログアウト時にクリア |
| DELETE | - | profile.key | deleteProfile(profile) |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | タイトル | L10().profileSelect | AppBarタイトル |
| MSG-002 | 情報 | L10().profileNone | プロファイルが0件の時 |
| MSG-003 | メニュー | L10().profileConnect | 長押しメニュー「接続」 |
| MSG-004 | メニュー | L10().profileEdit | 長押しメニュー「編集」 |
| MSG-005 | メニュー | L10().profileLogout | 長押しメニュー「ログアウト」 |
| MSG-006 | メニュー | L10().profileDelete | 長押しメニュー「削除」 |
| MSG-007 | 確認 | L10().delete | 削除確認ダイアログタイトル |
| MSG-008 | 確認 | L10().profileDelete + "?" | 削除確認ダイアログメッセージ |

## 例外処理

| 例外種別 | 発生条件 | 対応処理 |
|---------|---------|---------|
| プロファイルキーがnull | profile.key == null | 処理をスキップ |
| プロファイル取得失敗 | getProfileByKey()がnullを返す | 処理をスキップ |
| ウィジェット未マウント | 処理中に画面が閉じられた | mountedチェックで処理をスキップ |

## 備考

- 初回起動時にデモプロファイル（InvenTree Demo）が自動作成される
- プロファイルのトークン有無は左側のアイコン（user_check/user_cancel）で視覚的に表示
- 選択中プロファイルは背景色で強調表示される
- 接続状態は右側のアイコン（circle_check/loader/circle_x）で表示
- OneContextを使用してダイアログを表示

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、プロファイルデータの構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | user_profile.dart | `lib/user_profile.dart` | UserProfileクラスとUserProfileDBManagerクラスを理解する |

**読解のコツ**: UserProfileはkey, name, server, token, selectedのプロパティを持つ。UserProfileDBManagerはSembastデータベースを使用してプロファイルを永続化する。

#### Step 2: エントリーポイントを理解する

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | select_server.dart | `lib/settings/select_server.dart` | InvenTreeSelectServerWidgetと_InvenTreeSelectServerStateを理解する |

**主要処理フロー**:
1. **行14-17**: InvenTreeSelectServerWidgetクラスの定義
2. **行19-37**: コンストラクタで`_reload()`を呼び出しプロファイル一覧を取得
3. **行29-37**: `_reload()`でUserProfileDBManager().getAllProfiles()を呼び出し
4. **行42-55**: `_logoutProfile()`でトークンをクリアして接続を切断
5. **行60-71**: `_editProfile()`でProfileEditWidgetへ遷移
6. **行73-124**: `_selectProfile()`でプロファイル選択と接続処理
7. **行126-139**: `_deleteProfile()`でプロファイル削除
8. **行141-158**: `_getProfileIcon()`で接続状態アイコンを返す
9. **行160-278**: `build()`でUIを構築

#### Step 3: API連携を理解する

サーバー接続処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | api.dart | `lib/api.dart` | connectToServer(), disconnectFromServer()を理解する |

**主要処理フロー**:
- **行637-651**: `disconnectFromServer()`で接続状態をクリア
- **行656-701**: `connectToServer()`で接続処理を実行

### プログラム呼び出し階層図

```
InvenTreeSelectServerWidget (select_server.dart)
    |
    +-- _InvenTreeSelectServerState
            |
            +-- コンストラクタ
            |       +-- _reload()
            |               +-- UserProfileDBManager().getAllProfiles()
            |
            +-- build()
            |       |
            |       +-- ListView (プロファイル一覧)
            |       |       +-- ListTile x N
            |       |               +-- onTap: _selectProfile()
            |       |               +-- onLongPress: showDialog()
            |       |
            |       +-- AppBar
            |               +-- IconButton (+)
            |                       +-- _editProfile(createNew: true)
            |
            +-- _selectProfile()
            |       |
            |       +-- InvenTreeAPI().disconnectFromServer()
            |       +-- UserProfileDBManager().selectProfile()
            |       +-- UserProfileDBManager().getProfileByKey()
            |       |
            |       +-- [!hasToken]
            |       |       +-- Navigator.push(InvenTreeLoginWidget)
            |       |
            |       +-- [hasToken]
            |               +-- InvenTreeAPI().connectToServer()
            |
            +-- _editProfile()
            |       +-- Navigator.push(ProfileEditWidget)
            |
            +-- _logoutProfile()
            |       +-- profile.token = ""
            |       +-- UserProfileDBManager().updateProfile()
            |       +-- InvenTreeAPI().disconnectFromServer()
            |
            +-- _deleteProfile()
                    +-- UserProfileDBManager().deleteProfile()
                    +-- InvenTreeAPI().disconnectFromServer() [if connected]
```

### データフロー図

```
[入力]                          [処理]                              [出力]

画面表示                -->  _reload()                        -->  プロファイル一覧取得
                                |
                                v
UserProfileDBManager    -->  getAllProfiles()                 -->  profiles変数
                                |
                                v
                        build()                               -->  ListView描画
                                |
                                v
[プロファイルタップ]     -->  _selectProfile()                -->  接続処理開始
                                |
        +-------------------+---+
        |                       |
        v                       v
[!hasToken]             [hasToken]
Navigator.push          InvenTreeAPI.connectToServer()
(LoginWidget)                   |
        |                       v
        v               接続成功/失敗
ログイン画面            _reload()更新

[長押し]                -->  showDialog()                     -->  オプションメニュー
                                |
        +---+---+---+---+
        |   |   |       |
        v   v   v       v
   接続 編集 ログアウト 削除
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| select_server.dart | `lib/settings/select_server.dart` | ソース | サーバー選択画面のメインWidget |
| user_profile.dart | `lib/user_profile.dart` | ソース | UserProfile/UserProfileDBManagerクラス |
| api.dart | `lib/api.dart` | ソース | サーバー接続管理 |
| login.dart | `lib/settings/login.dart` | ソース | ログイン画面（遷移先） |
| dialogs.dart | `lib/widget/dialogs.dart` | ソース | confirmationDialog()の定義 |
| spinner.dart | `lib/widget/spinner.dart` | ソース | Spinnerウィジェット |
| app_colors.dart | `lib/app_colors.dart` | ソース | COLOR_SUCCESS, COLOR_WARNING, COLOR_DANGER等 |
| l10.dart | `lib/l10.dart` | ソース | 多言語対応 |
