ソースコード source code

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

● ご注意 ● このページで公開しているのはDartコードのみです。アプリの動作には画像・音声・多言語テキストなどの追加データが必要なため、ここに掲載している情報だけではアプリを完全に再現することはできません。 ● アプリは継続的に機能拡張しているため、ストアで公開している最新版とコード内容が異なる場合があります。 ● コードはコピーして自由にご利用いただけますが、著作権は放棄していません。そのため、コード全体の再掲載はご遠慮ください。一部の引用や改変しての再掲載は問題ありません。個人利用・商用利用を問わず、アプリ開発の参考として自由にお使いください。 ● このコードは「お手本」として公開しているものではありません。ミニアプリとして作成しているため、変数名など細部に配慮していない箇所や誤りが含まれる可能性があります。参考程度にご覧ください。 ● 他の開発者の公開コードを参考にした部分も含まれています。アプリ開発の熟練者が書いたコードではない点もご了承ください。 ● ここはエンジニア向けの技術情報共有サービスではないため、詳細な説明は省略しています。GitHub などでの公開予定はありません。 ● 本コードは無保証であり、動作や品質を保証するものではありません。
Copyright© エーオーシステム株式会社

● Notice ● Only the Dart source code is provided on this page. Additional assets such as images, audio files, and multilingual text are required for the application to function. Therefore, the information here alone is not sufficient to fully reproduce the app. ● The app is continuously being improved, so the code published here may differ from the latest version available on the app stores. ● You are free to copy and use the code, but the copyright is not waived. For this reason, please refrain from redistributing (reposting) the entire code. Partial quotation or redistribution of modified portions is allowed. You may use the code freely for reference in your own app development, whether for personal or commercial use. ● This code is not intended to serve as a coding example or best practice. As this is a small sample app, some variable names and details may lack refinement, and there may be mistakes. Please use it only as a reference. ● Some parts of the code are based on publicly available examples from other developers. Please note that this code was not written by an expert in Android app development. ● This is not a technical knowledge-sharing service for engineers, so detailed explanations are omitted. There are no plans to publish the code on GitHub. ● The code is provided "as is" without any warranty of any kind.
Copyright© ao-system, Inc.

下記コードの最終ビルド日: 2023-11-25

pubspec.yaml

name: pencilmethod
description: "Pencil method"
# 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: '>=3.3.0-152.0.dev <4.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.6
  package_info_plus: ^5.0.1
  shared_preferences: ^2.0.17
  flutter_localizations:    #多言語ライブラリの本体    # .arbファイルを更新したら flutter gen-l10n
    sdk: flutter
  intl: ^0.18.1     #多言語やフォーマッタなどの関連ライブラリ
  google_mobile_ads: ^3.1.0
  just_audio: ^0.9.35

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_launcher_icons: ^0.13.1    #flutter pub run flutter_launcher_icons
  flutter_native_splash: ^2.3.6     #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: ^3.0.1

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

flutter_launcher_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: '#ffcc00'
  image: 'assets/image/splash.png'
  color_dark: '#ffcc00'
  image_dark: 'assets/image/splash.png'
  fullscreen: true
  android_12:
    icon_background_color: '#ffcc00'
    image: 'assets/image/splash.png'
    icon_background_color_dark: '#ffcc00'
    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/image/pencil/
    - assets/sound/

  # 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
