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
/// Copyright© ao-system, Inc.
///
/// @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
}
}
}
/// Copyright© ao-system, Inc.
///
/// @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();
}
}
/// Copyright© ao-system, Inc.
///
/// @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': '中文',
};
}
/// Copyright© ao-system, Inc.
/// Copyright© ao-system, Inc.
///
/// @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;
}
}
/// Copyright© ao-system, Inc.
///
/// @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,
)
)
);
}
)
)
);
}
}
/// Copyright© ao-system, Inc.
///
/// @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;
}
}
/// Copyright© ao-system, Inc.
///
/// @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;
}
//----------------------------
}
/// Copyright© ao-system, Inc.
///
/// @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,
),
);
}
}
/// Copyright© ao-system, Inc.
///
/// @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;
}
}