Flutter | バスケットボールスコアアプリにチーム登録機能を追加

バスケットボールスコアアプリ(ランニングスコア)に新たにチーム登録機能を追加しました。この機能により、チーム情報(チーム名、所在地、登録日、編集日、表示順)を管理できます。

機能概要

新しいチーム登録機能では、以下の項目を扱います。

  • チーム名: チームの名前を指定します。
  • 所在地: チームの所在地を指定します。
  • 登録日: チームがアプリに登録された日付を自動で設定します。
  • 編集日: チーム情報が最後に編集された日付を自動で更新します。
  • 表示順: チームリストの表示順を設定し、ドラッグ&ドロップや編集フォームから変更できます。

チーム登録機能の実装

1. データベースのセットアップ

まず、SQLiteデータベースにチーム情報を保存するためのテーブルを作成します。以下のコードは、database_helper.dartファイルでテーブルを作成するためのものです。

// database_helper.dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
  static Database? _database;

  DatabaseHelper._privateConstructor();

  Future<Database> get database async => _database ??= await _initDatabase();

  Future<Database> _initDatabase() async {
    String path = join(await getDatabasesPath(), 'basketball_score_app.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
    );
  }

  Future _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE teams (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        location TEXT NOT NULL,
        registrationDate TEXT NOT NULL,
        lastEditedDate TEXT NOT NULL,
        displayOrder INTEGER NOT NULL
      )
    ''');
    await db.execute('''
      CREATE TABLE games (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        venue TEXT NOT NULL,
        date TEXT NOT NULL
      )
    ''');
    await db.execute('''
      CREATE TABLE plays (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        team TEXT NOT NULL,
        points INTEGER NOT NULL,
        quarter INTEGER NOT NULL,
        playType TEXT NOT NULL,
        time TEXT NOT NULL,
        playerNumber TEXT NOT NULL,
        gameId INTEGER NOT NULL,
        FOREIGN KEY (gameId) REFERENCES games(id)
      )
    ''');
  }
}

2. チーム情報の管理

次に、team_provider.dartファイルを作成し、チーム情報を管理するためのプロバイダーを設定します。

// team_provider.dart
import 'database_helper.dart';

class Team {
  int? id;
  String name;
  String location;
  String registrationDate;
  String lastEditedDate;
  int displayOrder;

  Team({
    this.id,
    required this.name,
    required this.location,
    required this.registrationDate,
    required this.lastEditedDate,
    required this.displayOrder,
  });

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name,
      'location': location,
      'registrationDate': registrationDate,
      'lastEditedDate': lastEditedDate,
      'displayOrder': displayOrder,
    };
  }

  static Team fromMap(Map<String, dynamic> map) {
    return Team(
      id: map['id'],
      name: map['name'],
      location: map['location'],
      registrationDate: map['registrationDate'],
      lastEditedDate: map['lastEditedDate'],
      displayOrder: map['displayOrder'],
    );
  }
}

class TeamProvider {
  final DatabaseHelper _dbHelper = DatabaseHelper.instance;

  Future<List<Team>> getAllTeams() async {
    final db = await _dbHelper.database;
    var results = await db.query('teams', orderBy: 'displayOrder');
    return results.map((map) => Team.fromMap(map)).toList();
  }

  Future<int> insertTeam(Team team) async {
    final db = await _dbHelper.database;
    return await db.insert('teams', team.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  Future<int> updateTeam(Team team) async {
    final db = await _dbHelper.database;
    return await db.update('teams', team.toMap(),
        where: 'id = ?', whereArgs: [team.id]);
  }

  Future<int> deleteTeam(int id) async {
    final db = await _dbHelper.database;
    return await db.delete('teams', where: 'id = ?', whereArgs: [id]);
  }
}

3. チーム登録画面の実装

次に、team_registration_screen.dartファイルを作成し、チーム登録画面を設定します。

// team_registration_screen.dart
import 'package:flutter/material.dart';
import 'team_provider.dart';

class TeamRegistrationScreen extends StatefulWidget {
  @override
  _TeamRegistrationScreenState createState() => _TeamRegistrationScreenState();
}

class _TeamRegistrationScreenState extends State<TeamRegistrationScreen> {
  final TeamProvider _teamProvider = TeamProvider();
  List<Team> _teams = [];
  TextEditingController _nameController = TextEditingController();
  TextEditingController _locationController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _loadTeams();
  }

  Future<void> _loadTeams() async {
    List<Team> teams = await _teamProvider.getAllTeams();
    setState(() {
      _teams = teams;
    });
  }

  Future<void> _addTeam() async {
    String name = _nameController.text;
    String location = _locationController.text;
    if (name.isNotEmpty && location.isNotEmpty) {
      Team newTeam = Team(
        name: name,
        location: location,
        registrationDate: DateTime.now().toString(),
        lastEditedDate: DateTime.now().toString(),
        displayOrder: _teams.length + 1,
      );
      await _teamProvider.insertTeam(newTeam);
      _nameController.clear();
      _locationController.clear();
      _loadTeams();
    }
  }

  void _showEditTeamDialog(Team team) {
    TextEditingController nameController =
        TextEditingController(text: team.name);
    TextEditingController locationController =
        TextEditingController(text: team.location);
    TextEditingController orderController =
        TextEditingController(text: team.displayOrder.toString());

    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text("チーム情報を編集"),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(
                controller: nameController,
                decoration: InputDecoration(hintText: 'チーム名'),
              ),
              TextField(
                controller: locationController,
                decoration: InputDecoration(hintText: '所在地'),
              ),
              TextField(
                controller: orderController,
                decoration: InputDecoration(hintText: '表示順'),
                keyboardType: TextInputType.number,
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('キャンセル'),
            ),
            TextButton(
              onPressed: () async {
                Team updatedTeam = Team(
                  id: team.id,
                  name: nameController.text,
                  location: locationController.text,
                  registrationDate: team.registrationDate,
                  lastEditedDate: DateTime.now().toString(),
                  displayOrder: int.tryParse(orderController.text) ?? team.displayOrder,
                );
                await _teamProvider.updateTeam(updatedTeam);
                _loadTeams();
                Navigator.of(context).pop();
              },
              child: Text('保存'),
            ),
          ],
        );
      },
    );
  }

  void _showDeleteConfirmationDialog(Team team) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text("チーム削除"),
          content: Text("本当にこのチームを削除しますか?"),
          actions: [
            TextButton(
              onPressed: () async {
                await _teamProvider.deleteTeam(team.id!);
                _loadTeams();
                Navigator.of(context).pop();
              },
              child: Text("はい"),
            ),
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text("いいえ"),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('チーム登録'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0

),
            child: Column(
              children: [
                TextField(
                  controller: _nameController,
                  decoration: InputDecoration(labelText: 'チーム名'),
                ),
                TextField(
                  controller: _locationController,
                  decoration: InputDecoration(labelText: '所在地'),
                ),
                ElevatedButton(
                  onPressed: _addTeam,
                  child: Text('チームを追加'),
                ),
              ],
            ),
          ),
          Expanded(
            child: ReorderableListView(
              onReorder: (oldIndex, newIndex) {
                setState(() {
                  if (newIndex > oldIndex) {
                    newIndex -= 1;
                  }
                  final team = _teams.removeAt(oldIndex);
                  _teams.insert(newIndex, team);
                  for (int i = 0; i < _teams.length; i++) {
                    _teams[i].displayOrder = i + 1;
                    _teamProvider.updateTeam(_teams[i]);
                  }
                });
              },
              children: List.generate(
                _teams.length,
                (index) {
                  final team = _teams[index];
                  return ListTile(
                    key: ValueKey(team.id),
                    title: Text(team.name),
                    subtitle: Text('所在地: ${team.location}'),
                    trailing: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        IconButton(
                          icon: Icon(Icons.edit),
                          onPressed: () => _showEditTeamDialog(team),
                        ),
                        IconButton(
                          icon: Icon(Icons.delete),
                          onPressed: () => _showDeleteConfirmationDialog(team),
                        ),
                        ReorderableDragStartListener(
                          index: index,
                          child: Icon(Icons.drag_handle),
                        ),
                      ],
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

4. ホームスクリーンの設定

最後に、ホームスクリーンにチーム登録機能を追加するために、home_screen.dartファイルを更新します。

// home_screen.dart
import 'package:flutter/material.dart';
import 'score_input_screen.dart';
import 'team_registration_screen.dart';
import 'settings_screen.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ホーム'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => TeamRegistrationScreen()),
                );
              },
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(horizontal: 40, vertical: 20),
                textStyle: TextStyle(fontSize: 18),
              ),
              child: Text('チーム登録'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => ScoreInputScreen()),
                );
              },
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(horizontal: 40, vertical: 20),
                textStyle: TextStyle(fontSize: 18),
              ),
              child: Text('スコア入力'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => SettingsScreen()),
                );
              },
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(horizontal: 40, vertical: 20),
                textStyle: TextStyle(fontSize: 18),
              ),
              child: Text('設定'),
            ),
          ],
        ),
      ),
    );
  }
}

イメージ

ホーム画面

チーム登録画面

最後に

この記事では、Flutterを使用してバスケットボールスコアアプリにチーム登録機能を追加する方法を説明しました。この機能により、ユーザーはチーム情報を管理し、アプリをより便利に活用できます。

関連記事

コメント

タイトルとURLをコピーしました