From c9e47715dda75b86f99341b8666e9208ec9ebf8f Mon Sep 17 00:00:00 2001 From: yjlee0321 Date: Wed, 2 Oct 2024 23:51:57 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EC=B9=B4=EC=B9=B4=EC=98=A4=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/RoutineAdeIntro/ProfileSetting.dart | 125 ++++-- lib/RoutineAdeIntro/WebViewPage.dart | 30 +- lib/main.dart | 8 +- lib/rotuine_myInfo/MyInfo.dart | 157 ------- lib/rotuine_myInfo/ProfileChange.dart | 183 -------- lib/routine_group/ChatScreen.dart | 33 +- lib/routine_statisrics/StaticsCalendar.dart | 470 -------------------- lib/routine_statisrics/StaticsCategory.dart | 284 ------------ lib/routine_statistics/StaticsCalendar.dart | 33 +- lib/routine_statistics/StaticsCategory.dart | 58 +-- lib/routine_user/token.dart | 58 ++- 11 files changed, 186 insertions(+), 1253 deletions(-) delete mode 100644 lib/rotuine_myInfo/MyInfo.dart delete mode 100644 lib/rotuine_myInfo/ProfileChange.dart delete mode 100644 lib/routine_statisrics/StaticsCalendar.dart delete mode 100644 lib/routine_statisrics/StaticsCategory.dart diff --git a/lib/RoutineAdeIntro/ProfileSetting.dart b/lib/RoutineAdeIntro/ProfileSetting.dart index ad5fb17..b5051a5 100644 --- a/lib/RoutineAdeIntro/ProfileSetting.dart +++ b/lib/RoutineAdeIntro/ProfileSetting.dart @@ -1,9 +1,11 @@ +import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; - +import 'package:http/http.dart' as http; import '../routine_home/MyRoutinePage.dart'; +import 'package:routine_ade/routine_user/token.dart'; // 토큰 가져오는 곳 class ProfileSetting extends StatefulWidget { const ProfileSetting({super.key}); @@ -45,6 +47,69 @@ class _ProfileSettingState extends State { }); } + Future _registerUserInfo() async { + const url = 'http://15.164.88.94/users/infos'; // API URL + + // POST 요청 준비 + final request = http.MultipartRequest('POST', Uri.parse(url)); + + // 토큰 확인 및 추가 + if (token == null || token.isEmpty) { + print('Token is missing.'); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('토큰이 없습니다. 다시 로그인해주세요.')), + ); + return; + } + + print('Current token: $token'); + request.headers['Authorization'] = 'Bearer $token'; // 헤더에 토큰 추가 + + // 닉네임과 한 줄 소개 추가 + request.fields['nickname'] = _nicknameController.text; + request.fields['intro'] = _bioController.text; + + // 선택한 이미지가 있으면 파일로 추가 + if (_imageFile != null) { + request.files.add(await http.MultipartFile.fromPath('image', _imageFile!.path)); + } + + try { + // 서버에 요청 전송 + final response = await request.send(); + final responseData = await http.Response.fromStream(response); + + // 상태 코드가 200 또는 201인 경우에 성공 처리 + if (response.statusCode == 200 || response.statusCode == 201) { + final responseBody = utf8.decode(responseData.bodyBytes); + print('User info registered successfully: $responseBody'); + + // MyRoutinePage로 이동 + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const MyRoutinePage(), + ), + ); + } else { + // 실패 시 서버에서 보낸 오류 메시지 출력 + final errorBody = utf8.decode(responseData.bodyBytes); + print('Failed to register user info. Status code: ${response.statusCode}'); + print('Response body: $errorBody'); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('정보 등록 실패: $errorBody')), + ); + } + } catch (e) { + print('Error during registration: $e'); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('정보 등록 중 오류가 발생했습니다.')), + ); + } + } + + @override Widget build(BuildContext context) { return Scaffold( @@ -64,7 +129,6 @@ class _ProfileSettingState extends State { }, ), ), - backgroundColor: Colors.white, body: Stack( children: [ Padding( @@ -74,22 +138,21 @@ class _ProfileSettingState extends State { children: [ const SizedBox(height: 20), GestureDetector( - onTap: _pickImage, // 프로필 사진을 클릭했을 때 이미지 선택 기능 실행 + onTap: _pickImage, child: Stack( children: [ CircleAvatar( radius: 50, backgroundImage: _imageFile != null ? FileImage(_imageFile!) - : const AssetImage( - 'assets/images/default_profile.png') + : const AssetImage('assets/images/default_profile.png') as ImageProvider, ), Positioned( bottom: 0, right: 0, child: GestureDetector( - onTap: _pickImage, // 카메라 아이콘 클릭 시 이미지 선택 기능 실행 + onTap: _pickImage, child: const CircleAvatar( backgroundColor: Colors.white, radius: 16, @@ -103,7 +166,7 @@ class _ProfileSettingState extends State { const SizedBox(height: 50), const Align( alignment: Alignment.centerLeft, - child: Text("닉네임 (필수)"), + child: Text("닉네임"), ), const SizedBox(height: 10), TextField( @@ -111,43 +174,26 @@ class _ProfileSettingState extends State { onChanged: _validateNickname, decoration: InputDecoration( hintText: '닉네임', - errorText: !_isNicknameValid - ? _nicknameErrorMessage - : null, // 에러 메시지 표시 - counterText: '', // 글자수 카운터 삭제 + errorText: !_isNicknameValid ? _nicknameErrorMessage : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: Colors.black, // 기본 테두리 검은색 + borderSide: BorderSide( + color: _isNicknameValid ? Colors.grey : Colors.red, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: Colors.black, // 포커스 시 테두리 검은색 - width: 2.0, - ), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: Colors.red, // 에러 상태 테두리 빨간색 - width: 2.0, - ), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: Colors.red, // 에러 상태에서 포커스 시 테두리 빨간색 - width: 2.0, + borderSide: BorderSide( + color: _isNicknameValid ? Colors.blue : Colors.red, ), ), ), + maxLength: 10, ), - const SizedBox(height: 40), + const SizedBox(height: 20), const Align( alignment: Alignment.centerLeft, - child: Text("한 줄 소개 (선택)"), + child: Text("한 줄 소개"), ), const SizedBox(height: 10), TextField( @@ -177,12 +223,13 @@ class _ProfileSettingState extends State { ), ), onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const MyRoutinePage(), - ), - ); + if (_isNicknameValid) { + _registerUserInfo(); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('닉네임이 유효하지 않습니다.')), + ); + } }, child: const Text( '완료', @@ -195,4 +242,4 @@ class _ProfileSettingState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/RoutineAdeIntro/WebViewPage.dart b/lib/RoutineAdeIntro/WebViewPage.dart index 38fe282..e1fe023 100644 --- a/lib/RoutineAdeIntro/WebViewPage.dart +++ b/lib/RoutineAdeIntro/WebViewPage.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:routine_ade/RoutineAdeIntro/ProfileSetting.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../routine_user/token.dart'; -//import 'token.dart'; // token.dart 파일 import class WebViewPage extends StatefulWidget { const WebViewPage({super.key}); @@ -16,7 +16,7 @@ class _WebViewPageState extends State { @override void initState() { super.initState(); - // 웹뷰 초기화 전에 SharedPreferences에서 토큰을 불러와 사용할 수 있습니다. + // 웹뷰 초기화 전에 SharedPreferences에서 토큰을 불러옴. _loadToken(); } @@ -25,7 +25,6 @@ class _WebViewPageState extends State { String? token = await TokenManager.getToken(); if (token != null) { print('저장된 토큰: $token'); - // 필요 시, 다른 동작 수행 } else { print('토큰이 없습니다.'); } @@ -38,7 +37,8 @@ class _WebViewPageState extends State { title: const Text('Kakao Login WebView'), ), body: WebView( - initialUrl: 'https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=25a0f887ecba2fdb77884c01ca0325b0&redirect_uri=http://15.164.88.94/users/login/kakao', + initialUrl: + 'https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=25a0f887ecba2fdb77884c01ca0325b0&redirect_uri=http://15.164.88.94/users/login/kakao', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { _controller = webViewController; @@ -46,17 +46,27 @@ class _WebViewPageState extends State { onPageFinished: (String url) async { // 페이지 로딩이 끝나면 URL을 확인 if (url.contains('token=')) { - final tokenStartIndex = url.indexOf('token=') + 6; // 'token='의 시작 위치 + final tokenStartIndex = + url.indexOf('token=') + 6; // 'token='의 시작 위치 final token = url.substring(tokenStartIndex); - print('OAuth 토큰: $token'); // 콘솔에 토큰 출력 + // URL이 정상적으로 응답하지 않을 때 예외 처리 + if (token.isNotEmpty) { + print('OAuth 토큰: $token'); // 콘솔에 토큰 출력 - // 추출한 토큰을 SharedPreferences에 저장 - await TokenManager.saveToken(token); - print('토큰 저장 완료'); + // 추출한 토큰을 SharedPreferences에 저장 + await TokenManager.saveToken(token); + print('토큰 저장 완료'); + } else { + print('토큰이 유효하지 않습니다.'); + } + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const ProfileSetting()), + ); } }, ), ); } -} +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 4b62f70..01e411e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,15 +5,17 @@ import 'routine_home/MyRoutinePage.dart'; import 'package:http/http.dart' as http; import 'routine_group/GroupMainPage.dart'; -void main() async{ +void main() async { await initializeDateFormatting(); - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( debugShowCheckedModeBanner: false, home: RoutineAde1(), ); diff --git a/lib/rotuine_myInfo/MyInfo.dart b/lib/rotuine_myInfo/MyInfo.dart deleted file mode 100644 index 04be275..0000000 --- a/lib/rotuine_myInfo/MyInfo.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:routine_ade/rotuine_myInfo/ProfileChange.dart'; - -import '../routine_group/GroupMainPage.dart'; -import 'package:routine_ade/routine_statisrics/StaticsCalendar.dart'; - -class MyInfo extends StatelessWidget { - const MyInfo({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - backgroundColor: const Color(0xFF8DCCFF), - centerTitle: true, - title: const Text( - '내 정보', - style: TextStyle( - color: Colors.white, fontSize: 25, fontWeight: FontWeight.bold), - ), - automaticallyImplyLeading: false, // 뒤로가기 제거 - actions: [ - IconButton( - icon: Stack( - children: [ - IconButton( - icon: Image.asset("assets/images/settings-cog.png"), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => ProfileChange()), - ); - }, - ), - Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.all(2), - ), - ), - ], - ), - onPressed: () { - // Handle settings button press - }, - ), - ], - ), - body: Center( - child: Column( - //mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox( - height: 70, - ), - // Profile Image - const CircleAvatar( - radius: 50, - backgroundImage: NetworkImage( - 'https://example.com/profile_image.jpg', // Add actual image URL or use AssetImage for local images - ), - ), - const SizedBox(height: 80), - - // Nickname Row - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: const ListTile( - title: Text('닉네임'), - trailing: Text('얄루'), - ), - ), - ), - const SizedBox(height: 20), - - // Introduction Box - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: const ListTile( - title: Text('한 줄 소개'), - subtitle: Text('다양한 루틴을 수행하는 루틴이입니다'), - ), - ), - ), - ], - ), - ), - bottomNavigationBar: _buildBottomAppBar(context), - ); - } - - // Bottom AppBar widget - Widget _buildBottomAppBar(BuildContext context) { - return BottomAppBar( - color: Colors.white, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildBottomAppBarItem( - context, "assets/images/tap-bar/routine01.png"), - _buildBottomAppBarItem(context, "assets/images/tap-bar/group01.png", - const GroupMainPage()), - _buildBottomAppBarItem( - context, - "assets/images/tap-bar/statistics01.png", - const StaticsCalendar()), - _buildBottomAppBarItem(context, "assets/images/tap-bar/more02.png", - const MyInfo()), // Current page - ], - ), - ); - } - - // Helper function to create a Bottom App Bar Item - Widget _buildBottomAppBarItem(BuildContext context, String asset, - [Widget? page]) { - return GestureDetector( - onTap: () { - if (page != null) { - Navigator.push( - context, MaterialPageRoute(builder: (context) => page)); - } - }, - child: SizedBox( - width: 60, - height: 60, - child: Image.asset(asset), - ), - ); - } -} diff --git a/lib/rotuine_myInfo/ProfileChange.dart b/lib/rotuine_myInfo/ProfileChange.dart deleted file mode 100644 index 49a3ebe..0000000 --- a/lib/rotuine_myInfo/ProfileChange.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:permission_handler/permission_handler.dart'; - -import '../routine_home/MyRoutinePage.dart'; - -class ProfileChange extends StatefulWidget { - const ProfileChange({super.key}); - - @override - _ProfileChangeState createState() => _ProfileChangeState(); -} - -class _ProfileChangeState extends State { - File? _imageFile; - final ImagePicker _picker = ImagePicker(); - - Future _pickImage() async { - final pickedFile = await _picker.pickImage(source: ImageSource.gallery); - - if (pickedFile != null) { - setState(() { - _imageFile = File(pickedFile.path); - }); - } else { - print('No image selected.'); - } - } - - final TextEditingController _nicknameController = TextEditingController(); - final TextEditingController _bioController = TextEditingController(); - bool _isNicknameValid = true; - String _nicknameErrorMessage = ''; - - void _validateNickname(String value) { - setState(() { - if (value.length > 10) { - _isNicknameValid = false; - _nicknameErrorMessage = '10글자 이내로 입력해주세요.'; - } else { - _isNicknameValid = true; - _nicknameErrorMessage = ''; - } - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - backgroundColor: const Color(0xFF8DCCFF), - centerTitle: true, - title: const Text( - '프로필 설정', - style: TextStyle( - color: Colors.white, fontSize: 25, fontWeight: FontWeight.bold), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: Stack( - children: [ - Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 20), - GestureDetector( - onTap: _pickImage, // 프로필 사진을 클릭했을 때 이미지 선택 기능 실행 - child: Stack( - children: [ - CircleAvatar( - radius: 50, - backgroundImage: _imageFile != null - ? FileImage(_imageFile!) - : const AssetImage( - 'assets/images/default_profile.png') - as ImageProvider, - ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: _pickImage, // 카메라 아이콘 클릭 시 이미지 선택 기능 실행 - child: const CircleAvatar( - backgroundColor: Colors.white, - radius: 16, - child: Icon(Icons.camera_alt, color: Colors.grey), - ), - ), - ), - ], - ), - ), - TextButton(child: const Text("프로필 사진 삭제"), onPressed: () {}), - const SizedBox(height: 50), - const Align( - alignment: Alignment.centerLeft, - child: Text("닉네임 (필수)"), - ), - const SizedBox(height: 10), - TextField( - controller: _nicknameController, - onChanged: _validateNickname, - decoration: InputDecoration( - hintText: '닉네임', - errorText: !_isNicknameValid - ? _nicknameErrorMessage - : '10글자 이내로 입력해주세요.', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide( - color: _isNicknameValid ? Colors.red : Colors.grey, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide( - color: _isNicknameValid ? Colors.blue : Colors.red, - ), - ), - ), - maxLength: 10, - ), - const SizedBox(height: 20), - const Align( - alignment: Alignment.centerLeft, - child: Text("한 줄 소개 (선택)"), - ), - const SizedBox(height: 10), - TextField( - controller: _bioController, - decoration: InputDecoration( - hintText: '한 줄 소개', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - ), - ), - ), - ], - ), - ), - Positioned( - bottom: 20, - left: 10, - right: 10, - child: SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF8DCCFF), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const MyRoutinePage(), - ), - ); - }, - child: const Text( - '변경 완료', - style: TextStyle(fontSize: 16, color: Colors.white), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/routine_group/ChatScreen.dart b/lib/routine_group/ChatScreen.dart index 4e7f10e..0d95adf 100644 --- a/lib/routine_group/ChatScreen.dart +++ b/lib/routine_group/ChatScreen.dart @@ -69,29 +69,6 @@ class ChatScreenState extends State with TickerProviderStateMixin { } } - // Future> fetchChatMessages(int groupId) async { - // final url = Uri.parse('http://15.164.88.94:8080/groups/$groupId/chatting'); - // final headers = { - // 'Content-Type': 'application/json', - // 'Authorization': 'Bearer $token', - // }; - - // try { - // final response = await http.get(url, headers: headers); - // if (response.statusCode == 200) { - // final decodedResponse = utf8.decode(response.bodyBytes); - // final data = json.decode(decodedResponse); - // print('Fetched chat messages: $data'); // 추가된 로그 - - // return data['groupChatting']; - // } else { - // throw Exception('Failed to load chat messages'); - // } - // } catch (e) { - // print('Error fetching chat messages: $e'); - // return []; - // } - // } Future _loadChatMessages() async { try { final chatMessages = await fetchChatMessages(widget.groupId); @@ -192,7 +169,7 @@ class ChatScreenState extends State with TickerProviderStateMixin { if (imageFile != null) { final mimeTypeData = - lookupMimeType(imageFile.path, headerBytes: [0xFF, 0xD8])?.split('/'); + lookupMimeType(imageFile.path, headerBytes: [0xFF, 0xD8])?.split('/'); final multipartFile = await http.MultipartFile.fromPath( 'image', imageFile.path, @@ -246,7 +223,7 @@ class ChatScreenState extends State with TickerProviderStateMixin { if (_nickname != null) { final response = - await createChatMessage(widget.groupId, text, _imageFile); + await createChatMessage(widget.groupId, text, _imageFile); final createdDate = response['createdDate']; final createdTime = response['createdTime']; @@ -396,7 +373,7 @@ class ChatMessage extends StatelessWidget { Widget build(BuildContext context) { return SizeTransition( sizeFactor: - CurvedAnimation(parent: animationController, curve: Curves.easeOut), + CurvedAnimation(parent: animationController, curve: Curves.easeOut), axisAlignment: 0.0, child: Container( margin: const EdgeInsets.symmetric(vertical: 10.0), @@ -419,7 +396,7 @@ class ChatMessage extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: - isMine ? MainAxisAlignment.end : MainAxisAlignment.start, + isMine ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ if (!isMine) Container( @@ -498,4 +475,4 @@ class ChatMessage extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/routine_statisrics/StaticsCalendar.dart b/lib/routine_statisrics/StaticsCalendar.dart deleted file mode 100644 index e7a76dd..0000000 --- a/lib/routine_statisrics/StaticsCalendar.dart +++ /dev/null @@ -1,470 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'package:table_calendar/table_calendar.dart'; - -import '../routine_group/GroupMainPage.dart'; -import '../routine_home/MyRoutinePage.dart'; -import '../routine_user/token.dart'; -import 'StaticsCategory.dart'; - -class StaticsCalendar extends StatefulWidget { - const StaticsCalendar({super.key}); - - @override - _StaticsCalendarState createState() => _StaticsCalendarState(); -} - -class _StaticsCalendarState extends State - with SingleTickerProviderStateMixin { - bool isExpanded = false; - late TabController _tabController; - CalendarFormat _calendarFormat = CalendarFormat.month; - DateTime _focusedDay = DateTime.now(); - DateTime? _selectedDay; - - RoutineStatistics? routineStatistics; // API에서 불러온 데이터 저장 변수 - - @override - void initState() { - super.initState(); - _tabController = TabController(length: 2, vsync: this); - _fetchRoutineStatistics(); // 통계 데이터 가져오기 - } - - @override - void dispose() { - _tabController.dispose(); - super.dispose(); - } - - // API 호출 함수 - Future _fetchRoutineStatistics() async { - final String url = - 'http://15.164.88.94:8080/users/statistics/calender?date=${_focusedDay.year}.${_focusedDay.month.toString().padLeft(2, '0')}'; - - try { - final response = await http.get( - Uri.parse(url), - headers: { - 'Authorization': 'Bearer $token', // 인증 토큰 헤더 추가 - 'Content-Type': 'application/json', - }, - ); - - if (response.statusCode == 200) { - final data = jsonDecode(response.body); - - setState(() { - routineStatistics = RoutineStatistics.fromJson(data); - }); - } else { - print('Failed to load routine statistics: ${response.statusCode}'); - throw Exception( - 'Failed to load routine statistics: ${response.statusCode}'); - } - } catch (e) { - print('Error fetching routine statistics: $e'); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text( - '통계', - style: TextStyle( - color: Colors.white, - fontSize: 25, - fontWeight: FontWeight.bold, - ), - ), - centerTitle: true, - backgroundColor: const Color(0xFF8DCCFF), - automaticallyImplyLeading: false, // 뒤로가기 제거 - bottom: PreferredSize( - preferredSize: const Size.fromHeight(90.0), - child: Container( - color: Colors.white, - child: Padding( - padding: const EdgeInsets.only(top: 40.0), - child: TabBar( - controller: _tabController, - tabs: const [ - Tab(text: "캘린더"), - Tab(text: "카테고리"), - ], - labelStyle: const TextStyle(fontSize: 18), - labelColor: Colors.black, - unselectedLabelColor: Colors.grey, - indicator: const UnderlineTabIndicator( - borderSide: BorderSide(width: 3.0, color: Color(0xFF8DCCFF)), - insets: EdgeInsets.symmetric(horizontal: 115.0), - ), - ), - ), - ), - ), - ), - backgroundColor: Colors.white, - body: TabBarView( - controller: _tabController, - children: [ - _buildCalendarTab(), - const StaticsCategory(), - ], - ), - bottomNavigationBar: BottomAppBar( - color: Colors.white, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const MyRoutinePage()), - ); - }, - child: SizedBox( - width: 60, - height: 60, - child: Image.asset("assets/images/tap-bar/routine01.png"), - ), - ), - GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const GroupMainPage()), - ); - }, - child: SizedBox( - width: 60, - height: 60, - child: Image.asset("assets/images/tap-bar/group01.png"), - ), - ), - GestureDetector( - onTap: () { - // 통계 버튼 클릭 시 동작할 코드 - }, - child: SizedBox( - width: 60, - height: 60, - child: Image.asset("assets/images/tap-bar/statistics02.png"), - ), - ), - GestureDetector( - onTap: () { - // 더보기 버튼 클릭 시 동작할 코드 - }, - child: SizedBox( - width: 60, - height: 60, - child: Image.asset("assets/images/tap-bar/more01.png"), - ), - ), - ], - ), - ), - ); - } - - Widget _buildCalendarTab() { - final totalCompletedRoutines = - routineStatistics?.completedRoutinesCount ?? 0; - - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildCalendarHeader(), - const SizedBox(height: 20), - Row( - children: [ - const SizedBox(width: 10), - const Text( - '이번 달 완료 루틴', - style: TextStyle(fontSize: 18), - ), - const Spacer(), - Text( - '${routineStatistics?.completedRoutinesCount ?? 0}개', // null 체크 추가 - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF8DCCFF), - ), - ), - ], - ), - const SizedBox(height: 30), - const Row( - children: [ - SizedBox(width: 10), - Text( - '이번 달 달성률', - style: TextStyle(fontSize: 18), - ), - ], - ), - const SizedBox(height: 20), - _buildCalendar(), - ], - ), - ); - } - - Widget _buildCalendarHeader() { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - icon: const Icon(Icons.chevron_left), - onPressed: () { - setState(() { - _focusedDay = DateTime(_focusedDay.year, _focusedDay.month - 1); - _fetchRoutineStatistics(); // 월을 변경할 때 데이터 갱신 - }); - }, - ), - Text( - '${_focusedDay.year}년 ${_focusedDay.month}월', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - IconButton( - icon: const Icon(Icons.chevron_right), - onPressed: () { - setState(() { - _focusedDay = DateTime(_focusedDay.year, _focusedDay.month + 1); - _fetchRoutineStatistics(); // 월을 변경할 때 데이터 갱신 - }); - }, - ), - ], - ), - ); - } - - Widget _buildCalendar() { - return Container( - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - width: 2.0, - ), - borderRadius: BorderRadius.circular(10.0), - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 30, 10, 5), - child: TableCalendar( - firstDay: DateTime.utc(2020, 1, 1), - lastDay: DateTime.utc(2030, 12, 31), - focusedDay: _focusedDay, - selectedDayPredicate: (day) { - return false; - }, - calendarFormat: _calendarFormat, - onFormatChanged: (format) { - setState(() { - _calendarFormat = format; - }); - }, - onPageChanged: (focusedDay) { - setState(() { - _focusedDay = focusedDay; - _fetchRoutineStatistics(); // 페이지 변경 시 데이터 갱신 - }); - }, - calendarBuilders: CalendarBuilders( - defaultBuilder: (context, date, _) { - final dayInfo = routineStatistics - ?.userRoutineCompletionStatistics.routineCompletionInfos - .firstWhere((element) => element.day == date.day, - orElse: () => - RoutineCompletionInfo(day: 0, level: 0)); - - return _buildDayCell(date, dayInfo?.level ?? 0); - }, - ), - calendarStyle: const CalendarStyle( - todayDecoration: BoxDecoration(), - todayTextStyle: TextStyle( - color: Colors.black, - fontSize: 16.0, - ), - selectedDecoration: BoxDecoration(), - defaultTextStyle: TextStyle( - fontSize: 16.0, - ), - weekendTextStyle: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), - outsideTextStyle: TextStyle( - fontSize: 16.0, - color: Colors.grey, - ), - ), - daysOfWeekStyle: DaysOfWeekStyle( - dowTextFormatter: (date, locale) { - const days = ['월', '화', '수', '목', '금', '토', '일']; - return days[date.weekday - 1]; - }, - weekdayStyle: const TextStyle( - fontSize: 14.0, - color: Colors.black, - ), - weekendStyle: const TextStyle( - fontSize: 14.0, - color: Colors.black, - ), - ), - headerVisible: false, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(180, 5, 10, 5), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Less"), - const SizedBox(width: 8.0), - Container( - width: 10.0, - height: 10.0, - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 - ), - ), - const SizedBox(width: 8.0), - Container( - width: 10.0, - height: 10.0, - decoration: BoxDecoration( - color: const Color(0xffCAF4FF), - shape: BoxShape.circle, - border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 - ), - ), - const SizedBox(width: 8.0), - Container( - width: 10.0, - height: 10.0, - decoration: BoxDecoration( - color: const Color(0xffA0DEFF), - shape: BoxShape.circle, - border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 - ), - ), - const SizedBox(width: 8.0), - Container( - width: 10.0, - height: 10.0, - decoration: BoxDecoration( - color: const Color(0xff5AB2FF), - shape: BoxShape.circle, - border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 - ), - ), - const SizedBox(width: 8.0), - const Text("More"), - ], - ), - ) - ], - ), - ); - } - - Widget _buildDayCell(DateTime date, int level) { - final colorMap = { - 0: Colors.white, // 완료 안 됨 - 1: const Color(0xffCAF4FF), // 낮은 달성도 - 2: const Color(0xffA0DEFF), // 중간 달성도 - 3: const Color(0xff5AB2FF), // 높은 달성도 - }; - - return Container( - margin: const EdgeInsets.all(2.0), - decoration: BoxDecoration( - color: colorMap[level] ?? Colors.grey[300], - shape: BoxShape.circle, - ), - child: Center( - child: Text( - '${date.day}', - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - ), - ), - ); - } -} - -// RoutineStatistics 데이터 모델 -class RoutineStatistics { - final int completedRoutinesCount; - final UserRoutineCompletionStatistics userRoutineCompletionStatistics; - - RoutineStatistics( - {required this.completedRoutinesCount, - required this.userRoutineCompletionStatistics}); - - factory RoutineStatistics.fromJson(Map json) { - return RoutineStatistics( - completedRoutinesCount: json['completedRoutinesCount'], - userRoutineCompletionStatistics: UserRoutineCompletionStatistics.fromJson( - json['userRoutineCompletionStatistics']), - ); - } -} - -class UserRoutineCompletionStatistics { - final List routineCompletionInfos; - - UserRoutineCompletionStatistics({required this.routineCompletionInfos}); - - factory UserRoutineCompletionStatistics.fromJson(Map json) { - var list = json['routineCompletionInfos'] as List; - List completionInfoList = list - .map((completionInfo) => RoutineCompletionInfo.fromJson(completionInfo)) - .toList(); - return UserRoutineCompletionStatistics( - routineCompletionInfos: completionInfoList); - } -} - -class RoutineCompletionInfo { - final int day; - final int level; - - RoutineCompletionInfo({required this.day, required this.level}); - - factory RoutineCompletionInfo.fromJson(Map json) { - return RoutineCompletionInfo( - day: json['day'], - level: json['level'], - ); - } -} diff --git a/lib/routine_statisrics/StaticsCategory.dart b/lib/routine_statisrics/StaticsCategory.dart deleted file mode 100644 index cad2d08..0000000 --- a/lib/routine_statisrics/StaticsCategory.dart +++ /dev/null @@ -1,284 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; - -import '../routine_user/token.dart'; - -class StaticsCategory extends StatefulWidget { - const StaticsCategory({super.key}); - - @override - _StaticsCategoryState createState() => _StaticsCategoryState(); -} - -class _StaticsCategoryState extends State { - int completedRoutinesCount = 0; - List routineCategoryStatistics = []; - - DateTime _focusedDay = DateTime.now(); // 포커스된 날짜 - - @override - void initState() { - super.initState(); - _fetchCategoryStatistics(); // 초기 데이터 로드 - } - - Future _fetchCategoryStatistics() async { - String url = - 'http://15.164.88.94:8080/users/statistics?date=${_focusedDay.year}.${_focusedDay.month.toString().padLeft(2, '0')}'; - - try { - final response = await http.get( - Uri.parse(url), - headers: { - 'Authorization': 'Bearer $token', // 인증 토큰 헤더 추가 - 'Content-Type': 'application/json', - }, - ); - - // UTF-8로 응답 디코딩 - var responseBody = utf8.decode(response.bodyBytes); - var data = json.decode(responseBody); - - print('Response body: $responseBody'); // 응답 확인용 출력 - - if (response.statusCode == 200) { - // 데이터가 null 또는 잘못된 형식일 경우 처리 - if (data != null && data is Map) { - var statistics = CategoryStatistics.fromJson(data); - setState(() { - completedRoutinesCount = statistics.completedRoutinesCount; - routineCategoryStatistics = - statistics.routineCategoryStatistics; // 필드 수정 - }); - print( - "Data loaded successfully: ${routineCategoryStatistics.length} categories"); - } else { - print('Received null or invalid data'); - setState(() { - routineCategoryStatistics = []; - }); - } - } else { - print('Failed to load statistics. Status code: ${response.statusCode}'); - setState(() { - routineCategoryStatistics = []; - }); - } - } catch (e) { - print('Error fetching data: $e'); - setState(() { - routineCategoryStatistics = []; - }); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildCalendarHeader(), - const SizedBox(height: 20), - _buildPieChart(), - const SizedBox(height: 20), - routineCategoryStatistics.isNotEmpty - ? Column( - children: routineCategoryStatistics - .map((stat) => _buildCategoryList( - stat.category, - stat.completedCount, - _getCategoryColor(stat.category))) - .toList(), - ) - : const Center( - child: Text( - 'No categories available for this month.', - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - ), - ], - ), - ), - ); - } - - Widget _buildCalendarHeader() { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - icon: const Icon(Icons.chevron_left), - onPressed: () { - setState(() { - _focusedDay = DateTime(_focusedDay.year, _focusedDay.month - 1); - _fetchCategoryStatistics(); // 월 변경 시 데이터 업데이트 - }); - }, - ), - Text( - '${_focusedDay.year}년 ${_focusedDay.month}월', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - IconButton( - icon: const Icon(Icons.chevron_right), - onPressed: () { - setState(() { - _focusedDay = DateTime(_focusedDay.year, _focusedDay.month + 1); - _fetchCategoryStatistics(); // 월 변경 시 데이터 업데이트 - }); - }, - ), - ], - ), - ); - } - - Widget _buildPieChart() { - return SizedBox( - height: 150, - child: Stack( - children: [ - PieChart( - PieChartData( - sections: routineCategoryStatistics.isNotEmpty - ? routineCategoryStatistics.map((stat) { - return PieChartSectionData( - color: _getCategoryColor(stat.category), - value: stat.completedCount.toDouble(), - title: '', - radius: 30, - ); - }).toList() - : [ - // 기본 섹션을 추가하여 빈 차트 처리 - PieChartSectionData( - color: Colors.grey, - radius: 30, - ) - ], - centerSpaceRadius: 55, - sectionsSpace: 0, - ), - ), - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - '이번 달 완료 루틴', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey), - ), - Text( - '$completedRoutinesCount개', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildCategoryList(String category, int completedCount, Color color) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.2), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 3), - ), - ], - ), - child: ListTile( - leading: CircleAvatar( - radius: 8, - backgroundColor: color, - ), - title: Text(category), - trailing: - Text('$completedCount 개', style: const TextStyle(fontSize: 16)), - ), - ), - ); - } - - Color _getCategoryColor(String category) { - switch (category) { - case '일상': - return const Color(0xffFDA598); - case '건강': - return const Color(0xff80CAFF); - case '자기개발': - return const Color(0xff85E0A3); - case '자기관리': - return const Color(0xffFFDE7A); - case '기타': - return const Color(0xffFFB2E5); - default: - return Colors.grey; - } - } -} - -class CategoryStatistics { - final int completedRoutinesCount; - final List routineCategoryStatistics; - - CategoryStatistics({ - required this.completedRoutinesCount, - required this.routineCategoryStatistics, - }); - - factory CategoryStatistics.fromJson(Map json) { - var list = - json['routineCategoryStatistics'] as List? ?? []; // API 응답의 필드명 사용 - List categoryStatisticsList = - list.map((stat) => RoutineCategoryStatistics.fromJson(stat)).toList(); - return CategoryStatistics( - completedRoutinesCount: - json['completedRoutinesCount'] ?? 0, // null일 경우 기본값 설정 - routineCategoryStatistics: categoryStatisticsList, // 필드명 수정 - ); - } -} - -class RoutineCategoryStatistics { - final String category; - final int completedCount; - - RoutineCategoryStatistics({ - required this.category, - required this.completedCount, - }); - - factory RoutineCategoryStatistics.fromJson(Map json) { - return RoutineCategoryStatistics( - category: json['category'] ?? 'Unknown', // null일 경우 기본값 설정 - completedCount: json['completedCount'] ?? 0, // null일 경우 기본값 설정 - ); - } -} diff --git a/lib/routine_statistics/StaticsCalendar.dart b/lib/routine_statistics/StaticsCalendar.dart index 4d109f6..e7a3150 100644 --- a/lib/routine_statistics/StaticsCalendar.dart +++ b/lib/routine_statistics/StaticsCalendar.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:table_calendar/table_calendar.dart'; -import '../routine_myInfo/MyInfo.dart'; + import '../routine_group/GroupMainPage.dart'; import '../routine_home/MyRoutinePage.dart'; import '../routine_user/token.dart'; @@ -161,10 +161,6 @@ class _StaticsCalendarState extends State GestureDetector( onTap: () { // 더보기 버튼 클릭 시 동작할 코드 - Navigator.push( - context, - MaterialPageRoute(builder: (context) => MyInfo()), - ); }, child: SizedBox( width: 60, @@ -272,7 +268,7 @@ class _StaticsCalendarState extends State child: Column( children: [ Padding( - padding: const EdgeInsets.fromLTRB(10, 15, 10, 5), + padding: const EdgeInsets.fromLTRB(10, 30, 10, 5), child: TableCalendar( firstDay: DateTime.utc(2020, 1, 1), lastDay: DateTime.utc(2030, 12, 31), @@ -297,8 +293,8 @@ class _StaticsCalendarState extends State final dayInfo = routineStatistics ?.userRoutineCompletionStatistics.routineCompletionInfos .firstWhere((element) => element.day == date.day, - orElse: () => - RoutineCompletionInfo(day: 0, level: 0)); + orElse: () => + RoutineCompletionInfo(day: 0, level: 0)); return _buildDayCell(date, dayInfo?.level ?? 0); }, @@ -328,16 +324,15 @@ class _StaticsCalendarState extends State return days[date.weekday - 1]; }, weekdayStyle: const TextStyle( - fontSize: 16.0, + fontSize: 14.0, color: Colors.black, ), weekendStyle: const TextStyle( - fontSize: 16.0, + fontSize: 14.0, color: Colors.black, ), ), headerVisible: false, - daysOfWeekHeight: 30.0, ), ), Padding( @@ -354,7 +349,7 @@ class _StaticsCalendarState extends State color: Colors.white, shape: BoxShape.circle, border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 + Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 ), ), const SizedBox(width: 8.0), @@ -365,7 +360,7 @@ class _StaticsCalendarState extends State color: const Color(0xffCAF4FF), shape: BoxShape.circle, border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 + Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 ), ), const SizedBox(width: 8.0), @@ -376,7 +371,7 @@ class _StaticsCalendarState extends State color: const Color(0xffA0DEFF), shape: BoxShape.circle, border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 + Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 ), ), const SizedBox(width: 8.0), @@ -387,7 +382,7 @@ class _StaticsCalendarState extends State color: const Color(0xff5AB2FF), shape: BoxShape.circle, border: - Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 + Border.all(color: Colors.grey, width: 1.0), // 테두리 추가 ), ), const SizedBox(width: 8.0), @@ -409,7 +404,7 @@ class _StaticsCalendarState extends State }; return Container( - margin: const EdgeInsets.all(4.0), + margin: const EdgeInsets.all(2.0), decoration: BoxDecoration( color: colorMap[level] ?? Colors.grey[300], shape: BoxShape.circle, @@ -419,7 +414,7 @@ class _StaticsCalendarState extends State '${date.day}', style: const TextStyle( color: Colors.black, - fontSize: 16.0, + fontWeight: FontWeight.bold, ), ), ), @@ -434,7 +429,7 @@ class RoutineStatistics { RoutineStatistics( {required this.completedRoutinesCount, - required this.userRoutineCompletionStatistics}); + required this.userRoutineCompletionStatistics}); factory RoutineStatistics.fromJson(Map json) { return RoutineStatistics( @@ -472,4 +467,4 @@ class RoutineCompletionInfo { level: json['level'], ); } -} +} \ No newline at end of file diff --git a/lib/routine_statistics/StaticsCategory.dart b/lib/routine_statistics/StaticsCategory.dart index 655ae7e..54cabb0 100644 --- a/lib/routine_statistics/StaticsCategory.dart +++ b/lib/routine_statistics/StaticsCategory.dart @@ -88,19 +88,19 @@ class _StaticsCategoryState extends State { const SizedBox(height: 20), routineCategoryStatistics.isNotEmpty ? Column( - children: routineCategoryStatistics - .map((stat) => _buildCategoryList( - stat.category, - stat.completedCount, - _getCategoryColor(stat.category))) - .toList(), - ) + children: routineCategoryStatistics + .map((stat) => _buildCategoryList( + stat.category, + stat.completedCount, + _getCategoryColor(stat.category))) + .toList(), + ) : const Center( - child: Text( - 'No categories available for this month.', - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - ), + child: Text( + 'No categories available for this month.', + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ), ], ), ), @@ -153,20 +153,20 @@ class _StaticsCategoryState extends State { PieChartData( sections: routineCategoryStatistics.isNotEmpty ? routineCategoryStatistics.map((stat) { - return PieChartSectionData( - color: _getCategoryColor(stat.category), - value: stat.completedCount.toDouble(), - title: '', - radius: 30, - ); - }).toList() + return PieChartSectionData( + color: _getCategoryColor(stat.category), + value: stat.completedCount.toDouble(), + title: '', + radius: 30, + ); + }).toList() : [ - // 기본 섹션을 추가하여 빈 차트 처리 - PieChartSectionData( - color: Colors.grey, - radius: 30, - ) - ], + // 기본 섹션을 추가하여 빈 차트 처리 + PieChartSectionData( + color: Colors.grey, + radius: 30, + ) + ], centerSpaceRadius: 55, sectionsSpace: 0, ), @@ -220,7 +220,7 @@ class _StaticsCategoryState extends State { ), title: Text(category), trailing: - Text('$completedCount 개', style: const TextStyle(fontSize: 16)), + Text('$completedCount 개', style: const TextStyle(fontSize: 16)), ), ), ); @@ -257,10 +257,10 @@ class CategoryStatistics { var list = json['routineCategoryStatistics'] as List? ?? []; // API 응답의 필드명 사용 List categoryStatisticsList = - list.map((stat) => RoutineCategoryStatistics.fromJson(stat)).toList(); + list.map((stat) => RoutineCategoryStatistics.fromJson(stat)).toList(); return CategoryStatistics( completedRoutinesCount: - json['completedRoutinesCount'] ?? 0, // null일 경우 기본값 설정 + json['completedRoutinesCount'] ?? 0, // null일 경우 기본값 설정 routineCategoryStatistics: categoryStatisticsList, // 필드명 수정 ); } @@ -281,4 +281,4 @@ class RoutineCategoryStatistics { completedCount: json['completedCount'] ?? 0, // null일 경우 기본값 설정 ); } -} +} \ No newline at end of file diff --git a/lib/routine_user/token.dart b/lib/routine_user/token.dart index ab5bced..537a01f 100644 --- a/lib/routine_user/token.dart +++ b/lib/routine_user/token.dart @@ -1,43 +1,39 @@ import 'package:shared_preferences/shared_preferences.dart'; -String token = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MjEwMzkzMDEsImV4cCI6MTczNjU5MTMwMSwidXNlcklkIjoyfQ.XLthojYmD3dA4TSeXv_JY7DYIjoaMRHB7OLx9-l2rvw'; -//테스트계정2 +// String token = +// 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MjEwMzkzMDEsImV4cCI6MTczNjU5MTMwMSwidXNlcklkIjoyfQ.XLthojYmD3dA4TSeXv_JY7DYIjoaMRHB7OLx9-l2rvw'; +// //테스트계정2 + +String token = ''; class TokenManager { - static Future saveToken(String token) async { - final prefs = await SharedPreferences.getInstance(); //토큰 저장 - await prefs.setString('authToken', token); + // 토큰을 저장하고 전역 변수 업데이트 + static Future saveToken(String newToken) async { + final prefs = + await SharedPreferences.getInstance(); // SharedPreferences에 저장 + await prefs.setString('authToken', newToken); + + token = newToken; // 전역 변수 업데이트 } + // 저장된 토큰을 불러오고 전역 변수 업데이트 static Future getToken() async { - final prefs = await SharedPreferences.getInstance(); //토큰 불러오기 - return prefs.getString('authToken'); + final prefs = + await SharedPreferences.getInstance(); // SharedPreferences에서 불러오기 + String? storedToken = prefs.getString('authToken'); + + if (storedToken != null) { + token = storedToken; // 전역 변수 업데이트 + } + + return storedToken; } + // 토큰을 삭제 static Future clearToken() async { - final prefs = await SharedPreferences.getInstance(); //토큰 삭제 + final prefs = + await SharedPreferences.getInstance(); // SharedPreferences에서 삭제 await prefs.remove('authToken'); + token = ''; // 전역 변수 초기화 } -} - - -// import 'package:shared_preferences/shared_preferences.dart'; -// -// class TokenManager { -// static Future saveToken(String token) async { -// final prefs = await SharedPreferences.getInstance(); //토큰 저장 -// await prefs.setString('authToken', token); -// } -// -// static Future getToken() async { -// final prefs = await SharedPreferences.getInstance(); //토큰 불러오기 -// return prefs.getString('authToken'); -// } -// -// static Future clearToken() async { -// final prefs = await SharedPreferences.getInstance(); //토큰 삭제 -// await prefs.remove('authToken'); -// } -// } - +} \ No newline at end of file From c079f527ca743bbc6a33b1926cfecc3a6ca67a98 Mon Sep 17 00:00:00 2001 From: gaeunpark7 Date: Sat, 5 Oct 2024 16:47:14 +0900 Subject: [PATCH 2/4] =?UTF-8?q?firebase=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 15 ++ android/app/google-services.json | 29 +++ android/app/src/main/AndroidManifest.xml | 3 +- lib/RoutineAdeIntro/RoutineAde1.dart | 51 ++++- lib/RoutineAdelntro/ProfileSetting.dart | 182 ------------------ lib/RoutineAdelntro/ProfileSetting2.dart | 81 -------- lib/RoutineAdelntro/RoutineAde1.dart | 79 -------- lib/main.dart | 5 +- macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 86 +++++++-- pubspec.yaml | 6 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 13 files changed, 183 insertions(+), 362 deletions(-) create mode 100644 android/app/google-services.json delete mode 100644 lib/RoutineAdelntro/ProfileSetting.dart delete mode 100644 lib/RoutineAdelntro/ProfileSetting2.dart delete mode 100644 lib/RoutineAdelntro/RoutineAde1.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 198a71f..1c80271 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,6 +3,21 @@ plugins { id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" + id 'com.google.gms.google-services' version '4.4.2' apply false +} + +dependencies { + // Import the Firebase BoM + implementation platform('com.google.firebase:firebase-bom:33.4.0') + + + // TODO: Add the dependencies for Firebase products you want to use + // When using the BoM, don't specify versions in Firebase dependencies + implementation 'com.google.firebase:firebase-analytics' + + + // Add the dependencies for any other desired Firebase products + // https://firebase.google.com/docs/android/setup#available-libraries } def localProperties = new Properties() diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..eaab8f6 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "480958477321", + "project_id": "routine-ade-621e0", + "storage_bucket": "routine-ade-621e0.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:480958477321:android:c2351c5c7efdd72fc3f4d8", + "android_client_info": { + "package_name": "com.example.routine_ade" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyBSwdB-8nt9YD1EoR61tUyVxQcZs2Q4r2U" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9200564..71c3967 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - + diff --git a/lib/RoutineAdeIntro/RoutineAde1.dart b/lib/RoutineAdeIntro/RoutineAde1.dart index 8d9c8e2..fbd8cbf 100644 --- a/lib/RoutineAdeIntro/RoutineAde1.dart +++ b/lib/RoutineAdeIntro/RoutineAde1.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; import 'WebViewPage.dart'; +import 'package:routine_ade/routine_user/token.dart'; void main() { runApp(const MyApp()); @@ -16,6 +20,7 @@ class MyApp extends StatelessWidget { ); } } +//webview에서 같이 파이어베이스 토큰을 받도록 수정. class RoutineAde1 extends StatefulWidget { const RoutineAde1({super.key}); @@ -25,6 +30,49 @@ class RoutineAde1 extends StatefulWidget { } class _RoutineAde1State extends State { + String? _firebaseToken; + @override + void initState() { + super.initState(); + _getFirebaseToken(); // 앱 실행 시 FCM 토큰을 가져옴 + } + + // Firebase 토큰을 가져오는 메서드 + Future _getFirebaseToken() async { + FirebaseMessaging messaging = FirebaseMessaging.instance; + String? firebaseToken = await messaging.getToken(); // Firebase 토큰 가져오기 + setState(() { + _firebaseToken = firebaseToken; + }); + print('FCM Token: $_firebaseToken'); + } + + // 서버로 Firebase 토큰을 보내는 메서드 + Future _sendTokenToServer(String userId, String firebaseToken) async { + const String apiUrl = ''; + + try { + final response = await http.post( + Uri.parse('$apiUrl/$userId/token'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', // 인증 토큰 + }, + body: jsonEncode({ + 'token': firebaseToken, + }), + ); + + if (response.statusCode == 200) { + print('토큰이 성공적으로 서버에 전송되었습니다.'); + } else { + print('서버 응답 에러: ${response.statusCode}'); + } + } catch (e) { + print('토큰 전송 실패: $e'); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -69,7 +117,8 @@ class _RoutineAde1State extends State { // WebView 페이지로 이동 Navigator.push( context, - MaterialPageRoute(builder: (context) => const WebViewPage()), + MaterialPageRoute( + builder: (context) => const WebViewPage()), ); }, child: Image.asset( diff --git a/lib/RoutineAdelntro/ProfileSetting.dart b/lib/RoutineAdelntro/ProfileSetting.dart deleted file mode 100644 index fb1e60e..0000000 --- a/lib/RoutineAdelntro/ProfileSetting.dart +++ /dev/null @@ -1,182 +0,0 @@ -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:permission_handler/permission_handler.dart'; - -import '../routine_home/MyRoutinePage.dart'; - -class ProfileSetting extends StatefulWidget { - const ProfileSetting({super.key}); - - @override - _ProfileSettingState createState() => _ProfileSettingState(); -} - -class _ProfileSettingState extends State { - File? _imageFile; - final ImagePicker _picker = ImagePicker(); - - Future _pickImage() async { - final pickedFile = await _picker.pickImage(source: ImageSource.gallery); - - if (pickedFile != null) { - setState(() { - _imageFile = File(pickedFile.path); - }); - } else { - print('No image selected.'); - } - } - - final TextEditingController _nicknameController = TextEditingController(); - final TextEditingController _bioController = TextEditingController(); - bool _isNicknameValid = true; - String _nicknameErrorMessage = ''; - - void _validateNickname(String value) { - setState(() { - if (value.length > 10) { - _isNicknameValid = false; - _nicknameErrorMessage = '10글자 이내로 입력해주세요.'; - } else { - _isNicknameValid = true; - _nicknameErrorMessage = ''; - } - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - backgroundColor: const Color(0xFF8DCCFF), - centerTitle: true, - title: const Text( - '프로필 설정', - style: TextStyle( - color: Colors.white, fontSize: 25, fontWeight: FontWeight.bold), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: Stack( - children: [ - Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 20), - GestureDetector( - onTap: _pickImage, // 프로필 사진을 클릭했을 때 이미지 선택 기능 실행 - child: Stack( - children: [ - CircleAvatar( - radius: 50, - backgroundImage: _imageFile != null - ? FileImage(_imageFile!) - : const AssetImage( - 'assets/images/default_profile.png') - as ImageProvider, - ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: _pickImage, // 카메라 아이콘 클릭 시 이미지 선택 기능 실행 - child: const CircleAvatar( - backgroundColor: Colors.white, - radius: 16, - child: Icon(Icons.camera_alt, color: Colors.grey), - ), - ), - ), - ], - ), - ), - const SizedBox(height: 50), - const Align( - alignment: Alignment.centerLeft, - child: Text("닉네임"), - ), - const SizedBox(height: 10), - TextField( - controller: _nicknameController, - onChanged: _validateNickname, - decoration: InputDecoration( - hintText: '닉네임', - errorText: !_isNicknameValid - ? _nicknameErrorMessage - : '10글자 이내로 입력해주세요.', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide( - color: _isNicknameValid ? Colors.red : Colors.grey, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide( - color: _isNicknameValid ? Colors.blue : Colors.red, - ), - ), - ), - maxLength: 10, - ), - const SizedBox(height: 20), - const Align( - alignment: Alignment.centerLeft, - child: Text("한 줄 소개"), - ), - const SizedBox(height: 10), - TextField( - controller: _bioController, - decoration: InputDecoration( - hintText: '한 줄 소개', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - ), - ), - ), - ], - ), - ), - Positioned( - bottom: 20, - left: 10, - right: 10, - child: SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF8DCCFF), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const MyRoutinePage(), - ), - ); - }, - child: const Text( - '완료', - style: TextStyle(fontSize: 16, color: Colors.white), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/RoutineAdelntro/ProfileSetting2.dart b/lib/RoutineAdelntro/ProfileSetting2.dart deleted file mode 100644 index 99f57ed..0000000 --- a/lib/RoutineAdelntro/ProfileSetting2.dart +++ /dev/null @@ -1,81 +0,0 @@ -// import 'dart:io'; -// -// import 'package:flutter/material.dart'; -// import 'package:image_picker/image_picker.dart'; -// -// -// class ProfileSetting2 extends StatefulWidget { -// const ProfileSetting2({Key? key}) : super(key: key); -// -// @override -// State createState() => _MyAppState(); -// } -// -// class _MyAppState extends State { -// XFile? _image; //이미지를 담을 변수 선언 -// final ImagePicker picker = ImagePicker(); //ImagePicker 초기화 -// -// //이미지를 가져오는 함수 -// Future getImage(ImageSource imageSource) async { -// //pickedFile에 ImagePicker로 가져온 이미지가 담긴다. -// final XFile? pickedFile = await picker.pickImage(source: imageSource); -// if (pickedFile != null) { -// setState(() { -// _image = XFile(pickedFile.path); //가져온 이미지를 _image에 저장 -// }); -// } -// } -// -// @override -// Widget build(BuildContext context) { -// return MaterialApp( -// home: Scaffold( -// appBar: AppBar(title: Text("Camera Test")), -// body: Column( -// crossAxisAlignment: CrossAxisAlignment.center, -// children: [ -// SizedBox(height: 30, width: double.infinity), -// _buildPhotoArea(), -// SizedBox(height: 20), -// _buildButton(), -// ], -// ), -// ), -// ); -// } -// -// Widget _buildPhotoArea() { -// return _image != null -// ? Container( -// width: 300, -// height: 300, -// child: Image.file(File(_image!.path)), //가져온 이미지를 화면에 띄워주는 코드 -// ) -// : Container( -// width: 300, -// height: 300, -// color: Colors.grey, -// ); -// } -// -// Widget _buildButton() { -// return Row( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// ElevatedButton( -// onPressed: () { -// getImage(ImageSource.camera); //getImage 함수를 호출해서 카메라로 찍은 사진 가져오기 -// }, -// child: Text("카메라"), -// ), -// SizedBox(width: 30), -// ElevatedButton( -// onPressed: () { -// getImage(ImageSource.gallery); //getImage 함수를 호출해서 갤러리에서 사진 가져오기 -// }, -// child: Text("갤러리"), -// ), -// ], -// ); -// } -// } \ No newline at end of file diff --git a/lib/RoutineAdelntro/RoutineAde1.dart b/lib/RoutineAdelntro/RoutineAde1.dart deleted file mode 100644 index 642625b..0000000 --- a/lib/RoutineAdelntro/RoutineAde1.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:routine_ade/RoutineAdelntro/ProfileSetting.dart'; -// import 'Pro'; -import 'package:routine_ade/routine_home/MyRoutinePage.dart'; - -import 'ProfileSetting2.dart'; - -class RoutineAde1 extends StatelessWidget { - const RoutineAde1({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, // Set the background color - body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(), // Pushes content towards the center - Image.asset( - 'assets/images/new-icons/RoutineAde.png', - width: 100, // Adjust width as needed - height: 100, // Adjust height as needed - ), - const SizedBox(height: 20), // Spacing between image and text - const Text( - '더 나은 하루, 루틴 에이드', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - const SizedBox(height: 8), - Text( - '루틴으로 더 나은 일상을\n함께 관리해보세요!', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: Colors.grey[700], - ), - ), - const Spacer(), // Pushes content towards the center - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), - child: SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF8DCCFF), // Button color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), // Rounded corners - ), - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ProfileSetting()), - ); - }, - child: const Text( - '시작하기', - style: TextStyle( - fontSize: 16, - color: Colors.white, - ), - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index 01e411e..09b812a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,8 +4,11 @@ import 'RoutineAdeIntro/RoutineAde1.dart'; import 'routine_home/MyRoutinePage.dart'; import 'package:http/http.dart' as http; import 'routine_group/GroupMainPage.dart'; +import 'package:firebase_core/firebase_core.dart'; void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); // Firebase 초기화 await initializeDateFormatting(); runApp(const MyApp()); } @@ -20,4 +23,4 @@ class MyApp extends StatelessWidget { home: RoutineAde1(), ); } -} \ No newline at end of file +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d993eb7..c829273 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,12 +6,16 @@ import FlutterMacOS import Foundation import file_selector_macos +import firebase_core +import firebase_messaging import path_provider_foundation import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index f387814..af5d1dc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" + url: "https://pub.dev" + source: hosted + version: "1.3.35" archive: dependency: transitive description: @@ -177,6 +185,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+2" + firebase_core: + dependency: "direct dev" + description: + name: firebase_core + sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" + url: "https://pub.dev" + source: hosted + version: "2.32.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 + url: "https://pub.dev" + source: hosted + version: "5.3.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 + url: "https://pub.dev" + source: hosted + version: "2.18.1" + firebase_messaging: + dependency: "direct dev" + description: + name: firebase_messaging + sha256: "980259425fa5e2afc03e533f33723335731d21a56fd255611083bceebf4373a8" + url: "https://pub.dev" + source: hosted + version: "14.7.10" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "87c4a922cb6f811cfb7a889bdbb3622702443c52a0271636cbc90d813ceac147" + url: "https://pub.dev" + source: hosted + version: "4.5.37" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "90dc7ed885e90a24bb0e56d661d4d2b5f84429697fd2cbb9e5890a0ca370e6f4" + url: "https://pub.dev" + source: hosted + version: "3.5.18" fl_chart: dependency: "direct main" description: @@ -332,10 +388,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.6.7" json_annotation: dependency: "direct main" description: @@ -428,18 +484,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -468,18 +524,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: "direct main" description: @@ -753,10 +809,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -865,10 +921,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" web: dependency: transitive description: @@ -934,5 +990,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: "3.4.4" + dart: "3.5.3" flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 01cf826..b3ecef6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: "A new Flutter project." version: 1.0.0+1 environment: - sdk: '3.4.4' + sdk: "3.5.3" dependencies: flutter: @@ -37,6 +37,8 @@ dev_dependencies: sdk: flutter flutter_lints: ^3.0.0 + firebase_core: ^2.0.0 + firebase_messaging: ^14.0.0 flutter: uses-material-design: true @@ -52,4 +54,4 @@ fonts: fonts: - asset: assets/fonts/NotoSansKR-Regular.otf - asset: assets/fonts/NotoSansKR-Bold.otf - - asset: assets/fonts/NotoSansKR-Medium.otf \ No newline at end of file + - asset: assets/fonts/NotoSansKR-Medium.otf diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 4e586f9..7ddc1a5 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,12 +7,15 @@ #include "generated_plugin_registrant.h" #include +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 1119879..8910784 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + firebase_core permission_handler_windows url_launcher_windows ) From 6487e83f6afdc8ef4ce00d7561d59b8aeebfdf2f Mon Sep 17 00:00:00 2001 From: gaeunpark7 Date: Sun, 6 Oct 2024 17:12:57 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=ED=8C=8C=EC=9D=B4=EC=96=B4=EB=B2=A0?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 16 ++++++- android/gradle.properties | 2 + lib/RoutineAdeIntro/RoutineAde1.dart | 39 ----------------- lib/RoutineAdeIntro/WebViewPage.dart | 48 +++++++++++++++++++-- pubspec.lock | 62 ++++++++++++++-------------- 5 files changed, 92 insertions(+), 75 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1c80271..6f87524 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,9 +1,21 @@ +buildscript { + ext.kotlin_version = '2.0.20' + repositories { + google() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.android.tools.build:gradle:7.2.2' + } +} + plugins { id "com.android.application" - id "kotlin-android" - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "org.jetbrains.kotlin.android" id "dev.flutter.flutter-gradle-plugin" id 'com.google.gms.google-services' version '4.4.2' apply false + // id "org.jetbrains.kotlin.android" version '2.0.20' } dependencies { diff --git a/android/gradle.properties b/android/gradle.properties index 3b5b324..5b1d455 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,5 @@ org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip + diff --git a/lib/RoutineAdeIntro/RoutineAde1.dart b/lib/RoutineAdeIntro/RoutineAde1.dart index fbd8cbf..ec118a9 100644 --- a/lib/RoutineAdeIntro/RoutineAde1.dart +++ b/lib/RoutineAdeIntro/RoutineAde1.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'WebViewPage.dart'; @@ -20,7 +19,6 @@ class MyApp extends StatelessWidget { ); } } -//webview에서 같이 파이어베이스 토큰을 받도록 수정. class RoutineAde1 extends StatefulWidget { const RoutineAde1({super.key}); @@ -34,43 +32,6 @@ class _RoutineAde1State extends State { @override void initState() { super.initState(); - _getFirebaseToken(); // 앱 실행 시 FCM 토큰을 가져옴 - } - - // Firebase 토큰을 가져오는 메서드 - Future _getFirebaseToken() async { - FirebaseMessaging messaging = FirebaseMessaging.instance; - String? firebaseToken = await messaging.getToken(); // Firebase 토큰 가져오기 - setState(() { - _firebaseToken = firebaseToken; - }); - print('FCM Token: $_firebaseToken'); - } - - // 서버로 Firebase 토큰을 보내는 메서드 - Future _sendTokenToServer(String userId, String firebaseToken) async { - const String apiUrl = ''; - - try { - final response = await http.post( - Uri.parse('$apiUrl/$userId/token'), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $token', // 인증 토큰 - }, - body: jsonEncode({ - 'token': firebaseToken, - }), - ); - - if (response.statusCode == 200) { - print('토큰이 성공적으로 서버에 전송되었습니다.'); - } else { - print('서버 응답 에러: ${response.statusCode}'); - } - } catch (e) { - print('토큰 전송 실패: $e'); - } } @override diff --git a/lib/RoutineAdeIntro/WebViewPage.dart b/lib/RoutineAdeIntro/WebViewPage.dart index e1fe023..59bd431 100644 --- a/lib/RoutineAdeIntro/WebViewPage.dart +++ b/lib/RoutineAdeIntro/WebViewPage.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:routine_ade/RoutineAdeIntro/ProfileSetting.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../routine_user/token.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; class WebViewPage extends StatefulWidget { const WebViewPage({super.key}); @@ -12,15 +15,17 @@ class WebViewPage extends StatefulWidget { class _WebViewPageState extends State { late WebViewController _controller; + String? _firebaseToken; @override void initState() { super.initState(); // 웹뷰 초기화 전에 SharedPreferences에서 토큰을 불러옴. _loadToken(); + _getFirebaseToken(); // 앱 실행 시 FCM 토큰을 가져옴 } - // 토큰을 불러와서 출력 (필요시 다른 처리) + // 토큰을 불러와서 출력 Future _loadToken() async { String? token = await TokenManager.getToken(); if (token != null) { @@ -30,6 +35,43 @@ class _WebViewPageState extends State { } } + // Firebase 토큰 가져오는 메서드 + Future _getFirebaseToken() async { + FirebaseMessaging messaging = FirebaseMessaging.instance; + String? firebaseToken = await messaging.getToken(); // Firebase 토큰 가져오기 + setState(() { + _firebaseToken = firebaseToken; + }); + print('FCM Token: $_firebaseToken'); + } + + // 서버로 Firebase 토큰을 보내는 메서드 + Future _sendTokenToServer(String userId, String firebaseToken) async { + final String apiUrl = 'http://15.164.88.94/users/$userId/token'; + String? token = await TokenManager.getToken(); // OAuth 토큰을 가져옴 + + try { + final response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', // OAuth 토큰을 추가 + }, + body: jsonEncode({ + 'token': firebaseToken, + }), + ); + + if (response.statusCode == 200) { + print('토큰이 성공적으로 서버에 전송되었습니다.'); + } else { + print('서버 응답 에러: ${response.statusCode}'); + } + } catch (e) { + print('토큰 전송 실패: $e'); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -38,7 +80,7 @@ class _WebViewPageState extends State { ), body: WebView( initialUrl: - 'https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=25a0f887ecba2fdb77884c01ca0325b0&redirect_uri=http://15.164.88.94/users/login/kakao', + 'https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=25a0f887ecba2fdb77884c01ca0325b0&redirect_uri=http://15.164.88.94/users/login/kakao', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { _controller = webViewController; @@ -69,4 +111,4 @@ class _WebViewPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index af5d1dc..b25ac2c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -157,18 +157,18 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4+1" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" firebase_core: dependency: "direct dev" description: @@ -205,18 +205,18 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 + sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88" url: "https://pub.dev" source: hosted - version: "2.18.1" + version: "2.17.5" firebase_messaging: dependency: "direct dev" description: name: firebase_messaging - sha256: "980259425fa5e2afc03e533f33723335731d21a56fd255611083bceebf4373a8" + sha256: a1662cc95d9750a324ad9df349b873360af6f11414902021f130c68ec02267c4 url: "https://pub.dev" source: hosted - version: "14.7.10" + version: "14.9.4" firebase_messaging_platform_interface: dependency: transitive description: @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: firebase_messaging_web - sha256: "90dc7ed885e90a24bb0e56d661d4d2b5f84429697fd2cbb9e5890a0ca370e6f4" + sha256: "0d34dca01a7b103ed7f20138bffbb28eb0e61a677bf9e78a028a932e2c7322d5" url: "https://pub.dev" source: hosted - version: "3.5.18" + version: "3.8.7" fl_chart: dependency: "direct main" description: @@ -266,10 +266,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" + sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.0.23" flutter_test: dependency: "direct dev" description: flutter @@ -324,10 +324,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56" + sha256: d3e5e00fdfeca8fd4ffb3227001264d449cc8950414c2ff70b0e06b9c628e643 url: "https://pub.dev" source: hosted - version: "0.8.12+12" + version: "0.8.12+15" image_picker_for_web: dependency: transitive description: @@ -388,10 +388,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: @@ -564,10 +564,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + sha256: f7544c346a0742aee1450f9e5c0f5269d7c602b9c95fdbcd9fb8f5b1df13b1cc url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.2.11" path_provider_foundation: dependency: transitive description: @@ -684,18 +684,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: @@ -857,10 +857,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + sha256: "8fc3bae0b68c02c47c5c86fa8bfa74471d42687b0eded01b78de87872db745e2" url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.12" url_launcher_ios: dependency: transitive description: @@ -929,10 +929,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "0.5.1" webview_flutter: dependency: "direct main" description: @@ -969,10 +969,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -991,4 +991,4 @@ packages: version: "3.1.2" sdks: dart: "3.5.3" - flutter: ">=3.22.0" + flutter: ">=3.24.0" From 929e822053010171d23b1685dcca04f1c0858526 Mon Sep 17 00:00:00 2001 From: gaeunpark7 Date: Mon, 7 Oct 2024 18:43:00 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=ED=8C=8C=EC=9D=B4=EC=96=B4=EB=B2=A0?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 11 +---------- android/settings.gradle | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 6f87524..8731457 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '2.0.20' + ext.kotlin_version = '1.9.0' repositories { google() mavenCentral() @@ -15,21 +15,12 @@ plugins { id "org.jetbrains.kotlin.android" id "dev.flutter.flutter-gradle-plugin" id 'com.google.gms.google-services' version '4.4.2' apply false - // id "org.jetbrains.kotlin.android" version '2.0.20' } dependencies { - // Import the Firebase BoM implementation platform('com.google.firebase:firebase-bom:33.4.0') - - - // TODO: Add the dependencies for Firebase products you want to use - // When using the BoM, don't specify versions in Firebase dependencies implementation 'com.google.firebase:firebase-analytics' - - // Add the dependencies for any other desired Firebase products - // https://firebase.google.com/docs/android/setup#available-libraries } def localProperties = new Properties() diff --git a/android/settings.gradle b/android/settings.gradle index 536165d..f75a38f 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "org.jetbrains.kotlin.android" version "1.9.0" apply false } include ":app"