ソースコード source code

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

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

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

pubspec.yaml

name: taplottery
description: "Tap Lottery"
# 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.5+6

environment:
  sdk: '>=3.3.0-48.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: ^4.1.0
  shared_preferences: ^2.0.17
  #http: ^1.1.0   #不要
  flutter_localizations:    #多言語ライブラリの本体    # .arbファイルを更新したら flutter gen-l10n
    sdk: flutter
  intl: ^0.18.1     #多言語やフォーマッタなどの関連ライブラリ
  google_mobile_ads: ^3.1.0
  just_audio: ^0.9.35
  flutter_svg: ^2.0.9

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_launcher_icons: ^0.13.1    #flutter pub run flutter_launcher_icons
  flutter_native_splash: ^2.3.5     #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: '#2A01AD'
  image: 'assets/image/splash.png'
  color_dark: '#2A01AD'
  image_dark: 'assets/image/splash.png'
  fullscreen: true
  android_12:
    icon_background_color: '#2A01AD'
    image: 'assets/image/splash.png'
    icon_background_color_dark: '#2A01AD'
    image_dark: 'assets/image/splash.png'

# The following section is specific to Flutter packages.
flutter:
  generate: true    #pub get時に多言語対応のファイルが自動生成される

  # 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/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:taplottery/const_value.dart';

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

  double _soundVolume = 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.audioResult);
    }
    for (int i = 0; i < _player02.length; i++) {
      await _player02[i].setVolume(0);
      await _player02[i].setAsset(ConstValue.audioTap);
    }
    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 soundVolume {
    return _soundVolume;
  }
  //setter
  set soundVolume(double vol) {
    _soundVolume = vol;
  }
  //最初に音が鳴らないのを回避する方法
  void playZero() async {
    AudioPlayer ap = AudioPlayer();
    await ap.setAsset(ConstValue.audioZero);
    await ap.load();
    await ap.play();
  }
  //
  void play01() async {
    if (_soundVolume == 0) {
      return;
    }
    _player01Ptr += 1;
    if (_player01Ptr >= _player01.length) {
      _player01Ptr = 0;
    }
    await _player01[_player01Ptr].setVolume(_soundVolume * 0.7);
    await _player01[_player01Ptr].pause();
    await _player01[_player01Ptr].seek(Duration.zero);
    await _player01[_player01Ptr].play();
  }
  void play02() async {
    if (_soundVolume == 0) {
      return;
    }
    _player02Ptr += 1;
    if (_player02Ptr >= _player02.length) {
      _player02Ptr = 0;
    }
    await _player02[_player02Ptr].setVolume(_soundVolume);
    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 prefCountdownTime = 'countdownTime';
  static const String prefResultDisplayDuration = 'resultDisplayDuration';
  static const String prefSoundVolume = 'soundVolume';
  static const String prefBackgroundImageNumber = 'backgroundImageNumber';
  //image
  static const String imageSpace1 = 'assets/image/space1.webp';
  static const List<String> imageBackGrounds = [
    'assets/image/bg1.webp',
    'assets/image/bg2.webp',
    'assets/image/bg3.webp',
    'assets/image/bg4.webp',
    'assets/image/bg5.webp',
    'assets/image/bg6.webp',
    'assets/image/bg7.webp',
    'assets/image/bg8.webp',
    'assets/image/bg9.webp',
    'assets/image/bg10.webp',
  ];
  static const String imageNumberNull = 'assets/image/number_null.webp';
  static const String imageNumber0 = 'assets/image/number0.webp';
  static const String imageNumber1 = 'assets/image/number1.webp';
  static const String imageNumber2 = 'assets/image/number2.webp';
  static const String imageNumber3 = 'assets/image/number3.webp';
  static const String imageNumber4 = 'assets/image/number4.webp';
  static const String imageNumber5 = 'assets/image/number5.webp';
  static const String imageNumber6 = 'assets/image/number6.webp';
  static const String imageNumber7 = 'assets/image/number7.webp';
  static const String imageNumber8 = 'assets/image/number8.webp';
  static const String imageNumber9 = 'assets/image/number9.webp';
  static const String imageRing1 = 'assets/image/ring1_blue1.svg';
  static const String imageRing2 = 'assets/image/ring1_blue2.svg';
  static const String imageRing3 = 'assets/image/ring1_blue3.svg';
  static const String imageRing4 = 'assets/image/ring1_blue4.svg';
  static const String imageFire = 'assets/image/ring1_fire.svg';
  static const List<String> imageNumbers = [
    imageNumberNull,
    imageNumber1,
    imageNumber2,
    imageNumber3,
    imageNumber4,
    imageNumber5,
    imageNumber6,
    imageNumber7,
    imageNumber8,
    imageNumber9,
  ];
  //color
  static const Color colorButtonBack = Color.fromARGB(60, 0,0,0);
  static const Color colorButtonFore = Color.fromARGB(255, 255, 255, 255);
  static const Color colorBack = Color.fromARGB(100, 42,1,173);
  static const Color colorSettingAccent = Color.fromARGB(255, 76,43,164);
  //sound
  static const String audioZero = 'assets/sound/zero.wav';    //無音1秒
  static const String audioTap = 'assets/sound/piko.wav';
  static const String audioResult = 'assets/sound/bigo.wav';
  //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:taplottery/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 'dart:math';
import 'package:flutter/gestures.dart';
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_svg/flutter_svg.dart';
import 'package:flutter/foundation.dart' show kIsWeb;

//自身で作成したclassを読み込む
import 'package:taplottery/const_value.dart';
import 'package:taplottery/language_state.dart';
import 'package:taplottery/version_state.dart';
import 'package:taplottery/setting.dart';
import 'package:taplottery/ad_mob.dart';
import 'package:taplottery/page_state.dart';
import 'package:taplottery/preferences.dart';
import 'package:taplottery/tap_position.dart';
import 'package:taplottery/audio_play.dart';
import 'package:taplottery/play_mode.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) {
    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 AdMob _adMob = AdMob(); //広告表示
  final AudioPlay _audioPlay = AudioPlay(); //広告表示
  int _countdownTime = 5; //0までカウントダウンされる秒数
  int _resultDisplayDuration = 5; //結果表示秒数
  int _countdownSubtraction = 0;  //_countdownTimeが代入されて実際にカウントダウンされる
  double _screenWidth = 0;  //画面幅
  double _screenHeight = 0; //画面高さ
  double _bgImageSize = 0;  //背景画像サイズ
  double _bgImageAngle = 0; //背景画像回転角度
  String _imageCountdownNumber = ConstValue.imageNumberNull;  //カウントダウン画像
  double _countdownScale = 3;   //カウントダウン画像の拡大率
  double _countdownOpacity = 0; //カウントダウン画像の非透明度
  int _timerCount = 30; //Timerで処理される1秒間の数
  List<TapPosition> _tapPositions = []; //タップ位置
  TapPosition _lastTapPosition = TapPosition(0,0);  //最後にタップされた位置
  PlayMode _playMode = PlayMode.ready;  //現在の状況
  int _backgroundImageNumber = 0;

  //アプリのバージョン取得
  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();
    (() async {
      await Preferences.initial();
      _countdownTime = Preferences.countdownTime;
      _resultDisplayDuration = Preferences.resultDisplayDuration;
      _audioPlay.soundVolume = Preferences.soundVolume;
      _backgroundImageNumber = Preferences.backgroundImageNumber;
      Timer.periodic(const Duration(milliseconds: (1000 ~/ 30)), (timer) {
        setState(() {
          _countdown();
          _bgImageAngle -= 0.002;
          if (_bgImageAngle < -314159265) {
            _bgImageAngle = 0;
          }
        });
      });
      setState(() {});
    })();
  }
  //ページ終了時に一度だけ呼ばれる
  @override
  void dispose() {
    PageState.setCurrentPage('');
    _adMob.dispose();
    super.dispose();
  }
  //画面全体
  @override
  Widget build(BuildContext context) {
    _screenWidth = MediaQuery.of(context).size.width;
    _screenHeight = MediaQuery.of(context).size.height;
    _bgImageSize = max(_screenWidth,_screenHeight);
    return Container(
      decoration: _decoration(),
      child: Scaffold(
        backgroundColor: Colors.transparent,  //Scaffoldの背景に画像を引いているのでここは透明
        appBar: AppBar(
          backgroundColor: ConstValue.colorBack,
          //タイトル表示
          title: const Text('Tap lottery',
            style: TextStyle(
              color: ConstValue.colorButtonFore,
              fontSize: 15.0,
            )
          ),
          //設定ボタン
          actions: <Widget>[
            TextButton(
              onPressed: () async {
                bool? ret = await Navigator.of(context).push(
                  MaterialPageRoute<bool>(
                    builder: (context) => const SettingPage(),
                  ),
                );
                //awaitで呼び出しているので、settingから戻ったら以下が実行される。
                if (ret!) { //設定で適用だった場合
                  _getCurrentLocale();
                  _countdownTime = Preferences.countdownTime;
                  _resultDisplayDuration = Preferences.resultDisplayDuration;
                  _audioPlay.soundVolume = Preferences.soundVolume;
                  _backgroundImageNumber = Preferences.backgroundImageNumber;
                  setState(() {});
                }
              },
              child: Text(
                AppLocalizations.of(context)!.setting,
                style: const TextStyle(
                  color: ConstValue.colorButtonFore,
                )
              )
            )
          ]
        ),
        body: SafeArea(
          child: Column(children:[
            Expanded(
              child: RawGestureDetector(
                gestures: {
                  MultiTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<MultiTapGestureRecognizer>(
                    () => MultiTapGestureRecognizer(),
                    (MultiTapGestureRecognizer instance) {
                      instance.onTapDown = (pointer, details) {
                        _onTapDown(details);
                      };
                    },
                  ),
                },
                child: Stack(children:[
                  _background(),
                  Container(
                    alignment: Alignment.center,
                    child: Opacity(
                      opacity: _countdownOpacity,
                      child: Transform.scale(
                        scale: _countdownScale,
                        child: Image.asset(
                          _imageCountdownNumber,
                        ),
                      )
                    )
                  ),
                  Stack(children:_tapCircles()),
                ])
              )
            ),
            //広告
            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(
      image: DecorationImage(
        image: AssetImage(ConstValue.imageSpace1),
        fit: BoxFit.cover,
      ),
    );
  }
  Widget _background() {
    return Transform.rotate(
      angle: _bgImageAngle,
      child: Transform.scale(
        scale: 2.4,
        child: Image.asset(
          ConstValue.imageBackGrounds[_backgroundImageNumber],
          width: _bgImageSize,
          height: _bgImageSize,
        ),
      ),
    );
  }
  //タップされた位置に円を表示
  List<Widget> _tapCircles() {
    if (_tapPositions.isEmpty) {
      return [Container()];
    }
    const double widthRatio = 0.3;
    List<Widget> circles = [];
    circles.add(Container());
    if (_playMode == PlayMode.judgeDraw) {
      TapPosition tapPosition = _tapPositions[0];
      Widget circle5 = Positioned(
        left: tapPosition.x - _screenWidth * widthRatio / 2,
        top: tapPosition.y - _screenWidth * widthRatio / 2,
        child: Transform.rotate(
          angle: _bgImageAngle * 35,
          child: Transform.scale(
            scale: 1 + (_bgImageAngle % 0.04 - 0.02).abs() * 20,
            child: Opacity(
              opacity: 0.5,
              child: SvgPicture.asset(
                ConstValue.imageFire,
                width: _screenWidth * widthRatio,
                height: _screenWidth * widthRatio,
              )
            )
          )
        )
      );
      circles.add(circle5);
    }
    if (_playMode == PlayMode.countdown || _playMode == PlayMode.judgeDraw) {
      for (TapPosition tapPosition in _tapPositions) {
        Widget circle1 = Positioned(
          left: tapPosition.x - _screenWidth * widthRatio / 2,
          top: tapPosition.y - _screenWidth * widthRatio / 2,
          child: Transform.rotate(
            angle: _bgImageAngle * 25,
            child: SvgPicture.asset(
              ConstValue.imageRing1,
              width: _screenWidth * widthRatio,
              height: _screenWidth * widthRatio,
            )
          )
        );
        Widget circle2 = Positioned(
          left: tapPosition.x - _screenWidth * widthRatio / 2,
          top: tapPosition.y - _screenWidth * widthRatio / 2,
          child: Transform.rotate(
            angle: _bgImageAngle * 60,
            child: SvgPicture.asset(
              ConstValue.imageRing2,
              width: _screenWidth * widthRatio,
              height: _screenWidth * widthRatio,
            )
          )
        );
        Widget circle3 = Positioned(
          left: tapPosition.x - _screenWidth * widthRatio / 2,
          top: tapPosition.y - _screenWidth * widthRatio / 2,
          child: Transform.rotate(
            angle: _bgImageAngle * -50,
            child: SvgPicture.asset(
              ConstValue.imageRing3,
              width: _screenWidth * widthRatio,
              height: _screenWidth * widthRatio,
            )
          )
        );
        Widget circle4 = Positioned(
          left: tapPosition.x - _screenWidth * widthRatio / 2,
          top: tapPosition.y - _screenWidth * widthRatio / 2,
          child: Transform.rotate(
            angle: _bgImageAngle * -100,
            child: SvgPicture.asset(
              ConstValue.imageRing4,
              width: _screenWidth * widthRatio,
              height: _screenWidth * widthRatio,
            )
          )
        );
        circles.add(circle1);
        circles.add(circle2);
        circles.add(circle3);
        circles.add(circle4);
      }
    }
    return circles;
  }
  //タップされたときの処理
  void _onTapDown(TapDownDetails details) {
    if (_playMode == PlayMode.ready || _playMode == PlayMode.countdown) {
      _lastTapPosition = TapPosition(details.localPosition.dx.toInt(), details.localPosition.dy.toInt());
      _tapPositions.add(_lastTapPosition);
      _countdownSubtraction = _countdownTime;
      _timerCount = 30;
      _playMode = PlayMode.countdown;
      _audioPlay.play02();
    }
  }
  //Timerで定期実行
  void _countdown() {
    //カウントダウン終了時または結果表示時
    if (_countdownSubtraction == 0) {
      if (_playMode == PlayMode.judgeStart) { //結果表示
        _judge();
      }
      return;
    }
    //タイミングがずれる時の処理
    _playMode = PlayMode.countdown;
    if (_tapPositions.isEmpty) {
      //タイミングによってはEmptyになってしまうので位置情報を入れ直す
      _tapPositions = [_lastTapPosition];
    }
    //数字画像を切り替え
    if (_timerCount == 30) {
      _imageCountdownNumber = ConstValue.imageNumbers[_countdownSubtraction];
    }
    _timerCount -= 1;
    if (_timerCount <= 0) {
      _timerCount = 30;
      _countdownSubtraction -= 1;
      if (_countdownSubtraction == 0) {
        _imageCountdownNumber = ConstValue.imageNumbers[0];
        _playMode = PlayMode.judgeStart;
      }
    }
    _countdownScale = 1 + (0.1 * (_timerCount / 30));
    if (_timerCount >= 20) {
      _countdownOpacity = (30 - _timerCount) / 10;
    } else if (_timerCount <= 5) {
      _countdownOpacity = _timerCount / 5;
    } else {
      _countdownOpacity = 1;
    }
  }
  //結果表示
  void _judge() {
    if (_tapPositions.isEmpty) {
      _playMode = PlayMode.ready;
      return;
    }
    if (_playMode == PlayMode.judgeStart) {
      _playMode = PlayMode.judgeDraw;
      _tapPositions.shuffle();
      TapPosition choice = _tapPositions[0];
      _tapPositions = [choice];
      _audioPlay.play01();
      Future.delayed(Duration(seconds: _resultDisplayDuration), () {
        _tapPositions = [];
        _playMode = PlayMode.ready;
      });
    }
  }
}

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/play_mode.dart

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

enum PlayMode {
  ready,  //準備中
  countdown,  //カウントダウン中
  judgeStart, //結果表示開始
  judgeDraw,  //結果表示中
}

lib/preferences.dart

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

import 'package:shared_preferences/shared_preferences.dart';

import 'package:taplottery/const_value.dart';

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

  static bool ready = false;

  //この値は常に最新にしておく
  static String _languageCode = '';
  static int _countdownTime = 5;
  static int _resultDisplayDuration = 5;
  static double _soundVolume = 1.0;
  static int _backgroundImageNumber = 0;

  static String get languageCode {
    return _languageCode;
  }
  static int get countdownTime {
    return _countdownTime;
  }
  static int get resultDisplayDuration {
    return _resultDisplayDuration;
  }
  static double get soundVolume {
    return _soundVolume;
  }
  static int get backgroundImageNumber {
    return _backgroundImageNumber;
  }

  static Future<void> initial() async {
    _languageCode = await getLanguageCode();
    _countdownTime = await getCountdownTime();
    _resultDisplayDuration = await getResultDisplayDuration();
    _soundVolume = await getSoundVolume();
    _backgroundImageNumber = await getBackgroundImageNumber();
    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> 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) ?? 5;
    return num;
  }

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

  //結果表示時間
  static Future<void> setResultDisplayDuration(int num) async {
    _resultDisplayDuration = num;
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setInt(ConstValue.prefResultDisplayDuration, num);
  }
  static Future<int> getResultDisplayDuration() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefResultDisplayDuration) ?? 5;
    return num;
  }

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

  //効果音音量
  static Future<void> setSoundVolume(double num) async {
    _soundVolume = num;
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setDouble(ConstValue.prefSoundVolume, num);
  }
  static Future<double> getSoundVolume() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final double num = prefs.getDouble(ConstValue.prefSoundVolume) ?? 1.0;
    return num;
  }

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

  //背景画像番号
  static Future<void> setBackgroundImageNumber(int num) async {
    _backgroundImageNumber = num;
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setInt(ConstValue.prefBackgroundImageNumber, num);
  }
  static Future<int> getBackgroundImageNumber() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefBackgroundImageNumber) ?? 0;
    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:taplottery/const_value.dart';
