Compare commits
3 Commits
162a09c54a
...
8f011689fb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f011689fb | ||
|
|
0ccd1bd007 | ||
|
|
7d19905c01 |
BIN
assets/audio/bgm/battle.wav
Normal file
BIN
assets/audio/bgm/battle.wav
Normal file
Binary file not shown.
BIN
assets/audio/bgm/boss.mp3
Normal file
BIN
assets/audio/bgm/boss.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/town.mp3
Normal file
BIN
assets/audio/bgm/town.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/victory.mp3
Normal file
BIN
assets/audio/bgm/victory.mp3
Normal file
Binary file not shown.
BIN
assets/audio/sfx/attack.wav
Normal file
BIN
assets/audio/sfx/attack.wav
Normal file
Binary file not shown.
BIN
assets/audio/sfx/click.wav
Normal file
BIN
assets/audio/sfx/click.wav
Normal file
Binary file not shown.
BIN
assets/audio/sfx/hit.wav
Normal file
BIN
assets/audio/sfx/hit.wav
Normal file
Binary file not shown.
BIN
assets/audio/sfx/item.wav
Normal file
BIN
assets/audio/sfx/item.wav
Normal file
Binary file not shown.
BIN
assets/audio/sfx/level_up.wav
Normal file
BIN
assets/audio/sfx/level_up.wav
Normal file
Binary file not shown.
BIN
assets/audio/sfx/quest_complete.wav
Normal file
BIN
assets/audio/sfx/quest_complete.wav
Normal file
Binary file not shown.
BIN
assets/audio/sfx/skill.wav
Normal file
BIN
assets/audio/sfx/skill.wav
Normal file
Binary file not shown.
@@ -1792,3 +1792,97 @@ String get uiLoading {
|
|||||||
if (isJapaneseLocale) return '読み込み中...';
|
if (isJapaneseLocale) return '読み込み中...';
|
||||||
return 'Loading...';
|
return 'Loading...';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 설정 화면 텍스트
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
String get uiSettings {
|
||||||
|
if (isKoreanLocale) return '설정';
|
||||||
|
if (isJapaneseLocale) return '設定';
|
||||||
|
return 'Settings';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiTheme {
|
||||||
|
if (isKoreanLocale) return '테마';
|
||||||
|
if (isJapaneseLocale) return 'テーマ';
|
||||||
|
return 'Theme';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiThemeLight {
|
||||||
|
if (isKoreanLocale) return '라이트';
|
||||||
|
if (isJapaneseLocale) return 'ライト';
|
||||||
|
return 'Light';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiThemeDark {
|
||||||
|
if (isKoreanLocale) return '다크';
|
||||||
|
if (isJapaneseLocale) return 'ダーク';
|
||||||
|
return 'Dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiThemeSystem {
|
||||||
|
if (isKoreanLocale) return '시스템';
|
||||||
|
if (isJapaneseLocale) return 'システム';
|
||||||
|
return 'System';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiLanguage {
|
||||||
|
if (isKoreanLocale) return '언어';
|
||||||
|
if (isJapaneseLocale) return '言語';
|
||||||
|
return 'Language';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiSound {
|
||||||
|
if (isKoreanLocale) return '사운드';
|
||||||
|
if (isJapaneseLocale) return 'サウンド';
|
||||||
|
return 'Sound';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiBgmVolume {
|
||||||
|
if (isKoreanLocale) return 'BGM 볼륨';
|
||||||
|
if (isJapaneseLocale) return 'BGM音量';
|
||||||
|
return 'BGM Volume';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiSfxVolume {
|
||||||
|
if (isKoreanLocale) return '효과음 볼륨';
|
||||||
|
if (isJapaneseLocale) return '効果音音量';
|
||||||
|
return 'SFX Volume';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiAnimationSpeed {
|
||||||
|
if (isKoreanLocale) return '애니메이션 속도';
|
||||||
|
if (isJapaneseLocale) return 'アニメーション速度';
|
||||||
|
return 'Animation Speed';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiSpeedSlow {
|
||||||
|
if (isKoreanLocale) return '느림';
|
||||||
|
if (isJapaneseLocale) return '遅い';
|
||||||
|
return 'Slow';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiSpeedNormal {
|
||||||
|
if (isKoreanLocale) return '보통';
|
||||||
|
if (isJapaneseLocale) return '普通';
|
||||||
|
return 'Normal';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiSpeedFast {
|
||||||
|
if (isKoreanLocale) return '빠름';
|
||||||
|
if (isJapaneseLocale) return '速い';
|
||||||
|
return 'Fast';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiAbout {
|
||||||
|
if (isKoreanLocale) return '정보';
|
||||||
|
if (isJapaneseLocale) return '情報';
|
||||||
|
return 'About';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get uiAboutDescription {
|
||||||
|
if (isKoreanLocale) return 'Progress Quest 6.4를 Flutter로 재구현한 오프라인 싱글플레이어 RPG입니다.';
|
||||||
|
if (isJapaneseLocale) return 'Progress Quest 6.4をFlutterで再実装したオフラインシングルプレイヤーRPGです。';
|
||||||
|
return 'An offline single-player RPG reimplementation of Progress Quest 6.4 in Flutter.';
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:askiineverdie/data/game_text_l10n.dart' as game_l10n;
|
import 'package:askiineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||||
|
import 'package:askiineverdie/src/core/audio/audio_service.dart';
|
||||||
import 'package:askiineverdie/src/core/engine/game_mutations.dart';
|
import 'package:askiineverdie/src/core/engine/game_mutations.dart';
|
||||||
import 'package:askiineverdie/src/core/engine/progress_service.dart';
|
import 'package:askiineverdie/src/core/engine/progress_service.dart';
|
||||||
import 'package:askiineverdie/src/core/engine/reward_service.dart';
|
import 'package:askiineverdie/src/core/engine/reward_service.dart';
|
||||||
@@ -30,6 +31,7 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
late final GameSessionController _controller;
|
late final GameSessionController _controller;
|
||||||
late final NotificationService _notificationService;
|
late final NotificationService _notificationService;
|
||||||
late final SettingsRepository _settingsRepository;
|
late final SettingsRepository _settingsRepository;
|
||||||
|
late final AudioService _audioService;
|
||||||
bool _isCheckingSave = true;
|
bool _isCheckingSave = true;
|
||||||
bool _hasSave = false;
|
bool _hasSave = false;
|
||||||
ThemeMode _themeMode = ThemeMode.system;
|
ThemeMode _themeMode = ThemeMode.system;
|
||||||
@@ -51,9 +53,11 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
);
|
);
|
||||||
_notificationService = NotificationService();
|
_notificationService = NotificationService();
|
||||||
_settingsRepository = SettingsRepository();
|
_settingsRepository = SettingsRepository();
|
||||||
|
_audioService = AudioService(settingsRepository: _settingsRepository);
|
||||||
|
|
||||||
// 초기 설정 로드
|
// 초기 설정 및 오디오 서비스 로드
|
||||||
_loadSettings();
|
_loadSettings();
|
||||||
|
_audioService.init();
|
||||||
// 세이브 파일 존재 여부 확인
|
// 세이브 파일 존재 여부 확인
|
||||||
_checkForExistingSave();
|
_checkForExistingSave();
|
||||||
}
|
}
|
||||||
@@ -87,6 +91,7 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
_notificationService.dispose();
|
_notificationService.dispose();
|
||||||
|
_audioService.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
228
lib/src/core/audio/audio_service.dart
Normal file
228
lib/src/core/audio/audio_service.dart
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
|
import 'package:askiineverdie/src/core/storage/settings_repository.dart';
|
||||||
|
|
||||||
|
/// 게임 오디오 서비스
|
||||||
|
///
|
||||||
|
/// BGM과 SFX를 관리하며, 설정과 연동하여 볼륨을 조절합니다.
|
||||||
|
class AudioService {
|
||||||
|
AudioService({SettingsRepository? settingsRepository})
|
||||||
|
: _settingsRepository = settingsRepository ?? SettingsRepository();
|
||||||
|
|
||||||
|
final SettingsRepository _settingsRepository;
|
||||||
|
|
||||||
|
// BGM 플레이어
|
||||||
|
AudioPlayer? _bgmPlayer;
|
||||||
|
|
||||||
|
// SFX 플레이어 풀 (동시 재생 지원)
|
||||||
|
final List<AudioPlayer> _sfxPlayers = [];
|
||||||
|
static const int _maxSfxPlayers = 5;
|
||||||
|
|
||||||
|
// 현재 볼륨
|
||||||
|
double _bgmVolume = 0.7;
|
||||||
|
double _sfxVolume = 0.8;
|
||||||
|
|
||||||
|
// 현재 재생 중인 BGM
|
||||||
|
String? _currentBgm;
|
||||||
|
|
||||||
|
// 초기화 여부
|
||||||
|
bool _initialized = false;
|
||||||
|
|
||||||
|
/// 서비스 초기화
|
||||||
|
Future<void> init() async {
|
||||||
|
if (_initialized) return;
|
||||||
|
|
||||||
|
// 설정에서 볼륨 불러오기
|
||||||
|
_bgmVolume = await _settingsRepository.loadBgmVolume();
|
||||||
|
_sfxVolume = await _settingsRepository.loadSfxVolume();
|
||||||
|
|
||||||
|
// BGM 플레이어 초기화
|
||||||
|
_bgmPlayer = AudioPlayer();
|
||||||
|
await _bgmPlayer!.setLoopMode(LoopMode.one);
|
||||||
|
await _bgmPlayer!.setVolume(_bgmVolume);
|
||||||
|
|
||||||
|
// SFX 플레이어 풀 초기화
|
||||||
|
for (var i = 0; i < _maxSfxPlayers; i++) {
|
||||||
|
final player = AudioPlayer();
|
||||||
|
await player.setVolume(_sfxVolume);
|
||||||
|
_sfxPlayers.add(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BGM 재생
|
||||||
|
///
|
||||||
|
/// [name]은 assets/audio/bgm/ 폴더 내 파일명 (확장자 제외)
|
||||||
|
/// 예: playBgm('battle') → assets/audio/bgm/battle.wav 또는 battle.mp3
|
||||||
|
Future<void> playBgm(String name) async {
|
||||||
|
if (!_initialized) await init();
|
||||||
|
if (_currentBgm == name) return; // 이미 재생 중
|
||||||
|
|
||||||
|
try {
|
||||||
|
_currentBgm = name;
|
||||||
|
// WAV 먼저 시도, 실패하면 MP3 시도
|
||||||
|
try {
|
||||||
|
await _bgmPlayer!.setAsset('assets/audio/bgm/$name.wav');
|
||||||
|
} catch (_) {
|
||||||
|
await _bgmPlayer!.setAsset('assets/audio/bgm/$name.mp3');
|
||||||
|
}
|
||||||
|
await _bgmPlayer!.play();
|
||||||
|
} catch (e) {
|
||||||
|
// 파일이 없으면 무시 (개발 중 에셋 미추가 상태)
|
||||||
|
_currentBgm = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BGM 정지
|
||||||
|
Future<void> stopBgm() async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
|
||||||
|
await _bgmPlayer!.stop();
|
||||||
|
_currentBgm = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BGM 일시정지
|
||||||
|
Future<void> pauseBgm() async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
await _bgmPlayer!.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BGM 재개
|
||||||
|
Future<void> resumeBgm() async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
if (_currentBgm != null) {
|
||||||
|
await _bgmPlayer!.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SFX 재생
|
||||||
|
///
|
||||||
|
/// [name]은 assets/audio/sfx/ 폴더 내 파일명 (확장자 제외)
|
||||||
|
/// 예: playSfx('attack') → assets/audio/sfx/attack.wav 또는 attack.mp3
|
||||||
|
Future<void> playSfx(String name) async {
|
||||||
|
if (!_initialized) await init();
|
||||||
|
if (_sfxVolume == 0) return; // 볼륨이 0이면 재생 안함
|
||||||
|
|
||||||
|
// 사용 가능한 플레이어 찾기
|
||||||
|
AudioPlayer? availablePlayer;
|
||||||
|
for (final player in _sfxPlayers) {
|
||||||
|
if (!player.playing) {
|
||||||
|
availablePlayer = player;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 플레이어가 사용 중이면 첫 번째 플레이어 재사용
|
||||||
|
availablePlayer ??= _sfxPlayers.first;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// WAV 먼저 시도, 실패하면 MP3 시도
|
||||||
|
try {
|
||||||
|
await availablePlayer.setAsset('assets/audio/sfx/$name.wav');
|
||||||
|
} catch (_) {
|
||||||
|
await availablePlayer.setAsset('assets/audio/sfx/$name.mp3');
|
||||||
|
}
|
||||||
|
await availablePlayer.seek(Duration.zero);
|
||||||
|
await availablePlayer.play();
|
||||||
|
} catch (e) {
|
||||||
|
// 파일이 없으면 무시
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BGM 볼륨 설정 (0.0 ~ 1.0)
|
||||||
|
Future<void> setBgmVolume(double volume) async {
|
||||||
|
_bgmVolume = volume.clamp(0.0, 1.0);
|
||||||
|
if (_initialized && _bgmPlayer != null) {
|
||||||
|
await _bgmPlayer!.setVolume(_bgmVolume);
|
||||||
|
}
|
||||||
|
await _settingsRepository.saveBgmVolume(_bgmVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SFX 볼륨 설정 (0.0 ~ 1.0)
|
||||||
|
Future<void> setSfxVolume(double volume) async {
|
||||||
|
_sfxVolume = volume.clamp(0.0, 1.0);
|
||||||
|
if (_initialized) {
|
||||||
|
for (final player in _sfxPlayers) {
|
||||||
|
await player.setVolume(_sfxVolume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await _settingsRepository.saveSfxVolume(_sfxVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 현재 BGM 볼륨
|
||||||
|
double get bgmVolume => _bgmVolume;
|
||||||
|
|
||||||
|
/// 현재 SFX 볼륨
|
||||||
|
double get sfxVolume => _sfxVolume;
|
||||||
|
|
||||||
|
/// 현재 재생 중인 BGM
|
||||||
|
String? get currentBgm => _currentBgm;
|
||||||
|
|
||||||
|
/// 서비스 정리
|
||||||
|
Future<void> dispose() async {
|
||||||
|
await _bgmPlayer?.dispose();
|
||||||
|
for (final player in _sfxPlayers) {
|
||||||
|
await player.dispose();
|
||||||
|
}
|
||||||
|
_sfxPlayers.clear();
|
||||||
|
_initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BGM 타입 열거형
|
||||||
|
enum BgmType {
|
||||||
|
/// 마을/상점 BGM
|
||||||
|
town,
|
||||||
|
|
||||||
|
/// 일반 전투 BGM
|
||||||
|
battle,
|
||||||
|
|
||||||
|
/// 보스 전투 BGM
|
||||||
|
boss,
|
||||||
|
|
||||||
|
/// 레벨업/퀘스트 완료 팡파레
|
||||||
|
victory,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SFX 타입 열거형
|
||||||
|
enum SfxType {
|
||||||
|
/// 공격
|
||||||
|
attack,
|
||||||
|
|
||||||
|
/// 피격
|
||||||
|
hit,
|
||||||
|
|
||||||
|
/// 스킬 사용
|
||||||
|
skill,
|
||||||
|
|
||||||
|
/// 아이템 획득
|
||||||
|
item,
|
||||||
|
|
||||||
|
/// UI 클릭
|
||||||
|
click,
|
||||||
|
|
||||||
|
/// 레벨업
|
||||||
|
levelUp,
|
||||||
|
|
||||||
|
/// 퀘스트 완료
|
||||||
|
questComplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BgmType을 파일명으로 변환
|
||||||
|
extension BgmTypeExtension on BgmType {
|
||||||
|
String get fileName => name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SfxType을 파일명으로 변환
|
||||||
|
extension SfxTypeExtension on SfxType {
|
||||||
|
String get fileName => switch (this) {
|
||||||
|
SfxType.attack => 'attack',
|
||||||
|
SfxType.hit => 'hit',
|
||||||
|
SfxType.skill => 'skill',
|
||||||
|
SfxType.item => 'item',
|
||||||
|
SfxType.click => 'click',
|
||||||
|
SfxType.levelUp => 'level_up',
|
||||||
|
SfxType.questComplete => 'quest_complete',
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,10 +3,13 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
|
|
||||||
/// 앱 설정 저장소 (SharedPreferences 기반)
|
/// 앱 설정 저장소 (SharedPreferences 기반)
|
||||||
///
|
///
|
||||||
/// 테마, 언어 등 사용자 설정을 로컬에 저장
|
/// 테마, 언어, 사운드 등 사용자 설정을 로컬에 저장
|
||||||
class SettingsRepository {
|
class SettingsRepository {
|
||||||
static const _keyThemeMode = 'theme_mode';
|
static const _keyThemeMode = 'theme_mode';
|
||||||
static const _keyLocale = 'locale';
|
static const _keyLocale = 'locale';
|
||||||
|
static const _keyBgmVolume = 'bgm_volume';
|
||||||
|
static const _keySfxVolume = 'sfx_volume';
|
||||||
|
static const _keyAnimationSpeed = 'animation_speed';
|
||||||
|
|
||||||
SharedPreferences? _prefs;
|
SharedPreferences? _prefs;
|
||||||
|
|
||||||
@@ -49,4 +52,40 @@ class SettingsRepository {
|
|||||||
await init();
|
await init();
|
||||||
return _prefs!.getString(_keyLocale);
|
return _prefs!.getString(_keyLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// BGM 볼륨 저장 (0.0 ~ 1.0)
|
||||||
|
Future<void> saveBgmVolume(double volume) async {
|
||||||
|
await init();
|
||||||
|
await _prefs!.setDouble(_keyBgmVolume, volume.clamp(0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BGM 볼륨 불러오기 (기본값: 0.7)
|
||||||
|
Future<double> loadBgmVolume() async {
|
||||||
|
await init();
|
||||||
|
return _prefs!.getDouble(_keyBgmVolume) ?? 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SFX 볼륨 저장 (0.0 ~ 1.0)
|
||||||
|
Future<void> saveSfxVolume(double volume) async {
|
||||||
|
await init();
|
||||||
|
await _prefs!.setDouble(_keySfxVolume, volume.clamp(0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SFX 볼륨 불러오기 (기본값: 0.8)
|
||||||
|
Future<double> loadSfxVolume() async {
|
||||||
|
await init();
|
||||||
|
return _prefs!.getDouble(_keySfxVolume) ?? 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 애니메이션 속도 저장 (0.5 ~ 2.0, 1.0이 기본)
|
||||||
|
Future<void> saveAnimationSpeed(double speed) async {
|
||||||
|
await init();
|
||||||
|
await _prefs!.setDouble(_keyAnimationSpeed, speed.clamp(0.5, 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 애니메이션 속도 불러오기 (기본값: 1.0)
|
||||||
|
Future<double> loadAnimationSpeed() async {
|
||||||
|
await init();
|
||||||
|
return _prefs!.getDouble(_keyAnimationSpeed) ?? 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import 'package:askiineverdie/src/features/game/widgets/potion_inventory_panel.d
|
|||||||
import 'package:askiineverdie/src/features/game/widgets/task_progress_panel.dart';
|
import 'package:askiineverdie/src/features/game/widgets/task_progress_panel.dart';
|
||||||
import 'package:askiineverdie/src/features/game/widgets/active_buff_panel.dart';
|
import 'package:askiineverdie/src/features/game/widgets/active_buff_panel.dart';
|
||||||
import 'package:askiineverdie/src/features/game/layouts/mobile_carousel_layout.dart';
|
import 'package:askiineverdie/src/features/game/layouts/mobile_carousel_layout.dart';
|
||||||
|
import 'package:askiineverdie/src/features/settings/settings_screen.dart';
|
||||||
|
import 'package:askiineverdie/src/core/storage/settings_repository.dart';
|
||||||
|
|
||||||
/// 게임 진행 화면 (Main.dfm 기반 3패널 레이아웃)
|
/// 게임 진행 화면 (Main.dfm 기반 3패널 레이아웃)
|
||||||
///
|
///
|
||||||
@@ -463,51 +465,17 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
return platform == TargetPlatform.iOS || platform == TargetPlatform.android;
|
return platform == TargetPlatform.iOS || platform == TargetPlatform.android;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 현재 언어명 가져오기
|
/// 설정 화면 표시
|
||||||
String _getCurrentLanguageName() {
|
void _showSettingsScreen(BuildContext context) {
|
||||||
final locale = game_l10n.currentGameLocale;
|
final settingsRepo = SettingsRepository();
|
||||||
if (locale == 'ko') return game_l10n.languageKorean;
|
SettingsScreen.show(
|
||||||
if (locale == 'ja') return game_l10n.languageJapanese;
|
context,
|
||||||
return game_l10n.languageEnglish;
|
settingsRepository: settingsRepo,
|
||||||
}
|
currentThemeMode: widget.currentThemeMode,
|
||||||
|
onThemeModeChange: (mode) {
|
||||||
/// 언어 선택 다이얼로그 표시
|
widget.onThemeModeChange?.call(mode);
|
||||||
void _showLanguageDialog(BuildContext context) {
|
},
|
||||||
showDialog<void>(
|
onLocaleChange: (locale) async {
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text(game_l10n.menuLanguage),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
_buildLanguageOption(context, 'en', game_l10n.languageEnglish),
|
|
||||||
_buildLanguageOption(context, 'ko', game_l10n.languageKorean),
|
|
||||||
_buildLanguageOption(context, 'ja', game_l10n.languageJapanese),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildLanguageOption(
|
|
||||||
BuildContext context,
|
|
||||||
String locale,
|
|
||||||
String label,
|
|
||||||
) {
|
|
||||||
final isSelected = game_l10n.currentGameLocale == locale;
|
|
||||||
return ListTile(
|
|
||||||
leading: Icon(
|
|
||||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
|
|
||||||
color: isSelected ? Theme.of(context).colorScheme.primary : null,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () async {
|
|
||||||
Navigator.pop(context); // 다이얼로그 닫기
|
|
||||||
// 안전한 언어 변경: 전체 화면 재생성
|
// 안전한 언어 변경: 전체 화면 재생성
|
||||||
final navigator = Navigator.of(this.context);
|
final navigator = Navigator.of(this.context);
|
||||||
await widget.controller.pause(saveOnStop: true);
|
await widget.controller.pause(saveOnStop: true);
|
||||||
@@ -698,11 +666,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
onPressed: () => widget.controller.loop?.cheatCompletePlot(),
|
onPressed: () => widget.controller.loop?.cheatCompletePlot(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// 언어 변경 버튼
|
// 설정 버튼
|
||||||
TextButton.icon(
|
IconButton(
|
||||||
onPressed: () => _showLanguageDialog(context),
|
icon: const Icon(Icons.settings),
|
||||||
icon: const Icon(Icons.language, size: 18),
|
tooltip: game_l10n.uiSettings,
|
||||||
label: Text(_getCurrentLanguageName()),
|
onPressed: () => _showSettingsScreen(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
412
lib/src/features/settings/settings_screen.dart
Normal file
412
lib/src/features/settings/settings_screen.dart
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:askiineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||||
|
import 'package:askiineverdie/src/core/storage/settings_repository.dart';
|
||||||
|
|
||||||
|
/// 통합 설정 화면
|
||||||
|
///
|
||||||
|
/// 언어, 테마, 사운드, 애니메이션 속도 등 모든 설정을 한 곳에서 관리
|
||||||
|
class SettingsScreen extends StatefulWidget {
|
||||||
|
const SettingsScreen({
|
||||||
|
super.key,
|
||||||
|
required this.settingsRepository,
|
||||||
|
required this.currentThemeMode,
|
||||||
|
required this.onThemeModeChange,
|
||||||
|
this.onLocaleChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SettingsRepository settingsRepository;
|
||||||
|
final ThemeMode currentThemeMode;
|
||||||
|
final void Function(ThemeMode mode) onThemeModeChange;
|
||||||
|
final void Function(String locale)? onLocaleChange;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||||
|
|
||||||
|
/// 설정 화면을 모달 바텀시트로 표시
|
||||||
|
static Future<void> show(
|
||||||
|
BuildContext context, {
|
||||||
|
required SettingsRepository settingsRepository,
|
||||||
|
required ThemeMode currentThemeMode,
|
||||||
|
required void Function(ThemeMode mode) onThemeModeChange,
|
||||||
|
void Function(String locale)? onLocaleChange,
|
||||||
|
}) {
|
||||||
|
return showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useSafeArea: true,
|
||||||
|
builder: (context) => DraggableScrollableSheet(
|
||||||
|
initialChildSize: 0.7,
|
||||||
|
minChildSize: 0.5,
|
||||||
|
maxChildSize: 0.95,
|
||||||
|
expand: false,
|
||||||
|
builder: (context, scrollController) => SettingsScreen(
|
||||||
|
settingsRepository: settingsRepository,
|
||||||
|
currentThemeMode: currentThemeMode,
|
||||||
|
onThemeModeChange: onThemeModeChange,
|
||||||
|
onLocaleChange: onLocaleChange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
|
double _bgmVolume = 0.7;
|
||||||
|
double _sfxVolume = 0.8;
|
||||||
|
double _animationSpeed = 1.0;
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadSettings() async {
|
||||||
|
final bgm = await widget.settingsRepository.loadBgmVolume();
|
||||||
|
final sfx = await widget.settingsRepository.loadSfxVolume();
|
||||||
|
final speed = await widget.settingsRepository.loadAnimationSpeed();
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_bgmVolume = bgm;
|
||||||
|
_sfxVolume = sfx;
|
||||||
|
_animationSpeed = speed;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
if (_isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.scaffoldBackgroundColor,
|
||||||
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 핸들 바
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(top: 8),
|
||||||
|
width: 40,
|
||||||
|
height: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.3),
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 헤더
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.settings, color: theme.colorScheme.primary),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
game_l10n.uiSettings,
|
||||||
|
style: theme.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
// 설정 목록
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
// 테마 설정
|
||||||
|
_buildSectionTitle(game_l10n.uiTheme),
|
||||||
|
_buildThemeSelector(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 언어 설정
|
||||||
|
_buildSectionTitle(game_l10n.uiLanguage),
|
||||||
|
_buildLanguageSelector(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 사운드 설정
|
||||||
|
_buildSectionTitle(game_l10n.uiSound),
|
||||||
|
_buildVolumeSlider(
|
||||||
|
label: game_l10n.uiBgmVolume,
|
||||||
|
value: _bgmVolume,
|
||||||
|
icon: Icons.music_note,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _bgmVolume = value);
|
||||||
|
widget.settingsRepository.saveBgmVolume(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildVolumeSlider(
|
||||||
|
label: game_l10n.uiSfxVolume,
|
||||||
|
value: _sfxVolume,
|
||||||
|
icon: Icons.volume_up,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _sfxVolume = value);
|
||||||
|
widget.settingsRepository.saveSfxVolume(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 애니메이션 속도
|
||||||
|
_buildSectionTitle(game_l10n.uiAnimationSpeed),
|
||||||
|
_buildAnimationSpeedSlider(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 정보
|
||||||
|
_buildSectionTitle(game_l10n.uiAbout),
|
||||||
|
_buildAboutCard(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSectionTitle(String title) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildThemeSelector() {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildThemeOption(
|
||||||
|
icon: Icons.light_mode,
|
||||||
|
label: game_l10n.uiThemeLight,
|
||||||
|
mode: ThemeMode.light,
|
||||||
|
),
|
||||||
|
_buildThemeOption(
|
||||||
|
icon: Icons.dark_mode,
|
||||||
|
label: game_l10n.uiThemeDark,
|
||||||
|
mode: ThemeMode.dark,
|
||||||
|
),
|
||||||
|
_buildThemeOption(
|
||||||
|
icon: Icons.brightness_auto,
|
||||||
|
label: game_l10n.uiThemeSystem,
|
||||||
|
mode: ThemeMode.system,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildThemeOption({
|
||||||
|
required IconData icon,
|
||||||
|
required String label,
|
||||||
|
required ThemeMode mode,
|
||||||
|
}) {
|
||||||
|
final isSelected = widget.currentThemeMode == mode;
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => widget.onThemeModeChange(mode),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? theme.colorScheme.primaryContainer
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
color: isSelected
|
||||||
|
? theme.colorScheme.primary
|
||||||
|
: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||||
|
color: isSelected
|
||||||
|
? theme.colorScheme.primary
|
||||||
|
: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLanguageSelector() {
|
||||||
|
final currentLocale = game_l10n.currentGameLocale;
|
||||||
|
final languages = [
|
||||||
|
('en', 'English', '🇺🇸'),
|
||||||
|
('ko', '한국어', '🇰🇷'),
|
||||||
|
('ja', '日本語', '🇯🇵'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
children: languages.map((lang) {
|
||||||
|
final isSelected = currentLocale == lang.$1;
|
||||||
|
return ListTile(
|
||||||
|
leading: Text(lang.$3, style: const TextStyle(fontSize: 24)),
|
||||||
|
title: Text(lang.$2),
|
||||||
|
trailing: isSelected
|
||||||
|
? Icon(Icons.check, color: Theme.of(context).colorScheme.primary)
|
||||||
|
: null,
|
||||||
|
onTap: () {
|
||||||
|
game_l10n.setGameLocale(lang.$1);
|
||||||
|
widget.settingsRepository.saveLocale(lang.$1);
|
||||||
|
widget.onLocaleChange?.call(lang.$1);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVolumeSlider({
|
||||||
|
required String label,
|
||||||
|
required double value,
|
||||||
|
required IconData icon,
|
||||||
|
required void Function(double) onChanged,
|
||||||
|
}) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final percentage = (value * 100).round();
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
value == 0 ? Icons.volume_off : icon,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(label),
|
||||||
|
Text('$percentage%'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: value,
|
||||||
|
onChanged: onChanged,
|
||||||
|
divisions: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAnimationSpeedSlider() {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final speedLabel = switch (_animationSpeed) {
|
||||||
|
<= 0.6 => game_l10n.uiSpeedSlow,
|
||||||
|
>= 1.4 => game_l10n.uiSpeedFast,
|
||||||
|
_ => game_l10n.uiSpeedNormal,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.speed, color: theme.colorScheme.primary),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(game_l10n.uiAnimationSpeed),
|
||||||
|
Text(speedLabel),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: _animationSpeed,
|
||||||
|
min: 0.5,
|
||||||
|
max: 2.0,
|
||||||
|
divisions: 6,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _animationSpeed = value);
|
||||||
|
widget.settingsRepository.saveAnimationSpeed(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAboutCard() {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'ASCII NEVER DIE',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
game_l10n.uiAboutDescription,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'v1.0.0',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,14 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import audio_session
|
||||||
|
import just_audio
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
|
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
80
pubspec.lock
80
pubspec.lock
@@ -9,6 +9,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.0"
|
||||||
|
audio_session:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_session
|
||||||
|
sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.25"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,6 +49,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -73,6 +89,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -109,6 +133,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.2"
|
version: "0.20.2"
|
||||||
|
just_audio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: just_audio
|
||||||
|
sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.46"
|
||||||
|
just_audio_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: just_audio_platform_interface
|
||||||
|
sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.6.0"
|
||||||
|
just_audio_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: just_audio_web
|
||||||
|
sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.16"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -237,6 +285,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.28.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -330,6 +386,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
synchronized:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -346,6 +410,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.6"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.2"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ dependencies:
|
|||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
path_provider: ^2.1.4
|
path_provider: ^2.1.4
|
||||||
shared_preferences: ^2.3.1
|
shared_preferences: ^2.3.1
|
||||||
|
just_audio: ^0.9.42
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -65,6 +66,8 @@ flutter:
|
|||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- assets/
|
- assets/
|
||||||
|
- assets/audio/bgm/
|
||||||
|
- assets/audio/sfx/
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
# https://flutter.dev/to/resolution-aware-images
|
||||||
|
|||||||
Reference in New Issue
Block a user