///
library;

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-3940256099942544/6300978111';  //test
      adBannerUnitId = 'ca-app-pub-0000000000000000/0000000000';
      _isAdMob = true;
    } else if (!kIsWeb && Platform.isIOS) {
      //adBannerUnitId = 'ca-app-pub-3940256099942544/6300978111';  //test
      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/audio_play.dart

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

import 'package:just_audio/just_audio.dart';

import 'package:pencilmethod/const_value.dart';

class AudioPlay {
  //音を重ねて連続再生できるようにインスタンスを用意しておき、順繰りに使う。
  static final List<AudioPlayer> _player01 = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  static final List<AudioPlayer> _player02 = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  int _player01Ptr = 0;
  int _player02Ptr = 0;

  double _soundVolume01 = 0.0;
  double _soundVolume02 = 0.0;

  //constructor
  AudioPlay() {
    constructor();
  }
  void constructor() async {
    for (int i = 0; i < _player01.length; i++) {
      await _player01[i].setVolume(0);
      await _player01[i].setAsset(ConstValue.audioReady);
    }
    for (int i = 0; i < _player02.length; i++) {
      await _player02[i].setVolume(0);
      await _player02[i].setAsset(ConstValue.audioAction);
    }
    playZero();
  }
  void dispose() {
    for (int i = 0; i < _player01.length; i++) {
      _player01[i].dispose();
    }
    for (int i = 0; i < _player02.length; i++) {
      _player02[i].dispose();
    }
  }
  //getter
  double get soundVolume01 {
    return _soundVolume01;
  }
  double get soundVolume02 {
    return _soundVolume02;
  }
  //setter
  set soundVolume01(double vol) {
    _soundVolume01 = vol;
  }
  set soundVolume02(double vol) {
    _soundVolume02 = vol;
  }
  //最初に音が鳴らないのを回避する方法
  void playZero() async {
    AudioPlayer ap = AudioPlayer();
    await ap.setAsset(ConstValue.audioZero);
    await ap.load();
    await ap.play();
  }
  //
  void play01() async {
    if (_soundVolume01 == 0) {
      return;
    }
    _player01Ptr += 1;
    if (_player01Ptr >= _player01.length) {
      _player01Ptr = 0;
    }
    await _player01[_player01Ptr].setVolume(_soundVolume01);
    await _player01[_player01Ptr].pause();
    await _player01[_player01Ptr].seek(const Duration(milliseconds: 100));
    await _player01[_player01Ptr].play();
  }
  //
  void play02() async {
    if (_soundVolume02 == 0) {
      return;
    }
    _player02Ptr += 1;
    if (_player02Ptr >= _player02.length) {
      _player02Ptr = 0;
    }
    await _player02[_player02Ptr].setVolume(_soundVolume02);
    await _player02[_player02Ptr].pause();
    await _player02[_player02Ptr].seek(Duration.zero);
    await _player02[_player02Ptr].play();
  }
}

lib/const_value.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///
library;

import 'package:flutter/material.dart';

class ConstValue {
  //pref
  static const String prefLanguageCode = 'languageCode';
  static const String prefCandidateText = 'candidateText';
  static const String prefCountdownTime = 'countdownTime';
  static const String prefSoundReadyVolume = 'soundReadyVolume';
  static const String prefSoundStartVolume = 'soundStartVolume';
  //
  static const String candidateTextDefault = 'oxygen\nhydrogen\nnitrogen\nchlorine\ncarbon\ntar';
  //color
  static const Color colorHeader = Color.fromRGBO(255,0,0,0.1);
  static const Color colorHeaderOn = Color.fromRGBO(255,255,255,0.3);
  static const Color colorNote = Color.fromRGBO(255,0,0,0.4);
  static const Color colorBack = Color.fromRGBO(255,204,0,1);
  static const Color colorSettingHeader = Color.fromRGBO(255,204,0,1);
  static const Color colorUiActiveColor = Color.fromRGBO(255,204,0,1);
  static const Color colorUiInactiveColor = Colors.black26;
  //sound
  static const String audioZero = 'assets/sound/zero.wav';    //無音1秒
  static const String audioReady = 'assets/sound/switch.wav';
  static const String audioAction = 'assets/sound/slide.wav';
  //image
  static const String imageBack = 'assets/image/back.png';
  static const List<String> imagePencils = [
    'assets/image/pencil/pencil001.webp',
    'assets/image/pencil/pencil002.webp',
    'assets/image/pencil/pencil003.webp',
    'assets/image/pencil/pencil004.webp',
    'assets/image/pencil/pencil005.webp',
    'assets/image/pencil/pencil006.webp',
    'assets/image/pencil/pencil007.webp',
    'assets/image/pencil/pencil008.webp',
    'assets/image/pencil/pencil009.webp',
    'assets/image/pencil/pencil010.webp',
    'assets/image/pencil/pencil011.webp',
    'assets/image/pencil/pencil012.webp',
    'assets/image/pencil/pencil013.webp',
    'assets/image/pencil/pencil014.webp',
    'assets/image/pencil/pencil015.webp',
    'assets/image/pencil/pencil016.webp',
    'assets/image/pencil/pencil017.webp',
    'assets/image/pencil/pencil018.webp',
    'assets/image/pencil/pencil019.webp',
    'assets/image/pencil/pencil020.webp',
    'assets/image/pencil/pencil021.webp',
    'assets/image/pencil/pencil022.webp',
    'assets/image/pencil/pencil023.webp',
    'assets/image/pencil/pencil024.webp',
    'assets/image/pencil/pencil025.webp',
    'assets/image/pencil/pencil026.webp',
    'assets/image/pencil/pencil027.webp',
    'assets/image/pencil/pencil028.webp',
    'assets/image/pencil/pencil029.webp',
    'assets/image/pencil/pencil030.webp',
    'assets/image/pencil/pencil031.webp',
    'assets/image/pencil/pencil032.webp',
    'assets/image/pencil/pencil033.webp',
    'assets/image/pencil/pencil034.webp',
    'assets/image/pencil/pencil035.webp',
    'assets/image/pencil/pencil036.webp',
    'assets/image/pencil/pencil037.webp',
    'assets/image/pencil/pencil038.webp',
    'assets/image/pencil/pencil039.webp',
    'assets/image/pencil/pencil040.webp',
    'assets/image/pencil/pencil041.webp',
    'assets/image/pencil/pencil042.webp',
    'assets/image/pencil/pencil043.webp',
    'assets/image/pencil/pencil044.webp',
    'assets/image/pencil/pencil045.webp',
    'assets/image/pencil/pencil046.webp',
    'assets/image/pencil/pencil047.webp',
    'assets/image/pencil/pencil048.webp',
    'assets/image/pencil/pencil049.webp',
    'assets/image/pencil/pencil050.webp',
    'assets/image/pencil/pencil051.webp',
    'assets/image/pencil/pencil052.webp',
    'assets/image/pencil/pencil053.webp',
    'assets/image/pencil/pencil054.webp',
    'assets/image/pencil/pencil055.webp',
    'assets/image/pencil/pencil056.webp',
    'assets/image/pencil/pencil057.webp',
    'assets/image/pencil/pencil058.webp',
    'assets/image/pencil/pencil059.webp',
    'assets/image/pencil/pencil060.webp',
  ];
  static const List<String> imageNumbers = [
    'assets/image/number_null.webp',
    'assets/image/number1.webp',
    'assets/image/number2.webp',
    'assets/image/number3.webp',
    'assets/image/number4.webp',
    'assets/image/number5.webp',
    'assets/image/number6.webp',
    'assets/image/number7.webp',
    'assets/image/number8.webp',
    'assets/image/number9.webp',
  ];
  //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/empty.dart

lib/language_state.dart

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

import 'package:pencilmethod/preferences.dart';

class LanguageState {

  static String _languageCode = 'en';

  //言語コードを記録
  static Future<void> setLanguageCode(String str) async {
    _languageCode = str;
    await Preferences.setLanguageCode(_languageCode);
  }
  //言語コードを返す
  static Future<String> getLanguageCode() async {
    _languageCode = await Preferences.getLanguageCode() ?? 'en';
    return _languageCode;
  }

}

lib/main.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-02
///
library;

import 'dart:async';
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' if (dart.library.html) 'empty.dart';
import 'package:flutter/foundation.dart' show kIsWeb;

//自身で作成したclassを読み込む
import 'package:pencilmethod/const_value.dart';
import 'package:pencilmethod/language_state.dart';
import 'package:pencilmethod/version_state.dart';
import 'package:pencilmethod/setting.dart';
import 'package:pencilmethod/ad_mob.dart';
import 'package:pencilmethod/page_state.dart';
import 'package:pencilmethod/preferences.dart';
import 'package:pencilmethod/audio_play.dart';

void main() {
  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) {
    if (kIsWeb == false) {
      MobileAds.instance.initialize();
    }
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      localizationsDelegates: AppLocalizations.localizationsDelegates,   //多言語化
      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> with SingleTickerProviderStateMixin {
  final GlobalKey _aspectRatioKey = GlobalKey();  //_textAreaのサイズ取得用
  final AdMob _adMob = AdMob(); //広告表示
  final AudioPlay _audioPlay = AudioPlay(); //効果音
  bool _busyFlag = false; //動作中
  int _tickNumber = 0;  //0~119で画像と結果表示を切り替え
  String _lotteryText = ''; //結果 e.g. 'はずれ'
  late Timer _timer;  //カウントダウンと画像切り替えで使用
  //countdown
  int _countdownSubtraction = 0;  //_countdownTimeが代入されて実際にカウントダウンされる
  String _imageCountdownNumber = ConstValue.imageNumbers[0];  //カウントダウン画像
  double _countdownScale = 3;   //カウントダウン画像の拡大率
  double _countdownOpacity = 0; //カウントダウン画像の非透明度
  int _timerCount = 30; //Timerで処理される1秒間の数
  //text animation
  late AnimationController _textAnimationController;
  late Animation<double> _textAnimation;
  //

  //アプリのバージョン取得
  void _getVersion() async {
    PackageInfo packageInfo = await PackageInfo.fromPlatform();
    setState(() {
      VersionState.versionSave(packageInfo.version);
    });
  }
  //言語準備
  void _getCurrentLocale() async {
    Locale locale = Locale(await LanguageState.getLanguageCode());
    if (mounted) {  //Widgetが存在する。Widgetが存在しない時の実行によるエラーを回避する為。
      context.findAncestorStateOfType<_MainAppState>()!
        ..localeLanguage = locale
        ..setState(() {});
    }
  }
  //ページ起動開始時に一度だけ呼ばれる
  @override
  void initState() {
    super.initState();
    _getVersion();
    _getCurrentLocale();
    LanguageState.getLanguageCode();
    _adMob.load();
    _audioPlay.playZero();
    //text animation
    _textAnimationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
    _textAnimation = Tween<double>(begin: 0.0,end: 1.0).animate(_textAnimationController);
    //
    (() async {
      await Preferences.initial();
      _audioPlay.soundVolume01 = Preferences.soundReadyVolume;
      _audioPlay.soundVolume02 = Preferences.soundStartVolume;
      setState(() {});
    })();
  }
  //ページ終了時に一度だけ呼ばれる
  @override
  void dispose() {
    PageState.setCurrentPage('');
    _adMob.dispose();
    _timer.cancel();
    _textAnimationController.dispose();
    super.dispose();
  }
  //画面全体
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: _decoration(),
      child: GestureDetector(
        onTap: () {
          _onClickStart();
        },
        child: Scaffold(
          backgroundColor: Colors.transparent,
          appBar: AppBar(
            backgroundColor: _busyFlag ? ConstValue.colorHeaderOn : ConstValue.colorHeader,
            //タイトル表示
            title: const Text('Pencil method',
              style: TextStyle(
                color: Colors.white,
                fontSize: 15.0,
              )
            ),
            //設定ボタン
            actions: <Widget>[
              Opacity(
                opacity: _busyFlag ? 0.1 : 1,
                child: TextButton(
                  onPressed: () async {
                    if (_busyFlag) {
                      return;
                    }
                    bool? ret = await Navigator.of(context).push(
                      MaterialPageRoute<bool>(builder:(context) => const SettingPage()),
                    );
                    //awaitで呼び出しているので、settingから戻ったら以下が実行される。
                    if (ret!) { //設定で適用だった場合
                      _getCurrentLocale();
                      _audioPlay.soundVolume01 = Preferences.soundReadyVolume;
                      _audioPlay.soundVolume02 = Preferences.soundStartVolume;
                      setState(() {});
                    }
                  },
                  child: Text(
                    AppLocalizations.of(context)!.setting,
                    style: const TextStyle(
                      color: Colors.white,
                    )
                  )
                )
              )
            ]
          ),
          body: SafeArea(
            child: Column(children:[
              Expanded(
                child: Stack(children:[
                  Center(
                    child: _pencilArea(),
                  ),
                  Center(
                    child: _preTextArea(),
                  ),
                  Center(
                    child: _textArea(),
                  ),
                  Center(
                    child: Opacity(
                      opacity: _countdownOpacity,
                      child: Transform.scale(
                        scale: _countdownScale,
                        child: Image.asset(
                          _imageCountdownNumber,
                        ),
                      )
                    )
                  ),
                  SizedBox(
                    width: double.infinity,
                    child: Text(AppLocalizations.of(context)!.start,
                      textAlign: TextAlign.center,
                      style: const TextStyle(
                        color: ConstValue.colorNote,
                      )
                    )
                  )
                ])
              ),
              //広告
              Padding(
                padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
                child: SizedBox(
                  width: double.infinity,
                  child: _adMob.getAdBannerWidget(),
                )
              )
            ])
          )
        )
      )
    );
  }
  Decoration _decoration() {
    return const BoxDecoration(
      //colorは起動時に真黒な画面にならないようにする為
      color: ConstValue.colorSettingHeader,
      image: DecorationImage(
        image: AssetImage(ConstValue.imageBack),
        fit: BoxFit.cover,
      ),
    );
  }
  //カウントダウンタイマー
  void _timerStart() {
    _timer = Timer.periodic(const Duration(milliseconds: (1000 ~/ 30)), (timer) {
      setState(() {
        _countdown();
      });
    });
  }
  //START
  void _onClickStart() {
    if (_busyFlag) {
      return;
    }
    if (_tickNumber != 0) {
      setState(() {
        _tickNumber = 0;
        _lotteryText = '';
      });
      return;
    }
    _busyFlag = true;
    _countdownSubtraction = Preferences.countdownTime;
    if (_countdownSubtraction == 0) { //カウントダウンしない場合
      _tickAction();
    } else {
      _audioPlay.soundVolume01 = Preferences.soundReadyVolume;
      _audioPlay.play01();
      _tickNumber = 0;
      _timerStart();
    }
  }
  //0~59まで変化。画像と結果表示を切り替え
  void _tickAction() {
    _audioPlay.soundVolume02 = Preferences.soundStartVolume;
    _audioPlay.play02();
    _tickNumber = 0;
    _lotteryText = '';
    _textAnimationController.reset();
    _timer = Timer.periodic(const Duration(milliseconds: (1000 ~/ 30)), (timer) {
      setState(() {
        _tickNumber += 1;
        if (_tickNumber >= 59) {
          _timer.cancel();
          _lotteryText = Preferences.nextCandidateText();
          _textAnimationController.forward();
          _busyFlag = false;
        }
      });
    });
  }
  //Timerで定期実行
  void _countdown() {
    //カウントダウン終了時
    if (_countdownSubtraction == 0) {
      return;
    }
    //数字画像を切り替え
    if (_timerCount == 30) {
      _imageCountdownNumber = ConstValue.imageNumbers[_countdownSubtraction];
    }
    _timerCount -= 1;
    if (_timerCount <= 0) {
      _timerCount = 30;
      _countdownSubtraction -= 1;
      if (_countdownSubtraction == 0) {
        _imageCountdownNumber = ConstValue.imageNumbers[0];
        _timer.cancel();
        _tickAction();
      }
    }
    _countdownScale = 1 + (0.1 * (_timerCount / 30));
    if (_timerCount >= 20) {
      _countdownOpacity = (30 - _timerCount) / 10;
    } else if (_timerCount <= 5) {
      _countdownOpacity = _timerCount / 5;
    } else {
      _countdownOpacity = 1;
    }
  }
  //pencil画像
  Widget _pencilArea() {
    List<Widget> widgets = [];
    for (int i = 0; i < ConstValue.imagePencils.length; i++) {
      widgets.add(
        Opacity(
          opacity: _tickNumber == i ? 1 : 0,
          child: Image.asset(ConstValue.imagePencils[i]),
        ),
      );
    }
    return Stack(children:widgets);
  }
  //_textAreaのサイズ取得用
  Widget _preTextArea() {
    return AspectRatio(
      key: _aspectRatioKey,
      aspectRatio: 1,
    );
  }
  //結果文字表示
  Widget _textArea() {
    late Size size;
    try {
      RenderBox renderBox = _aspectRatioKey.currentContext?.findRenderObject() as RenderBox;
      size = renderBox.size;
    } catch (_) {
      return Container();
    }
    return AspectRatio(
      aspectRatio: 1,
      child: Container(
        alignment: Alignment.center,
        padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
        child: AnimatedBuilder(
          animation: _textAnimation,
          builder: (context, child) {
            return Opacity(
              opacity: _textAnimation.value,
              child: Text(_lotteryText,
                style: TextStyle(
                  color: Colors.white,
                  fontSize: size.width * 0.07,
                )
              )
            );
          }
        )
      )
    );
  }
}

lib/page_state.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-05
///
library;

//現在のページを記録。initStateでタイミングが合わない時にbuild内で一度だけ実行させるために使用。
class PageState {

  static String _currentPage = '';

  static void setCurrentPage(String str) {
    _currentPage = str;
  }

  static String getCurrentPage() {
    return _currentPage;
  }

}

lib/preferences.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-26
///
library;

import 'dart:math';

import 'package:shared_preferences/shared_preferences.dart';

import 'package:pencilmethod/const_value.dart';

//デバイスに情報を保存
class Preferences {

  static bool ready = false;
  //この値は常に最新にしておく
  static String _languageCode = '';
  static String _candidateText = '';
  static List<String> _candidateList = ['-','-','-','-','-','-'];
  static int _countdownTime = 0;
  static double _soundReadyVolume = 0.5;
  static double _soundStartVolume = 0.5;

  static String get languageCode {
    return _languageCode;
  }
  static String get candidateText {
    return _candidateText;
  }
  static List<String> get candidateList {
    return _candidateList;
  }
  static int get countdownTime {
    return _countdownTime;
  }
  static double get soundReadyVolume {
    return _soundReadyVolume;
  }
  static double get soundStartVolume {
    return _soundStartVolume;
  }

  static Future<void> initial() async {
    _languageCode = await getLanguageCode();
    _candidateText = await getCandidateText();
    _candidateList = getCandidateList();
    if (_candidateText == '') {
      await setCandidateText(''); //空文字で初期値をセット
    }
    _countdownTime = await getCountdownTime();
    _soundReadyVolume = await getSoundReadyVolume();
    _soundStartVolume = await getSoundStartVolume();
    ready = true;
  }

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

  //言語コード
  static Future<void> setLanguageCode(String str) async {
    _languageCode = str;
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString(ConstValue.prefLanguageCode, str);
  }
  static Future<String> getLanguageCode() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String str = prefs.getString(ConstValue.prefLanguageCode) ?? 'en';
    return str;
  }

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

  //抽選
  static Future<void> setCandidateText(String str) async {
    if (str == '') {
      str = ConstValue.candidateTextDefault;
    }
    _candidateText = str;
    _candidateList = _makeCandidateList(str);
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString(ConstValue.prefCandidateText, _candidateText);
  }
  static Future<void> setCandidateTextDefault() async {
    setCandidateText('');
  }
  static Future<String> getCandidateText() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String str = prefs.getString(ConstValue.prefCandidateText) ?? ConstValue.candidateTextDefault;
    return str;
  }
  //抽選をListで返す
  static List<String> getCandidateList() {
    return _makeCandidateList(_candidateText);
  }
  //賞をListにする
  static List<String> _makeCandidateList(String str) {
    List<String> strList = ['-','-','-','-','-','-'];
    if (str == '') {
      return strList;
    }
    final List<String> lines = str.replaceAll('\r','').split('\n');
    for (int i = 0; i < min(6,lines.length); i++) {
      strList[i] = lines[i];
    }
    return strList;
  }
  //賞を1個取り出す
  static String nextCandidateText() {
    final List<int> numbers = [0,1,2,3,4,5];
    numbers.shuffle();
    return _candidateList[numbers[0]];
  }

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

  //カウントダウン時間
  static Future<void> setCountdownTime(int num) async {
    _countdownTime = num;
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setInt(ConstValue.prefCountdownTime, num);
  }
  static Future<int> getCountdownTime() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefCountdownTime) ?? 0;
    return num;
  }

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

  //効果音音量
  static Future<void> setSoundReadyVolume(double num) async {
    _soundReadyVolume = num;
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setDouble(ConstValue.prefSoundReadyVolume, num);
  }
  static Future<double> getSoundReadyVolume() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final double num = prefs.getDouble(ConstValue.prefSoundReadyVolume) ?? 0.5;
    return num;
  }

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

  //効果音音量
  static Future<void> setSoundStartVolume(double num) async {
    _soundStartVolume = num;
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setDouble(ConstValue.prefSoundStartVolume, num);
  }
  static Future<double> getSoundStartVolume() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final double num = prefs.getDouble(ConstValue.prefSoundStartVolume) ?? 0.5;
    return num;
  }

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

}

