ソースコード source code

下記アプリの主要なソースコードを公開しています。アプリ開発の参考になれば幸いです。

画像等が別途必要ですので下記情報のみでアプリが完成するものではありません。 アプリは少しずつ機能拡張していますのでストア公開されているアプリと内容が異なる場合があります。 コードはコピーして自由にお使いいただけます。ただし著作権は放棄しておりませんので全部の再掲載はご遠慮ください。部分的に再掲載したり、改変して再掲載するのは構いません。 自身のアプリ作成の参考として個人使用・商用問わず自由にお使いいただけます。 コード記述のお手本を示すものではありません。ミニアプリですので変数名などさほど気遣いしていない部分も有りますし間違いも有るかと思いますので参考程度にお考え下さい。 他の賢者の皆様が公開されているコードを参考にした箇所も含まれます。Flutter開発の熟練者が書いたコードではありません。 エンジニア向け技術情報共有サービスではありませんので説明は省いています。 GitHubなどへの公開は予定しておりません。

下記コードの最終ビルド日: 2023-02-06

pubspec.yaml

name: bingoonline
description: Bingo Online

# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=2.19.0 <3.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.5
  package_info_plus: ^3.0.2
  shared_preferences: ^2.0.17
  video_player: ^2.5.1
  flutter_svg: ^2.0.0+1
  http: ^0.13.5
  flutter_localizations:    #多言語ライブラリの本体    # .arbファイルを更新したら flutter gen-l10n
    sdk: flutter
  intl: ^0.17.0-nullsafety.1     #多言語やフォーマッタなどの関連ライブラリ
  flutter_tts: ^3.6.3
  google_mobile_ads: ^2.3.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_launcher_icons: ^0.11.0    #flutter pub run flutter_launcher_icons
  flutter_native_splash: ^2.2.16     #flutter pub run flutter_native_splash:create


  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.1

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

flutter_icons:
  android: "launcher_icon"
  ios: true
  image_path: "assets/icon/icon.png"
  adaptive_icon_background: "assets/icon/icon_back.png"
  adaptive_icon_foreground: "assets/icon/icon_fore.png"

flutter_native_splash:
  color: '#3DF99D'
  image: 'assets/image/splash.png'
  color_dark: '#3DF99D'
  image_dark: 'assets/image/splash.png'
  fullscreen: true
  android_12:
    icon_background_color: '#3DF99D'
    image: 'assets/image/splash.png'
    icon_background_color_dark: '#3DF99D'
    image_dark: 'assets/image/splash.png'

# The following section is specific to Flutter packages.
flutter:
  generate: true    #自動生成フラグの有効化

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  assets:
    - assets/image/
    - assets/movie/

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

lib/ad_mob.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-02-06
///

import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io';

class AdMob {
  late BannerAd _adMobBanner;
  bool _isAdMob = false;
  AdMob() { //constructor
    String adBannerUnitId = '';
    if (!kIsWeb && Platform.isAndroid) {
      adBannerUnitId = 'ca-app-pub-0000000000000000/0000000000';
      _isAdMob = true;
    } else if (!kIsWeb && Platform.isIOS) {
      adBannerUnitId = 'ca-app-pub-0000000000000000/0000000000';
      _isAdMob = true;
    }
    if (_isAdMob) {
      _adMobBanner = BannerAd(
        adUnitId: adBannerUnitId,
        size: AdSize.banner,
        request: const AdRequest(),
        listener: const BannerAdListener(),
      );
    }
  }
  void load() {
    if (_isAdMob) {
      _adMobBanner.load();
    }
  }
  void dispose() {
    if (_isAdMob) {
      _adMobBanner.dispose();
    }
  }
  Widget getAdBannerWidget() {
    if (_isAdMob) {
      return Container(
        alignment: Alignment.center,
        width: _adMobBanner.size.width.toDouble(),
        height: _adMobBanner.size.height.toDouble(),
        child: AdWidget(ad: _adMobBanner),
      );
    } else {
      return Container();
    }
  }
  double getAdBannerHeight() {
    if (_isAdMob) {
      return _adMobBanner.size.height.toDouble();
    } else {
      return 150;  //for web
    }
  }
}

lib/card.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/game_state.dart';
import 'package:bingoonline/progress_table.dart';
import 'package:bingoonline/ad_mob.dart';

class CardPage extends StatefulWidget {
  const CardPage({super.key});
  @override
  State<CardPage> createState() => _CardPageState();
}
class _CardPageState extends State<CardPage> {
  late Timer _timer;
  bool _timerToggle = false;
  final AdMob _adMob = AdMob();
  @override
  void initState() {
    super.initState();
    GameState.cardCellSetOpenClose();
    GameState.setConnectionCodeCardWatchFlag(true);
    _timer = Timer.periodic(const Duration(seconds: 5), (timer) {
      _timerToggle = _timerToggle ? false : true;
      GameState.connectionCodeCardWatch();
      setState(() {});
      if (GameState.getConnectionCodeCardWatchFlag() == false) {
        _timer.cancel();
      }
    });
    _adMob.load();
  }
  @override
  void dispose() {
    GameState.setConnectionCodeCardWatchFlag(false);
    _timer.cancel();
    _adMob.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    ProgressTable progressTable = ProgressTable();
    int connectionCodeCard = GameState.getConnectionCodeCard();
    return Scaffold(
      backgroundColor: const Color.fromRGBO(197, 172, 119, 1.0),
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        title: Text((connectionCodeCard == 0) ? AppLocalizations.of(context)!.card : connectionCodeCard.toString()),
        backgroundColor: ConstValue.colorCardAccent,
      ),
      body: Column(children:[
        Expanded(
          child: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(
                children: [
                  Container(
                    decoration: const BoxDecoration(
                      boxShadow: [
                        BoxShadow(
                          color: Color.fromRGBO(0,0,0,0.1),
                          spreadRadius: 2,
                          blurRadius: 2,
                          offset: Offset(0,1), // changes position of shadow
                        ),
                      ],
                    ),
                    child: Table(
                      border: TableBorder.all(width: 4, color: Colors.amberAccent),
                      columnWidths: const <int, TableColumnWidth>{
                        0: FlexColumnWidth(1),
                        1: FlexColumnWidth(1),
                        2: FlexColumnWidth(1),
                        3: FlexColumnWidth(1),
                        4: FlexColumnWidth(1),
                      },
                      children: [
                        TableRow(children:[_cellBingo('B'),_cellBingo('I'),_cellBingo('N'),_cellBingo('G'),_cellBingo('O')]),
                        TableRow(children:[_cellNumber(0),_cellNumber(5),_cellNumber(10),_cellNumber(15),_cellNumber(20)]),
                        TableRow(children:[_cellNumber(1),_cellNumber(6),_cellNumber(11),_cellNumber(16),_cellNumber(21)]),
                        TableRow(children:[_cellNumber(2),_cellNumber(7),_cellNumber(12),_cellNumber(17),_cellNumber(22)]),
                        TableRow(children:[_cellNumber(3),_cellNumber(8),_cellNumber(13),_cellNumber(18),_cellNumber(23)]),
                        TableRow(children:[_cellNumber(4),_cellNumber(9),_cellNumber(14),_cellNumber(19),_cellNumber(24)]),
                      ],
                    ),
                  ),
                  Container(height:30),
                  progressTable.table(),
                  Container(height:80),
                ],
              ),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
          child: SizedBox(
            width: double.infinity,
            child: _adMob.getAdBannerWidget(),
          ),
        ),
      ]),
    );
  }
  Widget _cellBingo(String str) {
    return Container(
      padding: const EdgeInsets.all(5),
      color: Colors.amberAccent,
      child: Center(child: Text(str,
        style: TextStyle(
          color: _timerToggle ? Colors.orange : Colors.white,
        ),
      )),
    );
  }
  Widget _cellNumber(int ptr) {
    final int num = GameState.cardCellPtrGetNumber(ptr);
    final bool openClose = GameState.cardCellPtrGetOpenBool(ptr);
    final String numStr = (num == 0) ? 'FREE' : num.toString();
    final double fontSize = (num == 0) ? 20 : 30;
    Container openMark = Container(
      margin: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: const Color.fromRGBO(255,0,0,0.3),
        border: Border.all(width: 4,color: const Color.fromRGBO(255,50,0,0.2)),
      ),
    );
    Container closeMark = Container();
    return AspectRatio(
      aspectRatio: 1,
      child: Stack(
        children: [
          Container(
            color: Colors.white,
            child: Center(child: Text(numStr,
              style: TextStyle(
                fontSize: fontSize,
              ),
            )),
          ),
          openClose ? openMark : closeMark,
        ],
      ),
    );
  }

}

