diff --git a/assets/images/phone.svg b/assets/images/phone.svg
new file mode 100644
index 0000000..c12e06c
--- /dev/null
+++ b/assets/images/phone.svg
@@ -0,0 +1,23 @@
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/safehouse.svg b/assets/images/safehouse.svg
new file mode 100644
index 0000000..6c21f93
--- /dev/null
+++ b/assets/images/safehouse.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/assets/logo/logo.svg b/assets/logo/logo.svg
new file mode 100644
index 0000000..cc6115b
--- /dev/null
+++ b/assets/logo/logo.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/voice/voice_1.mp3 b/assets/voice/voice_1.mp3
new file mode 100644
index 0000000..f4426c1
Binary files /dev/null and b/assets/voice/voice_1.mp3 differ
diff --git a/assets/voice/voice_2.mp3 b/assets/voice/voice_2.mp3
new file mode 100644
index 0000000..db16fa7
Binary files /dev/null and b/assets/voice/voice_2.mp3 differ
diff --git a/assets/voice/voice_3.mp3 b/assets/voice/voice_3.mp3
new file mode 100644
index 0000000..4641ed5
Binary files /dev/null and b/assets/voice/voice_3.mp3 differ
diff --git a/assets/voice/voice_4.mp3 b/assets/voice/voice_4.mp3
new file mode 100644
index 0000000..e75e4d7
Binary files /dev/null and b/assets/voice/voice_4.mp3 differ
diff --git a/lib/core/CustomDialogs.dart b/lib/core/CustomDialogs.dart
new file mode 100644
index 0000000..38c694c
--- /dev/null
+++ b/lib/core/CustomDialogs.dart
@@ -0,0 +1,57 @@
+// custom_dialogs.dart
+import 'package:flutter/material.dart';
+
+class CustomDialogs {
+ // 일반 다이얼로그 생성
+ static void showCustomDialog(
+ BuildContext context, {
+ required String title,
+ required String content,
+ String? confirmText = '확인',
+ String? cancelText = '취소',
+ VoidCallback? onConfirm,
+ VoidCallback? onCancel,
+ bool isTextField = false,
+ }) {
+ showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: Text(title),
+ content: isTextField
+ ? Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(content),
+ TextField(
+ keyboardType: TextInputType.number,
+ decoration: const InputDecoration(
+ labelText: '시간(분)',
+ ),
+ ),
+ ],
+ )
+ : Text(content),
+ actions: [
+ if (onCancel != null)
+ TextButton(
+ onPressed: () {
+ onCancel();
+ Navigator.of(context).pop();
+ },
+ child: Text(cancelText!),
+ ),
+ if (onConfirm != null)
+ ElevatedButton(
+ onPressed: () {
+ onConfirm();
+ Navigator.of(context).pop();
+ },
+ child: Text(confirmText!),
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/lib/core/SplashScreen.dart b/lib/core/SplashScreen.dart
index 50b0521..f3e0221 100644
--- a/lib/core/SplashScreen.dart
+++ b/lib/core/SplashScreen.dart
@@ -1,7 +1,9 @@
+import 'package:blueberry_flutter_template/feature/onboarding/OnboardingScreen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import 'TopScreen.dart';
@@ -28,6 +30,9 @@ class _SplashScreenState extends ConsumerState {
/// 앱 초기화를 단계별로 수행하는 함수
Future _initializeApp() async {
+ final prefs = await SharedPreferences.getInstance();
+ final hasSeenOnboarding = prefs.getBool('hasSeenOnboarding') ?? false;
+
try {
// 단계별로 로딩 상태를 업데이트
// 단계별 로딩은 사용하지 않음.
@@ -39,7 +44,10 @@ class _SplashScreenState extends ConsumerState {
await Future.delayed(const Duration(seconds: 3));
// 초기화 완료 후 메인 화면으로 전환
if (mounted) {
- context.goNamed(TopScreen.name);
+ hasSeenOnboarding
+ ? context.goNamed(OnboardingScreen.name) // 온보딩 화면을 본 경우
+ : context.goNamed(OnboardingScreen.name) // 온보딩 화면을 보지 않은 경우
+ ;
}
} catch (e) {
// 초기화 중 발생한 오류 처리
diff --git a/lib/feature/map/PoliceMapScreen.dart b/lib/feature/map/PoliceMapScreen.dart
index ff8cec3..bb506c4 100644
--- a/lib/feature/map/PoliceMapScreen.dart
+++ b/lib/feature/map/PoliceMapScreen.dart
@@ -1,10 +1,8 @@
import 'dart:async';
+
import 'package:blueberry_flutter_template/feature/map/provider/LocationProvider.dart';
import 'package:blueberry_flutter_template/feature/map/provider/PermissionProvider.dart';
import 'package:blueberry_flutter_template/feature/map/provider/PoliceStationProvider.dart';
-import 'package:blueberry_flutter_template/feature/map/widget/GoogleMapWidget.dart';
-import 'package:blueberry_flutter_template/feature/map/widget/PermissionDeniedWidget.dart';
-import 'package:blueberry_flutter_template/feature/map/widget/PoliceStationListWidget.dart';
import 'package:blueberry_flutter_template/feature/map/widget/SendMessageWidget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -47,38 +45,40 @@ class _PoliceMapScreenState extends ConsumerState {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
children: [
- Expanded(
- child: policeStationsAsyncValue.when(
- data: (policeStations) => GoogleMapWidget(
- googleMapControllerCompleter:
- _googleMapControllerCompleter,
- locationState: locationState,
- policeStationsAsyncValue: policeStationsAsyncValue,
- ),
- loading: () =>
- const Center(child: CircularProgressIndicator()),
- error: (error, stack) =>
- Center(child: Text('Error: $error')),
- ),
- ),
+ // Expanded(
+ // child: policeStationsAsyncValue.when(
+ // data: (policeStations) => GoogleMapWidget(
+ // googleMapControllerCompleter:
+ // _googleMapControllerCompleter,
+ // locationState: locationState,
+ // policeStationsAsyncValue: policeStationsAsyncValue,
+ // ),
+ // loading: () =>
+ // const Center(child: CircularProgressIndicator()),
+ // error: (error, stack) =>
+ // Center(child: Text('Error: $error')),
+ // ),
+ // ),
const SizedBox(height: 8),
- Expanded(
- child: PoliceStationListWidget(
- googleMapControllerCompleter:
- _googleMapControllerCompleter,
- locationState: locationState,
- policeStationsAsyncValue: policeStationsAsyncValue,
- ),
- ),
- SendMessage(locationState: locationState),
+ // Expanded(
+ // child: PoliceStationListWidget(
+ // googleMapControllerCompleter:
+ // _googleMapControllerCompleter,
+ // locationState: locationState,
+ // policeStationsAsyncValue: policeStationsAsyncValue,
+ // ),
+ // ),
+ Center(child: SendMessage(locationState: locationState)),
],
),
),
);
- } else {
- return PermissionDeniedWidget(permissionStatus: permissionStatus);
}
+ // else {
+ // return PermissionDeniedWidget(permissionStatus: permissionStatus);
+ // }
}(),
);
}
diff --git a/lib/feature/mypage/MyPageScreen.dart b/lib/feature/mypage/MyPageScreen.dart
index fdc0b69..6b71b6a 100644
--- a/lib/feature/mypage/MyPageScreen.dart
+++ b/lib/feature/mypage/MyPageScreen.dart
@@ -1,3 +1,4 @@
+import 'package:blueberry_flutter_template/core/CustomDialogs.dart';
import 'package:flutter/material.dart';
class MyPageScreen extends StatelessWidget {
@@ -8,27 +9,23 @@ class MyPageScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
- appBar: AppBar(
- title: const Text('마이페이지'),
- centerTitle: true,
- ),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- const Text(
- "nickname",
- textAlign: TextAlign.center,
- style: TextStyle(
- fontSize: 24.0,
- fontWeight: FontWeight.bold,
- ),
- ),
- const SizedBox(height: 40.0),
ElevatedButton(
onPressed: () {
// 발송 문자 지정 버튼의 기능 구현
+ CustomDialogs.showCustomDialog(
+ context,
+ title: '발송 문자 지정',
+ content: '구현 예정입니다.',
+ onConfirm: () {
+ // 발송 문자 선택 로직 구현
+ },
+ );
},
child: const Text('발송 문자 지정'),
),
@@ -36,6 +33,14 @@ class MyPageScreen extends StatelessWidget {
ElevatedButton(
onPressed: () {
// 주변 범죄 위험도 확인 버튼의 기능 구현
+ CustomDialogs.showCustomDialog(
+ context,
+ title: '발송 문자 지정',
+ content: '구현 예정입니다.',
+ onConfirm: () {
+ // 발송 문자 선택 로직 구현
+ },
+ );
},
child: const Text('주변 범죄 위험도 확인'),
),
@@ -43,6 +48,14 @@ class MyPageScreen extends StatelessWidget {
ElevatedButton(
onPressed: () {
// 자동 대화 시간 설정 버튼의 기능 구현
+ CustomDialogs.showCustomDialog(
+ context,
+ title: '발송 문자 지정',
+ content: '구현 예정입니다.',
+ onConfirm: () {
+ // 발송 문자 선택 로직 구현
+ },
+ );
},
child: const Text('자동 대화 시간 설정'),
),
diff --git a/lib/feature/onboarding/OnboardingScreen.dart b/lib/feature/onboarding/OnboardingScreen.dart
index 60a76a5..d04748d 100644
--- a/lib/feature/onboarding/OnboardingScreen.dart
+++ b/lib/feature/onboarding/OnboardingScreen.dart
@@ -2,13 +2,14 @@ import 'package:blueberry_flutter_template/core/TopScreen.dart';
import 'package:blueberry_flutter_template/feature/onboarding/widgets/OnboardingPageViewBuilder.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import 'OnboardingData.dart';
import 'widgets/OnboardingDotWidget.dart';
import 'widgets/OnboardingPageButton.dart';
class OnboardingScreen extends StatefulWidget {
- static const String name = '/onboarding';
+ static const String name = 'OnboardingScreen';
const OnboardingScreen({super.key});
@@ -38,8 +39,10 @@ class _OnboardingScreenState extends State {
});
}
- void _onNextPressed() {
+ void _onNextPressed() async {
if (_currentPage == OnboardingData.pageDataList.length - 1) {
+ final prefs = await SharedPreferences.getInstance();
+ await prefs.setBool('hasSeenOnboarding', true);
context.goNamed(TopScreen.name);
} else {
_pageController.nextPage(
diff --git a/lib/feature/voiceOutput/widget/VoiceOutputWidget.dart b/lib/feature/voiceOutput/widget/VoiceOutputWidget.dart
index be4e304..415220e 100644
--- a/lib/feature/voiceOutput/widget/VoiceOutputWidget.dart
+++ b/lib/feature/voiceOutput/widget/VoiceOutputWidget.dart
@@ -1,5 +1,8 @@
+import 'package:audioplayers/audioplayers.dart';
+import 'package:blueberry_flutter_template/gen/assets.gen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+
import '../provider/VoiceOutputProvider.dart';
class VoiceOutputWidget extends ConsumerWidget {
@@ -27,7 +30,7 @@ class VoiceOutputWidget extends ConsumerWidget {
children: data
.map((text) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: VoiceOutputRow(text: text),
+ child: VoiceOutputRow(text: text, index: data.indexOf(text)),
))
.toList(),
),
@@ -37,8 +40,10 @@ class VoiceOutputWidget extends ConsumerWidget {
class VoiceOutputRow extends StatelessWidget {
final String text;
+ final int index;
+ final player = AudioPlayer();
- const VoiceOutputRow({super.key, required this.text});
+ VoiceOutputRow({super.key, required this.text, required this.index});
@override
Widget build(BuildContext context) {
@@ -56,7 +61,10 @@ class VoiceOutputRow extends StatelessWidget {
),
const SizedBox(width: 8),
ElevatedButton(
- onPressed: () {},
+ onPressed: () {
+ final source = AssetSource("voice/voice_${index+1}.mp3");
+ player.play(source);
+ },
child: const Text("button"),
),
],
diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart
index 5a2eadd..44ad98c 100644
--- a/lib/gen/assets.gen.dart
+++ b/lib/gen/assets.gen.dart
@@ -127,6 +127,9 @@ class $AssetsLoginPageImagesGen {
class $AssetsLogoGen {
const $AssetsLogoGen();
+ /// File path: assets/logo/logo.svg
+ String get logo => 'assets/logo/logo.svg';
+
/// File path: assets/logo/logo_1.png
AssetGenImage get logo1 => const AssetGenImage('assets/logo/logo_1.png');
@@ -146,14 +149,38 @@ class $AssetsLogoGen {
AssetGenImage get mbtiLogo =>
const AssetGenImage('assets/logo/mbti_logo.webp');
+ /// File path: assets/logo/voice_logo.png
+ AssetGenImage get voiceLogo =>
+ const AssetGenImage('assets/logo/voice_logo.png');
+
/// List of all assets
- List get values =>
- [logo1, logo2, logo3, logo4, logo5, mbtiLogo];
+ List get values =>
+ [logo, logo1, logo2, logo3, logo4, logo5, mbtiLogo, voiceLogo];
+}
+
+class $AssetsVoiceGen {
+ const $AssetsVoiceGen();
+
+ /// File path: assets/voice/voice_1.mp3
+ String get voice1 => 'assets/voice/voice_1.mp3';
+
+ /// File path: assets/voice/voice_2.mp3
+ String get voice2 => 'assets/voice/voice_2.mp3';
+
+ /// File path: assets/voice/voice_3.mp3
+ String get voice3 => 'assets/voice/voice_3.mp3';
+
+ /// File path: assets/voice/voice_4.mp3
+ String get voice4 => 'assets/voice/voice_4.mp3';
+
+ /// List of all assets
+ List get values => [voice1, voice2, voice3, voice4];
}
class Assets {
Assets._();
+ static const String aEnv = '.env';
static const $Assets300x420Gen a300x420 = $Assets300x420Gen();
static const $Assets600x400Gen a600x400 = $Assets600x400Gen();
static const $Assets700x150Gen a700x150 = $Assets700x150Gen();
@@ -162,6 +189,10 @@ class Assets {
static const $AssetsLoginPageImagesGen loginPageImages =
$AssetsLoginPageImagesGen();
static const $AssetsLogoGen logo = $AssetsLogoGen();
+ static const $AssetsVoiceGen voice = $AssetsVoiceGen();
+
+ /// List of all assets
+ static List get values => [aEnv];
}
class AssetGenImage {
diff --git a/lib/router/RouterProvider.dart b/lib/router/RouterProvider.dart
index ef0abb5..6798b8f 100644
--- a/lib/router/RouterProvider.dart
+++ b/lib/router/RouterProvider.dart
@@ -1,3 +1,4 @@
+import 'package:blueberry_flutter_template/feature/onboarding/OnboardingScreen.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
@@ -26,8 +27,14 @@ final routerProvider = Provider((ref) {
name: TopScreen.name,
builder: (context, state) =>
ResponsiveLayoutBuilder(context, const TopScreen()),
- routes: [GoRoute(path: '')],
+ routes: [],
),
+ GoRoute(
+ path: '/onboarding',
+ name: OnboardingScreen.name,
+ builder: (context, state) =>
+ ResponsiveLayoutBuilder(context, const OnboardingScreen()),
+ )
],
);
});
diff --git a/pubspec.yaml b/pubspec.yaml
index 7ebe1c4..4375f35 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -110,7 +110,12 @@ dependencies:
geolocator: ^12.0.0
flutter_dotenv: ^5.1.0
- # apiKey
+ # shared preference
+ shared_preferences: ^2.2.3
+
+ # audio player
+ audioplayers: ^6.0.0
+
dev_dependencies:
@@ -121,6 +126,7 @@ dev_dependencies:
json_serializable: ^6.8.0
build_runner: ^2.4.11
+
flutter:
uses-material-design: true
assets:
@@ -133,6 +139,11 @@ flutter:
- assets/logo/
- assets/icon/
- assets/login_page_images/
+ - assets/voice/
+ - assets/voice/voice_1.mp3
+ - assets/voice/voice_2.mp3
+ - assets/voice/voice_3.mp3
+ - assets/voice/voice_4.mp3
# Run Commands to generate splash view
# 1) flutter clean