lib/setting.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///
library;

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

import 'package:pencilmethod/const_value.dart';
import 'package:pencilmethod/preferences.dart';
import 'package:pencilmethod/language_state.dart';
import 'package:pencilmethod/version_state.dart';
import 'package:pencilmethod/ad_mob.dart';
import 'package:pencilmethod/page_state.dart';

class SettingPage extends StatefulWidget {
  const SettingPage({super.key});

  @override
  State<SettingPage> createState() => _SettingPageState();
}

class _SettingPageState extends State<SettingPage> {
  final AdMob _adMob = AdMob(); //広告
  //これら変数はUIへの表示や入力の為に一時的に使用される。
  String _languageKey = ''; //言語コード 'en'
  String _languageValue = '';
  final TextEditingController _controllerCandidateText = TextEditingController();
  bool _candidateInitialFlag = false;
  int _countdownTime = 0;
  double _soundReadyVolume = 0.5;
  double _soundStartVolume = 0.5;

  //ページ起動時に一度だけ実行される
  @override
  void initState() {
    super.initState();
    _adMob.load();
  }
  //ページ終了時に一度だけ実行される
  @override
  void dispose() {
    PageState.setCurrentPage('');
    _adMob.dispose();
    _controllerCandidateText.dispose();
    super.dispose();
  }
  //ページ描画
  @override
  Widget build(BuildContext context) {
    //このページが開いたときに一度だけ実行される処理を記述。initStateではタイミングが合わない為。
    if (PageState.getCurrentPage() != 'setting') {
      PageState.setCurrentPage('setting');
      (() async {
        _languageKey = await LanguageState.getLanguageCode();
        _languageValue = ConstValue.languageCode[_languageKey] ?? '';
        await Preferences.initial();
        _controllerCandidateText.text = Preferences.candidateText;
        _countdownTime = Preferences.countdownTime;
        _soundReadyVolume = Preferences.soundReadyVolume;
        _soundStartVolume = Preferences.soundStartVolume;
        setState((){});
      })();
    }
    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),
        foregroundColor: const Color.fromRGBO(255,255,255,1),
        backgroundColor: ConstValue.colorSettingHeader,
        actions: [
          //設定OKボタン
          IconButton(
            icon: const Icon(Icons.check),
            onPressed: () async {
              await LanguageState.setLanguageCode(_languageKey);
              if (_candidateInitialFlag) {
                await Preferences.setCandidateTextDefault();
              } else {
                await Preferences.setCandidateText(_controllerCandidateText.text);
              }
              await Preferences.setCountdownTime(_countdownTime);
              await Preferences.setSoundReadyVolume(_soundReadyVolume);
              await Preferences.setSoundStartVolume(_soundStartVolume);
              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: 0, left: 16, right: 16, bottom: 0),
                    child: Row(children:<Widget>[
                      Expanded(
                        child: Text(AppLocalizations.of(context)!.candidate,style: const TextStyle(fontSize: 16)),
                      ),
                      Text(AppLocalizations.of(context)!.initial),
                      Switch(
                        value: _candidateInitialFlag,
                        onChanged: (bool value) {
                          setState(() {
                            _candidateInitialFlag = value;
                          });
                        },
                        activeColor: Colors.red,
                        inactiveThumbColor: ConstValue.colorUiInactiveColor,
                      ),
                    ]),
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 1, left: 16, right: 16, bottom: 16),
                    child: TextField(
                      controller: _controllerCandidateText,
                      maxLines: null, //nullで複数行のテキストエリア
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ),
                  _border(),
                  Padding(
                      padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                      child: Row(children: [
                        Text(AppLocalizations.of(context)!.countdownTime,style: const TextStyle(fontSize: 16)),
                        const Spacer(),
                      ])
                  ),
                  Padding(
                      padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 6),
                      child: Row(children: <Widget>[
                        Text(_countdownTime.toString()),
                        Expanded(
                            child: Slider(
                              value: _countdownTime.toDouble(),
                              min: 0,
                              max: 9,
                              divisions: 9,
                              onChanged: (double value) {
                                setState(() {
                                  _countdownTime = value.toInt();
                                });
                              },
                              activeColor: ConstValue.colorUiActiveColor,
                              inactiveColor: ConstValue.colorUiInactiveColor,
                            )
                        )
                      ])
                  ),
                  _border(),
                  Padding(
                      padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                      child: Row(children: [
                        Text(AppLocalizations.of(context)!.soundReadyVolume,style: const TextStyle(fontSize: 16)),
                        const Spacer(),
                      ])
                  ),
                  Padding(
                      padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 6),
                      child: Row(children: <Widget>[
                        Text(_soundReadyVolume.toString()),
                        Expanded(
                            child: Slider(
                              value: _soundReadyVolume,
                              min: 0.0,
                              max: 1.0,
                              divisions: 10,
                              onChanged: (double value) {
                                setState(() {
                                  _soundReadyVolume = value;
                                });
                              },
                              activeColor: ConstValue.colorUiActiveColor,
                              inactiveColor: ConstValue.colorUiInactiveColor,
                            )
                        )
                      ])
                  ),
                  _border(),
                  Padding(
                      padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                      child: Row(children: [
                        Text(AppLocalizations.of(context)!.soundStartVolume,style: const TextStyle(fontSize: 16)),
                        const Spacer(),
                      ])
                  ),
                  Padding(
                      padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 6),
                      child: Row(children: <Widget>[
                        Text(_soundStartVolume.toString()),
                        Expanded(
                            child: Slider(
                              value: _soundStartVolume,
                              min: 0.0,
                              max: 1.0,
                              divisions: 10,
                              onChanged: (double value) {
                                setState(() {
                                  _soundStartVolume = value;
                                });
                              },
                              activeColor: ConstValue.colorUiActiveColor,
                              inactiveColor: ConstValue.colorUiInactiveColor,
                            )
                        )
                      ])
                  ),
                  _border(),
                  Padding(
                      padding: const EdgeInsets.only(top: 18, 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  ${VersionState.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(),
          ),
        ),
      ]),
    );
  }
  //UIの仕切り用ボーダーライン
  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);
          });
        },
        activeColor: ConstValue.colorUiActiveColor,
      ),
    );
  }

}

