Skip to content

Add Flutter port scaffold for SwitchBot Controller#35

Closed
0suu wants to merge 1 commit into
mainfrom
codex/port-existing-application-to-flutter
Closed

Add Flutter port scaffold for SwitchBot Controller#35
0suu wants to merge 1 commit into
mainfrom
codex/port-existing-application-to-flutter

Conversation

@0suu

@0suu 0suu commented Feb 4, 2026

Copy link
Copy Markdown
Owner

Motivation

  • 既存の Electron アプリと全く同等の機能を持つ Flutter 版クライアントを新規に作成して移植作業の出発点を用意するため。

Description

  • 新規ディレクトリ flutter_app/ を追加し、Flutter アプリのスケルトンを配置しました(pubspec.yaml.gitignoreREADME.md を含む)。
  • アプリ状態管理と永続化を担う lib/app_state.dart を実装し、認証情報保存、ポーリング、デバイス/シーン取得・操作、設定の保持をサポートしています。
  • SwitchBot Cloud API 呼び出し用のクライアント lib/services/switchbot_api.dart を実装し、署名付きヘッダ生成と主要エンドポイント(デバイス一覧/ステータス、シーン一覧/実行、コマンド送信)を追加しました。
  • UI を構成する画面群を追加しました:lib/screens/devices_screen.dartlib/screens/device_detail_screen.dartlib/screens/scenes_screen.dartlib/screens/settings_screen.dart とアプリエントリ lib/main.dart、モデル定義 lib/models.dart、簡易ローカライズ lib/l10n.dart を実装しています。

Testing

  • 自動化されたテストは実行していません(ビルド/実行は未検証)。

Codex Task

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Feb 4, 2026

Copy link
Copy Markdown

Deploying switchbotcontroller with  Cloudflare Pages  Cloudflare Pages

Latest commit: 60e013f
Status: ✅  Deploy successful!
Preview URL: https://c3bc4a1f.switchbotcontroller.pages.dev
Branch Preview URL: https://codex-port-existing-applicat.switchbotcontroller.pages.dev

View logs

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @0suu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

このプルリクエストは、既存のElectron版SwitchBot Controllerと同等の機能を持つFlutter版クライアントの初期骨格を構築することを目的としています。これにより、今後の移植作業の出発点となる基盤が用意され、デバイスの操作、シーンの実行、設定管理などが可能なモバイルアプリケーションの実現に向けた第一歩となります。

Highlights

  • Flutterプロジェクトの初期設定: 新しいflutter_app/ディレクトリが追加され、Flutterアプリケーションの基本的なファイル構造と設定(.gitignoreREADME.mdpubspec.yamlなど)が配置されました。
  • アプリケーション状態管理の実装: lib/app_state.dartが追加され、認証情報、ポーリング間隔、テーマ、言語、最終タブインデックスなどのアプリケーション状態の管理と永続化機能が提供されます。
  • SwitchBot APIクライアントの統合: lib/services/switchbot_api.dartが実装され、SwitchBot Cloud APIとの通信を処理します。これには、署名付きヘッダの生成、デバイス一覧、デバイスステータス、シーン一覧の取得、シーンの実行、デバイスへのコマンド送信が含まれます。
  • 主要なUI画面の構築: デバイス一覧、デバイス詳細、シーン一覧、設定画面など、アプリケーションの主要なユーザーインターフェースがlib/screens/以下に構築されました。また、アプリのエントリポイント(lib/main.dart)、データモデル(lib/models.dart)、簡易ローカライズ(lib/l10n.dart)も含まれています。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • flutter_app/.gitignore
    • Flutterプロジェクトの標準的な無視ファイルが追加されました。
  • flutter_app/README.md
    • Flutter版SwitchBot Controllerの概要、セットアップ方法、および提供される機能が記載されました。
  • flutter_app/lib/app_state.dart
    • アプリケーションの状態管理、認証情報の保存とクリア、ポーリング間隔、テーマ、言語、最終タブインデックスの設定と永続化、デバイスとシーンの取得・更新、デバイスステータスの取得、シーンの実行、コマンド送信機能が実装されました。
  • flutter_app/lib/l10n.dart
    • アプリケーションの多言語対応(日本語と英語)のための文字列定義が追加されました。
  • flutter_app/lib/main.dart
    • Flutterアプリケーションのエントリポイント、メインウィジェット、およびデバイス、シーン、設定画面を切り替えるナビゲーションバーを含むホーム画面が定義されました。
  • flutter_app/lib/models.dart
    • Device, DeviceStatus, Sceneのデータモデルと、AppLanguageのenumが定義されました。
  • flutter_app/lib/screens/device_detail_screen.dart
    • 個々のデバイスの詳細表示、ステータス更新、およびコマンド送信のためのUIが実装されました。
  • flutter_app/lib/screens/devices_screen.dart
    • デバイスの一覧表示、フィルタリング、および詳細画面へのナビゲーション機能が実装されました。
  • flutter_app/lib/screens/scenes_screen.dart
    • シーンの一覧表示と、各シーンを実行するためのUIが実装されました。
  • flutter_app/lib/screens/settings_screen.dart
    • APIトークンとシークレットの入力・検証・保存、ポーリング間隔の設定、テーマと言語の選択を行う設定画面が実装されました。
  • flutter_app/lib/services/switchbot_api.dart
    • SwitchBot Cloud APIと通信するためのクライアントが実装されました。これには、署名付きヘッダの生成、デバイス一覧、デバイスステータス、シーン一覧の取得、シーンの実行、デバイスへのコマンド送信などの機能が含まれます。
  • flutter_app/pubspec.yaml
    • Flutterプロジェクトの依存関係(http, shared_preferences, crypto)とSDKバージョンが定義されました。
