diff --git a/asset/image/icon/basketball.svg b/asset/image/icon/basketball.svg new file mode 100644 index 0000000..8acecd9 --- /dev/null +++ b/asset/image/icon/basketball.svg @@ -0,0 +1,3 @@ + + + diff --git a/asset/image/icon/plus.svg b/asset/image/icon/plus.svg new file mode 100644 index 0000000..92bf1cc --- /dev/null +++ b/asset/image/icon/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/main.dart b/lib/main.dart index f6733b4..5766391 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,13 @@ import 'package:match/modules/home/view/home_view.dart'; import 'package:match/modules/splash/binding/splash_binding.dart'; import 'package:match/provider/routes/routes.dart'; + +import 'modules/create/binding/create_binding.dart'; +import 'modules/event/binding/event_binding.dart'; +import 'modules/match/binding/match_binding.dart'; + import 'modules/friendlyMatch/main_match.dart'; + import 'provider/routes/pages.dart'; import 'util/const/style/global_color.dart'; @@ -49,8 +55,8 @@ class MyApp extends StatelessWidget { GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate ], - initialRoute: "/splash", - initialBinding: SplashBinding(), + initialRoute: "/match", + initialBinding: MatchBinding(), smartManagement: SmartManagement.full, navigatorKey: Get.key, ); diff --git a/lib/model/event/event.dart b/lib/model/event/event.dart new file mode 100644 index 0000000..f5074af --- /dev/null +++ b/lib/model/event/event.dart @@ -0,0 +1,27 @@ +class Event { + final String eventId; + final String thumbnail; + final String title; + final String smallTitle; + final String startDate; + final String endDate; + + Event({ + required this.eventId, + required this.thumbnail, + required this.title, + required this.smallTitle, + required this.startDate, + required this.endDate, + }); +} + +// 하드코딩된 이벤트 데이터 +final Event hardcodedEvent = Event( + eventId: '1', + thumbnail: 'https://example.com/image.jpg', // 적절한 이미지 URL 사용 + title: '가톨릭대학교 총장배', + smallTitle: '가톨릭대학교 체육관', + startDate: '2023-01-01', + endDate: '2023-01-31', +); diff --git a/lib/modules/create/binding/create_binding.dart b/lib/modules/create/binding/create_binding.dart new file mode 100644 index 0000000..59c7f0f --- /dev/null +++ b/lib/modules/create/binding/create_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import '../controller/create_controller.dart'; + +class CreateBinding implements Bindings { + @override + void dependencies() { + Get.put(CreateController()); + } +} diff --git a/lib/modules/create/controller/create_controller.dart b/lib/modules/create/controller/create_controller.dart new file mode 100644 index 0000000..dd1d112 --- /dev/null +++ b/lib/modules/create/controller/create_controller.dart @@ -0,0 +1,34 @@ +import 'package:get/get.dart'; + +class CreateController extends GetxController { + RxString tournamentName = ''.obs; + RxString tournamentDate = ''.obs; + RxString tournamentLocation = ''.obs; + RxString tournamentTeam = ''.obs; + RxString tournamentHead = ''.obs; + + void setTournamentName(String name) { + tournamentName.value = name; + } + + void setTournamentDate(String date) { + tournamentDate.value = date; + } + void setTournamentLocation(String location) { + tournamentLocation.value = location; + } + void setTournamentTeam(String team) { + tournamentTeam.value = team; + } + void setTournamentHead(String head) { + tournamentHead.value = head; + } + + @override + void onInit() { + super.onInit(); + } + +// 필요한 다른 메서드들... +} + diff --git a/lib/modules/create/view/create_view.dart b/lib/modules/create/view/create_view.dart new file mode 100644 index 0000000..66a417c --- /dev/null +++ b/lib/modules/create/view/create_view.dart @@ -0,0 +1,182 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +import '../../../util/components/global_app_bar.dart'; +import '../../../util/components/global_modal.dart'; +import '../../../util/const/style/global_color.dart'; +import '../../../util/const/style/global_text_styles.dart'; +import '../../event/view/event_view.dart'; +import '../../match/view/match_view.dart'; +import '../controller/create_controller.dart'; + +class CreateTournamentScreen extends StatelessWidget { + final CreateController controller = Get.put(CreateController()); + + CreateTournamentScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + // 아이콘 디렉토리 + final String iconDir = "assets/icons/"; + + // 텍스트 컨트롤러들 + return Scaffold( + appBar: CommonAppBar.basic("대회 생성"), + body: SingleChildScrollView( // SingleChildScrollView 추가 + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, // 왼쪽으로 정렬 + children: [ + SizedBox(height: 10.h), + Text( + "대회명", + style: AppTextStyles.L1Medium13, + ), + SizedBox(height: 10.h), + CupertinoTextField( + // 대회명 입력 필드 + controller: TextEditingController(text: controller.tournamentName.value), + placeholder: '대회명을 입력해주세요', + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: AppColors.grey1), + ), + cursorColor: AppColors.black, + style: AppTextStyles.T1Bold13.copyWith(color: AppColors.grey8), + ), + SizedBox(height: 26.h), + Text( + "대회 기간", + style: AppTextStyles.L1Medium13, + ), + SizedBox(height: 10.h), + CupertinoTextField( + // 대회 기간 입력 필드 + controller: TextEditingController(text: controller.tournamentDate.value), + placeholder: '대회 기간을 입력해주세요', + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: AppColors.grey1), + ), + cursorColor: AppColors.black, + style: AppTextStyles.T1Bold13.copyWith(color: AppColors.grey8), + ), + SizedBox(height: 26.h), + Text( + "대회 장소", + style: AppTextStyles.L1Medium13, + ), + SizedBox(height: 10.h), + CupertinoTextField( + // 대회 기간 입력 필드 + controller: TextEditingController(text: controller.tournamentLocation.value), + placeholder: '대회 장소를 입력해주세요', + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: AppColors.grey1), + ), + cursorColor: AppColors.black, + style: AppTextStyles.T1Bold13.copyWith(color: AppColors.grey8), + ), + SizedBox(height: 26.h), + Text( + "동아리 명", + style: AppTextStyles.L1Medium13, + ), + SizedBox(height: 10.h), + CupertinoTextField( + // 대회 기간 입력 필드 + controller: TextEditingController(text: controller.tournamentTeam.value), + placeholder: '동아리 명을 입력해주세요', + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: AppColors.grey1), + ), + cursorColor: AppColors.black, + style: AppTextStyles.T1Bold13.copyWith(color: AppColors.grey8), + ), + SizedBox(height: 26.h), + Text( + "대표자 명", + style: AppTextStyles.L1Medium13, + ), + SizedBox(height: 10.h), + CupertinoTextField( + // 대회 기간 입력 필드 + controller: TextEditingController(text: controller.tournamentHead.value), + placeholder: '대표자 명을 입력해주세요', + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: AppColors.grey1), + ), + cursorColor: AppColors.black, + style: AppTextStyles.T1Bold13.copyWith(color: AppColors.grey8), + ), + SizedBox(height: 16.h), + // 기타 위젯들... + Padding( + padding: EdgeInsets.symmetric(vertical: 24.h), + child: Row( + children: [ + Expanded( // 버튼의 가로 크기를 확장합니다. + child: ElevatedButton( + onPressed: () { + // CommonDialog 모달 표시 + showDialog( + context: context, + builder: (BuildContext context) { + return CommonDialog( + title: "대회 신청 확인", + subtitle: "대회에 신청하시겠습니까?", + onGrant: () async { + // 확인 버튼을 눌렀을 때의 작업 + Get.to(() => const MatchScreen()); + }, + grantText: "확인", + ); + }, + ); + }, + style: ElevatedButton.styleFrom( + primary: Colors.blue, + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Text( + "신청하기", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + // 기타 위젯들... + ], + ), + ), + ), + ); + } +} + +// AppColors, AppTextStyles 등 필요한 스타일을 정의합니다. diff --git a/lib/modules/event/controller/event_controller.dart b/lib/modules/event/controller/event_controller.dart index e8337fd..dde20d0 100644 --- a/lib/modules/event/controller/event_controller.dart +++ b/lib/modules/event/controller/event_controller.dart @@ -1,6 +1,31 @@ import 'package:get/get.dart'; +// import '../../../model/event/event.dart'; +// import '../../../provider/api/event_api.dart'; +// import '../../../provider/api/util/global_api_field.dart'; +// import '../../../util/const/style/global_logger.dart'; + +// class EventController extends GetxController { +// ///* event list +// RxList eventList = [].obs; +// +// ///* pagination 함수 +// Future getMoreNotice(int index) async { +// if (!EventApi.event.isLast) { +// eventList.addAll(await EventApi.getEventList(getMore: true)); +// } +// } +// +// @override +// void onInit() async { +// super.onInit(); +// eventList.assignAll(await EventApi.getEventList()); +// } +// } + + class EventController extends GetxController { + Rx selectIdx = 0.obs; @override void onInit() { super.onInit(); diff --git a/lib/modules/event/view/event_view.dart b/lib/modules/event/view/event_view.dart index ccb122f..e622bac 100644 --- a/lib/modules/event/view/event_view.dart +++ b/lib/modules/event/view/event_view.dart @@ -1,15 +1,73 @@ +import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:match/modules/event/widget/event_widget.dart'; +// import 'package:match/provider/api/util/pagination_function.dart'; +import 'package:match/util/const/style/global_text_styles.dart'; +// import '../../../provider/api/util/global_api_field.dart'; +import '../../../util/components/global_app_bar.dart'; +import '../../../util/components/global_widget.dart'; +// import '../../../util/const/style/global_logger.dart'; +// import '../../../util/const/style/global_skeleton.dart'; import '../controller/event_controller.dart'; +///

이벤트 화면

+///이벤트 탭을 눌렀을때 나오는 화면 class EventScreen extends GetView { const EventScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( - body: Text('event'), + appBar: CommonAppBar.basic("진행중인 경기"), + body: SafeArea( + child: Column( + children: [ + ///*1.제목 header + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h) + // .copyWith(bottom: 17.h), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // "진행중인 경기", + // style: AppTextStyles.T1Bold16, + // textAlign: TextAlign.center, + // ), + // // alarmButton() + // ], + // ), + // ), + + ///*2. event body + ///carousel slider로 구성 + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 8.h) + .copyWith(bottom: 17.h), + width: double.infinity, + height: double.infinity, + child: CarouselSlider.builder( + itemCount: 5, // 예시로 5개의 위젯을 표시합니다. + itemBuilder: (context, index, realIndex) { + // EventWidget 호출을 하드코딩된 데이터로 수정합니다. + return EventWidget(); // 여기서 event 매개변수를 제거했습니다. + }, + options: CarouselOptions( + autoPlay: false, + aspectRatio: 299.w / 445.h, + viewportFraction: 1, + scrollDirection: Axis.vertical, + ), + ), + ), + ), + ], + ), + ), ); } } diff --git a/lib/modules/event/widget/event_widget.dart b/lib/modules/event/widget/event_widget.dart index e69de29..79e0574 100644 --- a/lib/modules/event/widget/event_widget.dart +++ b/lib/modules/event/widget/event_widget.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +import '../../../model/event/event.dart'; +import '../../../provider/routes/routes.dart'; +import '../../../util/const/style/global_color.dart'; +import '../../../util/const/style/global_text_styles.dart'; +import '../../home/widget/home_widget.dart'; + +///

이벤트 아이템 위젯

