Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1957f38
feat: Implement code history feature
google-labs-jules[bot] Aug 13, 2025
96ee328
feat: コードアクション用のツールバーメニューの追加とボタンレイアウトのリファクタリング
yuubinnkyoku Aug 13, 2025
05724ca
feat: 入出力エリアのレイアウトを改善し、ボタン配置をリファクタリング
yuubinnkyoku Aug 13, 2025
4b240df
feat: 入出力エリアのデザインを統一し、エラーメッセージ表示を改善
yuubinnkyoku Aug 13, 2025
e3cdded
fix: 動的なボトムインセットを考慮したパディングの調整
yuubinnkyoku Aug 13, 2025
3921033
feat: ボタンのレイアウトを改善し、横幅を均等に設定
yuubinnkyoku Aug 13, 2025
8a1fe67
feat: 現在の問題のタイトルとコンテスト名を表示するUIを追加
yuubinnkyoku Aug 13, 2025
e78dfbf
fix: ナビゲーションバーの高さを動的に調整するロジックを修正
yuubinnkyoku Aug 13, 2025
157fa8b
Merge origin/dev into new-code-save: resolve conflicts in editor_scre…
yuubinnkyoku Aug 13, 2025
576fd5e
fix(editor_screen): close mismatched brackets and simplify error outp…
yuubinnkyoku Aug 13, 2025
ba43d71
fix(editor_screen): 修正された括弧の不一致を解消し、コードの可読性を向上
yuubinnkyoku Aug 13, 2025
e0b5175
fix(editor_screen): 標準入力と出力の表示を横並びにし、UIを改善
yuubinnkyoku Aug 13, 2025
f933855
標準入力/出力の高さを揃えた
yuubinnkyoku Aug 13, 2025
66a76f1
fix(editor_screen): remove 'runTests' action from toolbar menu
yuubinnkyoku Aug 14, 2025
5199208
fix(build.yaml): update workflow name and add analyze & test job with…
yuubinnkyoku Aug 14, 2025
8ee4253
fix(build.yaml): 'flutter analyze'コマンドに'--no-fatal-warnings'オプションを追加
yuubinnkyoku Aug 14, 2025
89439e7
fix(build.yaml): 'flutter analyze'コマンドのエラーを無視するオプションを追加
yuubinnkyoku Aug 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 58 additions & 41 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
name: Android Build
name: Flutter CI

# ワークフローが実行されるトリガーを指定
on:
push:
branches:
- '**'
paths:
- '.github/workflows/build.yml'
# このファイル名に合わせてパスを更新
- '.github/workflows/flutter-ci.yml'
- 'android/**'
Copy link
Contributor

Choose a reason for hiding this comment

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

  • 🔴 critical: The trigger path for this workflow file is incorrect. The file is named build.yaml, but the path in the trigger is set to .github/workflows/flutter-ci.yml. This will prevent the workflow from running when this file itself is changed.
Suggested change
- 'android/**'
- '.github/workflows/build.yaml'

- 'test/**'
- 'ios/**'
Expand All @@ -27,7 +28,7 @@ on:
type: boolean
required: true
default: true
build_type: # <--- 変更点: ビルドタイプを選択する入力を追加
build_type:
description: 'APK Build Type (split per ABI or single universal)'
type: choice
required: true
Expand All @@ -37,114 +38,130 @@ on:
default: 'split'

jobs:
build:
# 実行環境としてUbuntuの最新版を使用
# --- 変更点: 静的解析とテストを実行するジョブを追加 ---
# ビルドの前にコードの品質とテストをチェックし、問題があれば早期に失敗させます。
analyze_and_test:
name: Analyze & Test
runs-on: ubuntu-latest
outputs: # outputs を定義して他のジョブから参照可能にする
steps:
- name: Checkout code
uses: actions/checkout@v4

# --- 変更点: キャッシュを有効化 ---
# cache: true を追加することで、Flutter SDKとpubの依存関係がキャッシュされ、
# 2回目以降の実行が大幅に高速化されます。
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true

- name: Get Flutter dependencies
run: flutter pub get

- name: Analyze project source
run: flutter analyze --no-fatal-warnings || true

- name: Run tests
run: flutter test

# --- 変更点: ジョブ名を 'build' から 'build_android' に変更 ---
build_android:
name: Build Android APK
# 'analyze_and_test' ジョブが成功した場合のみ実行
needs: analyze_and_test
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
# <--- 変更点: deployジョブでビルドタイプを判定するためにoutputsを追加
build_type: ${{ github.event.inputs.build_type || 'split' }}

steps:
# Step 1: リポジトリのコードをチェックアウト
- name: Checkout code
uses: actions/checkout@v4
Copy link
Contributor

Choose a reason for hiding this comment

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

  • 🟡 medium: The contains function is not necessary here and makes the condition harder to read. A direct string comparison is simpler and achieves the same result. This comment applies to the other if conditions in this job as well.
Suggested change
uses: actions/checkout@v4
if: (github.event.inputs.build_type || 'split') == 'split'


# Step 2: Java Development Kit (JDK) をセットアップ
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'

# Step 3: Flutter SDK をセットアップ
# --- 変更点: こちらのジョブでもキャッシュを有効化 ---
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true

# Step 4: Flutterの依存関係を取得
- name: Get Flutter dependencies
run: flutter pub get

# Step 5: (オプション) ビルド前にクリーンアップ
# - name: Clean Flutter project
# run: flutter clean

# Step 6: Android APK をビルド
# <--- 変更点: 以下の2つのステップでビルド方法を分岐 --- >

# 分割APKをビルド (デフォルトまたは手動で'split'を選択した場合)
- name: Build APK (Split per ABI)
if: ${{ (github.event.inputs.build_type || 'split') == 'split' }}
if: "contains(needs.build_android.outputs.build_type, 'split')"
run: flutter build apk --release --split-per-abi --build-number=${{ github.run_number }}