lib/card_cell.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

class CardCell {
  int number = 0;
  int open = 0;
  CardCell(this.number, this.open); //constructor
  getNumber() {
    return number;
  }
  getOpen() {
    return open;
  }
  setNumber(int number) {
    this.number = number;
  }
  setOpen(int open) {
    this.open = open;
  }
}

lib/card_init.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/game_state.dart';
import 'package:bingoonline/card.dart';
import 'package:bingoonline/card_offline.dart';
import 'package:bingoonline/ad_mob.dart';

class CardInitPage extends StatefulWidget {
  const CardInitPage({super.key});
  @override
  State<CardInitPage> createState() => _CardInitPageState();
}
class _CardInitPageState extends State<CardInitPage> {
  bool _onlineButtonDisable = false;
  String _message = '';
  String _messageConnectionCodeCard = '';
  int _inputNumber = 0;
  final AdMob _adMob = AdMob();
  @override
  void initState() {
    super.initState();
    GameState.cardIsInitialToNew();
    GameState.setConnectionCodeCardWatchFlag(false);
    _adMob.load();
  }
  @override
  void dispose() {
    _adMob.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    final int number = GameState.getConnectionCodeCard();
    _messageConnectionCodeCard = '${AppLocalizations.of(context)!.currentConnectionCode}: ${number == 0 ? AppLocalizations.of(context)!.none : number.toString()}';
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        title: Text(AppLocalizations.of(context)!.cardSetting),
        backgroundColor: ConstValue.colorCardAccent,
      ),
      body: Column(children:[
        Expanded(
          child: GestureDetector(
            onTap: () => FocusScope.of(context).unfocus(),  //背景タップでキーボードを仕舞う
            child: SingleChildScrollView(
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(top: 0, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          AppLocalizations.of(context)!.enterTheConnectionCodeIssuedByTheOrganizerToJoinTheGame,
                          style: const TextStyle(
                            fontSize: 12,
                          ),
                          textAlign: TextAlign.left,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 6, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          AppLocalizations.of(context)!.connectionCode6DigitNumber,
                          style: const TextStyle(
                            fontSize: 10,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 1, left: 60, right: 60, bottom: 0),
                      child: TextField(
                        textAlign: TextAlign.center,
                        keyboardType: TextInputType.number, //数字のキーボード
                        inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                        //初期値を設定する場合 controller: TextEditingController(text: 'aaa'),
                        decoration: const InputDecoration(
                          hintText: '000000',
                          hintStyle: TextStyle(color: Color.fromRGBO(0,0,0,0.1)),
                        ),
                        style: const TextStyle(
                          fontSize: 30,
                        ),
                        onChanged: (text) {
                          _inputNumber = int.tryParse(text) ?? 0;
                        },
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 6, left: 0, right: 0, bottom: 0),
                      child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children:[
                            _onlineGame(),
                            const SizedBox(width:10,height:20),
                            _offlineGame(),
                          ]
                      )
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 24, left: 0, right: 0, bottom: 24),
                      child: SizedBox(
                        child: Text(
                          _message,
                          style: const TextStyle(
                            fontSize: 12,
                            color: Colors.redAccent,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                    _border(),
                    Padding(
                      padding: const EdgeInsets.only(top: 18, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          AppLocalizations.of(context)!.replaceWithANewCard,
                          style: const TextStyle(
                            fontSize: 12,
                          ),
                          textAlign: TextAlign.left,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 4, left: 0, right: 0, bottom: 18),
                      child: _newCard(),
                    ),
                    _border(),
                    Padding(
                      padding: const EdgeInsets.only(top: 18, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          _messageConnectionCodeCard,
                          style: const TextStyle(
                            fontSize: 12,
                            color: ConstValue.colorCardAccent,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 4, left: 0, right: 0, bottom: 36),
                      child: _cardPage(),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
          child: SizedBox(
            width: double.infinity,
            child: _adMob.getAdBannerWidget(),
          ),
        ),
      ]),
    );
  }
  Widget _border() {
    return Container(
      decoration: BoxDecoration(
        border: Border(
          top: BorderSide(
            color: Colors.grey.shade300,
            width: 1,
          ),
        ),
      ),
    );
  }
  Widget _onlineGame() {
    return OutlinedButton.icon(
      onPressed: _onlineButtonDisable ? null : () {
        _onlineButtonDisable = true;
        _connectionCodeCardJoin();
      },
      style: OutlinedButton.styleFrom(
        foregroundColor: ConstValue.colorCardAccent,
        side: const BorderSide(
          color: ConstValue.colorCardAccent,
        ),
      ),
      icon: const Icon(Icons.cloud_outlined),
      label: Text(AppLocalizations.of(context)!.joinOnline),
    );
  }
  Widget _offlineGame() {
    return OutlinedButton.icon(
      onPressed: () {
        setState(() {
          GameState.setConnectionCodeCard(0);
          _message = AppLocalizations.of(context)!.joinedOfflineWithoutUsingAConnectionCode;
        });
      },
      style: OutlinedButton.styleFrom(
        foregroundColor: ConstValue.colorCardAccent,
        side: const BorderSide(
          color: ConstValue.colorCardAccent,
        ),
      ),
      icon: const Icon(Icons.cloud_off_outlined),
      label: Text(AppLocalizations.of(context)!.joinOffline),
    );
  }
  Widget _newCard() {
    return OutlinedButton.icon(
      onPressed: () {
        showDialog(
          context: context,
          builder: (_) {
            return AlertDialog(
              title: Text(AppLocalizations.of(context)!.newCard),
              content: Text(AppLocalizations.of(context)!.replaceWithANewCardIsItOk),
              actions: <Widget>[
                TextButton(
                  child: Text(AppLocalizations.of(context)!.cancel),
                  onPressed: () => Navigator.pop(context),
                ),
                TextButton(
                  child: Text(AppLocalizations.of(context)!.ok),
                  onPressed: () => {
                    GameState.cardNew(),
                    Navigator.pop(context),
                  },
                ),
              ],
            );
          },
        );
      },
      style: OutlinedButton.styleFrom(
        foregroundColor: ConstValue.colorCardAccent,
        side: const BorderSide(
          color: ConstValue.colorCardAccent,
        ),
      ),
      icon: const Icon(Icons.insert_drive_file_outlined),
      label: Text(AppLocalizations.of(context)!.newCard),
    );
  }
  Widget _cardPage() {
    return Directionality(
      textDirection: TextDirection.rtl,
      child: ElevatedButton.icon(
        onPressed: () {
          if (GameState.getConnectionCodeCard() == 0) {
            Navigator.push(
              context,
              MaterialPageRoute<void>(
                builder: (BuildContext context) => const CardOfflinePage(),
              ),
            );
          } else{
            Navigator.push(
              context,
              MaterialPageRoute<void>(
                builder: (BuildContext context) => const CardPage(),
              ),
            );
          }
        },
        style: ElevatedButton.styleFrom(
          backgroundColor: ConstValue.colorCardAccent,
          elevation: 2,
        ),
        icon: const Icon(Icons.chevron_left),
        label: Text(AppLocalizations.of(context)!.bingoCard),
      )
    );
  }
  void _connectionCodeCardJoin() async {
    if (_inputNumber < 100000 || _inputNumber > 999999) {
      setState(() {
        _message = AppLocalizations.of(context)!.wrongNumber;
      });
      _onlineButtonDisable = false;
      return;
    }
    final int number = await GameState.connectionCodeCardJoin(_inputNumber);
    for (int i = 5; i > 0; i--) {
      await Future.delayed(const Duration(milliseconds: 1000));
      setState(() {
        _message = '${AppLocalizations.of(context)!.connecting}.$i';
      });
    }
    if (number == 0) {
      setState(() {
        _message = AppLocalizations.of(context)!.wrongNumberOrHasExpired;
      });
    } else {
      setState(() {
        _message = '${AppLocalizations.of(context)!.connected}: $number';
      });
    }
    _onlineButtonDisable = false;
  }
}

lib/card_offline.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/game_state.dart';
import 'package:bingoonline/ad_mob.dart';

class CardOfflinePage extends StatefulWidget {
  const CardOfflinePage({super.key});
  @override
  State<CardOfflinePage> createState() => _CardOfflinePageState();
}
class _CardOfflinePageState extends State<CardOfflinePage> {
  final AdMob _adMob = AdMob();
  @override
  void initState() {
    super.initState();
    GameState.cardCellSetOpenClose();
    _adMob.load();
  }
  @override
  void dispose() {
    _adMob.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color.fromRGBO(197, 172, 119, 1.0),
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        title: Text(AppLocalizations.of(context)!.cardOffline),
        backgroundColor: ConstValue.colorCardOfflineAccent,
      ),
      body: Column(children:[
        Expanded(
          child: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(
                children: [
                  Container(
                    decoration: const BoxDecoration(
                      boxShadow: [
                        BoxShadow(
                          color: Color.fromRGBO(0,0,0,0.1),
                          spreadRadius: 2,
                          blurRadius: 2,
                          offset: Offset(0,1), // changes position of shadow
                        ),
                      ],
                    ),
                    child: Table(
                      border: TableBorder.all(width: 4, color: Colors.amberAccent),
                      columnWidths: const <int, TableColumnWidth>{
                        0: FlexColumnWidth(1),
                        1: FlexColumnWidth(1),
                        2: FlexColumnWidth(1),
                        3: FlexColumnWidth(1),
                        4: FlexColumnWidth(1),
                      },
                      children: [
                        TableRow(children:[_cellBingo('B'),_cellBingo('I'),_cellBingo('N'),_cellBingo('G'),_cellBingo('O')]),
                        TableRow(children:[
                          InkWell(onTap:(){_cellTap(0);},child:_cellNumber(0)),
                          InkWell(onTap:(){_cellTap(5);},child:_cellNumber(5)),
                          InkWell(onTap:(){_cellTap(10);},child:_cellNumber(10)),
                          InkWell(onTap:(){_cellTap(15);},child:_cellNumber(15)),
                          InkWell(onTap:(){_cellTap(20);},child:_cellNumber(20)),
                        ]),
                        TableRow(children:[
                          InkWell(onTap:(){_cellTap(1);},child:_cellNumber(1)),
                          InkWell(onTap:(){_cellTap(6);},child:_cellNumber(6)),
                          InkWell(onTap:(){_cellTap(11);},child:_cellNumber(11)),
                          InkWell(onTap:(){_cellTap(16);},child:_cellNumber(16)),
                          InkWell(onTap:(){_cellTap(21);},child:_cellNumber(21)),
                        ]),
                        TableRow(children:[
                          InkWell(onTap:(){_cellTap(2);},child:_cellNumber(2)),
                          InkWell(onTap:(){_cellTap(7);},child:_cellNumber(7)),
                          InkWell(onTap:(){_cellTap(12);},child:_cellNumber(12)),
                          InkWell(onTap:(){_cellTap(17);},child:_cellNumber(17)),
                          InkWell(onTap:(){_cellTap(22);},child:_cellNumber(22)),
                        ]),
                        TableRow(children:[
                          InkWell(onTap:(){_cellTap(3);},child:_cellNumber(3)),
                          InkWell(onTap:(){_cellTap(8);},child:_cellNumber(8)),
                          InkWell(onTap:(){_cellTap(13);},child:_cellNumber(13)),
                          InkWell(onTap:(){_cellTap(18);},child:_cellNumber(18)),
                          InkWell(onTap:(){_cellTap(23);},child:_cellNumber(23)),
                        ]),
                        TableRow(children:[
                          InkWell(onTap:(){_cellTap(4);},child:_cellNumber(4)),
                          InkWell(onTap:(){_cellTap(9);},child:_cellNumber(9)),
                          InkWell(onTap:(){_cellTap(14);},child:_cellNumber(14)),
                          InkWell(onTap:(){_cellTap(19);},child:_cellNumber(19)),
                          InkWell(onTap:(){_cellTap(24);},child:_cellNumber(24)),
                        ]),
                      ],
                    ),
                  ),
                  Container(height:80),
                ],
              ),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
          child: SizedBox(
            width: double.infinity,
            child: _adMob.getAdBannerWidget(),
          ),
        ),
      ]),
    );
  }
  Widget _cellBingo(String str) {
    return Container(
      padding: const EdgeInsets.all(5),
      color: Colors.amberAccent,
      child: Center(child: Text(str,
        style: const TextStyle(
          color: ConstValue.colorCardOfflineAccent,
        ),
      )),
    );
  }
  Widget _cellNumber(int ptr) {
    final int num = GameState.cardCellPtrGetNumber(ptr);
    final bool openClose = GameState.cardCellPtrGetOpenBool(ptr);
    final String numStr = (num == 0) ? 'FREE' : num.toString();
    final double fontSize = (num == 0) ? 20 : 30;
    Container openMark = Container(
      margin: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: const Color.fromRGBO(255,0,0,0.3),
        border: Border.all(width: 4,color: const Color.fromRGBO(255,50,0,0.2)),
      ),
    );
    Container closeMark = Container();
    return AspectRatio(
      aspectRatio: 1,
      child: Stack(
        children: [
          Container(
            color: Colors.white,
            child: Center(child: Text(numStr,
              style: TextStyle(
                fontSize: fontSize,
              ),
            )),
          ),
          openClose ? openMark : closeMark,
        ],
      ),
    );
  }
  void _cellTap(int ptr) {
    final int num = GameState.cardCellPtrGetNumber(ptr);
    if (GameState.ballHistoryContain(num)) {
      setState(() {
        GameState.ballHistoryRemove(num);
      });
    } else {
      setState(() {
        GameState.ballHistoryAdd(num);
      });
    }
  }
}

lib/const_value.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';

class ConstValue {
  //pref
  static const String prefLanguageCode = 'languageCode';
  static const String prefSpeakFlag = 'speakFlag';
  static const String prefSpeakVoiceIndex = 'speakVoiceIndex';
  static const String prefConnectionCodeMachine = 'connectionCodeMachine';
  static const String prefConnectionCodeCard = 'connectionCodeCard';
  static const String prefBallHistories = 'ballHistories';
  static const String prefCardNumbers = 'cardNumbers';
  //image
  static const String topBack = 'assets/image/machine_card.webp';
  //color
  static const Color colorSettingAccent = Color.fromARGB(255, 24, 89, 185);
  static const Color colorMachineAccent = Color.fromARGB(255, 57, 182, 124);
  static const Color colorCardAccent = Color.fromARGB(255, 232, 163, 0);
  static const Color colorCardOfflineAccent = Color.fromARGB(255, 201, 63, 120);
  //api
  //(((非公開)))
  //string
  static const Map<String,String> languageCode = {
    'en': 'English',
    'bg': 'български език',
    'cs': 'Čeština',
    'da': 'dansk',
    'de': 'Deutsch',
    'el': 'Ελληνικά',
    'es': 'Español',
    'et': 'eesti keel',
    'fi': 'Suomen kieli',
    'fr': 'Français',
    'hu': 'magyar nyelv',
    'it': 'Italiano',
    'ja': '日本語',
    'lt': 'lietuvių kalba',
    'lv': 'Latviešu',
    'nl': 'Nederlands',
    'pl': 'Polski',
    'pt': 'Português',
    'ro': 'limba română',
    'ru': 'русский',
    'sk': 'Slovenčina',
    'sv': 'svenska',
    'th': 'ภาษาไทย',
    'zh': '中文',
  };

}

lib/game_state.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:http/http.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/preferences.dart';
import 'package:bingoonline/card_cell.dart';

class GameState {