import 'package:taplottery/preferences.dart';
import 'package:taplottery/language_state.dart';
import 'package:taplottery/version_state.dart';
import 'package:taplottery/ad_mob.dart';
import 'package:taplottery/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 = '';
  int _countdownTime = 5;
  int _resultDisplayDuration = 5;
  double _soundVolume = 0.0;
  int _backgroundImageNumber = 0;

  //ページ起動時に一度だけ実行される
  @override
  void initState() {
    super.initState();
    _adMob.load();
  }
  //ページ終了時に一度だけ実行される
  @override
  void dispose() {
    PageState.setCurrentPage('');
    _adMob.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();
        _countdownTime = Preferences.countdownTime;
        _resultDisplayDuration = Preferences.resultDisplayDuration;
        _soundVolume = Preferences.soundVolume;
        _backgroundImageNumber = Preferences.backgroundImageNumber;
        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.colorSettingAccent,
        actions: [
          //設定OKボタン
          IconButton(
            icon: const Icon(Icons.check),
            onPressed: () async {
              await LanguageState.setLanguageCode(_languageKey);
              await Preferences.setCountdownTime(_countdownTime);
              await Preferences.setResultDisplayDuration(_resultDisplayDuration);
              await Preferences.setSoundVolume(_soundVolume);
              await Preferences.setBackgroundImageNumber(_backgroundImageNumber);
              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: 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: 1,
                          max: 9,
                          divisions: 8,
                          onChanged: (double value) {
                            setState(() {
                              _countdownTime = value.toInt();
                            });
                          }
                        )
                      )
                    ])
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                    child: Row(children: [
                      Text(AppLocalizations.of(context)!.resultDisplayDuration,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(_resultDisplayDuration.toString()),
                      Expanded(
                        child: Slider(
                          value: _resultDisplayDuration.toDouble(),
                          min: 1,
                          max: 9,
                          divisions: 8,
                          onChanged: (double value) {
                            setState(() {
                              _resultDisplayDuration = value.toInt();
                            });
                          }
                        )
                      )
                    ])
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                    child: Row(children: [
                      Text(AppLocalizations.of(context)!.soundVolume,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(_soundVolume.toString()),
                      Expanded(
                        child: Slider(
                          value: _soundVolume,
                          min: 0.0,
                          max: 1.0,
                          divisions: 10,
                          onChanged: (double value) {
                            setState(() {
                              _soundVolume = value;
                            });
                          }
                        )
                      )
                    ])
                  ),
                  _border(),
                  Padding(
                      padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                      child: Row(children: [
                        Text(AppLocalizations.of(context)!.backgroundImageNumber,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(_backgroundImageNumber.toString()),
                        Expanded(
                            child: Slider(
                                value: _backgroundImageNumber.toDouble(),
                                min: 0,
                                max: 9,
                                divisions: 9,
                                onChanged: (double value) {
                                  setState(() {
                                    _backgroundImageNumber = value.toInt();
                                  });
                                }
                            )
                        )
                      ])
                  ),
                  _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);
          });
        },
      ),
    );
  }

}