+class EventWidget extends StatelessWidget { + // 매개변수를 제거하고, hardcodedEvent를 사용합니다. + const EventWidget({super.key}); + + @override + Widget build(BuildContext context) { + // 여기에서 hardcodedEvent를 사용합니다. + final event = hardcodedEvent; + + return GestureDetector( + onTap: () { + Get.toNamed(Routes.event_detail, arguments: {"eventId": event.eventId}); + }, + child: Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(30.r), + bottomRight: Radius.circular(30.r), + ), + border: Border.all( + color: Colors.black, // 테두리 색상 지정 + width: 1.0, // 테두리 두께 지정 + ), + ), + child: Stack( + children: [ + Positioned( + top: 0, + left: 0, + right: 0, + child: Image.asset( + 'asset/image/iv_test_contest.jpeg', + fit: BoxFit.contain, + ), + ), + Positioned( + left: 18.w, + bottom: 22.h, + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + event.title, + style: AppTextStyles.T1Bold14.copyWith( + color: AppColors.black), + ), + SizedBox(height: 11.h), + Text( + event.smallTitle, + style: AppTextStyles.T1Bold12.copyWith( + color: Color(0xFF3B3B3B)), + ), + SizedBox(height: 11.h), + Container( + padding: EdgeInsets.symmetric( + horizontal: 8.w, vertical: 2.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.r), + color: AppColors.grey2, + ), + child: Text( + "진행중 ${event.startDate} - ${event.endDate}", + style: AppTextStyles.T1Bold10.copyWith( + color: Color(0xFF3B3B3B)), + )), + ], + ), + ), + ) + ], + )), + ); + } +} \ No newline at end of file diff --git a/lib/modules/event_detail/binding/event_detail_binding.dart b/lib/modules/event_detail/binding/event_detail_binding.dart new file mode 100644 index 0000000..6ab0880 --- /dev/null +++ b/lib/modules/event_detail/binding/event_detail_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import '../controller/event_detail_controller.dart'; + +class EventDetailBinding extends Bindings { + @override + void dependencies() { + Get.put(EventDetailController()); + } +} diff --git a/lib/modules/event_detail/controller/event_detail_controller.dart b/lib/modules/event_detail/controller/event_detail_controller.dart new file mode 100644 index 0000000..b853909 --- /dev/null +++ b/lib/modules/event_detail/controller/event_detail_controller.dart @@ -0,0 +1,60 @@ +import 'package:get/get.dart'; +// import 'package:match/provider/api/event_api.dart'; +// import 'package:match/util/const/global_mock_data.dart'; +// +// import '../../../model/event_detail/event_detail.dart'; + +// class EventDetailController extends GetxController { +// int eventId = Get.arguments['eventId'] ?? 0; +// Rx eventDetail = tmpEventDetail.obs; +// @override +// void onInit() async { +// super.onInit(); +// eventDetail.value = +// await EventApi.getEventDetail(eventId: eventId) ?? tmpEventDetail; +// } +// } + +// class EventDetailController extends GetxController { +// Rx selectIdx = 0.obs; +// @override +// void onInit() { +// super.onInit(); +// } +// } +import 'package:get/get.dart'; + + + +class EventDetailController extends GetxController { + final Rx> eventInfo = Rx({ + "matchname": "", + "title": "대회", + "title1": "가톨릭대학교 총장배 대회", + "date": "신청 기간", + "dateStart": "2022-12-13", + "dateEnd": "2022-12-20", + "location": "장소", + "location1": "가톨릭대학교 체육관", + "name": "주최자", + "name1": "최유빈", + "phone": "연락처", + "phone1": "010-1234-5678", + "startDate": "2023-01-01", + "endDate": "2023-01-31", + }); + + final Rx>> eventContents = Rx([ + {"contentsType": "IMG", "contents": "asset/image/iv_test_contest.jpeg"}, // 경로 수정 + {"contentsType": "TEXT", "contents": ""}, + // 다른 내용들을 여기에 추가합니다. + ]); + + @override + void onInit() { + super.onInit(); + // 여기서 필요한 초기화 작업을 수행합니다. + } +} + + diff --git a/lib/modules/event_detail/view/event_detail_view.dart b/lib/modules/event_detail/view/event_detail_view.dart new file mode 100644 index 0000000..0675285 --- /dev/null +++ b/lib/modules/event_detail/view/event_detail_view.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:match/modules/event_detail/controller/event_detail_controller.dart'; +import 'package:match/util/components/global_app_bar.dart'; +import 'package:match/util/const/style/global_color.dart'; +import 'package:match/util/const/style/global_text_styles.dart'; + +import '../../../util/components/global_modal.dart'; +import '../../home/widget/home_widget.dart'; + +///

이벤트 상세 화면

+///공지사항,알림과 유사한 구조 +class EventDetailScreen extends GetView { + const EventDetailScreen({super.key}); + + @override + Widget build(BuildContext context) { + final controller = Get.find(); + + return Scaffold( + appBar: CommonAppBar.basic("모집중인 경기"), + body: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Obx( + () => ListView( + physics: const ScrollPhysics(), + children: [ + ...controller.eventContents.value.map((content) { + return content["contentsType"] == "IMG" + ? Image.asset( // Image.network 대신 Image.asset 사용 + content["contents"] as String, + width: double.infinity, + fit: BoxFit.cover, // 이미지를 적절히 조절하려면 BoxFit 사용 + ) + : Text( + content["contents"] as String, + style: AppTextStyles.S1SemiBold14, + ); + }).toList(), + SizedBox(height: 16.h), + Text( + controller.eventInfo.value["title"] ?? "", + style: AppTextStyles.L1Medium13, + ), + Text( + "${controller.eventInfo.value["title1"]}", + style: AppTextStyles.S1SemiBold13.copyWith( + color: AppColors.grey3, + ), + ), + SizedBox(height: 10.h), + Text( + controller.eventInfo.value["date"] ?? "", + style: AppTextStyles.L1Medium13, + ), + Text( + "${controller.eventInfo.value["dateStart"]} ~ ${controller.eventInfo.value["dateEnd"]}", + style: AppTextStyles.S1SemiBold13.copyWith( + color: AppColors.grey3, + ), + ), + SizedBox(height: 10.h), + Text( + controller.eventInfo.value["location"] ?? "", + style: AppTextStyles.L1Medium13, + ), + Text( + "${controller.eventInfo.value["location1"]}", + style: AppTextStyles.S1SemiBold13.copyWith( + color: AppColors.grey3, + ), + ), + SizedBox(height: 10.h), + Text( + controller.eventInfo.value["name"] ?? "", + style: AppTextStyles.L1Medium13, + ), + Text( + "${controller.eventInfo.value["name1"]}", + style: AppTextStyles.S1SemiBold13.copyWith( + color: AppColors.grey3, + ), + ), + SizedBox(height: 10.h), + Text( + controller.eventInfo.value["phone"] ?? "", + style: AppTextStyles.L1Medium13, + ), + Text( + "${controller.eventInfo.value["phone1"]}", + style: AppTextStyles.S1SemiBold13.copyWith( + color: AppColors.grey3, + ), + ), + SizedBox(height: 20.h), + Text( + "${controller.eventInfo.value["startDate"]} ~ ${controller.eventInfo.value["endDate"]}", + style: AppTextStyles.S1SemiBold14 + ), + SizedBox(height: 12.h), + Divider( + thickness: 1, + color: AppColors.divider1, + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 24.h), + child: ElevatedButton( + onPressed: () { + // 여기에 버튼을 눌렀을 때의 동작을 정의합니다. + showDialog( + context: context, + builder: (BuildContext context) { + return CommonDialog( + title: "신청 완료", + subtitle: "신청이 완료되었습니다.", + onGrant: () async { + Get.back(); // 다이얼로그 닫기 + // 필요한 추가 작업을 여기에 구현합니다. + }, + grantText: "확인", + ); + }, + ); + }, + style: ElevatedButton.styleFrom( + primary: Colors.blue, // 버튼 색상 + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Text( + "신청하기", + style: TextStyle( + fontSize: 16.sp, // 텍스트 크기 + color: Colors.white, // 텍스트 색상 + ), + ), + ), + ), + // Container( + // width: double.infinity, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(10.r), + // ), + // child: Image.network(tmpBackgroundImg2), + // ), + // SizedBox( + // height: 23.h, + // ), + // Text( + // "매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다.매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다.매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다.매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다.매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다. 매칭 정보에 대한 텍스트를 입력합니다.", + // style: AppTextStyles.S1SemiBold14.copyWith( + // color: AppColors.grey7, + // ), + // ), + ], + ), + ), + )); + } +} diff --git a/lib/modules/match/binding/match_binding.dart b/lib/modules/match/binding/match_binding.dart new file mode 100644 index 0000000..8aca754 --- /dev/null +++ b/lib/modules/match/binding/match_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import '../controller/match_controller.dart'; + +class MatchBinding implements Bindings { + @override + void dependencies() { + Get.put(MatchController()); + } +} diff --git a/lib/modules/match/controller/match_controller.dart b/lib/modules/match/controller/match_controller.dart new file mode 100644 index 0000000..e08a861 --- /dev/null +++ b/lib/modules/match/controller/match_controller.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; + +class MatchController extends GetxController { + Rx selectIdx = 0.obs; + @override + void onInit() { + super.onInit(); + } +} diff --git a/lib/modules/match/view/match_view.dart b/lib/modules/match/view/match_view.dart new file mode 100644 index 0000000..b3fb64a --- /dev/null +++ b/lib/modules/match/view/match_view.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:get/get.dart'; + +import '../../create/view/create_view.dart'; +import '../../event/view/event_view.dart'; +import '../controller/match_controller.dart'; +import '../widget/custom_component.dart'; +import '../widget/filter.dart'; + +class MatchScreen extends GetView { + const MatchScreen({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 4, // 탭의 수 + child: Scaffold( + appBar: AppBar( + backgroundColor: Colors.white, // AppBar 배경색 변경 + title: Text( + '경기 모아보기', + style: TextStyle(color: Colors.black), // AppBar 타이틀 글자색 변경 + ), + actions: [ + GestureDetector( + onTap: () { + // 경기 생성 뷰로 이동하는 로직을 구현합니다. + Get.to(() => CreateTournamentScreen()); + }, + child: Container( + width: 36.w, + height: 24.h, + padding: EdgeInsets.all(8.h), // SVG 아이콘과 컨테이너의 패딩 + child: SvgPicture.asset( + 'asset/image/icon/plus.svg', // SVG 파일 경로 + color: Colors.black, // SVG 아이콘 색상 + ), + ), + ), + SizedBox(width: 8.w), // 버튼과 화면 끝 사이의 공간 + GestureDetector( + onTap: () { + // 경기 생성 뷰로 이동하는 로직을 구현합니다. + Get.to(() => const EventScreen()); + }, + child: Container( + width: 36.w, + height: 24.h, + padding: EdgeInsets.all(8.h), // SVG 아이콘과 컨테이너의 패딩 + child: SvgPicture.asset( + 'asset/image/icon/basketball.svg', // SVG 파일 경로 + color: Colors.black, // SVG 아이콘 색상 + ), + ), + ), + SizedBox(width: 8.w), // 버튼과 화면 끝 사이의 공간 + ], + // 다른 AppBar 설정들... + bottom: PreferredSize( + preferredSize: Size(360.w, 60.h), + child: Container( // TabBar 배경 스타일 적용 + color: Color(0xFFFFF6F3), // 탭바의 배경색 + child: TabBar( + isScrollable: true, // 탭이 여러 개일 경우 스크롤 가능 + indicatorSize: TabBarIndicatorSize.tab, // 인디케이터 크기를 탭에 맞춤 + indicator: BoxDecoration( + gradient: LinearGradient( + begin: Alignment(0.78, -0.62), + end: Alignment(-0.78, 0.62), + colors: [Color(0xFFF4A58A), Color(0xFFED6B4E)], + ), + borderRadius: BorderRadius.circular(33), // 인디케이터의 둥근 모서리 + ), + labelColor: Colors.white, // 선택된 탭의 글자색 + unselectedLabelColor: Colors.grey, // 선택되지 않은 탭의 글자색 + tabs: [ + Tab(text: '전체'), + Tab(text: '모집중'), + Tab(text: '진행중'), + Tab(text: '완료'), + ], + ), + ), + ), + ), + body: TabBarView( + children: [ + BasketballCardWidget(), // 전체 + BasketballCardWidget(), // 모집중 + BasketballCardWidget(), // 진행중 + BasketballCardWidget(), // 완료 + ], + ), // 여기에 기존 Scaffold의 나머지 부분을 넣습니다. + ), + // bottomNavigationBar: BottomNavigationBar( + // type: BottomNavigationBarType.fixed, + // showUnselectedLabels: true, + // currentIndex: controller.selectIdx.value, + // onTap: (value) => controller.selectIdx.value = value, + // selectedItemColor: Colors.grey[900], + // unselectedItemColor: Colors.grey[300], + // items: [ + // BottomNavigationBarItem( + // icon: Icon(Icons.home), + // label: '홈', + // ), + // BottomNavigationBarItem( + // icon: Icon(Icons.event), + // label: '이벤트', + // ), + // BottomNavigationBarItem( + // icon: Icon(Icons.person), + // label: 'MY', + // ), + // ], + // ), + ); + } +} + +class BasketballCardWidget extends StatelessWidget { + const BasketballCardWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + child: Column( + children: [ + // Image.asset( + // 'asset/image/iv_test_contest.jpeg', + // width: 300.w, + // height: 200.h, + // fit: BoxFit.cover, + // ), + // Container( + // width: 315.w, + // height: 161.h, + // decoration: ShapeDecoration( + // gradient: LinearGradient( + // begin: Alignment(0.78, -0.62), + // end: Alignment(-0.78, 0.62), + // colors: [Color(0xFF8AB4F4), Color(0xFF4D8DED)], + // ), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(16), + // ), + // ), + // ), + // 여기에 새로운 Container 추가 + // Filter(), + SizedBox(height: 12.h), + for (var i = 0; i < 3; i++) CustomComponent(), + // ElevatedButton( + // onPressed: () { + // // 신청하기 버튼 로직 + // }, + // child: const Text('신청하기'), + // ), + ], + ), + ); + } +} + diff --git a/lib/modules/match/widget/custom_component.dart b/lib/modules/match/widget/custom_component.dart new file mode 100644 index 0000000..b3238c6 --- /dev/null +++ b/lib/modules/match/widget/custom_component.dart @@ -0,0 +1,277 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:match/util/const/style/global_text_styles.dart'; + +import '../../../util/const/style/global_color.dart'; +class CustomComponent extends StatelessWidget { + const CustomComponent({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + + return Container( + width: 315.w, + height: 120.h, // 높이 수정 + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 315.w, + height: 28.h, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 28, + height: 28, + decoration: ShapeDecoration( + image: DecorationImage( + image: NetworkImage("https://via.placeholder.com/28x28"), + fit: BoxFit.fill, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + shadows: [ + BoxShadow( + color: Color(0x0C000000), + blurRadius: 4, + offset: Offset(0, 1), + spreadRadius: 0, + ) + ], + ), + ), + SizedBox(width: 8.w), + Text( + '가톨릭대학교 총장배 대회', + style: AppTextStyles.body2Regular13, + ), + + ], + ), + ), + SizedBox(height: 12.h), + Container( + width: 315.w, + height: 68.h, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 290.w, + height: 68.h, + padding: EdgeInsets.symmetric(horizontal: 8.w), // 좌우 간격 + child: Row( + mainAxisAlignment: MainAxisAlignment.center, // 가운데 정렬 + children: [ + Container( + width: 70, + height: 36, + child: Stack( + children: [ + Positioned( + left: 34, + top: 0, + child: Container( + width: 36, + height: 36, + decoration: ShapeDecoration( + image: DecorationImage( + image: NetworkImage("https://via.placeholder.com/36x36"), + fit: BoxFit.fill, + ), + shape: OvalBorder(), + ), + ), + ), + Positioned( + left: 0, + top: 0, + child: Container( + width: 36, + height: 36, + decoration: ShapeDecoration( + image: DecorationImage( + image: NetworkImage("https://via.placeholder.com/36x36"), + fit: BoxFit.fill, + ), + shape: OvalBorder( + side: BorderSide( + width: 2, + strokeAlign: BorderSide.strokeAlignOutside, + color: Color(0xFFFFF6F3), + ), + ), + ), + ), + ), + ], + ), + ), + SizedBox(width: 10.w), + Container( + width: 172, + height: 38, + child: Stack( + children: [ + Positioned( + left: 0, + top: 17, + child: Container( + width: 69, + height: 21, + child: Stack( + children: [ + Positioned( + left: 0, + top: 3, + child: Text( + '바스타즈', + style: TextStyle( + color: Color(0xFF9F9F9F), + fontSize: 10, + fontFamily: 'Poppins', + fontWeight: FontWeight.w400, + height: 0, + ), + ), + ), + Positioned( + left: 60, + top: 0, + child: Text( + '2', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF181829), + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + height: 0, + ), + ), + ), + ], + ), + ), + ), + Positioned( + left: 103, + top: 17, + child: Container( + width: 68, + height: 21, + child: Stack( + children: [ + Positioned( + left: 60, + top: 0, + child: Text( + '7', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF181829), + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + height: 0, + ), + ), + ), + Positioned( + left: 0, + top: 3, + child: Text( + '버스타죠', + style: TextStyle( + color: Color(0xFF9F9F9F), + fontSize: 10, + fontFamily: 'Poppins', + fontWeight: FontWeight.w400, + height: 0, + ), + ), + ), + ], + ), + ), + ), + Positioned( + left: 84, + top: 17, + child: Text( + '-', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF181829), + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + height: 0, + ), + ), + ), + Positioned( + left: 0, + top: 0, + child: Text( + '바스타즈 vs 버스타죠', + style: TextStyle( + color: Color(0xFF181829), + fontSize: 12, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + height: 0, + ), + ), + ), + ], + ), + ), + ], + ), //여기까지 사진 두개 + + decoration: ShapeDecoration( + color: Color(0xFFFFF6F3), + shape: RoundedRectangleBorder( + side: BorderSide(width: 0.50, color: Color(0xFFF4A58A)), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + bottomLeft: Radius.circular(16), + ), + ), + ), + ), + Container( + width: 25.w, + height: 68.h, + decoration: ShapeDecoration( + gradient: LinearGradient( + begin: Alignment(0.78, -0.62), + end: Alignment(-0.78, 0.62), + colors: [Color(0xFFF4A58A), Color(0xFFED6B4E)], + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + ), + ), + ), + ], + ), + ), + // 추가적인 컨텐츠가 필요하면 여기에 포함시킬 수 있습니다. + ], + ), + ); + } +} + + diff --git a/lib/modules/match/widget/filter.dart b/lib/modules/match/widget/filter.dart new file mode 100644 index 0000000..9a9016d --- /dev/null +++ b/lib/modules/match/widget/filter.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:match/util/const/style/global_text_styles.dart'; + +import '../../../util/const/style/global_color.dart'; +class Filter extends StatelessWidget { + const Filter({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + + return Container( + width: 355, + height: 60, + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + child: Container( + width: 355, + height: 60, + decoration: ShapeDecoration( + color: Color(0xFFFFF6F3), + shape: RoundedRectangleBorder( + side: BorderSide(width: 1, color: Color(0x33F4A58A)), + borderRadius: BorderRadius.circular(33), + ), + ), + ), + ), + Positioned( + left: 25, + top: 10, + child: Container( + width: 306, + height: 40, + child: Stack( + children: [ + Positioned( + left: 0, + top: 10, + child: SizedBox( + width: 46, + child: Text( + 'Sports', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF959595), + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + height: 0, + ), + ), + ), + ), + Positioned( + left: 68, + top: 0, + child: Container( + width: 94, + height: 40, + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + child: Container( + width: 94, + height: 40, + decoration: ShapeDecoration( + gradient: LinearGradient( + begin: Alignment(0.78, -0.62), + end: Alignment(-0.78, 0.62), + colors: [Color(0xFFF4A58A), Color(0xFFED6B4E)], + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(33), + ), + ), + ), + ), + Positioned( + left: 17, + top: 10, + child: Text( + 'Matches', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + height: 0, + ), + ), + ), + ], + ), + ), + ), + Positioned( + left: 184, + top: 10, + child: Text( + 'Upcoming', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF959595), + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w400, + height: 0, + ), + ), + ), + Positioned( + left: 279, + top: 10, + child: Text( + 'Live', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF959595), + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w400, + height: 0, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} + + diff --git a/lib/modules/match/widget/match_widget.dart b/lib/modules/match/widget/match_widget.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/provider/routes/pages.dart b/lib/provider/routes/pages.dart index 96c688c..832c239 100644 --- a/lib/provider/routes/pages.dart +++ b/lib/provider/routes/pages.dart @@ -5,9 +5,13 @@ import 'package:match/modules/buring_match/view/burning_match_pay_view.dart'; import 'package:match/modules/buring_match/view/burning_match_view.dart'; import 'package:match/modules/donate/binding/donate_binding.dart'; import 'package:match/modules/donate/view/donate_view.dart'; + +import 'package:match/modules/match/view/match_view.dart'; + import 'package:match/modules/friendlyMatch/board/board.dart'; import 'package:match/modules/friendlyMatch/calendar/calendar_1.dart'; import 'package:match/modules/friendlyMatch/main_match.dart'; + import 'package:match/modules/mypage/binding/mypage_binding.dart'; import 'package:match/modules/mypage/view/mypage_view.dart'; import 'package:match/modules/splash/binding/splash_binding.dart'; @@ -15,8 +19,11 @@ import 'package:match/modules/splash/view/splash_view.dart'; import 'package:match/modules/login/binding/login_binding.dart'; import 'package:match/modules/login/view/login_view.dart'; +import '../../modules/create/view/create_view.dart'; import '../../modules/event/binding/event_binding.dart'; import '../../modules/event/view/event_view.dart'; +import '../../modules/event_detail/binding/event_detail_binding.dart'; +import '../../modules/event_detail/view/event_detail_view.dart'; import '../../modules/home/binding/home_binding.dart'; import '../../modules/home/view/home_view.dart'; import '../../modules/main/binding/main_binding.dart'; @@ -29,6 +36,26 @@ import 'routes.dart'; class Pages { static final routes = [ GetPage( + + title: "경기 화면", + name: Routes.match, + page: () => const MatchScreen(), + transition: Transition.noTransition, + // binding: MatchBiding(), + curve: Curves.easeIn, + popGesture: false, + ), + GetPage( + title: "온보딩 화면", + name: Routes.onboarding, + page: () => const OnboardingScreen(), + transition: Transition.noTransition, + binding: OnboardingBiding(), + curve: Curves.easeIn, + popGesture: false, + ), + GetPage( + title: "스플래시 화면", name: Routes.splash, page: () => const SplashScreen(), @@ -93,6 +120,15 @@ class Pages { curve: Curves.easeIn, popGesture: false, ), + GetPage( + title: "이벤트 상세화면", + name: Routes.event_detail, + page: () => const EventDetailScreen(), + transition: Transition.noTransition, + binding: EventDetailBinding(), + curve: Curves.easeIn, + popGesture: false, + ), GetPage( title: "마이페이지 화면", name: Routes.mypage, @@ -103,6 +139,17 @@ class Pages { popGesture: false, ), GetPage( + + title: "대회생성 화면", + name: Routes.create, + page: () => CreateTournamentScreen (), + transition: Transition.noTransition, + binding: MypageBinding(), + curve: Curves.easeIn, + popGesture: false, + ), + + title: "교류전메인", name: Routes.main_match, page: () => MainMatchScreen(), diff --git a/lib/provider/routes/routes.dart b/lib/provider/routes/routes.dart index 0507828..8f74309 100644 --- a/lib/provider/routes/routes.dart +++ b/lib/provider/routes/routes.dart @@ -1,6 +1,12 @@ abstract class _Paths { _Paths._(); + //이벤트 상세 + static const event_detail = '/eventDetail'; + + /// 경기 화면 + static const match = "/match"; + /// 스플래시 화면 static const splash = "/splash"; @@ -31,6 +37,10 @@ abstract class _Paths { /// 마이페이지 화면 static const mypage = "/mypage"; + + /// 대회생성 화면 + static const create = "/create"; + // 교류전 메인화면 static const main_match = "/main_match"; @@ -42,10 +52,12 @@ abstract class _Paths { // 상세보기 화면 static const view_detail = "/detail"; -} + abstract class Routes { Routes._(); + /// 경기 화면 + static const match = _Paths.match; /// 스플래시 화면 static const splash = _Paths.splash; @@ -74,9 +86,16 @@ abstract class Routes { /// 이벤트 화면 static const event = _Paths.event; + /// 이벤트 상세 화면 + static const event_detail = _Paths.event_detail; + /// 마이페이지 화면 static const mypage = _Paths.mypage; + + /// 대회생성 화면 + static const create = _Paths.create; + // 교류전 메인화면 static const main_match = _Paths.main_match; @@ -88,4 +107,5 @@ abstract class Routes { // 상세보기 화면 static const view_detail = _Paths.view_detail; + } diff --git a/lib/util/components/global_modal.dart b/lib/util/components/global_modal.dart new file mode 100644 index 0000000..5301b29 --- /dev/null +++ b/lib/util/components/global_modal.dart @@ -0,0 +1,134 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +// import 'package:match/modules/payment/view/payment_expire_view.dart'; +import 'package:match/util/components/global_button.dart'; +import 'package:match/util/const/style/global_color.dart'; +import 'package:match/util/const/style/global_text_styles.dart'; +import 'package:match/util/method/permission_handler.dart'; +// import 'package:permission_handler/permission_handler.dart'; + +class CommonDialog extends StatelessWidget { + final String title; + final String? subtitle; + final Future Function() onGrant; + final String grantText; + + const CommonDialog( + {super.key, + required this.title, + required this.subtitle, + required this.onGrant, + this.grantText = "설정하기"}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + title: Padding( + padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 30.w), + child: Text(title, style: AppTextStyles.T1Bold16), + ), + content: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + subtitle != null + ? Text(subtitle!, + style: AppTextStyles.S1SemiBold13.copyWith( + color: AppColors.grey6)) + : SizedBox.shrink(), + SizedBox(height: 28.h), + Divider( + thickness: 1, + color: AppColors.divider1, + ), + SizedBox( + height: 5.h, + ), + Row( + children: [ + Expanded( + child: Center( + child: GestureDetector( + onTap: () async { + Get.back(); + }, + child: Text( + "취소", + style: AppTextStyles.T1Bold14, + ), + ), + )), + Padding( + padding: EdgeInsets.symmetric(horizontal: 5.w), + child: VerticalDivider( + thickness: 1, + color: AppColors.divider1, + ), + ), + Expanded( + child: Center( + child: GestureDetector( + onTap: onGrant, + child: Text( + grantText, + style: AppTextStyles.T1Bold14, + ), + ), + )) + ], + ) + ], + ), + )); + } + + factory CommonDialog.galleryPermission({required BuildContext context}) { + return CommonDialog( + title: "갤러리 권한 설정", + subtitle: "갤러리 권한을 설정해야\n이미지를 업로드할 수 있습니다.", + onGrant: () async { + // await openAppSettings(); + }, + ); + } + + factory CommonDialog.payDelete({required BuildContext context}) { + return CommonDialog( + title: "정기기부를 해지하시겠어요?", + subtitle: null, + grantText: "해지하기", + onGrant: () async { + //TODO: delete api 호출 + // await Get.to(() => const PaymentExpireScreen()); + }, + ); + } + + factory CommonDialog.delete( + {required BuildContext context, + required Future Function() onGrant}) { + return CommonDialog( + title: "댓글을 삭제하시겠어요?", + subtitle: null, + grantText: "삭제하기", + onGrant: onGrant, + ); + } + + factory CommonDialog.report( + {required String text, + required BuildContext context, + required Future Function() onGrant}) { + return CommonDialog( + title: "정말 댓글을 $text하시겠어요?", + subtitle: null, + grantText: text, + onGrant: onGrant, + ); + } +} diff --git a/lib/util/components/global_text_field.dart b/lib/util/components/global_text_field.dart new file mode 100644 index 0000000..610b553 --- /dev/null +++ b/lib/util/components/global_text_field.dart @@ -0,0 +1,656 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:get/get.dart'; + +import '../const/global_variable.dart'; +import '../const/style/global_color.dart'; + +import '../const/style/global_text_styles.dart'; + + +///

CupertinoTextField를 이용한 TextField

+///* [SearchScreen], [DonationSearchScreen], [ProjectScreen] (댓글)에서 사용 +/// @param textController: 텍스트 컨트롤러 +class CommonSearchField extends StatelessWidget { + final TextEditingController textController; + final String placeHolder; + final bool isSearchScreen; + final bool hasPrefix; + final bool alwaysSuffix; + final Future Function(String) onSubmitted; + final Future Function(String) onChanged; + final Future Function(String)? suffixOnTap; + final bool isPlain; + final String suffixActiveIcon; + final String suffixUnActiveIcon; + + const CommonSearchField( + {super.key, + required this.textController, + required this.placeHolder, + this.isSearchScreen = true, + this.hasPrefix = true, + this.alwaysSuffix = false, + required this.onSubmitted, + required this.onChanged, + required this.suffixOnTap, + this.isPlain = false, + this.suffixActiveIcon = "search_cancel_22", + this.suffixUnActiveIcon = "search_cancel_22"}); + + @override + Widget build(BuildContext context) { + return //1. 뒤로가기 아이콘 + 검색 필드 + Row( + children: [ + isSearchScreen + ? Container( + margin: EdgeInsets.only(right: 14.w), + child: GestureDetector( + onTap: () { + Get.back(); + }, + child: SvgPicture.asset( + iconDir + "ic_arrow_left_24.svg", + width: 24.w, + )), + ) + : SizedBox.shrink(), + Expanded( + child: CupertinoTextField( + controller: textController, + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w), + decoration: BoxDecoration( + color: isPlain ? AppColors.white : AppColors.searchBackground, + borderRadius: BorderRadius.circular(8.r), + border: isPlain + ? (textController.text.isNotEmpty && + textController.value.text != null) + ? Border.all(color: AppColors.grey8) + : Border.all(color: AppColors.grey1) + : null, + ), + keyboardType: TextInputType.text, + cursorColor: AppColors.black, + cursorHeight: 18.h, + style: AppTextStyles.T1Bold13.copyWith( + color: AppColors.grey8, + height: 1.5, + ), + placeholder: placeHolder, + placeholderStyle: AppTextStyles.T1Bold13.copyWith( + color: AppColors.grey4, height: 1.5), + prefixMode: OverlayVisibilityMode.notEditing, + prefix: hasPrefix + ? Padding( + padding: EdgeInsets.only(left: 14.w), + child: SvgPicture.asset(iconDir + "ic_search_16.svg")) + : null, + // clearButtonMode: OverlayVisibilityMode.editing, + suffixMode: !alwaysSuffix + ? OverlayVisibilityMode.editing + : OverlayVisibilityMode.always, + suffix: GestureDetector( + onTap: () async { + if (suffixOnTap != null) { + await suffixOnTap!(textController.text); + } else { + textController.clear(); + } + }, + child: Obx( + () => Padding( + padding: EdgeInsets.only(right: 14.w), + child: SvgPicture.asset( + "${iconDir}ic_$suffixUnActiveIcon.svg") + + ), + ), + //자동 키보드 활성화 + // onSubmitted: ((value) async { + // + // await onSubmitted(value); + // }), + // onChanged: ((value) async { + // + // await onChanged(value); + ), + ), + ) + ], + ); + } + + factory CommonSearchField.comment({ + required TextEditingController textController, + required Future Function(String) onSubmit, + }) { + return CommonSearchField( + isSearchScreen: false, + textController: textController, + placeHolder: "댓글을 남겨 응원해주세요.", + alwaysSuffix: true, + hasPrefix: false, + suffixActiveIcon: "comment_send_active_30", + suffixUnActiveIcon: "comment_send_30", + onSubmitted: onSubmit, + onChanged: (value) async {}, + suffixOnTap: onSubmit, + ); + } +} + +class CommonInputField extends StatelessWidget { + final TextEditingController textController; + final String placeHolder; + final bool autoFocus; + final bool alwaysSuffix; + final TextInputType inputType; + final double? cursorHeight; + final Future Function(String) onSubmitted; + final Future Function(String) onChanged; + final Future Function()? suffixOnTap; + final int? maxLength; + final int? maxLines; + + const CommonInputField( + {super.key, + required this.textController, + required this.placeHolder, + required this.alwaysSuffix, + required this.onSubmitted, + required this.onChanged, + this.inputType = TextInputType.text, + this.suffixOnTap, + required this.autoFocus, + this.cursorHeight, + this.maxLength, + this.maxLines}); + + @override + Widget build(BuildContext context) { + return CupertinoTextField( + maxLines: maxLines, + maxLength: maxLength, + controller: textController, + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.r), + border: textController.text.isNotEmpty + ? Border.all(color: AppColors.grey8) + : Border.all(color: AppColors.grey1)), + keyboardType: inputType, + textAlignVertical: + cursorHeight != null ? TextAlignVertical(y: cursorHeight!) : null, + cursorColor: AppColors.black, + cursorHeight: 18.h, + style: AppTextStyles.T1Bold13.copyWith( + color: AppColors.grey8, + height: 1.5, + ), + placeholder: placeHolder, + placeholderStyle: + AppTextStyles.T1Bold13.copyWith(color: AppColors.grey4, height: 1.5), + suffixMode: !alwaysSuffix + ? OverlayVisibilityMode.editing + : OverlayVisibilityMode.always, + suffix: GestureDetector( + onTap: () async { + textController.clear(); + //TODO) textController 가 초기화 될 때 onChange callback + if (onChanged != null) { + await onChanged(""); + } + }, + child: Padding( + padding: EdgeInsets.only(right: 14.w), + //TODO 추후 다른 아이콘 추가시 변수 추가 + child: SvgPicture.asset(iconDir + "ic_search_cancel_22.svg")), + ), + //자동 키보드 활성화 + autofocus: autoFocus ? true : false, + onSubmitted: ((value) async { + await onSubmitted(value); + }), + onChanged: ((value) async { + await onChanged(value); + }), + ); + } + + //TODO) signIn 로그인 + /// id 입력 + factory CommonInputField.signInID( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "이메일을 입력해주세요.", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + inputType: TextInputType.emailAddress, + autoFocus: true); + } + + /// pw 입력 + factory CommonInputField.signInPW( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "비밀번호를 입력해주세요.", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + inputType: TextInputType.visiblePassword, + autoFocus: true); + } + + /// 비밀번호 찾기 + factory CommonInputField.findPW( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "이메일을 입력해주세요.", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + //TODO) signUp 회원가입 + /// id 입력 + factory CommonInputField.signUpId( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "이메일을 입력해주세요.", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + /// id confirm num 입력 + factory CommonInputField.signUpIdConfirm( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "인증번호를 입력해주세요.", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + /// pw 입력 + factory CommonInputField.signUpPw( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "비밀번호를 입력해주세요. 영문, 숫자, 특수문자 조합 6-20자", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + /// pw confirm 입력 + factory CommonInputField.signUpPwConfirm( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "비밀번호를 입력해주세요. ", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + /// 이름 입력 + factory CommonInputField.userName( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "이름을 입력해주세요.", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + /// 전화번호 + factory CommonInputField.userPhone({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "-없이 번호만 입력해주세요", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: (value) async { + if (value.length > 11) { + Fluttertoast.showToast(msg: "전화번호는 11자리를 넘을 수 없습니다."); + textController.text = value.substring(0, 11); + textController.selection = TextSelection.fromPosition( // 커서를 맨 끝으로 이동 + TextPosition(offset: textController.text.length), + ); + } else { + // 정상적으로 변경 처리 + onChange(value); + } + }, + inputType: TextInputType.phone, + autoFocus: true); + } + + /// 전화번호 인증 + factory CommonInputField.userPhoneConfirm({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "인증번호를 입력해주세요", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: (value) async { + if (value.length > 6) { + Fluttertoast.showToast(msg: "인증번호는 6자리를 넘을 수 없습니다."); + textController.text = value.substring(0, 6); + textController.selection = TextSelection.fromPosition( // 커서를 맨 끝으로 이동 + TextPosition(offset: textController.text.length), + ); + } else { + onChange(value); + } + }, + inputType: TextInputType.phone, + autoFocus: true); + } + + /// 비밀번호 찾기 - 이메일 + factory CommonInputField.findPwAuthEmail({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "이메일을 입력해주세요", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + /// 비밀번호 찾기 - 인증번호 + factory CommonInputField.findPwAuthNum({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "인증번호를 입력해주세요", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + inputType: TextInputType.phone, + autoFocus: true); + } + + /// 비밀번호 찾기 - 새로운 비밀번호 + factory CommonInputField.newPw({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "비밀번호 (영문, 숫자 조합 6~20자)", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + /// 비밀번호 찾기 - 새로운 비밀번호 확인 + factory CommonInputField.newPwConfirm({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "비밀번호 확인", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + //TODO) 카드정보 입력 + /// 카드번호 + factory CommonInputField.cardNum({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "NNNN - NNNN - NNNN - NNNN", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: (value) async { + if (value.length > 16) { + Fluttertoast.showToast(msg: "카드번호는 16자리를 넘을 수 없습니다."); + textController.text = value.substring(0, 16); + textController.selection = TextSelection.fromPosition( // 커서를 맨 끝으로 이동 + TextPosition(offset: textController.text.length), + ); + } else { + onChange(value); + } + }, + inputType: TextInputType.phone, + autoFocus: true); + } + + // 카드 유효기간의 유효성 검사를 수행하는 함수 + bool isCardExpiryValid(String expiryDate) { + // 입력값의 길이가 4가 아니면 유효하지 않음 + if (expiryDate.length != 4) { + return false; + } + // 연도와 월 분리 + int year = int.tryParse(expiryDate.substring(2, 4)) ?? 0; + int month = int.tryParse(expiryDate.substring(0, 2)) ?? 0; + // 연도와 월이 유효한 범위인지 확인 + if (month < 1 || month > 12) { + return false; + } + // 현재 날짜와 비교 + DateTime currentDate = DateTime.now(); + int currentYear = currentDate.year % 100; // 현재 연도의 마지막 두 자리 + int currentMonth = currentDate.month; + // 입력된 유효기간이 현재 날짜보다 이전인지 확인 + if (year < currentYear || (year == currentYear && month < currentMonth)) { + return false; + } + return true; + } + + /// 유효기간 + factory CommonInputField.cardExp({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "MM / YY", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: (value) async { + if (value.length > 4) { + Fluttertoast.showToast(msg: "4자리를 넘을 수 없습니다."); + textController.text = value.substring(0, 4); + textController.selection = TextSelection.fromPosition( // 커서를 맨 끝으로 이동 + TextPosition(offset: textController.text.length), + ); + } else { + /// 입력 유효성 검사 + int year = int.tryParse(value.substring(2, 4)) ?? 0; + int month = int.tryParse(value.substring(0, 2)) ?? 0; + + if (month < 1 || month > 12) { + Fluttertoast.showToast(msg: "유효기간을 바르게 입력해주세요"); + } + + DateTime currentDate = DateTime.now(); + int currentYear = currentDate.year % 100; + int currentMonth = currentDate.month; + + if (year < currentYear || (year == currentYear && month < currentMonth)) { + Fluttertoast.showToast(msg: "유효기간을 바르게 입력해주세요"); + } + + onChange(value); + } + }, + inputType: TextInputType.phone, + autoFocus: true); + } + + /// cvc + factory CommonInputField.cardCvc({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "NNN", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: (value) async { + if (value.length > 3) { + Fluttertoast.showToast(msg: "3자리를 넘을 수 없습니다."); + textController.text = value.substring(0, 3); + textController.selection = TextSelection.fromPosition( // 커서를 맨 끝으로 이동 + TextPosition(offset: textController.text.length), + ); + } else { + onChange(value); + } + }, + inputType: TextInputType.phone, + autoFocus: true); + } + + /// 생년월일 + factory CommonInputField.cardUserBirth({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "NNNNNN", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: (value) async { + if (value.length > 10) { + Fluttertoast.showToast(msg: "6-10자리를 넘을 수 없습니다."); + textController.text = value.substring(0, 10); + textController.selection = TextSelection.fromPosition( // 커서를 맨 끝으로 이동 + TextPosition(offset: textController.text.length), + ); + } else { + onChange(value); + } + }, + inputType: TextInputType.phone, + autoFocus: true); + } + + /// 카드 비밀번호 + factory CommonInputField.cardPw({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "NN **", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: (value) async { + if (value.length > 2) { + Fluttertoast.showToast(msg: "2자리를 넘을 수 없습니다."); + textController.text = value.substring(0, 2); + textController.selection = TextSelection.fromPosition( // 커서를 맨 끝으로 이동 + TextPosition(offset: textController.text.length), + ); + } else { + onChange(value); + } + }, + inputType: TextInputType.phone, + autoFocus: true); + } + + factory CommonInputField.nickName( + {required TextEditingController textController, + required Future Function(String) onChange}) { + return CommonInputField( + textController: textController, + placeHolder: "닉네임을 입력해주세요", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + autoFocus: true); + } + + factory CommonInputField.phone({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + textController: textController, + placeHolder: "변경할 휴대폰 번호를 입력", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + inputType: TextInputType.phone, + autoFocus: false); + } + + factory CommonInputField.survey({ + required TextEditingController textController, + required Future Function(String) onChange, + }) { + return CommonInputField( + maxLines: 50, + maxLength: 1000, + textController: textController, + placeHolder: "리뷰 내용을 입력해주세요. (선택)", + alwaysSuffix: false, + onSubmitted: (value) async {}, + onChanged: onChange, + inputType: TextInputType.text, + autoFocus: false, + cursorHeight: -1.0, + ); + } +} + +Future scrollAnimate( + BuildContext context, ScrollController scrollController) async { + await scrollController.animateTo(MediaQuery.of(context).viewInsets.bottom, + duration: Duration(milliseconds: 100), curve: Curves.easeIn); +} diff --git a/lib/util/const/style/global_text_styles.dart b/lib/util/const/style/global_text_styles.dart index 46b314e..362eb4e 100644 --- a/lib/util/const/style/global_text_styles.dart +++ b/lib/util/const/style/global_text_styles.dart @@ -18,6 +18,12 @@ class AppTextStyles { fontSize: 24.sp, fontWeight: FontWeight.w700, letterSpacing: -0.1); + static var L1Medium13 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 13.sp, + fontWeight: FontWeight.w500, + letterSpacing: -0.1); static var heading2Bold18 = TextStyle( color: _defaultTextColor, fontFamily: _defaultFontFamily, @@ -69,4 +75,54 @@ class AppTextStyles { fontSize: 12.sp, fontWeight: FontWeight.w700, letterSpacing: -0.08); + static var T1Bold10 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 10.sp, + fontWeight: FontWeight.w700, + letterSpacing: -0.08); + static var T1Bold12 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 12.sp, + fontWeight: FontWeight.w700, + letterSpacing: -0.08); + static var T1Bold13 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 13.sp, + fontWeight: FontWeight.w700, + letterSpacing: -0.08); + static var T1Bold14 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 14.sp, + fontWeight: FontWeight.w700, + letterSpacing: -0.08); + static var T1Bold16 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 16.sp, + fontWeight: FontWeight.w700, + letterSpacing: -0.08); + static var S1SemiBold13 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + letterSpacing: -0.08); + static var S1SemiBold14 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + letterSpacing: -0.08); + static var T1Bold15 = TextStyle( + color: _defaultTextColor, + fontFamily: _defaultFontFamily, + fontSize: 15.sp, + fontWeight: FontWeight.w700, + letterSpacing: -0.08); } + + diff --git a/pubspec.yaml b/pubspec.yaml index 761be8d..d9064c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,8 @@ flutter: - asset/image/iv_test_banner.png - asset/image/iv_test_contest.jpeg - asset/image/logo/iv_top_logo_hustle.png + - asset/image/icon/plus.svg + - asset/image/icon/basketball.svg - asset/image/logo/iv_hustle_logo.png - asset/image/logo/iv_hustleHome_logo.png