  (((非公開)))

}

lib/history_table.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';

import 'package:bingoonline/game_state.dart';

class HistoryTable {
  Widget table() {
    return Container(
      color: Colors.white,
      child: Table(
        border: TableBorder.all(width: 1, color: Colors.greenAccent),
        columnWidths: const <int, TableColumnWidth>{
          0: FlexColumnWidth(1),
          1: FlexColumnWidth(1),
          2: FlexColumnWidth(1),
          3: FlexColumnWidth(1),
          4: FlexColumnWidth(1),
          5: FlexColumnWidth(1),
          6: FlexColumnWidth(1),
          7: FlexColumnWidth(1),
          8: FlexColumnWidth(1),
          9: FlexColumnWidth(1),
        },
        children: [
          TableRow(children: [
            cellHistory(0),
            cellHistory(1),
            cellHistory(2),
            cellHistory(3),
            cellHistory(4),
            cellHistory(5),
            cellHistory(6),
            cellHistory(7),
            cellHistory(8),
            cellHistory(9),
          ]),
          TableRow(children: [
            cellHistory(10),
            cellHistory(11),
            cellHistory(12),
            cellHistory(13),
            cellHistory(14),
            cellHistory(15),
            cellHistory(16),
            cellHistory(17),
            cellHistory(18),
            cellHistory(19),
          ]),
          TableRow(children: [
            cellHistory(20),
            cellHistory(21),
            cellHistory(22),
            cellHistory(23),
            cellHistory(24),
            cellHistory(25),
            cellHistory(26),
            cellHistory(27),
            cellHistory(28),
            cellHistory(29),
          ]),
          TableRow(children: [
            cellHistory(30),
            cellHistory(31),
            cellHistory(32),
            cellHistory(33),
            cellHistory(34),
            cellHistory(35),
            cellHistory(36),
            cellHistory(37),
            cellHistory(38),
            cellHistory(39),
          ]),
          TableRow(children: [
            cellHistory(40),
            cellHistory(41),
            cellHistory(42),
            cellHistory(43),
            cellHistory(44),
            cellHistory(45),
            cellHistory(46),
            cellHistory(47),
            cellHistory(48),
            cellHistory(49),
          ]),
          TableRow(children: [
            cellHistory(50),
            cellHistory(51),
            cellHistory(52),
            cellHistory(53),
            cellHistory(54),
            cellHistory(55),
            cellHistory(56),
            cellHistory(57),
            cellHistory(58),
            cellHistory(59),
          ]),
          TableRow(children: [
            cellHistory(60),
            cellHistory(61),
            cellHistory(62),
            cellHistory(63),
            cellHistory(64),
            cellHistory(65),
            cellHistory(66),
            cellHistory(67),
            cellHistory(68),
            cellHistory(69),
          ]),
          TableRow(children: [
            cellHistory(70),
            cellHistory(71),
            cellHistory(72),
            cellHistory(73),
            cellHistory(74),
            cellHistory(99),
            cellHistory(99),
            cellHistory(99),
            cellHistory(99),
            cellHistory(99),
          ]),
        ],
      ),
    );
  }
  Widget cellHistory(int ptr) {
    if (GameState.ballHistoryLength() > ptr) {
      Color bgColor = (GameState.ballHistoryLength() - 1 == ptr) ? Colors.orangeAccent : Colors.yellow;
      return Container(
        padding: const EdgeInsets.all(1),
        color: bgColor,
        child: Center(child: Text(GameState.ballHistoryValue(ptr).toString(),
          style: const TextStyle(
            fontSize: 16,
          ),
        )),
      );
    } else {
      return Container();
    }
  }

}

lib/machine.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/game_state.dart';
import 'package:bingoonline/tts_state.dart';
import 'package:bingoonline/TextToSpeech.dart';
import 'package:bingoonline/history_table.dart';
import 'package:bingoonline/progress_table.dart';
import 'package:bingoonline/ad_mob.dart';

class MachinePage extends StatefulWidget {
  const MachinePage({super.key, required this.machineSpeed});
  final int machineSpeed;
  @override
  State<MachinePage> createState() => _MachinePageState();
}
class _MachinePageState extends State<MachinePage> {
  late VideoPlayerController _videoController;
  late double _ballSizeMax;
  late double _ballTextSizeMax;
  Duration _movieDuration = const Duration(milliseconds: 7000);
  double _ballSize = 0;
  double _ballTextSize = 0;
  double _ballTextMarginSize = 0;
  Duration _ballAnimationDuration = const Duration(milliseconds: 0);
  final Duration _ballAnimationDurationMin = const Duration(milliseconds: 0);
  final Duration _ballAnimationDurationMax = const Duration(milliseconds: 500);
  int _ballNumber = 0;
  bool _startButtonIsDisabled = false;
  String _message = '';
  bool _speakFlag = true;
  int _speakVoiceIndex = 0;
  final TextToSpeech _textToSpeech = TextToSpeech();
  final AdMob _adMob = AdMob();
  //
  @override
  void initState() {
    super.initState();
    _videoController = VideoPlayerController.asset('assets/movie/bingo4mbps.mp4');
    _videoController.initialize().then((_) {
      setState((){});
    });
    if (widget.machineSpeed == 1) {
      _movieDuration = const Duration(milliseconds: 6900);
    } else if (widget.machineSpeed == 2) {
      _movieDuration = const Duration(milliseconds: 3300);
    } else if (widget.machineSpeed == 3) {
      _movieDuration = const Duration(milliseconds: 2100);
    } else if (widget.machineSpeed == 4) {
      _movieDuration = const Duration(milliseconds: 1600);
    } else if (widget.machineSpeed == 5) {
      _movieDuration = const Duration(milliseconds: 1100);
    }
    (() async {
      _speakFlag = await TtsState.getSpeakFlag();
      _speakVoiceIndex = await TtsState.getSpeakVoiceIndex();
      await _textToSpeech.setSpeakVoiceFromIndex(_speakVoiceIndex);
    })();
    _adMob.load();
  }
  @override
  void dispose() {
    _videoController.dispose();
    _adMob.dispose();
    super.dispose();
  }
  //
  int nextBallNumber() {
    List<int> numbers = [];
    for (int i = 1; i <= 75; i++) {
      if (GameState.ballHistoryContain(i) == false) {
        numbers.add(i);
      }
    }
    if (numbers.isEmpty) {
      return 0;
    }
    numbers.shuffle();
    return numbers[0];
  }
  //
  @override
  Widget build(BuildContext context) {
    HistoryTable historyTable = HistoryTable();
    ProgressTable progressTable = ProgressTable();
    _ballSizeMax = MediaQuery.of(context).size.width / 2;
    _ballTextSizeMax = _ballSizeMax * 0.8;
    _ballTextMarginSize = (_ballSizeMax - _ballTextSizeMax) / 2;
    final int connectionCodeMachine = GameState.getConnectionCodeMachine();
    return Scaffold(
      backgroundColor: const Color.fromRGBO(40,245,146,1),
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        title: Text((connectionCodeMachine == 0) ? AppLocalizations.of(context)!.lotteryMachine : connectionCodeMachine.toString()),
        backgroundColor: ConstValue.colorMachineAccent,
      ),
      body: Column(children:[
        Expanded(
          child: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(
                children: [
                  Stack(
                    children:[
                      AspectRatio(
                        aspectRatio: 1,
                        child: VideoPlayer(_videoController),
                      ),
                      Positioned(
                        bottom: 0,
                        right: 0,
                        child: OutlinedButton(
                          onPressed: _startButtonIsDisabled ? null : () {
                            _startButtonIsDisabled = true;
                            setState(() {
                              _ballAnimationDuration = _ballAnimationDurationMin;
                              _ballSize = 0;
                              _ballTextSize = 0;
                            });
                            _videoController.setPlaybackSpeed(widget.machineSpeed.toDouble());
                            _videoController.seekTo(Duration.zero).then((_) => {
                              _videoController.play(),
                              Future.delayed(_movieDuration).then((_) => {
                                setState(() {
                                  _ballNumber = nextBallNumber();
                                  _ballAnimationDuration = _ballAnimationDurationMax;
                                  _ballSize = _ballSizeMax;
                                  _ballTextSize = _ballTextSizeMax;
                                }),
                                Future.delayed(_ballAnimationDurationMax).then((_) {
                                  if (_ballNumber != 0 && _speakFlag) {
                                    _textToSpeech.speak(_ballNumber.toString());
                                  }
                                }),
                                Future.delayed(_ballAnimationDurationMax * 2).then((_) async {
                                  _message = '';
                                  if (_ballNumber == 0) {
                                    _message = AppLocalizations.of(context)!.ended;
                                  } else {
                                    bool ret = await GameState.ballHistoryAdd(_ballNumber);
                                    if (ret == false) {
                                      if (!mounted) { return; }
                                      _message = AppLocalizations.of(context)!.failedToConnect;
                                    }
                                  }
                                  setState(() {});
                                  _startButtonIsDisabled = false;
                                }),
                              }),
                            });
                          },
                          style: OutlinedButton.styleFrom(
                            foregroundColor: Colors.white,
                            side: const BorderSide(
                              color: Colors.white,
                            ),
                          ),
                          child: Text(AppLocalizations.of(context)!.start),
                        ),
                      ),
                      Stack(
                        children:[
                          SizedBox(
                            child: AnimatedContainer(
                              duration: _ballAnimationDuration,
                              width: _ballSize,
                              height: _ballSize,
                              child: SvgPicture.asset('assets/image/ball.svg'),
                            ),
                          ),
                          Positioned(
                            top: _ballTextMarginSize,
                            left: _ballTextMarginSize,
                            child: SizedBox(
                              child: AnimatedContainer(
                                duration: _ballAnimationDuration,
                                width: _ballTextSize,
                                height: _ballTextSize,
                                child: FittedBox(
                                  fit: BoxFit.fitWidth,
                                  child: Text((_ballNumber == 0) ? 'END' : _ballNumber.toString(),style: const TextStyle(fontWeight: FontWeight.w700)),
                                ),
                              ),
                            ),
                          ),
                        ]
                      ),
                    ]
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 2, left: 0, right: 0, bottom: 30),
                    child: SizedBox(
                      child: Text(
                        _message,
                        style: const TextStyle(
                          fontSize: 10,
                          color: Colors.redAccent,
                        ),
                        textAlign: TextAlign.center,
                      ),
                    ),
                  ),
                  historyTable.table(),
                  Container(height:30),
                  progressTable.table(),
                  Container(height:80),
                ],
              ),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
          child: SizedBox(
            width: double.infinity,
            child: _adMob.getAdBannerWidget(),
          ),
        ),
      ]),
    );
  }

}

lib/machine_init.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/machine.dart';
import 'package:bingoonline/game_state.dart';
import 'package:bingoonline/ad_mob.dart';

class MachineInitPage extends StatefulWidget {
  const MachineInitPage({super.key});
  @override
  State<MachineInitPage> createState() => _MachineInitPageState();
}
class _MachineInitPageState extends State<MachineInitPage> {
  int _machineSpeed = 1;
  bool _onlineButtonDisable = false;
  String _connectionMessage = '';
  final AdMob _adMob = AdMob();
  @override
  void initState() {
    super.initState();
    _adMob.load();
  }
  @override
  void dispose() {
    _adMob.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        title: Text(AppLocalizations.of(context)!.machineSetting),
        backgroundColor: ConstValue.colorMachineAccent,
      ),
      body: Column(children:[
        Expanded(
          child: GestureDetector(
            onTap: () => FocusScope.of(context).unfocus(),  //背景タップでキーボードを仕舞う
            child: SingleChildScrollView(
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(top: 0, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          AppLocalizations.of(context)!.giveTheConnectionCodeToTheParticipant,
                          style: const TextStyle(
                            fontSize: 12,
                          ),
                          textAlign: TextAlign.left,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 6, left: 0, right: 0, bottom: 0),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children:[
                          _onlineGame(),
                          const SizedBox(width:10,height:1),
                          _offlineGame(),
                        ]
                      )
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 12, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          AppLocalizations.of(context)!.connectionCode,
                          style: const TextStyle(
                            fontSize: 10,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 2, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          GameState.getConnectionCodeMachineStr(),
                          style: const TextStyle(
                            fontSize: 80,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 2, left: 0, right: 0, bottom: 24),
                      child: SizedBox(
                        child: Text(
                          _connectionMessage,
                          style: const TextStyle(
                            fontSize: 12,
                            color: Colors.redAccent,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                    _border(),
                    Padding(
                      padding: const EdgeInsets.only(top: 12, left: 0, right: 0, bottom: 0),
                      child: SizedBox(
                        child: Text(
                          AppLocalizations.of(context)!.initializeTheLotteryResults,
                          style: const TextStyle(
                            fontSize: 12,
                          ),
                          textAlign: TextAlign.left,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 2, left: 0, right: 0, bottom: 12),
                      child: _newGame(),
                    ),
                    _border(),
                    Padding(
                      padding: const EdgeInsets.only(top: 20, left: 0, right: 0, bottom: 0),
                      child: _machinePage(),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 6, left: 0, right: 0, bottom: 36),
                      child: Center(
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children:[
                            Text(AppLocalizations.of(context)!.speed),
                            const SizedBox(width:10),
                            DropdownButton(
                              items: const [
                                DropdownMenuItem(value:1,child:Text('1')),
                                DropdownMenuItem(value:2,child:Text('2')),
                                DropdownMenuItem(value:3,child:Text('3')),
                                DropdownMenuItem(value:4,child:Text('4')),
                                DropdownMenuItem(value:5,child:Text('5')),
                              ],
                              onChanged: (int? value) {
                                setState(() {
                                  _machineSpeed = value!;
                                });
                              },
                              value: _machineSpeed,
                            ),
                          ]
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
          child: SizedBox(
            width: double.infinity,
            child: _adMob.getAdBannerWidget(),
          ),
        ),
      ]),
    );
  }
  Widget _border() {
    return Container(
      decoration: BoxDecoration(
        border: Border(
          top: BorderSide(
            color: Colors.grey.shade300,
            width: 1,
          ),
        ),
      ),
    );
  }
  Widget _onlineGame() {
    return OutlinedButton.icon(
      onPressed: _onlineButtonDisable ? null : () {
        showDialog(
          context: context,
          builder: (_) {
            return AlertDialog(
              title: Text(AppLocalizations.of(context)!.heldOnline),
              content: Text(AppLocalizations.of(context)!.connectionCodeWillBeIssuedIsItOk),
              actions: <Widget>[
                TextButton(
                  child: Text(AppLocalizations.of(context)!.cancel),
                  onPressed: () {
                    Navigator.pop(context);
                  },
                ),
                TextButton(
                  child: Text(AppLocalizations.of(context)!.ok),
                  onPressed: () {
                    Navigator.pop(context);
                    _connectionCodeMachineCreate();
                  },
                ),
              ],
            );
          },
        );
      },
      style: OutlinedButton.styleFrom(
          foregroundColor: ConstValue.colorMachineAccent,
          side: const BorderSide(
            color: ConstValue.colorMachineAccent,
          ),
      ),
      icon: const Icon(Icons.cloud_outlined),
      label: Text(AppLocalizations.of(context)!.heldOnline),
    );
  }
  Widget _offlineGame() {
    return OutlinedButton.icon(
      onPressed: () {
        showDialog(
          context: context,
          builder: (_) {
            return AlertDialog(
              title: Text(AppLocalizations.of(context)!.heldOffline),
              content: Text(AppLocalizations.of(context)!.connectionCodeWillBeInvalidIsItOk),
              actions: <Widget>[
                TextButton(
                  child: Text(AppLocalizations.of(context)!.cancel),
                  onPressed: () => Navigator.pop(context),
                ),
                TextButton(
                  child: Text(AppLocalizations.of(context)!.ok),
                  onPressed: () => {
                    setState(() {
                      GameState.setConnectionCodeMachine(0);
                      _connectionMessage = AppLocalizations.of(context)!.connectionCodeIsNoLongerValid;
                    }),
                    Navigator.pop(context),
                  },
                ),
              ],
            );
          },
        );
      },
      style: OutlinedButton.styleFrom(
        foregroundColor: ConstValue.colorMachineAccent,
        side: const BorderSide(
          color: ConstValue.colorMachineAccent,
        ),
      ),
      icon: const Icon(Icons.cloud_off_outlined),
      label: Text(AppLocalizations.of(context)!.heldOffline),
    );
  }
  Widget _newGame() {
    return OutlinedButton.icon(
      onPressed: () {
        showDialog(
          context: context,
          builder: (_) {
            return AlertDialog(
              title: Text(AppLocalizations.of(context)!.newGame),
              content: Text(AppLocalizations.of(context)!.lotteryResultIsInitializedIsItOk),
              actions: <Widget>[
                TextButton(
                  child: Text(AppLocalizations.of(context)!.cancel),
                  onPressed: () => Navigator.pop(context),
                ),
                TextButton(
                  child: Text(AppLocalizations.of(context)!.ok),
                  onPressed: () => {
                    _connectionCodeMachineClear(),
                    Navigator.pop(context),
                  },
                ),
              ],
            );
          },
        );
      },
      style: OutlinedButton.styleFrom(
        foregroundColor: ConstValue.colorMachineAccent,
        side: const BorderSide(
          color: ConstValue.colorMachineAccent,
        ),
      ),
      icon: const Icon(Icons.insert_drive_file_outlined),
      label: Text(AppLocalizations.of(context)!.newGame),
    );
  }
  Widget _machinePage() {
    return Directionality(
        textDirection: TextDirection.rtl,
        child: ElevatedButton.icon(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute<void>(
                builder: (BuildContext context) => MachinePage(machineSpeed: _machineSpeed),
              ),
            );
          },
          style: ElevatedButton.styleFrom(
            backgroundColor: ConstValue.colorMachineAccent,
            elevation: 2,
          ),
          icon: const Icon(Icons.chevron_left),
          label: Text(AppLocalizations.of(context)!.bingoMachine),
        )
    );
  }
  void _connectionCodeMachineCreate() async {
    setState(() {
      _onlineButtonDisable = true;
      _connectionMessage = AppLocalizations.of(context)!.connectionCodeIsBeingIssued;
    });
    final int number = await GameState.connectionCodeMachineCreate();
    for (int i = 10; i > 0; i--) {
      await Future.delayed(const Duration(milliseconds: 1000));
      setState(() {
        _connectionMessage = AppLocalizations.of(context)!.connectionCodeIsBeingIssued + i.toString();
      });
    }
    setState(() {
      _onlineButtonDisable = false;
      _connectionMessage = (number == 0) ? AppLocalizations.of(context)!.failedToConnectPleaseTryAgain : AppLocalizations.of(context)!.connectionCodeHasBeenIssued;
    });
  }
  void _connectionCodeMachineClear() async {
    final bool ret = await GameState.connectionCodeMachineClear();
    setState(() {
      _connectionMessage = (ret) ? AppLocalizations.of(context)!.theLotteryResultsHaveBeenInitialized : AppLocalizations.of(context)!.failedToInitializeLotteryResults;
    });
  }
}

lib/main.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';    //Webの時はコメントアウトする。Android,iOSの時は有効にする

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/game_state.dart';
import 'package:bingoonline/setting.dart';
import 'package:bingoonline/machine_init.dart';
import 'package:bingoonline/card_init.dart';
import 'package:bingoonline/ad_mob.dart';

void main() {
  //MobileAds.instance.initialize(); ここに入れると進行しなかった
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {    //statefulに変更して言語変更に対応
  const MainApp({super.key});
  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  Locale localeLanguage = const Locale('en');
  @override
  Widget build(BuildContext context) {
    MobileAds.instance.initialize();    //Webの時はコメントアウトする。Android,iOSの時は有効にする
    //List<Locale> localeList() {
    //  List<Locale> locales = [];
    //  ConstValue.languageCode.forEach((key, value) {
    //    locales.add(Locale(key));
    //  });
    //  return locales;
    //}
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      //localizationsDelegates: [   //多言語化
      //  AppLocalizations.delegate,
      //  GlobalMaterialLocalizations.delegate,
      //  GlobalWidgetsLocalizations.delegate,
      //  GlobalCupertinoLocalizations.delegate,
      //],
      localizationsDelegates: AppLocalizations.localizationsDelegates,   //多言語化
      //supportedLocales: localeList(), //言語リストを指定する場合
      supportedLocales: AppLocalizations.supportedLocales,  //自動で言語リストを生成
      locale: localeLanguage,
      home: const MainHomePage(),
    );
  }
}

class MainHomePage extends StatefulWidget {
  const MainHomePage({super.key});
  @override
  State<MainHomePage> createState() => _MainHomePageState();
}

class _MainHomePageState extends State<MainHomePage> {
  final AdMob _adMob = AdMob();
  Future _getVersion() async {
    PackageInfo packageInfo = await PackageInfo.fromPlatform();
    setState(() {
      GameState.versionSave(packageInfo.version);
    });
  }
  void _getCurrentLocale() async {
    Locale locale = Locale(await GameState.getLanguageCode());
    if (!mounted) {
      return;
    }
    context.findAncestorStateOfType<_MainAppState>()!
      ..localeLanguage = locale
      ..setState(() {});
  }
  @override
  void initState() {
    super.initState();
    _getVersion();
    _getCurrentLocale();
    GameState.getLanguageCode();
    GameState.ballHistoryLoad();
    GameState.connectionCodeMachineLoad();
    GameState.connectionCodeCardLoad();
    _adMob.load();
  }
  @override
  void dispose() {
    _adMob.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          Container(
            decoration: const BoxDecoration(
              image: DecorationImage(
                image: AssetImage(ConstValue.topBack),
                fit: BoxFit.cover,
              ),
            ),
          ),
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.only(top: 5, left: 0, right: 10, bottom: 0),
              child: Row(children:[
                const Spacer(),
                OutlinedButton(
                  onPressed: () async {
                    bool? ret = await Navigator.of(context).push(
                      MaterialPageRoute<bool>(
                        builder: (BuildContext context) => const SettingPage(),
                      ),
                    );
                    if (ret!) {
                      _getCurrentLocale();
                    }
                  },
                  style: OutlinedButton.styleFrom(
                    foregroundColor: const Color.fromRGBO(0,0,0,0.4),
                    side: const BorderSide(
                      color: Color.fromRGBO(0,0,0,0.4),
                    ),
                  ),
                  child: Text(AppLocalizations.of(context)!.setting),
                ),
              ]),
            ),
          ),
          Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Directionality(
                  textDirection: TextDirection.rtl,
                  child: ElevatedButton.icon(
                    onPressed: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute<void>(
                          builder: (BuildContext context) => const MachineInitPage(),
                        ),
                      );
                    },
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color.fromRGBO(0,0,0,0.3),
                      elevation: 0,
                    ),
                    icon: const Icon(Icons.chevron_left),
                    label: Text(AppLocalizations.of(context)!.bingoMachine,style: const TextStyle(fontSize:20)),
                  ),
                ),
                Container(height: 30),
                Directionality(
                  textDirection: TextDirection.rtl,
                  child: ElevatedButton.icon(
                    onPressed: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute<void>(
                          builder: (BuildContext context) => const CardInitPage(),
                        ),
                      );
                    },
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color.fromRGBO(0,0,0,0.3),
                      elevation: 0,
                    ),
                    icon: const Icon(Icons.chevron_left),
                    label: Text(AppLocalizations.of(context)!.bingoCard,style: const TextStyle(fontSize:20)),
                  ),
                ),
              ],
            ),
          ),
          Column(children:[
            const Spacer(),
            SizedBox(
              width: double.infinity,
              child: _adMob.getAdBannerWidget(),
            ),
          ]),
        ],
      ),
    );
  }
}

lib/preferences.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:shared_preferences/shared_preferences.dart';

import 'package:bingoonline/const_value.dart';

class Preferences {

  //----------------------------

  static Future<void> setLanguageCode(String str) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString(ConstValue.prefLanguageCode, str);
  }

  static Future<String?> getLanguageCode() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String? str = prefs.getString(ConstValue.prefLanguageCode);
    return str;
  }

  //----------------------------

  static Future<void> setSpeakFlag(bool flag) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefSpeakFlag, flag ? 1 : 0);
  }

  static Future<bool> getSpeakFlag() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final bool flag = (prefs.getInt(ConstValue.prefSpeakFlag) == 0) ? false : true;
    return flag;
  }

  //----------------------------

  static Future<void> setSpeakVoiceIndex(int num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefSpeakVoiceIndex, num);
  }

  static Future<int> getSpeakVoiceIndex() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefSpeakVoiceIndex) ?? 0;
    return num;
  }

  //----------------------------

  static Future<void> setBallHistories(String str) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString(ConstValue.prefBallHistories, str);
  }

  static Future<String?> getBallHistories() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String? str = prefs.getString(ConstValue.prefBallHistories);
    return str;
  }

  //----------------------------

  static Future<void> setConnectionCodeMachine(int num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefConnectionCodeMachine, num);
  }

  static Future<int?> getConnectionCodeMachine() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int? num = prefs.getInt(ConstValue.prefConnectionCodeMachine);
    return num;
  }

  //----------------------------

  static Future<void> setConnectionCodeCard(int num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefConnectionCodeCard, num);
  }

  static Future<int?> getConnectionCodeCard() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int? num = prefs.getInt(ConstValue.prefConnectionCodeCard);
    return num;
  }

  //----------------------------

  static Future<void> setCardNumbers(String str) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString(ConstValue.prefCardNumbers, str);
  }

  static Future<String?> getCardNumbers() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String? str = prefs.getString(ConstValue.prefCardNumbers);
    return str;
  }

  //----------------------------

}