Activity
  • このプルリクエストには、現時点では人間によるコメントやレビュー活動はありません。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

このプルリクエストは、SwitchBot Controller の Flutter 版の足場を構築するもので、状態管理、API 統合、および基本的な UI 画面が含まれています。構造はよく整理されており、状態管理に ChangeNotifier、永続化に shared_preferences を使用している点は、この規模の Flutter アプリケーションに適しています。API クライアントは署名生成とエラー解析を適切に処理しています。

しかし、主にローカライズ、エラー処理の一貫性、および軽微なコードの重複に関して、いくつかの改善点があります。これらに対処することで、保守性、ユーザーエクスペリエンス、および堅牢性が向上します。

Comment on lines +107 to +109
'parameter': parameter ?? 'default',
'commandType': commandType,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

sendCommand メソッドにおいて、parameternull の場合にデフォルト値として 'default' が設定されています。SwitchBot API の仕様を確認し、null の場合は parameter フィールド自体をリクエストボディから省略する方が適切である可能性があります。現在の実装では、APIが 'default' という文字列を特定のコマンドの有効なパラメータとして期待しない場合、予期せぬ動作を引き起こす可能性があります。

      body: jsonEncode({
        'command': command,
        if (parameter != null) 'parameter': parameter,
        'commandType': commandType,
      }),


String _token = '';
String _secret = '';
int _pollingIntervalSeconds = 10;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ポーリング間隔のデフォルト値 10 がハードコードされています。この値をクラス定数として定義することで、コードの可読性と保守性が向上します。また、load メソッドでのデフォルト値の取得もその定数を使用するように変更してください。

Suggested change
int _pollingIntervalSeconds = 10;
static const int defaultPollingIntervalSeconds = 10;
int _pollingIntervalSeconds = defaultPollingIntervalSeconds;

final prefs = await SharedPreferences.getInstance();
_token = prefs.getString('token') ?? '';
_secret = prefs.getString('secret') ?? '';
_pollingIntervalSeconds = prefs.getInt('pollingIntervalSeconds') ?? 10;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ポーリング間隔のデフォルト値 10 がハードコードされています。この値をクラス定数として定義することで、コードの可読性と保守性が向上します。また、load メソッドでのデフォルト値の取得もその定数を使用するように変更してください。

Suggested change
_pollingIntervalSeconds = prefs.getInt('pollingIntervalSeconds') ?? 10;
_pollingIntervalSeconds = prefs.getInt('pollingIntervalSeconds') ?? AppState.defaultPollingIntervalSeconds;

Comment on lines +100 to +104
Future<bool> validateCredentials() async {
if (_token.isEmpty || _secret.isEmpty) {
_errorMessage = '認証情報が未入力です。';
notifyListeners();
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

認証情報の空チェック (_token.isEmpty || _secret.isEmpty) とエラーメッセージの設定が複数の非同期メソッドで繰り返されています。このロジックをプライベートなヘルパーメソッドに抽出し、エラーメッセージを AppStrings を使用してローカライズすることで、コードの重複を減らし、保守性と多言語対応を向上させることができます。

Comment on lines +109 to +114
notifyListeners();
return true;
} catch (error) {
_errorMessage = error.toString();
notifyListeners();
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

validateCredentials メソッド内で _errorMessage の更新後に notifyListeners() が複数回呼び出されています。try-catch ブロック内で状態が変更された後、finally ブロックで一度だけ notifyListeners() を呼び出すことで、不必要なウィジェットの再構築を避けることができます。このパターンは他の非同期メソッド (refreshDevices, refreshScenes, fetchDeviceStatus, executeScene, sendCommand) にも適用できます。

      _errorMessage = null;
      return true;
    } catch (error) {
      _errorMessage = error.toString();
      return false;
    } finally {
      notifyListeners();
    }

Comment on lines +70 to +71
const SnackBar(content: Text('コマンドを送信しました。')),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

コマンド送信成功時のスナックバーメッセージ 'コマンドを送信しました。' がハードコードされています。AppStrings を使用してこのメッセージをローカライズし、多言語対応と一貫性を確保することを検討してください。

style: TextStyle(color: Theme.of(context).colorScheme.error),
),
if (statusEntries.isEmpty)
const Text('ステータスがありません。')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ステータスがない場合のメッセージ 'ステータスがありません。' がハードコードされています。AppStrings を使用してこのメッセージをローカライズし、多言語対応と一貫性を確保することを検討してください。

Comment on lines +60 to +61
? '${scene.sceneName} を実行しました。'
: widget.strings.apiError;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

シーン実行成功時のスナックバーメッセージがハードコードされています。AppStrings を使用してこのメッセージをローカライズし、多言語対応と一貫性を確保することを検討してください。

title: Text(scene.sceneName),
trailing: TextButton(
onPressed: onExecute,
child: const Text('実行'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

シーン実行ボタンのテキスト '実行' がハードコードされています。AppStrings を使用してこのテキストをローカライズし、多言語対応と一貫性を確保することを検討してください。

Comment on lines +97 to +98
child: Text(widget.strings.pollingInterval),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ポーリング間隔を設定する ElevatedButton のテキストが、TextField のラベルと同じ 'ポーリング間隔(秒)' になっています。ボタンの目的をより明確にするために、テキストを '適用''保存' などに変更することを検討してください。AppStrings に新しいエントリを追加してローカライズすることも可能です。

Suggested change
child: Text(widget.strings.pollingInterval),
),
onPressed: _applyPollingInterval,
child: Text('適用'),

@0suu 0suu closed this Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant