From c9eb2c08eb6fdecfbf3eb6b34f8ea29d03409190 Mon Sep 17 00:00:00 2001 From: hwoo7449 Date: Fri, 27 Feb 2026 01:58:38 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=84=20=ED=95=A8=EC=88=98=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=EB=93=A4=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/screens/settings_screen.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index f36eac9..734e257 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -18,7 +18,7 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State with WidgetsBindingObserver { - // initState에서 권한 상태를 가져오기 위해 Future 사용 + // initState에서 권한 상태를 가져오기 위해 Future 사용 // TODO: 삭제 예정 late Future _permissionFuture; /// 설정에서 1x1 위젯에 대표 식당으로 사용 가능한 전체 식당 리스트 @@ -37,6 +37,7 @@ class _SettingsScreenState extends State // 앱 라이프사이클 변경 감지를 위해 옵저버 등록 WidgetsBinding.instance.addObserver(this); + // TODO: 삭제 예정 _permissionFuture = PermissionService.canScheduleExactAlarms(); // 설정 화면에 진입하면 배너 닫힘 상태를 초기화합니다. @@ -53,6 +54,7 @@ class _SettingsScreenState extends State super.dispose(); } + // TODO: 삭제 예정 @override void didChangeAppLifecycleState(AppLifecycleState state) { // 시스템 설정에서 돌아왔을 때 (앱이 다시 활성화될 때) @@ -75,6 +77,7 @@ class _SettingsScreenState extends State } } + // TODO: 삭제 예정 Future _resetBannerDismissalStatus() async { final prefs = await SharedPreferences.getInstance(); // '닫음' 상태를 false로 되돌려서, 홈 화면에서 배너가 다시 보일 수 있도록 함 From 7439db9ca7f81983248397ced2d9aad92b39f382 Mon Sep 17 00:00:00 2001 From: hwoo7449 Date: Fri, 27 Feb 2026 01:59:51 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20AppBar=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/screens/settings_screen.dart | 78 +++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 734e257..01a4a6a 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -2,6 +2,7 @@ import 'package:bobmoo/ui/theme/app_colors.dart'; import 'package:bobmoo/models/university.dart'; import 'package:bobmoo/providers/univ_provider.dart'; import 'package:bobmoo/services/permission_service.dart'; +import 'package:bobmoo/ui/theme/app_typography.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:home_widget/home_widget.dart'; @@ -162,32 +163,7 @@ class _SettingsScreenState extends State final univColor = context.watch().univColor; return Scaffold( - appBar: AppBar( - toolbarHeight: 70.h, - backgroundColor: univColor, - elevation: 4.0, - shadowColor: Colors.black, - surfaceTintColor: Colors.transparent, - scrolledUnderElevation: 0, - centerTitle: true, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios_new, - color: Colors.white, - size: 22.w, - ), - onPressed: () => Navigator.of(context).pop(), - ), - title: Text( - '설정', - style: TextStyle( - fontSize: 20.sp, - fontWeight: FontWeight.w600, - color: Colors.white, - letterSpacing: 20.sp * 0.03, - ), - ), - ), + appBar: _buildAppBar(), body: ListView( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h), children: [ @@ -495,6 +471,56 @@ class _SettingsScreenState extends State ); } + AppBar _buildAppBar() { + return AppBar( + toolbarHeight: 111.h, + backgroundColor: AppColors.colorWhite, + automaticallyImplyLeading: false, // 기본 leading 끄기 + title: null, + flexibleSpace: SafeArea( + child: Padding( + padding: EdgeInsets.only(top: 60.h, left: 10.w, right: 10.w), + child: SizedBox( + height: 56.h, + child: Stack( + alignment: Alignment.center, + children: [ + // Leading + Align( + alignment: Alignment.centerLeft, + child: IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: Icon( + Icons.arrow_back_ios_new, + color: AppColors.colorGray3, + size: 26.w, + weight: 2, + ), + ), + ), + + // Title + Text( + "설정", + style: AppTypography.head.b21, + ), + ], + ), + ), + ), + ), + + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: Divider( + height: 1.h, + thickness: 1.h, + color: AppColors.colorGray5, + ), + ), + ); + } + /// 섹션 제목 위젯 Widget _buildSectionTitle(String title) { return Padding( From 3044666a50a13f7e5085fd0dc5baa45563315707 Mon Sep 17 00:00:00 2001 From: hwoo7449 Date: Fri, 27 Feb 2026 12:52:16 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EC=B9=B4=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cards/setting_section_card.dart | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 lib/ui/components/cards/setting_section_card.dart diff --git a/lib/ui/components/cards/setting_section_card.dart b/lib/ui/components/cards/setting_section_card.dart new file mode 100644 index 0000000..ffae550 --- /dev/null +++ b/lib/ui/components/cards/setting_section_card.dart @@ -0,0 +1,41 @@ +import 'package:bobmoo/ui/theme/app_colors.dart'; +import 'package:bobmoo/ui/theme/app_typography.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class SettingSectionCard extends StatelessWidget { + final String title; + final Widget child; + + const SettingSectionCard({ + super.key, + required this.title, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.r), + color: AppColors.colorWhite, + ), + padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 18.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppTypography.caption.sb11.copyWith( + color: AppColors.colorGray3, + ), + ), + SizedBox( + height: 8.h, + ), + child, + ], + ), + ); + } +} From 37d2af313557cb3671d1156b89a4ccdf294cf854 Mon Sep 17 00:00:00 2001 From: hwoo7449 Date: Fri, 27 Feb 2026 12:52:47 +0900 Subject: [PATCH 4/4] =?UTF-8?q?style:=20=EC=84=A4=EC=A0=95=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/screens/settings_screen.dart | 430 ++++++++++++------------------- 1 file changed, 166 insertions(+), 264 deletions(-) diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 01a4a6a..31096c8 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,3 +1,4 @@ +import 'package:bobmoo/ui/components/cards/setting_section_card.dart'; import 'package:bobmoo/ui/theme/app_colors.dart'; import 'package:bobmoo/models/university.dart'; import 'package:bobmoo/providers/univ_provider.dart'; @@ -5,6 +6,7 @@ import 'package:bobmoo/services/permission_service.dart'; import 'package:bobmoo/ui/theme/app_typography.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:home_widget/home_widget.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; @@ -102,7 +104,6 @@ class _SettingsScreenState extends State Future _saveSelectedCafeteria( String cafeteriaName, - Color univColor, ) async { // SharedPreferences 대신 HomeWidget을 사용하여 데이터를 저장합니다. await HomeWidget.saveWidgetData( @@ -137,7 +138,7 @@ class _SettingsScreenState extends State ), duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating, - backgroundColor: univColor, + backgroundColor: AppColors.colorBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.r), ), @@ -160,105 +161,69 @@ class _SettingsScreenState extends State @override Widget build(BuildContext context) { - final univColor = context.watch().univColor; - return Scaffold( appBar: _buildAppBar(), body: ListView( - padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h), + padding: EdgeInsets.symmetric(horizontal: 19.w, vertical: 30.h), children: [ - // --- 학교 설정 섹션 (추후 확장 가능) --- - _buildSectionTitle('학교 설정'), - SizedBox(height: 12.h), - - // 학교 설정 카드 - _buildSettingsCard( - onTap: _openSelectSchool, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '내 학교 : ${context.watch().univName}', - style: TextStyle( - fontSize: 15.sp, - color: Colors.black, - ), - ), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + SettingSectionCard( + title: "학교 설정", + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: _openSelectSchool, + child: Column( children: [ - Text( - '학교설정', - style: TextStyle( - fontSize: 17.sp, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.watch().univName, + style: AppTypography.caption.m15, + ), + Icon( + Icons.arrow_forward_ios, + color: AppColors.colorGray3, + size: 20.w, + ), + ], ), - Icon( - Icons.chevron_right, - color: AppColors.greyTextColor, - size: 24.w, + SizedBox(height: 3.h), + Divider( + height: 1.h, + thickness: 1.h, + color: AppColors.colorGray5, ), ], ), - ], + ), ), ), - SizedBox(height: 32.h), - - // --- 위젯 설정 섹션 --- - _buildSectionTitle('위젯 설정'), - SizedBox(height: 12.h), + SizedBox(height: 20.h), // 대표 식당 선택 카드 - _buildSettingsCard( + SettingSectionCard( + title: "위젯 설정", child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: univColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8.r), - ), - child: Icon( - Icons.restaurant_menu, - color: univColor, - size: 22.w, - ), - ), - SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '2x2 위젯 대표 식당', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - SizedBox(height: 2.h), Text( '기본 위젯에 표시될 식당을 선택하세요', - style: TextStyle( - fontSize: 12.sp, - color: AppColors.greyTextColor, - ), + style: AppTypography.caption.m15, ), ], ), ), ], ), - SizedBox(height: 16.h), + SizedBox(height: 8.h), // 식당 선택 칩들 Wrap( spacing: 8.w, @@ -266,7 +231,7 @@ class _SettingsScreenState extends State children: _cafeteriaList.map((cafeteria) { final isSelected = _selectedCafeteria == cafeteria; return GestureDetector( - onTap: () => _saveSelectedCafeteria(cafeteria, univColor), + onTap: () => _saveSelectedCafeteria(cafeteria), child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: EdgeInsets.symmetric( @@ -274,21 +239,19 @@ class _SettingsScreenState extends State vertical: 10.h, ), decoration: BoxDecoration( - color: isSelected ? univColor : Colors.grey.shade100, - borderRadius: BorderRadius.circular(20.r), - border: Border.all( - color: isSelected - ? univColor - : Colors.grey.shade300, - width: 1.5, - ), + color: isSelected + ? AppColors.colorBlack + : Colors.grey.shade100, + borderRadius: BorderRadius.circular(30.r), ), child: Text( cafeteria, style: TextStyle( - fontSize: 14.sp, + fontSize: 13.sp, fontWeight: FontWeight.w500, - color: isSelected ? Colors.white : Colors.black87, + color: isSelected + ? AppColors.colorWhite + : AppColors.colorBlack, ), ), ), @@ -299,137 +262,127 @@ class _SettingsScreenState extends State ), ), - SizedBox(height: 16.h), - - // 위젯 실시간 업데이트 카드 - FutureBuilder( - future: _permissionFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return _buildSettingsCard( - child: Row( - children: [ - SizedBox( - width: 20.w, - height: 20.w, - child: CircularProgressIndicator( - strokeWidth: 2, - color: univColor, - ), - ), - SizedBox(width: 16.w), - Text( - '권한 상태를 확인 중...', - style: TextStyle( - fontSize: 14.sp, - color: AppColors.greyTextColor, - ), - ), - ], - ), - ); - } - - final bool hasPermission = snapshot.data ?? false; - - return _buildSettingsCard( - onTap: () async { - await PermissionService.openAlarmPermissionSettings(); - setState(() { - _permissionFuture = - PermissionService.canScheduleExactAlarms(); - }); - }, - child: Row( - children: [ - Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: hasPermission - ? Colors.green.withValues(alpha: 0.1) - : Colors.orange.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8.r), - ), - child: Icon( - hasPermission ? Icons.update : Icons.schedule, - color: hasPermission ? Colors.green : Colors.orange, - size: 22.w, - ), - ), - SizedBox(width: 12.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '위젯 실시간 업데이트', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - SizedBox(height: 2.h), - Text( - hasPermission - ? '활성화됨 · 매분 자동 업데이트' - : '비활성화됨 · 탭하여 권한 설정', - style: TextStyle( - fontSize: 12.sp, - color: hasPermission - ? Colors.green.shade600 - : AppColors.greyTextColor, - ), - ), - ], - ), - ), - Container( - padding: EdgeInsets.symmetric( - horizontal: 10.w, - vertical: 4.h, - ), - decoration: BoxDecoration( - color: hasPermission - ? Colors.green.withValues(alpha: 0.1) - : Colors.orange.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12.r), - ), - child: Text( - hasPermission ? 'ON' : 'OFF', - style: TextStyle( - fontSize: 12.sp, - fontWeight: FontWeight.w700, - color: hasPermission ? Colors.green : Colors.orange, - ), - ), - ), - ], - ), - ); - }, - ), - - SizedBox(height: 32.h), - - // --- 앱 정보 섹션 (추후 확장 가능) --- - _buildSectionTitle('앱 정보'), - SizedBox(height: 12.h), - - _buildSettingsCard( + SizedBox(height: 20.h), + + // // 위젯 실시간 업데이트 카드 + // FutureBuilder( + // future: _permissionFuture, + // builder: (context, snapshot) { + // if (snapshot.connectionState == ConnectionState.waiting) { + // return _buildSettingsCard( + // child: Row( + // children: [ + // SizedBox( + // width: 20.w, + // height: 20.w, + // child: CircularProgressIndicator( + // strokeWidth: 2, + // color: univColor, + // ), + // ), + // SizedBox(width: 16.w), + // Text( + // '권한 상태를 확인 중...', + // style: TextStyle( + // fontSize: 14.sp, + // color: AppColors.greyTextColor, + // ), + // ), + // ], + // ), + // ); + // } + + // final bool hasPermission = snapshot.data ?? false; + + // return _buildSettingsCard( + // onTap: () async { + // await PermissionService.openAlarmPermissionSettings(); + // setState(() { + // _permissionFuture = + // PermissionService.canScheduleExactAlarms(); + // }); + // }, + // child: Row( + // children: [ + // Container( + // padding: EdgeInsets.all(8.w), + // decoration: BoxDecoration( + // color: hasPermission + // ? Colors.green.withValues(alpha: 0.1) + // : Colors.orange.withValues(alpha: 0.1), + // borderRadius: BorderRadius.circular(8.r), + // ), + // child: Icon( + // hasPermission ? Icons.update : Icons.schedule, + // color: hasPermission ? Colors.green : Colors.orange, + // size: 22.w, + // ), + // ), + // SizedBox(width: 12.w), + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // '위젯 실시간 업데이트', + // style: TextStyle( + // fontSize: 16.sp, + // fontWeight: FontWeight.w600, + // color: Colors.black87, + // ), + // ), + // SizedBox(height: 2.h), + // Text( + // hasPermission + // ? '활성화됨 · 매분 자동 업데이트' + // : '비활성화됨 · 탭하여 권한 설정', + // style: TextStyle( + // fontSize: 12.sp, + // color: hasPermission + // ? Colors.green.shade600 + // : AppColors.greyTextColor, + // ), + // ), + // ], + // ), + // ), + // Container( + // padding: EdgeInsets.symmetric( + // horizontal: 10.w, + // vertical: 4.h, + // ), + // decoration: BoxDecoration( + // color: hasPermission + // ? Colors.green.withValues(alpha: 0.1) + // : Colors.orange.withValues(alpha: 0.1), + // borderRadius: BorderRadius.circular(12.r), + // ), + // child: Text( + // hasPermission ? 'ON' : 'OFF', + // style: TextStyle( + // fontSize: 12.sp, + // fontWeight: FontWeight.w700, + // color: hasPermission ? Colors.green : Colors.orange, + // ), + // ), + // ), + // ], + // ), + // ); + // }, + // ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.r), + color: AppColors.colorWhite, + ), + padding: EdgeInsets.symmetric(vertical: 13.h, horizontal: 10.w), child: Row( children: [ - Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: univColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8.r), - ), - child: Icon( - Icons.info_outline, - color: univColor, - size: 22.w, - ), + SvgPicture.asset( + 'assets/icons/icon_bob.svg', + width: 35.w, ), SizedBox(width: 12.w), Expanded( @@ -438,29 +391,15 @@ class _SettingsScreenState extends State children: [ Text( '밥묵자', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - SizedBox(height: 2.h), - Text( - '인하대학교 학식 정보 앱', - style: TextStyle( - fontSize: 12.sp, - color: AppColors.greyTextColor, - ), + style: AppTypography.caption.m15, ), ], ), ), Text( _appVersion, - style: TextStyle( - fontSize: 12.sp, - color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + style: AppTypography.caption.sb11.copyWith( + color: AppColors.colorGray3, ), ), ], @@ -520,41 +459,4 @@ class _SettingsScreenState extends State ), ); } - - /// 섹션 제목 위젯 - Widget _buildSectionTitle(String title) { - return Padding( - padding: EdgeInsets.only(left: 4.w), - child: Text( - title, - style: TextStyle( - fontSize: 13.sp, - fontWeight: FontWeight.w600, - color: AppColors.greyTextColor, - letterSpacing: 13.sp * 0.02, - ), - ), - ); - } - - /// 설정 카드 위젯 - Widget _buildSettingsCard({ - required Widget child, - VoidCallback? onTap, - }) { - return Material( - color: Colors.white, - borderRadius: BorderRadius.circular(16.r), - elevation: 2, - shadowColor: Colors.black.withValues(alpha: 0.08), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(16.r), - child: Padding( - padding: EdgeInsets.all(16.w), - child: child, - ), - ), - ); - } }