lib/progress_table.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';

import 'package:bingoonline/game_state.dart';

class ProgressTable {
  Widget table() {
    return Table(
      border: TableBorder.all(width: 1, color: const Color.fromRGBO(128,128,128,0.2)),
      columnWidths: const <int, TableColumnWidth>{
        0: FlexColumnWidth(1.0),
        1: FlexColumnWidth(1.0),
        2: FlexColumnWidth(1.0),
        3: FlexColumnWidth(1.0),
        4: FlexColumnWidth(1.0),
      },
      children: [
        TableRow(children: [
          cellProgressBingo('B'),
          cellProgressBingo('I'),
          cellProgressBingo('N'),
          cellProgressBingo('G'),
          cellProgressBingo('O'),
        ]),
        TableRow(children: [
          cellProgress(1),
          cellProgress(16),
          cellProgress(31),
          cellProgress(46),
          cellProgress(61),
        ]),
        TableRow(children: [
          cellProgress(2),
          cellProgress(17),
          cellProgress(32),
          cellProgress(47),
          cellProgress(62),
        ]),
        TableRow(children: [
          cellProgress(3),
          cellProgress(18),
          cellProgress(33),
          cellProgress(48),
          cellProgress(63),
        ]),
        TableRow(children: [
          cellProgress(4),
          cellProgress(19),
          cellProgress(34),
          cellProgress(49),
          cellProgress(64),
        ]),
        TableRow(children: [
          cellProgress(5),
          cellProgress(20),
          cellProgress(35),
          cellProgress(50),
          cellProgress(65),
        ]),
        TableRow(children: [
          cellProgress(6),
          cellProgress(21),
          cellProgress(36),
          cellProgress(51),
          cellProgress(66),
        ]),
        TableRow(children: [
          cellProgress(7),
          cellProgress(22),
          cellProgress(37),
          cellProgress(52),
          cellProgress(67),
        ]),
        TableRow(children: [
          cellProgress(8),
          cellProgress(23),
          cellProgress(38),
          cellProgress(53),
          cellProgress(68),
        ]),
        TableRow(children: [
          cellProgress(9),
          cellProgress(24),
          cellProgress(39),
          cellProgress(54),
          cellProgress(69),
        ]),
        TableRow(children: [
          cellProgress(10),
          cellProgress(25),
          cellProgress(40),
          cellProgress(55),
          cellProgress(70),
        ]),
        TableRow(children: [
          cellProgress(11),
          cellProgress(26),
          cellProgress(41),
          cellProgress(56),
          cellProgress(71),
        ]),
        TableRow(children: [
          cellProgress(12),
          cellProgress(27),
          cellProgress(42),
          cellProgress(57),
          cellProgress(72),
        ]),
        TableRow(children: [
          cellProgress(13),
          cellProgress(28),
          cellProgress(43),
          cellProgress(58),
          cellProgress(73),
        ]),
        TableRow(children: [
          cellProgress(14),
          cellProgress(29),
          cellProgress(44),
          cellProgress(59),
          cellProgress(74),
        ]),
        TableRow(children: [
          cellProgress(15),
          cellProgress(30),
          cellProgress(45),
          cellProgress(60),
          cellProgress(75),
        ]),
      ],
    );
  }
  Widget cellProgressBingo(String str) {
    return Container(
      padding: const EdgeInsets.all(1),
      color: const Color.fromRGBO(0,0,0,0.1),
      child: Center(child: Text(str,
        style: const TextStyle(
          color: Colors.white,
        ),
      )),
    );
  }
  Widget cellProgress(int num) {
    Color bgColor = Colors.white;
    if (GameState.ballHistoryContain(num)) {
      bgColor = Colors.yellow;
      if (GameState.ballHistoryIsLast(num)) {
        bgColor = Colors.orangeAccent;
      }
    }
    return Container(
      padding: const EdgeInsets.only(left: 0, top: 5, right: 0, bottom: 5),
      color: bgColor,
      child: Center(child: Text(num.toString(),
        style: const TextStyle(
          fontSize: 16,
        ),
      )),
    );
  }

}