lib/version_state.dart

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

class VersionState {

  static String _version = '';

  //バージョンを記録
  static void versionSave(String str) {
    _version = str;
  }
  //バージョンを返す
  static String versionLoad() {
    return _version;
  }

}

lib/l10n/app_bg.arb

{
	"@@locale":"bg",
	"@locale": {
		"description": "ブルガリア"
	},
	"setting": "Настройка",
	"start": "Започнете с докосване на екрана",
	"candidate": "Кандидат",
	"initial": "Първоначална стойност",
	"countdownTime": "Време за обратно броене",
	"soundReadyVolume": "Сила на звука на звуковия ефект: Звук за обратно броене",
	"soundStartVolume": "Сила на звука на звуковия ефект: Стартирайте звука",
	"language": "език",
	"usage1": "Метод с молив.",
	"usage2": "Завъртете молива, за да получите отговора.",
	"usage3": "Регистрирайте до 6 кандидата, разделени с нов ред. Регистрации с повече от 6 ще бъдат игнорирани.",
	"usage4": "напр.\nкислород\nводород водород\nазот\nхлор\nвъглерод\nкатран",

	"dummy": "dummy"
}

lib/l10n/app_cs.arb

{
	"@@locale":"cs",
	"@locale": {
		"description": "チェコ"
	},
	"setting": "Nastavení",
	"start": "Začněte klepnutím na obrazovku",
	"candidate": "Kandidát",
	"initial": "Počáteční hodnota",
	"countdownTime": "Čas odpočítávání",
	"soundReadyVolume": "Hlasitost zvukového efektu: Zvuk odpočítávání",
	"soundStartVolume": "Hlasitost zvukového efektu: Spustit zvuk",
	"language": "Jazyk",
	"usage1": "Tužková metoda.",
	"usage2": "Hoďte tužkou a získejte odpověď.",
	"usage3": "Zaregistrujte až 6 kandidátů oddělených zalomením řádků. Registrace s více než 6 budou ignorovány.",
	"usage4": "např.\nkyslík\nvodík vodík\ndusík\nchlór\nuhlík\ndehet",

	"dummy": "dummy"
}