lib/tap_position.dart

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

class TapPosition {
  int x;
  int y;

  TapPosition(this.x, this.y);
}

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": "Настройка",
	"countdownTime": "Време за обратно броене",
	"resultDisplayDuration": "Продължителност на показване на резултата",
	"soundVolume": "Сила на звука на звуковия ефект",
	"backgroundImageNumber": "Фоново изображение",
	"language": "език",
	"usage1": "Един човек ще бъде избран чрез теглене на жребий от няколко души.",
	"usage2": "Докоснете и задръжте екрана с няколко души. Резултатите от лотарията ще бъдат показани след края на обратното броене.",
	"usage3": "Можете да участвате в лотарията по време на обратното броене.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_cs.arb

{
	"@@locale":"cs",
	"@locale": {
		"description": "チェコ"
	},
	"setting": "Nastavení",
	"countdownTime": "Čas odpočítávání",
	"resultDisplayDuration": "Trvání zobrazení výsledku",
	"soundVolume": "Hlasitost zvukového efektu",
	"backgroundImageNumber": "Obrázek na pozadí",
	"language": "Jazyk",
	"usage1": "Jedna osoba bude vybrána losováním z více lidí.",
	"usage2": "Dotkněte se a podržte obrazovku s více lidmi. Výsledky loterie se zobrazí po skončení odpočítávání.",
	"usage3": "Během odpočítávání se můžete zúčastnit loterie.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_da.arb

{
	"@@locale":"da",
	"@locale": {
		"description": "デンマーク"
	},
	"setting": "Indstilling",
	"countdownTime": "Nedtællingstid",
	"resultDisplayDuration": "Resultatvisningsvarighed",
	"soundVolume": "Lydeffekt lydstyrke",
	"backgroundImageNumber": "Baggrundsbillede",
	"language": "Sprog",
	"usage1": "En person vil blive udvalgt ved lodtrækning blandt flere personer.",
	"usage2": "Tryk og hold på skærmen med flere personer. Lotteriresultaterne vil blive vist efter nedtællingen er slut.",
	"usage3": "Du kan deltage i lotteriet under nedtællingen.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_de.arb

{
	"@@locale":"de",
	"@locale": {
		"description": "ドイツ"
	},
	"setting": "Einstellung",
	"countdownTime": "Countdown-Zeit",
	"resultDisplayDuration": "Dauer der Ergebnisanzeige",
	"soundVolume": "Lautstärke des Soundeffekts",
	"backgroundImageNumber": "Hintergrundbild",
	"language": "Sprache",
	"usage1": "Aus mehreren Personen wird eine Person ausgelost.",
	"usage2": "Berühren und halten Sie den Bildschirm mit mehreren Personen. Die Lotterieergebnisse werden nach Ablauf des Countdowns angezeigt.",
	"usage3": "Während des Countdowns können Sie an der Verlosung teilnehmen.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_el.arb

{
	"@@locale":"el",
	"@locale": {
		"description": "ギリシャ"
	},
	"setting": "Σύνθεση",
	"countdownTime": "Ώρα αντίστροφης μέτρησης",
	"resultDisplayDuration": "Διάρκεια εμφάνισης αποτελεσμάτων",
	"soundVolume": "Ένταση ήχου εφέ",
	"backgroundImageNumber": "Εικόνα φόντου",
	"language": "Γλώσσα",
	"usage1": "Ένα άτομο θα επιλεγεί με κλήρωση από πολλά άτομα.",
	"usage2": "Αγγίξτε παρατεταμένα την οθόνη με πολλά άτομα. Τα αποτελέσματα της κλήρωσης θα εμφανιστούν μετά το τέλος της αντίστροφης μέτρησης.",
	"usage3": "Μπορείτε να συμμετάσχετε στην κλήρωση κατά τη διάρκεια της αντίστροφης μέτρησης.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_en.arb

{
	"@@locale":"en",
	"@locale": {
		"description": "英語"
	},
	"setting": "Setting",
	"countdownTime": "Countdown Time",
	"resultDisplayDuration": "Result display duration",
	"soundVolume": "Sound Effect Volume",
	"backgroundImageNumber": "Background image",
	"language": "Language",
	"usage1": "One person will be selected by drawing lots from multiple people.",
	"usage2": "Touch and hold the screen with multiple people. The lottery results will be displayed after the countdown ends.",
	"usage3": "You can participate in the lottery during the countdown.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_es.arb

{
	"@@locale":"es",
	"@locale": {
		"description": "スペイン"
	},
	"setting": "Configuración",
	"countdownTime": "Tiempo de cuenta regresiva",
	"resultDisplayDuration": "Duración de la visualización de resultados",
	"soundVolume": "Volumen del efecto de sonido",
	"backgroundImageNumber": "Imagen de fondo",
	"language": "Idioma",
	"usage1": "Se seleccionará una persona mediante sorteo entre varias personas.",
	"usage2": "Toque y mantenga presionada la pantalla con varias personas. Los resultados de la lotería se mostrarán una vez que finalice la cuenta regresiva.",
	"usage3": "Puedes participar en la lotería durante la cuenta atrás.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_et.arb

{
	"@@locale":"et",
	"@locale": {
		"description": "エストニア"
	},
	"setting": "Seadistamine",
	"countdownTime": "Loendusaeg",
	"resultDisplayDuration": "Tulemuse kuvamise kestus",
	"soundVolume": "Heliefekti helitugevus",
	"backgroundImageNumber": "Taustapilt",
	"language": "Keel",
	"usage1": "Üks inimene valitakse loosi teel mitme inimese seast.",
	"usage2": "Puudutage ja hoidke ekraani koos mitme inimesega. Loterii tulemused kuvatakse pärast loenduse lõppu.",
	"usage3": "Loosis saab osaleda loenduse ajal.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_fi.arb

{
	"@@locale":"fi",
	"@locale": {
		"description": "フィンランド"
	},
	"setting": "Asetus",
	"countdownTime": "Lähtölaskenta aika",
	"resultDisplayDuration": "Tuloksen näytön kesto",
	"soundVolume": "Äänitehosteen äänenvoimakkuus",
	"backgroundImageNumber": "Taustakuva",
	"language": "Kieli",
	"usage1": "Yksi henkilö valitaan arpomalla useiden henkilöiden kesken.",
	"usage2": "Kosketa ja pidä näyttöä painettuna useiden ihmisten kanssa. Lottotulokset näkyvät lähtölaskennan päätyttyä.",
	"usage3": "Voit osallistua arvontaan lähtölaskennan aikana.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_fr.arb

{
	"@@locale":"fr",
	"@locale": {
		"description": "フランス"
	},
	"setting": "Paramètre",
	"countdownTime": "Temps de compte à rebours",
	"resultDisplayDuration": "Durée d'affichage des résultats",
	"soundVolume": "Volume des effets sonores",
	"backgroundImageNumber": "Image de fond",
	"language": "Langue",
	"usage1": "Une personne sera sélectionnée par tirage au sort parmi plusieurs personnes.",
	"usage2": "Touchez et maintenez l'écran avec plusieurs personnes. Les résultats de la loterie seront affichés une fois le compte à rebours terminé.",
	"usage3": "Vous pouvez participer à la loterie pendant le compte à rebours.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_hu.arb

{
	"@@locale":"hu",
	"@locale": {
		"description": "ハンガリー"
	},
	"setting": "Beállítás",
	"countdownTime": "Visszaszámlálás ideje",
	"resultDisplayDuration": "Az eredmény megjelenítési időtartama",
	"soundVolume": "Hangeffektus hangereje",
	"backgroundImageNumber": "Háttérkép",
	"language": "Nyelv",
	"usage1": "Egy személyt több ember közül sorsolással választanak ki.",
	"usage2": "Érintse meg és tartsa lenyomva a képernyőt több emberrel. A sorsolás eredménye a visszaszámlálás után jelenik meg.",
	"usage3": "A sorsoláson a visszaszámlálás alatt lehet részt venni.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_it.arb

{
	"@@locale":"it",
	"@locale": {
		"description": "イタリア"
	},
	"setting": "Collocamento",
	"countdownTime": "Tempo del conto alla rovescia",
	"resultDisplayDuration": "Durata della visualizzazione dei risultati",
	"soundVolume": "Volume dell'effetto sonoro",
	"backgroundImageNumber": "Immagine di sfondo",
	"language": "Lingua",
	"usage1": "Una persona verrà selezionata mediante estrazione a sorte tra più persone.",
	"usage2": "Tocca e tieni premuto lo schermo con più persone. I risultati della lotteria verranno visualizzati al termine del conto alla rovescia.",
	"usage3": "Puoi partecipare alla lotteria durante il conto alla rovescia.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_ja.arb

{
	"@@locale":"ja",
	"@locale": {
		"description": "日本"
	},
	"setting": "設定",
	"countdownTime": "抽選時間(秒)",
	"resultDisplayDuration": "抽選結果表示時間(秒)",
	"soundVolume": "効果音の音量",
	"backgroundImageNumber": "背景画像",
	"language": "言語",
	"usage1": "複数人から抽選して1人を決めます。",
	"usage2": "画面を複数人で長押ししたままにします。カウントダウン終了後に抽選結果が表示されます。",
	"usage3": "カウントダウン中の抽選に参加できます。",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_lt.arb

{
	"@@locale":"lt",
	"@locale": {
		"description": "リトアニア"
	},
	"setting": "Nustatymas",
	"countdownTime": "Atgalinės atskaitos laikas",
	"resultDisplayDuration": "Rezultato rodymo trukmė",
	"soundVolume": "Garso efekto garsumas",
	"backgroundImageNumber": "Fono vaizdas",
	"language": "Kalba",
	"usage1": "Vienas asmuo bus atrinktas burtų keliu iš kelių žmonių.",
	"usage2": "Palieskite ir palaikykite ekraną su keliais žmonėmis. Loterijos rezultatai bus rodomi pasibaigus atgaliniam skaičiavimui.",
	"usage3": "Dalyvauti loterijoje galite laiko skaičiavimo metu.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_lv.arb

{
	"@@locale":"lv",
	"@locale": {
		"description": "ラトビア"
	},
	"setting": "Iestatījums",
	"countdownTime": "Atpakaļskaitīšanas laiks",
	"resultDisplayDuration": "Rezultāta parādīšanas ilgums",
	"soundVolume": "Skaņas efekta skaļums",
	"backgroundImageNumber": "Fona attēls",
	"language": "Valoda",
	"usage1": "Viena persona tiks izvēlēta, izlozējot no vairākiem cilvēkiem.",
	"usage2": "Pieskarieties ekrānam un turiet to kopā ar vairākiem cilvēkiem. Loterijas rezultāti tiks parādīti pēc laika atskaites beigām.",
	"usage3": "Loterijā var piedalīties laika atskaites laikā.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_nl.arb

{
	"@@locale":"nl",
	"@locale": {
		"description": "オランダ"
	},
	"setting": "Instelling",
	"countdownTime": "Afteltijd",
	"resultDisplayDuration": "Weergaveduur resultaat",
	"soundVolume": "Geluidseffectvolume",
	"backgroundImageNumber": "Achtergrond afbeelding",
	"language": "Taal",
	"usage1": "Door loting uit meerdere personen wordt één persoon geselecteerd.",
	"usage2": "Blijf het scherm aanraken met meerdere mensen. De loterijresultaten worden weergegeven nadat het aftellen is afgelopen.",
	"usage3": "Tijdens het aftellen kun je meedoen aan de loterij.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_pl.arb

{
	"@@locale":"pl",
	"@locale": {
		"description": "ポーランド"
	},
	"setting": "Ustawienie",
	"countdownTime": "Czas odliczania",
	"resultDisplayDuration": "Czas wyświetlania wyniku",
	"soundVolume": "Głośność efektu dźwiękowego",
	"backgroundImageNumber": "Zdjęcie w tle",
	"language": "Język",
	"usage1": "Jedna osoba zostanie wybrana w drodze losowania spośród wielu osób.",
	"usage2": "Dotknij i przytrzymaj ekran z wieloma osobami. Wyniki loterii zostaną wyświetlone po zakończeniu odliczania.",
	"usage3": "Podczas odliczania możesz wziąć udział w loterii.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_pt.arb

{
	"@@locale":"pt",
	"@locale": {
		"description": "ポルトガル"
	},
	"setting": "Contexto",
	"countdownTime": "Tempo de contagem regressiva",
	"resultDisplayDuration": "Duração da exibição do resultado",
	"soundVolume": "Volume do efeito sonoro",
	"backgroundImageNumber": "Imagem de fundo",
	"language": "Linguagem",
	"usage1": "Uma pessoa será selecionada por sorteio de várias pessoas.",
	"usage2": "Toque e segure a tela com várias pessoas. Os resultados da loteria serão exibidos após o término da contagem regressiva.",
	"usage3": "Você pode participar da loteria durante a contagem regressiva.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_ro.arb

{
	"@@locale":"ro",
	"@locale": {
		"description": "ルーマニア"
	},
	"setting": "Setare",
	"countdownTime": "Timp de numărătoare inversă",
	"resultDisplayDuration": "Durata de afișare a rezultatelor",
	"soundVolume": "Volumul efectului sonor",
	"backgroundImageNumber": "Imagine de fundal",
	"language": "Limba",
	"usage1": "O persoană va fi selectată prin tragere la sorți din mai multe persoane.",
	"usage2": "Atingeți lung ecranul cu mai multe persoane. Rezultatele loteriei vor fi afișate după încheierea numărătorii inverse.",
	"usage3": "Puteți participa la loterie în timpul numărătoarei inverse.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_ru.arb

{
	"@@locale":"ru",
	"@locale": {
		"description": "ロシア"
	},
	"setting": "Параметр",
	"countdownTime": "Время обратного отсчета",
	"resultDisplayDuration": "Продолжительность отображения результата",
	"soundVolume": "Громкость звукового эффекта",
	"backgroundImageNumber": "Изображение на заднем плане",
	"language": "Язык",
	"usage1": "Один человек будет выбран путем жеребьевки от нескольких человек.",
	"usage2": "Коснитесь и удерживайте экран вместе с несколькими людьми. Результаты лотереи будут отображены после завершения обратного отсчета.",
	"usage3": "Вы можете принять участие в лотерее во время обратного отсчета.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_sk.arb

{
	"@@locale":"sk",
	"@locale": {
		"description": "スロバキア"
	},
	"setting": "Nastavenie",
	"countdownTime": "Čas odpočítavania",
	"resultDisplayDuration": "Trvanie zobrazenia výsledku",
	"soundVolume": "Hlasitosť zvukových efektov",
	"backgroundImageNumber": "Obrázok na pozadí",
	"language": "Jazyk",
	"usage1": "Jedna osoba bude vybraná žrebovaním z viacerých ľudí.",
	"usage2": "Dotknite sa a podržte obrazovku s viacerými ľuďmi. Výsledky lotérie sa zobrazia po skončení odpočítavania.",
	"usage3": "Počas odpočítavania sa môžete zúčastniť lotérie.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_sv.arb

{
	"@@locale":"sv",
	"@locale": {
		"description": "スウェーデン"
	},
	"setting": "Miljö",
	"countdownTime": "Nedräkningstid",
	"resultDisplayDuration": "Resultatvisningslängd",
	"soundVolume": "Ljudeffektvolym",
	"backgroundImageNumber": "Bakgrundsbild",
	"language": "Språk",
	"usage1": "En person kommer att väljas ut genom lottning från flera personer.",
	"usage2": "Tryck och håll på skärmen med flera personer. Lotteriresultatet kommer att visas efter att nedräkningen är slut.",
	"usage3": "Du kan delta i lotteriet under nedräkningen.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_th.arb

{
	"@@locale":"th",
	"@locale": {
		"description": "タイ"
	},
	"setting": "การตั้งค่า",
	"countdownTime": "เวลานับถอยหลัง",
	"resultDisplayDuration": "ระยะเวลาแสดงผล",
	"soundVolume": "ระดับเสียงเอฟเฟกต์",
	"backgroundImageNumber": "ภาพพื้นหลัง",
	"language": "ภาษา",
	"usage1": "1 คนจะถูกเลือกโดยการจับสลากจากหลายๆ คน",
	"usage2": "แตะหน้าจอค้างไว้พร้อมกันหลายคน ผลลอตเตอรี่จะแสดงหลังการนับถอยหลังสิ้นสุดลง",
	"usage3": "คุณสามารถเข้าร่วมการจับสลากได้ในช่วงนับถอยหลัง",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_zh.arb

{
	"@@locale":"zh",
	"@locale": {
		"description": "中国"
	},
	"setting": "环境",
	"countdownTime": "倒计时时间",
	"resultDisplayDuration": "结果显示时长",
	"soundVolume": "音效音量",
	"backgroundImageNumber": "背景图",
	"language": "语言",
	"usage1": "将从多人中抽签选出一名人。",
	"usage2": "多人触摸并按住屏幕。 倒计时结束后将显示抽奖结果。",
	"usage3": "您可以在倒计时期间参与抽奖。",
	"usage4": "",

	"dummy": "dummy"
}