lib/setting.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:bingoonline/const_value.dart';
import 'package:bingoonline/game_state.dart';
import 'package:bingoonline/TextToSpeech.dart';
import 'package:bingoonline/tts_state.dart';
import 'package:bingoonline/ad_mob.dart';

class SettingPage extends StatefulWidget {
  const SettingPage({super.key});
  @override
  State<SettingPage> createState() => _SettingPageState();
}

class _SettingPageState extends State<SettingPage> {
  String _languageKey = '';
  String _languageValue = '';
  bool _speakFlag = true;
  int _speakVoiceIndex = 0;
  List<DropdownMenuItem> _speakVoiceMenuItem = [];
  final AdMob _adMob = AdMob();
  @override
  void initState() {
    super.initState();
    (() async {
      _languageKey = await GameState.getLanguageCode();
      _languageValue = ConstValue.languageCode[_languageKey] ?? '';
      _speakFlag = await TtsState.getSpeakFlag();
      _speakVoiceIndex = await TtsState.getSpeakVoiceIndex();
      await _initTts();
      setState(() {});
    })();
    _adMob.load();
  }
  Future<void> _initTts() async {
    TextToSpeech textToSpeech = TextToSpeech();
    List<String> speakVoices = await textToSpeech.getSpeakVoices();
    _speakVoiceMenuItem = [];
    for (int i = 0; i < speakVoices.length; i++) {
      _speakVoiceMenuItem.add(DropdownMenuItem(value: i, child: Text(speakVoices[i])));
    }
  }
  @override
  void dispose() {
    _adMob.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        leading: IconButton(
          icon: const Icon(Icons.close),
          onPressed: () {
            Navigator.of(context).pop(false); //falseを返す
          },
        ),
        title: Text(AppLocalizations.of(context)!.setting),
        backgroundColor: ConstValue.colorSettingAccent,
        actions: [
          IconButton(
            icon: const Icon(Icons.check),
            onPressed: () async {
              await GameState.setLanguageCode(_languageKey);
              await TtsState.setSpeakFlag(_speakFlag);
              await TtsState.setSpeakVoiceIndex(_speakVoiceIndex);
              if (!mounted) {
                return;
              }
              Navigator.of(context).pop(true);  //trueを返す
            },
          ),
        ],
      ),
      body: Column(children:[
        Expanded(
          child: GestureDetector(
            onTap: () => FocusScope.of(context).unfocus(),  //背景タップでキーボードを仕舞う
            child: SingleChildScrollView(
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(top: 6, left: 0, right: 0, bottom: 0),
                      child: SwitchListTile(
                        title: Text(AppLocalizations.of(context)!.vocalization),
                        value: _speakFlag,
                        onChanged: (bool value) {
                          setState(() {
                            _speakFlag = value;
                          });
                        },
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 0),
                      child: Row(children: [
                        Text(AppLocalizations.of(context)!.readOutTheNumberOnTheBall),
                        const Spacer(),
                      ]),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 4, left: 16, right: 16, bottom: 24),
                      child: Row(children: [
                        Text(AppLocalizations.of(context)!.voice,
                          style: const TextStyle(
                            fontSize: 16,
                          ),
                        ),
                        const Spacer(),
                        DropdownButton(
                          items: _speakVoiceMenuItem,
                          onChanged: (value) {
                            setState(() {
                              _speakVoiceIndex = value;
                            });
                          },
                          value: _speakVoiceIndex,
                        ),
                      ]),
                    ),
                    _border(),
                    Padding(
                      padding: const EdgeInsets.only(top: 24, left: 0, right: 0, bottom: 0),
                      child: Row(children:[
                        const SizedBox(width:16),
                        Text(AppLocalizations.of(context)!.language,
                          style: const TextStyle(
                            fontSize: 16,
                          ),
                        ),
                        const Spacer(),
                      ]),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 12, left: 0, right: 0, bottom: 18),
                      child: Table(
                        children: <TableRow>[
                          TableRow(children: <Widget>[
                            _languageTableCell(0),
                            _languageTableCell(1),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(2),
                            _languageTableCell(3),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(4),
                            _languageTableCell(5),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(6),
                            _languageTableCell(7),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(8),
                            _languageTableCell(9),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(10),
                            _languageTableCell(11),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(12),
                            _languageTableCell(13),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(14),
                            _languageTableCell(15),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(16),
                            _languageTableCell(17),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(18),
                            _languageTableCell(19),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(20),
                            _languageTableCell(21),
                          ]),
                          TableRow(children: <Widget>[
                            _languageTableCell(22),
                            _languageTableCell(23),
                          ]),
                        ]
                      ),
                    ),
                    _border(),
                    Padding(
                      padding: const EdgeInsets.only(top: 24, left: 0, right: 0, bottom: 24),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children:[
                          Text(AppLocalizations.of(context)!.usage1),
                          const SizedBox(height:15),
                          Text(AppLocalizations.of(context)!.usage2),
                          const SizedBox(height:15),
                          Text(AppLocalizations.of(context)!.usage3),
                          const SizedBox(height:15),
                          Text(AppLocalizations.of(context)!.usage4),
                        ]
                      ),
                    ),
                    _border(),
                    Padding(
                      padding: const EdgeInsets.only(top: 24, left: 0, right: 0, bottom: 36),
                      child: SizedBox(
                        child: Text(
                          'version  ${GameState.versionLoad()}',
                          style: const TextStyle(
                            fontSize: 10,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
          child: SizedBox(
            width: double.infinity,
            child: _adMob.getAdBannerWidget(),
          ),
        ),
      ]),
    );
  }
  Widget _border() {
    return Container(
      decoration: BoxDecoration(
        border: Border(
          top: BorderSide(
            color: Colors.grey.shade300,
            width: 1,
          ),
        ),
      ),
    );
  }
  TableCell _languageTableCell(int index) {
    return TableCell(
      child: RadioListTile(
        visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity,vertical: VisualDensity.minimumDensity),
        contentPadding: EdgeInsets.zero,
        title: Text(ConstValue.languageCode.values.elementAt(index)),
        value: ConstValue.languageCode.values.elementAt(index),
        groupValue: _languageValue,
        onChanged: (String? value) {
          setState(() {
            _languageValue = value ?? '';
            _languageKey = ConstValue.languageCode.keys.elementAt(index);
          });
        },
      ),
    );
  }

}