lib/l10n/app_da.arb

{
	"@@locale":"da",
	"@locale": {
		"description": "デンマーク"
	},
	"setting": "Indstilling",
	"start": "Start med at trykke på skærmen",
	"candidate": "Kandidat",
	"initial": "Startværdi",
	"countdownTime": "Nedtællingstid",
	"soundReadyVolume": "Lydeffekt lydstyrke: Nedtællingslyd",
	"soundStartVolume": "Lydeffekt lydstyrke: Start lyd",
	"language": "Sprog",
	"usage1": "Blyant metode.",
	"usage2": "Rul med blyanten for at få svaret.",
	"usage3": "Tilmeld op til 6 kandidater, adskilt af linjeskift. Tilmeldinger med mere end 6 vil blive ignoreret.",
	"usage4": "f.eks.\nilt\nbrint brint\nnitrogen\nklor\nkulstof\ntjære",

	"dummy": "dummy"
}

lib/l10n/app_de.arb

{
	"@@locale":"de",
	"@locale": {
		"description": "ドイツ"
	},
	"setting": "Einstellung",
	"start": "Tippen Sie zunächst auf den Bildschirm",
	"candidate": "Kandidat",
	"initial": "Ursprünglicher Wert",
	"countdownTime": "Countdown-Zeit",
	"soundReadyVolume": "Lautstärke des Soundeffekts: Countdown-Sound",
	"soundStartVolume": "Lautstärke des Soundeffekts: Ton starten",
	"language": "Sprache",
	"usage1": "Bleistiftmethode.",
	"usage2": "Rollen Sie den Bleistift, um die Antwort zu erhalten.",
	"usage3": "Registrieren Sie bis zu 6 Kandidaten, getrennt durch Zeilenumbrüche. Anmeldungen mit mehr als 6 werden ignoriert.",
	"usage4": "z.B.\nSauerstoff\nWasserstoff Wasserstoff\nStickstoff\nChlor\nKohlenstoff\nTeer",

	"dummy": "dummy"
}