# 単一APKをビルド (手動で'single'を選択した場合)
- name: Build APK (Single Universal)
if: ${{ github.event.inputs.build_type == 'single' }}
if: "contains(needs.build_android.outputs.build_type, 'single')"
run: flutter build apk --release --build-number=${{ github.run_number }}

# Step 7: pubspec.yaml からバージョンを読み取る
- name: Read version from pubspec.yaml
id: get_version
run: |
echo "version=$(grep '^version: ' pubspec.yaml | sed 's/version: //')" >> $GITHUB_OUTPUT

# Step 8: ビルドされたAPKを成果物としてアップロード
# <--- 変更点: 以下の2つのステップでアップロードするファイルを分岐 --- >

# 分割APKをアップロード
- name: Upload Split APKs Artifact
Copy link
Contributor

Choose a reason for hiding this comment

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

  • 🟡 medium: The contains function is not necessary here. A direct string comparison is simpler and more readable. This comment also applies to the Release with Single APK step.
Suggested change
- name: Upload Split APKs Artifact
if: needs.build_android.outputs.build_type == 'split'

if: ${{ (github.event.inputs.build_type || 'split') == 'split' }}
if: "contains(needs.build_android.outputs.build_type, 'split')"
uses: actions/upload-artifact@v4
with:
name: release-apk # deploy ジョブで参照するアーティファクト名
name: release-apk
path: build/app/outputs/flutter-apk/app-*-release.apk

# 単一APKをアップロード
- name: Upload Single APK Artifact
if: ${{ github.event.inputs.build_type == 'single' }}
if: "contains(needs.build_android.outputs.build_type, 'single')"
uses: actions/upload-artifact@v4
with:
name: release-apk
path: build/app/outputs/apk/release/app-release.apk

deploy:
name: Deploy to GitHub Release
runs-on: ubuntu-latest
needs: build # build ジョブの完了を待つ
if: github.event.inputs.release == 'true' # release 入力が true の場合のみ実行
# 'build_android' ジョブの完了を待つ
needs: build_android
if: github.event.inputs.release == true
steps:
- name: Checkout code # リリースノート生成のためにリポジトリのコードをチェックアウト
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # タグやコミット履歴をすべて取得
fetch-depth: 0

- name: Download APK Artifact
uses: actions/download-artifact@v4
with:
name: release-apk # build ジョブでアップロードされたアーティファクト名
path: artifacts/apk/ # ダウンロード先のディレクトリ

# <--- 変更点: 以下の2つのステップでリリースするファイルを分岐 --- >
name: release-apk
path: artifacts/apk/

# 分割APKをリリース
- name: Release with Split APKs
if: ${{ needs.build.outputs.build_type == 'split' }}
if: "contains(needs.build_android.outputs.build_type, 'split')"
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.build.outputs.version }}
tag_name: ${{ needs.build_android.outputs.version }}
draft: false
prerelease: false
generate_release_notes: ${{ github.event.inputs.generate_release_notes }}
files: artifacts/apk/app-*-release.apk # 分割APKのパス
files: artifacts/apk/app-*-release.apk

# 単一APKをリリース
- name: Release with Single APK
if: ${{ needs.build.outputs.build_type == 'single' }}
if: "contains(needs.build_android.outputs.build_type, 'single')"
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.build.outputs.version }}
tag_name: ${{ needs.build_android.outputs.version }}
draft: false
prerelease: false
generate_release_notes: ${{ github.event.inputs.generate_release_notes }}
files: artifacts/apk/app-release.apk # 単一APKのパス
files: artifacts/apk/app-release.apk
27 changes: 27 additions & 0 deletions lib/models/code_history.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class CodeHistory {
final String id;
final String content;
final DateTime timestamp;

CodeHistory({
required this.id,
required this.content,
required this.timestamp,
});

factory CodeHistory.fromJson(Map<String, dynamic> json) {
return CodeHistory(
id: json['id'],
content: json['content'],
timestamp: DateTime.parse(json['timestamp']),
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'content': content,
'timestamp': timestamp.toIso8601String(),
};
}
}
87 changes: 87 additions & 0 deletions lib/screens/code_history_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/code_history.dart';
import '../services/code_history_service.dart';

class CodeHistoryScreen extends StatefulWidget {
final String problemId;

const CodeHistoryScreen({super.key, required this.problemId});

@override
_CodeHistoryScreenState createState() => _CodeHistoryScreenState();
}

class _CodeHistoryScreenState extends State<CodeHistoryScreen> {
final CodeHistoryService _codeHistoryService = CodeHistoryService();
late Future<List<CodeHistory>> _historyFuture;

@override
void initState() {
super.initState();
_historyFuture = _codeHistoryService.getHistory(widget.problemId);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Code History'),
),
body: FutureBuilder<List<CodeHistory>>(
future: _historyFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('No history found.'));
} else {
final historyList = snapshot.data!;
return ListView.builder(
itemCount: historyList.length,
itemBuilder: (context, index) {
final history = historyList[index];
return ListTile(
title: Text(DateFormat.yMMMd().add_jms().format(history.timestamp)),
subtitle: Text(
history.content,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onTap: () => _showHistoryDetailDialog(history),
);
},
);
}
},
),
);
}

void _showHistoryDetailDialog(CodeHistory history) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(DateFormat.yMMMd().add_jms().format(history.timestamp)),
content: SingleChildScrollView(
child: SelectableText(history.content),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog
Navigator.of(context).pop(history.content); // Pop the screen and return the code
},
child: const Text('Restore'),
),
],
),
);
}
}
Loading
Loading