lib/text_to_speech.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:flutter_tts/flutter_tts.dart';

class TextToSpeech {

  late FlutterTts _flutterTts;

  TextToSpeech() {
    _flutterTts = FlutterTts();
  }

  void setVoice(Map<String,String> voiceMap) {
    _flutterTts.setVoice(voiceMap);
  }

  void speak(String str) {
    _flutterTts.speak(str);
  }

  Future<List<String>> getSpeakVoices() async {
    List<String> speakVoices = [];
    speakVoices.add("   ");
    List<Object?> ttsVoices = await _flutterTts.getVoices;
    var availableVoices = ttsVoices.cast<Map>().map((e) => e.cast<String,String>()).toList();
    for (var voice in availableVoices) {
      speakVoices.add("${voice['locale']} ${voice['name']}");
    }
    speakVoices.sort();
    return speakVoices;
  }

  Future<void> setSpeakVoiceFromIndex(int speakVoiceIndex) async {
    List<String> speakVoices = await getSpeakVoices();
    if (speakVoices.length <= speakVoiceIndex) {
      speakVoiceIndex = 0;
    }
    List<String> ary = speakVoices[speakVoiceIndex].split(' ');
    Map<String,String> voiceMap = {'locale':ary[0],'name':ary[1]};
    setVoice(voiceMap);
  }

}

lib/tts_state.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:bingoonline/preferences.dart';

class TtsState {

  static bool _speakFlag = true;
  static int _speakVoiceIndex = 0;

  //---------------------------------------------

  //発声フラグを記録
  static Future<void> setSpeakFlag(bool flag) async {
    _speakFlag = flag;
    await Preferences.setSpeakFlag(_speakFlag);
  }
  //発声フラグを返す
  static Future<bool> getSpeakFlag() async {
    _speakFlag = await Preferences.getSpeakFlag();
    return _speakFlag;
  }

  //---------------------------------------------

  //音声のインデックスを記録
  static Future<void> setSpeakVoiceIndex(int num) async {
    _speakVoiceIndex = num;
    await Preferences.setSpeakVoiceIndex(_speakVoiceIndex);
  }
  //音声のインデックスを返す
  static Future<int> getSpeakVoiceIndex() async {
    _speakVoiceIndex = await Preferences.getSpeakVoiceIndex();
    return _speakVoiceIndex;
  }

  //---------------------------------------------

}