lib/l10n/app_el.arb

{
	"@@locale":"el",
	"@locale": {
		"description": "ギリシャ"
	},
	"setting": "Σύνθεση",
	"start": "Ξεκινήστε πατώντας στην οθόνη",
	"candidate": "Υποψήφιος",
	"initial": "Αρχική τιμή",
	"countdownTime": "Χρόνος αντίστροφης μέτρησης",
	"soundReadyVolume": "Ένταση ήχου εφέ: Ήχος αντίστροφης μέτρησης",
	"soundStartVolume": "Ένταση ήχου εφέ: Έναρξη ήχου",
	"language": "Γλώσσα",
	"usage1": "Μέθοδος μολυβιού.",
	"usage2": "Κυλήστε το μολύβι για να πάρετε την απάντηση.",
	"usage3": "Εγγραφείτε έως και 6 υποψηφίους, χωρισμένα με αλλαγές γραμμής. Εγγραφές με περισσότερους από 6 θα αγνοηθούν.",
	"usage4": "π.χ.\nοξυγόνο\nυδρογόνο υδρογόνο\nάζωτο\nχλώριο\nάνθρακας\nπίσσα",

	"dummy": "dummy"
}

lib/l10n/app_en.arb

{
	"@@locale":"en",
	"@locale": {
		"description": "英語"
	},
	"setting": "Setting",
	"start": "Start by tapping on the screen",
	"candidate": "Candidate",
	"initial":"Initial value",
	"countdownTime": "Countdown time",
	"soundReadyVolume": "Sound effect volume: Countdown sound",
	"soundStartVolume": "Sound effect volume: Start sound",
	"language": "Language",
	"usage1": "Pencil method.",
	"usage2": "Roll the pencil to get the answer.",
	"usage3": "Register up to 6 candidates, separated by line breaks. Registrations with more than 6 will be ignored.",
	"usage4": "e.g.\noxygen\nhydrogen\nnitrogen\nchlorine\ncarbon\ntar",

	"dummy": "dummy"
}

lib/l10n/app_es.arb

{
	"@@locale":"es",
	"@locale": {
		"description": "スペイン"
	},
	"setting": "Configuración",
	"start": "Comience tocando la pantalla",
	"candidate": "Candidato",
	"initial": "Valor inicial",
	"countdownTime": "tiempo de cuenta regresiva",
	"soundReadyVolume": "Volumen del efecto de sonido: sonido de cuenta regresiva",
	"soundStartVolume": "Volumen del efecto de sonido: sonido de inicio",
	"language": "Idioma",
	"usage1": "Método del lápiz.",
	"usage2": "Haz rodar el lápiz para obtener la respuesta.",
	"usage3": "Registra hasta 6 candidatos, separados por saltos de línea. Las inscripciones con más de 6 serán ignoradas.",
	"usage4": "p.ej.\noxígeno\nhidrógeno hidrógeno\nnitrógeno\ncloro\ncarbón\nalquitrán",

	"dummy": "dummy"
}

lib/l10n/app_et.arb

{
	"@@locale":"et",
	"@locale": {
		"description": "エストニア"
	},
	"setting": "Seadistamine",
	"start": "Alustage ekraani puudutamisest",
	"candidate": "kandidaat",
	"initial": "Algne väärtus",
	"countdownTime": "Loendusaeg",
	"soundReadyVolume": "Heliefekti helitugevus: tagasilugemise heli",
	"soundStartVolume": "Heliefekti helitugevus: käivitage heli",
	"language": "Keel",
	"usage1": "Pliiatsi meetod.",
	"usage2": "Vastuse saamiseks rullige pliiatsit.",
	"usage3": "Registreerige kuni 6 kandidaati, eraldatuna reavahedega. Rohkem kui 6 kandidaate eiratakse.",
	"usage4": "nt.\nhapnikku\nvesinik vesinik\nlämmastik\nkloor\nsüsinik\ntõrva",

	"dummy": "dummy"
}

lib/l10n/app_fi.arb

{
	"@@locale":"fi",
	"@locale": {
		"description": "フィンランド"
	},
	"setting": "Asetus",
	"start": "Aloita napauttamalla näyttöä",
	"candidate": "ehdokas",
	"initial": "Alkuarvo",
	"countdownTime": "Lähtölaskenta aika",
	"soundReadyVolume": "Äänitehosteen äänenvoimakkuus: Ajastinääni",
	"soundStartVolume": "Äänitehosteen voimakkuus: Aloita ääni",
	"language": "Kieli",
	"usage1": "Lyijykynämenetelmä.",
	"usage2": "Pyöritä kynää saadaksesi vastauksen.",
	"usage3": "Rekisteröi korkeintaan 6 ehdokasta rivinvaihdoilla erotettuina. Yli 6 ehdokkaat jätetään huomioimatta.",
	"usage4": "esim.\nhappi\nvety vety\ntyppeä\nkloori\nhiili\nterva",

	"dummy": "dummy"
}

lib/l10n/app_fr.arb

{
	"@@locale":"fr",
	"@locale": {
		"description": "フランス"
	},
	"setting": "Paramètre",
	"start": "Commencez par appuyer sur l'écran",
	"candidate": "Candidat",
	"initial": "Valeur initiale",
	"countdownTime": "Temps de compte à rebours",
	"soundReadyVolume": "Volume de l'effet sonore : son du compte à rebours",
	"soundStartVolume": "Volume de l'effet sonore : Démarrer le son",
	"language": "Langue",
	"usage1": "Méthode au crayon.",
	"usage2": "Faites rouler le crayon pour obtenir la réponse.",
	"usage3": "Inscrivez jusqu'à 6 candidats, séparés par des sauts de ligne. Les inscriptions de plus de 6 seront ignorées.",
	"usage4": "par exemple.\noxygène\nhydrogène hydrogène\nazote\nchlore\ncarbone\nle goudron",

	"dummy": "dummy"
}

