From 8251461cd8e0c395310bddd7b9438b37497c5f58 Mon Sep 17 00:00:00 2001 From: kth5352 Date: Thu, 12 Sep 2024 10:28:22 +0900 Subject: [PATCH] code refactoring --- android/app/build.gradle | 4 +- android/build.gradle | 3 + lib/Screen/Car/car_add_screen.dart | 145 ++++---------- lib/Screen/Car/car_screen.dart | 150 ++++---------- lib/Screen/Car/services/car_add_service.dart | 24 +++ lib/Screen/Car/services/car_service.dart | 43 ++++ lib/Screen/Car/widgets/car_list.dart | 40 ++++ lib/Screen/Card/card_add_screen.dart | 152 +-------------- lib/Screen/Card/card_screen.dart | 87 +++------ lib/Screen/Card/services/card_service.dart | 62 ++++++ lib/Screen/Card/utils/card_add_utils.dart | 48 +++++ lib/Screen/Card/widgets/card_add_form.dart | 107 ++++++++++ .../usedDetail/services/used_service.dart | 26 +++ lib/Screen/usedDetail/used_detail_screen.dart | 184 ++---------------- .../usedDetail/utils/used_formatter.dart | 16 ++ .../usedDetail/widgets/used_list_item.dart | 81 ++++++++ 16 files changed, 568 insertions(+), 604 deletions(-) create mode 100644 lib/Screen/Car/services/car_add_service.dart create mode 100644 lib/Screen/Car/services/car_service.dart create mode 100644 lib/Screen/Car/widgets/car_list.dart create mode 100644 lib/Screen/Card/services/card_service.dart create mode 100644 lib/Screen/Card/utils/card_add_utils.dart create mode 100644 lib/Screen/Card/widgets/card_add_form.dart create mode 100644 lib/Screen/usedDetail/services/used_service.dart create mode 100644 lib/Screen/usedDetail/utils/used_formatter.dart create mode 100644 lib/Screen/usedDetail/widgets/used_list_item.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 0c84745..5e89666 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -66,4 +66,6 @@ flutter { source '../..' } -dependencies {} +dependencies { + implementation 'com.naver.maps:map-sdk:3.19.1' +} diff --git a/android/build.gradle b/android/build.gradle index abcaa22..c1ea301 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,6 +15,9 @@ allprojects { repositories { google() mavenCentral() + maven { + url 'https://repository.map.naver.com/archive/maven' + } } } diff --git a/lib/Screen/Car/car_add_screen.dart b/lib/Screen/Car/car_add_screen.dart index 94096ca..70b41d3 100644 --- a/lib/Screen/Car/car_add_screen.dart +++ b/lib/Screen/Car/car_add_screen.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; - +import 'package:appfront/Screen/Car/services/car_add_service.dart'; import 'package:appfront/userData.dart'; class CarAddScreen extends ConsumerStatefulWidget { @@ -17,125 +15,50 @@ class _CarAddScreenState extends ConsumerState { TextEditingController carNumberController = TextEditingController(); Future registerCar() async { - String apiUrl = "https://api.parkchargego.link/api/v1/car"; String carNum = carNumberController.text; if (carNum.isEmpty) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Error"), - content: const Text("차량 번호는 필수 요소입니다."), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); + _showDialog("Error", "차량 번호는 필수 요소입니다."); return; } - debugPrint("Request Body: $carNum"); - try { - final data = ref.read(userDataProvider); - final accessToken = await data.storage!.read(key: "accessToken"); - final refreshToken = await data.storage!.read(key: "refreshToken"); - var response = await http.post( - Uri.parse(apiUrl), - headers: { - 'Content-Type': 'application/json', - 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' - }, - body: jsonEncode({"carNum": carNum}), - ); + final data = ref.read(userDataProvider); + final accessToken = await data.storage!.read(key: "accessToken"); + final refreshToken = await data.storage!.read(key: "refreshToken"); - debugPrint("Response status: ${response.statusCode}"); - debugPrint("Response body: ${response.body}"); + final response = + await CarService.registerCar(carNum, accessToken!, refreshToken!); - if (response.statusCode == 201) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Success"), - content: const Text("차량 등록 성공."), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } else if (response.statusCode == 409) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Error"), - content: const Text("이미 등록된 차량 번호입니다."), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } else { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Error"), - content: const Text("차량 등록 실패"), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - } catch (e) { - print("차량 등록 실패"); - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Error"), - content: const Text("차량 등록 실패"), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); + if (response == 201) { + _showDialog("Success", "차량 등록 성공.", onSuccess: true); + } else if (response == 409) { + _showDialog("Error", "이미 등록된 차량 번호입니다."); + } else { + _showDialog("Error", "차량 등록 실패"); } } + void _showDialog(String title, String content, {bool onSuccess = false}) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + if (onSuccess) Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/Screen/Car/car_screen.dart b/lib/Screen/Car/car_screen.dart index b476f32..e298539 100644 --- a/lib/Screen/Car/car_screen.dart +++ b/lib/Screen/Car/car_screen.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'package:appfront/Screen/Car/car_add_screen.dart'; +import 'package:appfront/Screen/Car/services/car_service.dart'; +import 'package:appfront/Screen/Car/widgets/car_list.dart'; import 'package:appfront/userData.dart'; +import 'package:appfront/Screen/Car/car_add_screen.dart'; class CarScreen extends ConsumerStatefulWidget { const CarScreen({super.key}); @@ -25,128 +23,60 @@ class _CarScreenState extends ConsumerState { final data = ref.read(userDataProvider); final accessToken = await data.storage!.read(key: "accessToken"); final refreshToken = await data.storage!.read(key: "refreshToken"); - fetchCars(accessToken!, refreshToken!); + await _fetchCars(accessToken!, refreshToken!); }); } - Future fetchCars(String accessToken, String refreshToken) async { - String apiUrl = "https://api.parkchargego.link/api/v1/car"; - try { - var response = await http.get(Uri.parse(apiUrl), headers: { - 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' + Future _fetchCars(String accessToken, String refreshToken) async { + final fetchedCars = await CarService.fetchCars(accessToken, refreshToken); + if (fetchedCars != null) { + setState(() { + cars = fetchedCars; + isLoading = false; }); - debugPrint("Response headers: ${response.headers}"); - debugPrint("Response Body: ${response.body}"); - debugPrint("Response statusCode: ${response.statusCode}"); - if (response.statusCode == 200) { - var jsonResponse = jsonDecode(response.body) as List; - setState(() { - cars = jsonResponse.map((car) { - return { - 'id': car['id'].toString(), - 'carNum': car['carNum'].toString() - }; - }).toList(); - isLoading = false; - }); - } else { - setState(() { - isLoading = false; - }); - } - } catch (e) { - print('차량 조회 실패'); + } else { setState(() { isLoading = false; }); } } - Future deleteCar(String carId) async { + Future _deleteCar(String carId) async { final data = ref.read(userDataProvider); final accessToken = await data.storage!.read(key: "accessToken"); final refreshToken = await data.storage!.read(key: "refreshToken"); - String apiUrl = "https://api.parkchargego.link/api/v1/car/$carId"; - try { - var response = await http.delete(Uri.parse(apiUrl), headers: { - 'Content-Type': 'application/json', - 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' - }); - if (response.statusCode == 200) { - print("차량 삭제 성공"); - await fetchCars(accessToken!, refreshToken!); - } else { - print("차량 삭제 실패"); - } - } catch (e) { - print('차량 삭제 실패'); - } + await CarService.deleteCar(carId, accessToken!, refreshToken!); + await _fetchCars(accessToken, refreshToken); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text("차량 관리"), - centerTitle: true, - ), - body: Center( - child: isLoading - ? const CircularProgressIndicator() - : cars.isEmpty - ? null - : ListView.builder( - itemCount: cars.length, - itemBuilder: (context, index) { - return Container( - alignment: Alignment.center, - margin: const EdgeInsets.symmetric( - vertical: 5, horizontal: 10), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - color: const Color(0xFF39c5bb), - width: 3, - ), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '차량 번호: ${cars[index]['carNum']}', - style: const TextStyle(fontSize: 16), - ), - IconButton( - icon: const Icon(Icons.delete, - color: Colors.blueGrey), - onPressed: () async { - await deleteCar(cars[index]['id']!); - }, - ), - ], - ), - ); - }, - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute(builder: (context) => const CarAddScreen()), - ); - final data = ref.read(userDataProvider); - final accessToken = await data.storage!.read(key: "accessToken"); - final refreshToken = await data.storage!.read(key: "refreshToken"); - fetchCars(accessToken!, refreshToken!); - }, - foregroundColor: Colors.white, - backgroundColor: const Color(0xFF39c5bb), - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), - child: const Icon(Icons.add), - )); + appBar: AppBar( + title: const Text("차량 관리"), + centerTitle: true, + ), + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : cars.isEmpty + ? const Center(child: Text('차량이 없습니다.')) + : CarList(cars: cars, onDelete: _deleteCar), + floatingActionButton: FloatingActionButton( + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute(builder: (context) => const CarAddScreen()), + ); + final data = ref.read(userDataProvider); + final accessToken = await data.storage!.read(key: "accessToken"); + final refreshToken = await data.storage!.read(key: "refreshToken"); + await _fetchCars(accessToken!, refreshToken!); + }, + foregroundColor: Colors.white, + backgroundColor: const Color(0xFF39c5bb), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), + child: const Icon(Icons.add), + ), + ); } } diff --git a/lib/Screen/Car/services/car_add_service.dart b/lib/Screen/Car/services/car_add_service.dart new file mode 100644 index 0000000..a248b4c --- /dev/null +++ b/lib/Screen/Car/services/car_add_service.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class CarService { + static const String apiUrl = "https://api.parkchargego.link/api/v1/car"; + + static Future registerCar( + String carNum, String accessToken, String refreshToken) async { + try { + var response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' + }, + body: jsonEncode({"carNum": carNum}), + ); + return response.statusCode; + } catch (e) { + print("차량 등록 실패: $e"); + return null; + } + } +} diff --git a/lib/Screen/Car/services/car_service.dart b/lib/Screen/Car/services/car_service.dart new file mode 100644 index 0000000..c9d7d7d --- /dev/null +++ b/lib/Screen/Car/services/car_service.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class CarService { + static const String apiUrl = "https://api.parkchargego.link/api/v1/car"; + + static Future>?> fetchCars( + String accessToken, String refreshToken) async { + try { + final response = await http.get(Uri.parse(apiUrl), headers: { + 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken', + }); + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body) as List; + return jsonResponse + .map((car) => { + 'id': car['id'].toString(), + 'carNum': car['carNum'].toString(), + }) + .toList(); + } + } catch (e) { + print('차량 조회 실패: $e'); + } + return null; + } + + static Future deleteCar( + String carId, String accessToken, String refreshToken) async { + final apiUrlWithId = "$apiUrl/$carId"; + try { + final response = await http.delete(Uri.parse(apiUrlWithId), headers: { + 'Content-Type': 'application/json', + 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken', + }); + if (response.statusCode != 200) { + print("차량 삭제 실패"); + } + } catch (e) { + print('차량 삭제 실패: $e'); + } + } +} diff --git a/lib/Screen/Car/widgets/car_list.dart b/lib/Screen/Car/widgets/car_list.dart new file mode 100644 index 0000000..e399847 --- /dev/null +++ b/lib/Screen/Car/widgets/car_list.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +class CarList extends StatelessWidget { + final List> cars; + final Function(String) onDelete; + + const CarList({super.key, required this.cars, required this.onDelete}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: cars.length, + itemBuilder: (context, index) { + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: const Color(0xFF39c5bb), width: 3), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '차량 번호: ${cars[index]['carNum']}', + style: const TextStyle(fontSize: 16), + ), + IconButton( + icon: const Icon(Icons.delete, color: Colors.blueGrey), + onPressed: () => onDelete(cars[index]['id']!), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/Screen/Card/card_add_screen.dart b/lib/Screen/Card/card_add_screen.dart index 31ba6c6..e7a4962 100644 --- a/lib/Screen/Card/card_add_screen.dart +++ b/lib/Screen/Card/card_add_screen.dart @@ -1,83 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:appfront/userData.dart'; +import 'package:appfront/Screen/Card/widgets/card_add_form.dart'; -class CardAddScreen extends ConsumerStatefulWidget { +class CardAddScreen extends StatelessWidget { const CardAddScreen({super.key}); - @override - _CardAddScreenState createState() => _CardAddScreenState(); -} - -class _CardAddScreenState extends ConsumerState { - TextEditingController cardNumberController = TextEditingController(); - TextEditingController monthController = TextEditingController(); - TextEditingController yearController = TextEditingController(); - - Future registerCard() async { - String apiUrl = "https://api.parkchargego.link/api/v1/payment/card"; - try { - final data = ref.read(userDataProvider); - final accessToken = await data.storage!.read(key: "accessToken"); - final refreshToken = await data.storage!.read(key: "refreshToken"); - var response = await http.post( - Uri.parse(apiUrl), - headers: { - 'Content-Type': 'application/json', - 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken', - }, - body: jsonEncode({ - "number": cardNumberController.text, - "expiryMonth": monthController.text, - "expiryYear": yearController.text, - }), - ); - if (response.statusCode == 201) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("카드 등록"), - content: const Text("카드 등록에 성공했습니다."), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } else { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Error"), - content: const Text("카드 등록에 실패했습니다."), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - } catch (e) { - print("카드 등록에 실패했습니다."); - } - } - @override Widget build(BuildContext context) { return Scaffold( @@ -85,78 +11,8 @@ class _CardAddScreenState extends ConsumerState { title: const Text("카드 등록"), centerTitle: true, ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: cardNumberController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(16), - ], - decoration: const InputDecoration( - hintText: "0000 0000 0000 0000", - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xff39c5bb)), - ), - ), - ), - ), - Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: monthController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(2), - ], - decoration: const InputDecoration( - hintText: "MM", - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xff39c5bb)), - ), - ), - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: yearController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(2), - ], - decoration: const InputDecoration( - hintText: "YY", - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xff39c5bb)), - ), - ), - ), - ), - ), - ], - ), - ElevatedButton( - onPressed: registerCard, - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: const Color(0xff39c5bb)), - child: const Text('등록 완료'), - ), - ], - ), + body: const Center( + child: CardAddForm(), // 폼 위젯을 따로 분리하여 호출 ), ); } diff --git a/lib/Screen/Card/card_screen.dart b/lib/Screen/Card/card_screen.dart index f8dc7b3..8b869e6 100644 --- a/lib/Screen/Card/card_screen.dart +++ b/lib/Screen/Card/card_screen.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'package:appfront/Screen/Card/card_add_screen.dart'; +import 'package:appfront/Screen/Card/services/card_service.dart'; import 'package:appfront/userData.dart'; +import 'package:appfront/Screen/Card/card_add_screen.dart'; class CardScreen extends ConsumerStatefulWidget { const CardScreen({super.key}); @@ -21,63 +18,32 @@ class _CardScreenState extends ConsumerState { @override void initState() { super.initState(); - fetchCard(); + _fetchCard(); } - Future fetchCard() async { - String apiUrl = "https://api.parkchargego.link/api/v1/payment/card"; - try { - final data = ref.read(userDataProvider); - final accessToken = await data.storage!.read(key: "accessToken"); - final refreshToken = await data.storage!.read(key: "refreshToken"); - var response = await http.get( - Uri.parse(apiUrl), - headers: { - 'Content-Type': 'application/json', - 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' - }, - ); - if (response.statusCode == 200) { - var data = jsonDecode(response.body); - setState(() { - cardNumber = data['number']; - isLoading = false; - }); - } else { - setState(() { - isLoading = false; - }); - } - } catch (e) { - print('카드 조회에 실패했습니다.'); - setState(() { - isLoading = false; - }); - } + Future _fetchCard() async { + final data = ref.read(userDataProvider); + final accessToken = await data.storage!.read(key: "accessToken"); + final refreshToken = await data.storage!.read(key: "refreshToken"); + + final fetchedCard = + await CardService.fetchCard(accessToken!, refreshToken!); + setState(() { + cardNumber = fetchedCard; + isLoading = false; + }); } - Future deleteCard() async { - String apiUrl = "https://api.parkchargego.link/api/v1/payment/card"; - try { - final data = ref.read(userDataProvider); - final accessToken = await data.storage!.read(key: "accessToken"); - final refreshToken = await data.storage!.read(key: "refreshToken"); - var response = await http.delete( - Uri.parse(apiUrl), - headers: { - 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' - }, - ); - if (response.statusCode == 200) { - print("카드를 삭제했습니다."); - setState(() { - cardNumber = null; - }); - } else { - print("카드 삭제에 실패하였습니다."); - } - } catch (e) { - print('카드 삭제에 실패하였습니다.'); + Future _deleteCard() async { + final data = ref.read(userDataProvider); + final accessToken = await data.storage!.read(key: "accessToken"); + final refreshToken = await data.storage!.read(key: "refreshToken"); + + final success = await CardService.deleteCard(accessToken!, refreshToken!); + if (success) { + setState(() { + cardNumber = null; + }); } } @@ -99,7 +65,7 @@ class _CardScreenState extends ConsumerState { MaterialPageRoute( builder: (context) => const CardAddScreen()), ); - fetchCard(); + _fetchCard(); }, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, @@ -129,9 +95,8 @@ class _CardScreenState extends ConsumerState { MediaQuery.of(context).size.width * 0.05, )), const SizedBox(height: 20), - const SizedBox(width: 10), ElevatedButton( - onPressed: deleteCard, + onPressed: _deleteCard, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: const Color(0xFF39c5bb), diff --git a/lib/Screen/Card/services/card_service.dart b/lib/Screen/Card/services/card_service.dart new file mode 100644 index 0000000..d08ad82 --- /dev/null +++ b/lib/Screen/Card/services/card_service.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class CardService { + static const String apiUrl = "https://api.parkchargego.link/api/v1/payment/card"; + + static Future fetchCard(String accessToken, String refreshToken) async { + try { + var response = await http.get( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken', + }, + ); + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return data['number']; + } + } catch (e) { + print('카드 조회 실패: $e'); + } + return null; + } + + static Future registerCard( + String cardNumber, String month, String year, String accessToken, String refreshToken) async { + try { + var response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken', + }, + body: jsonEncode({ + "number": cardNumber, + "expiryMonth": month, + "expiryYear": year, + }), + ); + return response.statusCode == 201; + } catch (e) { + print("카드 등록 실패: $e"); + return false; + } + } + + static Future deleteCard(String accessToken, String refreshToken) async { + try { + var response = await http.delete( + Uri.parse(apiUrl), + headers: { + 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken', + }, + ); + return response.statusCode == 200; + } catch (e) { + print("카드 삭제 실패: $e"); + return false; + } + } +} diff --git a/lib/Screen/Card/utils/card_add_utils.dart b/lib/Screen/Card/utils/card_add_utils.dart new file mode 100644 index 0000000..491f597 --- /dev/null +++ b/lib/Screen/Card/utils/card_add_utils.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:appfront/Screen/Card/services/card_service.dart'; +import 'package:appfront/userData.dart'; + +Future registerCard(BuildContext context, WidgetRef ref, + String cardNumber, String month, String year) async { + final data = ref.read(userDataProvider); + final accessToken = await data.storage!.read(key: "accessToken"); + final refreshToken = await data.storage!.read(key: "refreshToken"); + + final success = await CardService.registerCard( + cardNumber, + month, + year, + accessToken!, + refreshToken!, + ); + + if (success) { + _showDialog(context, "카드 등록", "카드 등록에 성공했습니다.", true); + return true; + } else { + _showDialog(context, "Error", "카드 등록에 실패했습니다.", false); + return false; + } +} + +void _showDialog( + BuildContext context, String title, String content, bool success) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () { + Navigator.of(context).pop(); // 다이얼로그 닫기 + }, + ), + ], + ); + }, + ); +} diff --git a/lib/Screen/Card/widgets/card_add_form.dart b/lib/Screen/Card/widgets/card_add_form.dart new file mode 100644 index 0000000..be679c7 --- /dev/null +++ b/lib/Screen/Card/widgets/card_add_form.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:appfront/Screen/Card/utils/card_add_utils.dart'; + +class CardAddForm extends ConsumerStatefulWidget { + const CardAddForm({super.key}); + + @override + _CardAddFormState createState() => _CardAddFormState(); +} + +class _CardAddFormState extends ConsumerState { + TextEditingController cardNumberController = TextEditingController(); + TextEditingController monthController = TextEditingController(); + TextEditingController yearController = TextEditingController(); + + Future _registerCard() async { + final success = await registerCard( + context, + ref, + cardNumberController.text, + monthController.text, + yearController.text, + ); + + if (success) { + Navigator.of(context).pop(); // 다이얼로그 닫기 + Navigator.of(context).pop(); // 카드 등록 페이지 닫기 + } + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: cardNumberController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(16), + ], + decoration: const InputDecoration( + hintText: "0000 0000 0000 0000", + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color(0xff39c5bb)), + ), + ), + ), + ), + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: monthController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(2), + ], + decoration: const InputDecoration( + hintText: "MM", + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color(0xff39c5bb)), + ), + ), + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: yearController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(2), + ], + decoration: const InputDecoration( + hintText: "YY", + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color(0xff39c5bb)), + ), + ), + ), + ), + ), + ], + ), + ElevatedButton( + onPressed: _registerCard, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: const Color(0xff39c5bb)), + child: const Text('등록 완료'), + ), + ], + ); + } +} diff --git a/lib/Screen/usedDetail/services/used_service.dart b/lib/Screen/usedDetail/services/used_service.dart new file mode 100644 index 0000000..c121f1b --- /dev/null +++ b/lib/Screen/usedDetail/services/used_service.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class UsedService { + static const String apiUrl = + "https://api.parkchargego.link/api/v1/parking-transaction"; + + static Future?> fetchUsedData( + String accessToken, String refreshToken) async { + try { + var response = await http.get( + Uri.parse(apiUrl), + headers: { + 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' + }, + ); + + if (response.statusCode == 200) { + return jsonDecode(response.body); + } + } catch (e) { + print('이용 기록 조회 실패: $e'); + } + return null; + } +} diff --git a/lib/Screen/usedDetail/used_detail_screen.dart b/lib/Screen/usedDetail/used_detail_screen.dart index f3386f1..cb9b1f6 100644 --- a/lib/Screen/usedDetail/used_detail_screen.dart +++ b/lib/Screen/usedDetail/used_detail_screen.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:intl/intl.dart'; +import 'package:appfront/Screen/usedDetail/services/used_service.dart'; +import 'package:appfront/Screen/usedDetail/widgets/used_list_item.dart'; import 'package:appfront/userData.dart'; class UsedScreen extends ConsumerStatefulWidget { @@ -24,48 +22,17 @@ class _UsedScreenState extends ConsumerState { final data = ref.read(userDataProvider); final accessToken = await data.storage!.read(key: "accessToken"); final refreshToken = await data.storage!.read(key: "refreshToken"); - fetchused(accessToken!, refreshToken!); + await _fetchUsedData(accessToken!, refreshToken!); }); } - Future fetchused(String accessToken, String refreshToken) async { - String apiUrl = "https://api.parkchargego.link/api/v1/parking-transaction"; - try { - var response = await http.get(Uri.parse(apiUrl), headers: { - 'Cookie': 'access-token=$accessToken; refresh-token=$refreshToken' - }); - - if (response.statusCode == 200) { - setState(() { - jsonResponse = jsonDecode(response.body); - isLoading = false; - }); - } else { - setState(() { - isLoading = false; - }); - } - } catch (e) { - print('이용 기록 조회 실패'); - setState(() { - isLoading = false; - }); - } - } - - String formatDateTime(dynamic dateTime) { - if (dateTime == null || dateTime.toString().isEmpty) return ''; - try { - final parsedDate = DateTime.parse(dateTime.toString()); - final formatter = DateFormat('yyyy년 MM월 dd일 HH시 mm분'); - return formatter.format(parsedDate); - } catch (e) { - return dateTime.toString(); - } - } - - String formatValue(dynamic value) { - return value?.toString() ?? ''; + Future _fetchUsedData(String accessToken, String refreshToken) async { + final fetchedData = + await UsedService.fetchUsedData(accessToken, refreshToken); + setState(() { + jsonResponse = fetchedData ?? []; + isLoading = false; + }); } @override @@ -84,136 +51,7 @@ class _UsedScreenState extends ConsumerState { itemCount: jsonResponse.length, itemBuilder: (context, index) { final item = jsonResponse[index]; - return Container( - alignment: Alignment.center, - margin: const EdgeInsets.symmetric( - vertical: 5, horizontal: 10), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - color: const Color(0xFF39c5bb), - width: 3, - ), - borderRadius: BorderRadius.circular(12), - ), - child: item['exitTime'] == null - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '주차중', - style: TextStyle(fontSize: 14), - ), - Text( - '차량 번호: ${formatValue(item['carNum'])}', - style: const TextStyle(fontSize: 14), - ), - Text( - '입차 시간: ${formatDateTime(item['entryTime'])}', - style: const TextStyle(fontSize: 14), - ), - ]) - : (item['totalAmount'] == null) && - (item['exitTime'] != null) - ? Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Text( - '회차 처리됨', - style: TextStyle(fontSize: 14), - ), - Text( - '차량 번호: ${formatValue(item['carNum'])}', - style: const TextStyle(fontSize: 14), - ), - Text( - '입차 시간: ${formatDateTime(item['entryTime'])}', - style: const TextStyle(fontSize: 14), - ), - Text( - '출차 시간: ${formatDateTime(item['exitTime'])}', - style: const TextStyle(fontSize: 14), - ), - ]) - : Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - '차량 번호: ${formatValue(item['carNum'])}', - style: const TextStyle(fontSize: 14), - ), - Text( - '결제 여부: ${item['isPaid'] == true ? '결제 완료' : '결제 미완료'}', - style: const TextStyle(fontSize: 14), - ), - Text( - '입차 시간: ${formatDateTime(item['entryTime'])}', - style: const TextStyle(fontSize: 14), - ), - Text( - '출차 시간: ${formatDateTime(item['exitTime'])}', - style: const TextStyle(fontSize: 14), - ), - if (item['chargeStartTime'] != null && - item['chargeStartTime'] - .toString() - .isNotEmpty) - Text( - '충전 시작 시간: ${formatDateTime(item['chargeStartTime'])}', - style: const TextStyle(fontSize: 14), - ), - if (item['chargeTime'] != null && - item['chargeTime'] - .toString() - .isNotEmpty) - Text( - '충전 시간: ${(item['chargeTime'] / 60).toString()} 분', - style: const TextStyle(fontSize: 14), - ), - Text( - '결제 시간: ${formatDateTime(item['paymentTime'])}', - style: const TextStyle(fontSize: 14), - ), - if (item['chargeAmount'] != null && - item['chargeAmount'] - .toString() - .isNotEmpty) - Text( - '충전 요금: ${formatValue(item['chargeAmount'])}원', - style: const TextStyle(fontSize: 14), - ), - if (item['parkingAmount'] != null && - item['parkingAmount'] - .toString() - .isNotEmpty) - Text( - '주차 요금: ${formatValue(item['parkingAmount'])}원', - style: const TextStyle(fontSize: 14), - ) - else - const Text( - '충전 요금: 0원', - style: TextStyle(fontSize: 14), - ), - if (item['totalAmount'] != null && - item['totalAmount'] - .toString() - .isNotEmpty) - Text( - '합계: ${formatValue(item['totalAmount'])}원', - style: const TextStyle(fontSize: 14), - ) - else - const Text( - '총 요금: 0원', - style: TextStyle(fontSize: 14), - ), - ], - ), - ); + return UsedListItem(item: item); }, ), ), diff --git a/lib/Screen/usedDetail/utils/used_formatter.dart b/lib/Screen/usedDetail/utils/used_formatter.dart new file mode 100644 index 0000000..2ad703e --- /dev/null +++ b/lib/Screen/usedDetail/utils/used_formatter.dart @@ -0,0 +1,16 @@ +import 'package:intl/intl.dart'; + +String formatDateTime(dynamic dateTime) { + if (dateTime == null || dateTime.toString().isEmpty) return ''; + try { + final parsedDate = DateTime.parse(dateTime.toString()); + final formatter = DateFormat('yyyy년 MM월 dd일 HH시 mm분'); + return formatter.format(parsedDate); + } catch (e) { + return dateTime.toString(); + } +} + +String formatValue(dynamic value) { + return value?.toString() ?? ''; +} diff --git a/lib/Screen/usedDetail/widgets/used_list_item.dart b/lib/Screen/usedDetail/widgets/used_list_item.dart new file mode 100644 index 0000000..2b06b98 --- /dev/null +++ b/lib/Screen/usedDetail/widgets/used_list_item.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:appfront/Screen/usedDetail/utils/used_formatter.dart'; + +class UsedListItem extends StatelessWidget { + final dynamic item; + + const UsedListItem({super.key, required this.item}); + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: const Color(0xFF39c5bb), + width: 3, + ), + borderRadius: BorderRadius.circular(12), + ), + child: item['exitTime'] == null + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('주차중', style: TextStyle(fontSize: 14)), + Text('차량 번호: ${formatValue(item['carNum'])}', + style: const TextStyle(fontSize: 14)), + Text('입차 시간: ${formatDateTime(item['entryTime'])}', + style: const TextStyle(fontSize: 14)), + ], + ) + : (item['totalAmount'] == null) && (item['exitTime'] != null) + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('회차 처리됨', style: TextStyle(fontSize: 14)), + Text('차량 번호: ${formatValue(item['carNum'])}', + style: const TextStyle(fontSize: 14)), + Text('입차 시간: ${formatDateTime(item['entryTime'])}', + style: const TextStyle(fontSize: 14)), + Text('출차 시간: ${formatDateTime(item['exitTime'])}', + style: const TextStyle(fontSize: 14)), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('차량 번호: ${formatValue(item['carNum'])}', + style: const TextStyle(fontSize: 14)), + Text( + '결제 여부: ${item['isPaid'] == true ? '결제 완료' : '결제 미완료'}', + style: const TextStyle(fontSize: 14)), + Text('입차 시간: ${formatDateTime(item['entryTime'])}', + style: const TextStyle(fontSize: 14)), + Text('출차 시간: ${formatDateTime(item['exitTime'])}', + style: const TextStyle(fontSize: 14)), + if (item['chargeStartTime'] != null) + Text( + '충전 시작 시간: ${formatDateTime(item['chargeStartTime'])}', + style: const TextStyle(fontSize: 14)), + if (item['chargeTime'] != null) + Text('충전 시간: ${(item['chargeTime'] / 60).toString()} 분', + style: const TextStyle(fontSize: 14)), + Text('결제 시간: ${formatDateTime(item['paymentTime'])}', + style: const TextStyle(fontSize: 14)), + if (item['chargeAmount'] != null) + Text('충전 요금: ${formatValue(item['chargeAmount'])}원', + style: const TextStyle(fontSize: 14)), + if (item['parkingAmount'] != null) + Text('주차 요금: ${formatValue(item['parkingAmount'])}원', + style: const TextStyle(fontSize: 14)), + if (item['totalAmount'] != null) + Text('합계: ${formatValue(item['totalAmount'])}원', + style: const TextStyle(fontSize: 14)), + ], + ), + ); + } +}