lib/l10n/app_hu.arb

{
	"@@locale":"hu",
	"@locale": {
		"description": "ハンガリー"
	},
	"setting": "Beállítás",
	"start": "Kezdje a képernyő megérintésével",
	"candidate": "Jelölt",
	"initial": "Kezdő érték",
	"countdownTime": "Visszaszámlálási idő",
	"soundReadyVolume": "Hangeffektus hangereje: Visszaszámláló hang",
	"soundStartVolume": "Hangeffektus hangereje: Hang indítása",
	"language": "Nyelv",
	"usage1": "Ceruza módszer.",
	"usage2": "Forgassa a ceruzát, hogy megkapja a választ.",
	"usage3": "Legfeljebb 6 jelentkezőt regisztrálhat, sortöréssel elválasztva. A 6-nál több jelentkezőt figyelmen kívül hagyjuk.",
	"usage4": "például.\noxigén\nhidrogén hidrogén\nnitrogén\nklór\nszén\nkátrány",

	"dummy": "dummy"
}

lib/l10n/app_it.arb

{
	"@@locale":"it",
	"@locale": {
		"description": "イタリア"
	},
	"setting": "Collocamento",
	"start": "Inizia toccando lo schermo",
	"candidate": "Candidato",
	"initial": "Valore iniziale",
	"countdownTime": "Tempo del conto alla rovescia",
	"soundReadyVolume": "Volume dell'effetto sonoro: suono del conto alla rovescia",
	"soundStartVolume": "Volume dell'effetto sonoro: avvia il suono",
	"language": "Lingua",
	"usage1": "Metodo della matita.",
	"usage2": "Tira la matita per ottenere la risposta.",
	"usage3": "Iscrivi fino a 6 candidati, separati da interruzioni di riga. Le iscrizioni con più di 6 verranno ignorate.",
	"usage4": "per esempio.\nossigeno\nidrogeno idrogeno\nazoto\ncloro\ncarbonio\ncatrame",

	"dummy": "dummy"
}

lib/l10n/app_ja.arb

{
	"@@locale":"ja",
	"@locale": {
		"description": "日本"
	},
	"setting": "設定",
	"start": "画面内をタップでスタート",
	"candidate": "候補",
	"initial":"初期値",
	"countdownTime": "カウントダウン時間",
	"soundReadyVolume": "音量:開始音",
	"soundStartVolume": "音量:抽選音",
	"language": "言語",
	"usage1": "鉛筆法",
	"usage2": "鉛筆を転がして回答を導きます。",
	"usage3": "候補は改行区切りで最大6個登録します。6個を超える登録は無視されます。",
	"usage4": "例)\n酸素\n水素\n窒素\n塩素\n炭素\nタール",

	"dummy": "dummy"
}

lib/l10n/app_lt.arb

{
	"@@locale":"lt",
	"@locale": {
		"description": "リトアニア"
	},
	"setting": "Nustatymas",
	"start": "Pradėkite bakstelėdami ekraną",
	"candidate": "Kandidatas",
	"initial": "Pradinė vertė",
	"countdownTime": "Atgalinės atskaitos laikas",
	"soundReadyVolume": "Garso efekto garsumas: Atgalinės atskaitos garsas",
	"soundStartVolume": "Garso efekto garsumas: Pradėti garsą",
	"language": "Kalba",
	"usage1": "Pieštuko metodas.",
	"usage2": "Pasukite pieštuką, kad gautumėte atsakymą.",
	"usage3": "Registruokitės iki 6 kandidatų, atskirtų eilučių pertraukomis. Registracijos su daugiau nei 6 bus ignoruojamos.",
	"usage4": "pvz.\ndeguonies\nvandenilis vandenilis\nazoto\nchloro\nanglies\ndeguto",

	"dummy": "dummy"
}

lib/l10n/app_lv.arb

{
	"@@locale":"lv",
	"@locale": {
		"description": "ラトビア"
	},
	"setting": "Iestatījums",
	"start": "Sāciet, pieskaroties ekrānam",
	"candidate": "Kandidāts",
	"initial": "Sākotnējā vērtība",
	"countdownTime": "Atpakaļskaitīšanas laiks",
	"soundReadyVolume": "Skaņas efekta skaļums: Atpakaļskaitīšanas skaņa",
	"soundStartVolume": "Skaņas efekta skaļums: Sākt skaņu",
	"language": "Valoda",
	"usage1": "Zīmuļa metode.",
	"usage2": "Ritiniet zīmuli, lai iegūtu atbildi.",
	"usage3": "Reģistrējiet līdz 6 kandidātiem, atdalot tos ar rindu pārtraukumiem. Reģistrācijas ar vairāk nekā 6 tiks ignorētas.",
	"usage4": "piem.\nskābeklis\nūdeņradis ūdeņradis\nslāpeklis\nhlors\nogleklis\ndarva",

	"dummy": "dummy"
}

lib/l10n/app_nl.arb

{
	"@@locale":"nl",
	"@locale": {
		"description": "オランダ"
	},
	"setting": "Instelling",
	"start": "Begin door op het scherm te tikken",
	"candidate": "Kandidaat",
	"initial": "Beginwaarde",
	"countdownTime": "Afteltijd",
	"soundReadyVolume": "Volume geluidseffect: aftelgeluid",
	"soundStartVolume": "Volume geluidseffect: Start geluid",
	"language": "Taal",
	"usage1": "Potloodmethode.",
	"usage2": "Rol het potlood om het antwoord te krijgen.",
	"usage3": "Registreer maximaal 6 kandidaten, gescheiden door regeleinden. Inschrijvingen met meer dan 6 worden genegeerd.",
	"usage4": "bijv.\nzuurstof\nwaterstof waterstof\nstikstof\nchloor\nkoolstof\nteer",

	"dummy": "dummy"
}

lib/l10n/app_pl.arb

{
	"@@locale":"pl",
	"@locale": {
		"description": "ポーランド"
	},
	"setting": "Ustawienie",
	"start": "Zacznij od dotknięcia ekranu",
	"candidate": "Kandydat",
	"initial": "Wartość początkowa",
	"countdownTime": "Czas odliczania",
	"soundReadyVolume": "Głośność efektu dźwiękowego: Dźwięk odliczania",
	"soundStartVolume": "Głośność efektu dźwiękowego: Włącz dźwięk",
	"language": "Język",
	"usage1": "Metoda ołówkowa.",
	"usage2": "Rzuć ołówkiem, aby uzyskać odpowiedź.",
	"usage3": "Zarejestruj maksymalnie 6 kandydatów, oddzielonych podziałami wierszy. Rejestracje zawierające więcej niż 6 kandydatów będą ignorowane.",
	"usage4": "np.\ntlen\nwodór wodór\nazot\nchlor\nwęgiel\nsmoła",

	"dummy": "dummy"
}

lib/l10n/app_pt.arb

{
	"@@locale":"pt",
	"@locale": {
		"description": "ポルトガル"
	},
	"setting": "Contexto",
	"start": "Comece tocando na tela",
	"candidate": "Candidato",
	"initial": "Valor inicial",
	"countdownTime": "Tempo de contagem regressiva",
	"soundReadyVolume": "Volume do efeito sonoro: Som de contagem regressiva",
	"soundStartVolume": "Volume do efeito sonoro: som inicial",
	"language": "Linguagem",
	"usage1": "Método do lápis.",
	"usage2": "Role o lápis para obter a resposta.",
	"usage3": "Cadastre até 6 candidatos, separados por quebras de linha. Inscrições com mais de 6 serão ignoradas.",
	"usage4": "por exemplo.\noxigênio\nhidrogênio hidrogênio\nazoto\ncloro\ncarbono\nalcatrão",

	"dummy": "dummy"
}

lib/l10n/app_ro.arb

{
	"@@locale":"ro",
	"@locale": {
		"description": "ルーマニア"
	},
	"setting": "Setare",
	"start": "Începeți prin atingerea ecranului",
	"candidate": "Candidat",
	"initial": "Valoarea initiala",
	"countdownTime": "Timp de numărătoare inversă",
	"soundReadyVolume": "Volumul efectului sonor: sunet de numărătoare inversă",
	"soundStartVolume": "Volumul efectului sonor: porniți sunetul",
	"language": "Limba",
	"usage1": "Metoda creionului.",
	"usage2": "Rotiți creionul pentru a obține răspunsul.",
	"usage3": "Înregistrați până la 6 candidați, separați prin întreruperi de rând. Înregistrările cu mai mult de 6 vor fi ignorate.",
	"usage4": "de exemplu.\noxigen\nhidrogen hidrogen\nazot\nclor\ncarbon\ngudron",

	"dummy": "dummy"
}

lib/l10n/app_ru.arb

{
	"@@locale":"ru",
	"@locale": {
		"description": "ロシア"
	},
	"setting": "Параметр",
	"start": "Начните с нажатия на экран",
	"candidate": "Кандидат",
	"initial": "Начальное значение",
	"countdownTime": "Время обратного отсчета",
	"soundReadyVolume": "Громкость звукового эффекта: звук обратного отсчета",
	"soundStartVolume": "Громкость звукового эффекта: Звук запуска",
	"language": "Язык",
	"usage1": "Карандашный метод.",
	"usage2": "Вращайте карандаш, чтобы получить ответ.",
	"usage3": "Зарегистрируйте до 6 кандидатов, разделенных переносами строк. Регистрация более 6 кандидатов будет игнорироваться.",
	"usage4": "например\nкислород\nводород водород\nазот\nхлор\nуглерод\nсмола",

	"dummy": "dummy"
}

lib/l10n/app_sk.arb

{
	"@@locale":"sk",
	"@locale": {
		"description": "スロバキア"
	},
	"setting": "Nastavenie",
	"start": "Začnite ťuknutím na obrazovku",
	"candidate": "Kandidát",
	"initial": "Pôvodná hodnota",
	"countdownTime": "Čas odpočítavania",
	"soundReadyVolume": "Hlasitosť zvukového efektu: Zvuk odpočítavania",
	"soundStartVolume": "Hlasitosť zvukového efektu: Spustenie zvuku",
	"language": "Jazyk",
	"usage1": "Ceruzka metóda.",
	"usage2": "Odpoveď získate rolovaním ceruzky.",
	"usage3": "Zaregistrujte maximálne 6 kandidátov oddelených zalomením riadkov. Registrácie s viac ako 6 budú ignorované.",
	"usage4": "napr.\nkyslík\nvodík vodík\ndusíka\nchlór\nuhlíka\ndecht",

	"dummy": "dummy"
}

lib/l10n/app_sv.arb

{
	"@@locale":"sv",
	"@locale": {
		"description": "スウェーデン"
	},
	"setting": "Miljö",
	"start": "Börja med att trycka på skärmen",
	"candidate": "Kandidat",
	"initial": "Ursprungligt värde",
	"countdownTime": "Nedräkningstid",
	"soundReadyVolume": "Ljudeffektvolym: Ljud för nedräkning",
	"soundStartVolume": "Ljudeffektvolym: Startljud",
	"language": "Språk",
	"usage1": "Blyertsmetod.",
	"usage2": "Rulla med pennan för att få svaret.",
	"usage3": "Registrera upp till 6 kandidater, åtskilda av radbrytningar. Registreringar med fler än 6 kommer att ignoreras.",
	"usage4": "t.ex.\nsyre\nväte väte\nkväve\nklor\nkol\ntjära",

	"dummy": "dummy"
}

lib/l10n/app_th.arb

{
	"@@locale":"th",
	"@locale": {
		"description": "タイ"
	},
	"setting": "การตั้งค่า",
	"start": "เริ่มต้นด้วยการแตะบนหน้าจอ",
	"candidate": "ผู้สมัคร",
	"initial": "ค่าเริ่มต้น",
	"countdownTime": "เวลานับถอยหลัง",
	"soundReadyVolume": "ระดับเอฟเฟกต์เสียง: เสียงนับถอยหลัง",
	"soundStartVolume": "ระดับเสียงเอฟเฟกต์: เริ่มเสียง",
	"language": "ภาษา",
	"usage1": "วิธีดินสอ",
	"usage2": "ม้วนดินสอเพื่อหาคำตอบ",
	"usage3": "ลงทะเบียนผู้สมัครได้สูงสุด 6 คน คั่นด้วยการขึ้นบรรทัดใหม่ การลงทะเบียนที่มีมากกว่า 6 คนจะถูกละเว้น",
	"usage4": "เช่น.\nออกซิเจน\nไฮโดรเจน ไฮโดรเจน\nไนโตรเจน\nคลอรีน\nคาร์บอน\nทาร์",

	"dummy": "dummy"
}

lib/l10n/app_zh.arb

{
	"@@locale":"zh",
	"@locale": {
		"description": "中国"
	},
	"setting": "环境",
	"start": "首先点击屏幕开始",
	"candidate": "候选人",
	"initial": "初始值",
	"countdownTime": "倒计时时间",
	"soundReadyVolume": "音效音量: 倒计时声音",
	"soundStartVolume": "音效音量:开始声音",
	"language": "语言",
	"usage1": "铅笔法。",
	"usage2": "滚动铅笔以获得答案。",
	"usage3": "最多注册 6 名候选人,以换行符分隔。超过 6 名的注册将被忽略。",
	"usage4": "例如\n氧\n氢 氢\n氮\n氯\n碳\n柏油",

	"dummy": "dummy"
}