Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ed06bf9
feat: 화면 기준 사이즈 변경
hwoo7449 Feb 18, 2026
75da1fb
chore: 색상정의 파일 위치 이동
hwoo7449 Feb 18, 2026
34fd595
feat: 타이포그래피 추가
hwoo7449 Feb 18, 2026
386295a
feat: 검색 기능 SearchProvider 추가
hwoo7449 Feb 20, 2026
9c3642c
feat: 앱에 SearchProvider 등록
hwoo7449 Feb 20, 2026
7a4565a
feat: 검색 기능 구현
hwoo7449 Feb 20, 2026
32576d0
chore: 웹 관련 폴더 삭제
hwoo7449 Feb 20, 2026
0d827cd
feat: 자간 px->퍼센트 수정
hwoo7449 Feb 20, 2026
0d32290
refactor: AppBar 분리
hwoo7449 Feb 20, 2026
c59290d
feat: 학교 설정 화면에서 뒤로가기 버튼 비활성화
hwoo7449 Feb 20, 2026
0fd9cb5
feat: Figma 색상 스타일 적용
hwoo7449 Feb 20, 2026
2cc1fcb
feat: 타이포그래피 색상 인자 추가
hwoo7449 Feb 20, 2026
0959a35
feat: 타이포그래피 인스턴스화 방지
hwoo7449 Feb 20, 2026
3e2620b
chore: 오타 수정 투명도->불투명도
hwoo7449 Feb 20, 2026
03e133a
feat: 카드 그림자용 AppShadow 디자인 토큰 추가
hwoo7449 Feb 20, 2026
6151b0d
feat: 기본 테마에 AppColors 디자인 토큰 적용
hwoo7449 Feb 20, 2026
814fcd6
feat: PrimaryButton(주요 CTA 버튼) 컴포넌트 추가
hwoo7449 Feb 20, 2026
4121a2f
style: 학교 선택 화면 UI 스타일 조정
hwoo7449 Feb 20, 2026
186e9bb
feat: 학교 선택/취소 기능 추가
hwoo7449 Feb 20, 2026
1e8ebc4
feat: 학교 모델 비교시 이름기준으로 비교(==) 연산자 오버로딩
hwoo7449 Feb 20, 2026
feb527a
refactor: 학교 검색 일부 최적화
hwoo7449 Feb 20, 2026
b05e2f9
refactor: SearchProvider 검색 로직 최적화
hwoo7449 Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:bobmoo/constants/app_colors.dart';
import 'package:bobmoo/providers/search_provider.dart';
import 'package:bobmoo/ui/theme/app_colors.dart';
import 'package:bobmoo/constants/app_constants.dart';
import 'package:bobmoo/locator.dart';
import 'package:bobmoo/models/university.dart';
Expand Down Expand Up @@ -50,6 +51,9 @@ void main() async {
ChangeNotifierProvider(
create: (_) => UnivProvider()..init(),
),
ChangeNotifierProvider(
create: (_) => SearchProvider()..init(),
),
],
child: const BobMooApp(),
),
Expand All @@ -62,7 +66,7 @@ class BobMooApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(402, 874),
designSize: const Size(360, 800),
minTextAdapt: true,
builder: (context, child) {
return MaterialApp(
Expand Down Expand Up @@ -139,7 +143,7 @@ class BobMooApp extends StatelessWidget {
fontFamily: 'Pretendard',

// 배경색 설정
scaffoldBackgroundColor: AppColors.background,
scaffoldBackgroundColor: AppColors.colorGray4,

colorScheme: ColorScheme.fromSeed(
// [기본 설정]
Expand All @@ -148,11 +152,8 @@ class BobMooApp extends StatelessWidget {

// [표면/컴포넌트 컬러]
// surface: 카드, 바텀시트, 다이얼로그의 기본 배경색
surface: Colors.white,
onSurface: Colors.black,

// outline: 입력창 테두리, 카드 외곽선, 리스트 구분선
outline: AppColors.grayDividerColor,
surface: AppColors.colorWhite,
onSurface: AppColors.colorBlack,

// 색상 생성 알고리즘을 왜곡없이 그대로 사용
dynamicSchemeVariant: DynamicSchemeVariant.fidelity,
Expand Down
7 changes: 7 additions & 0 deletions lib/models/university.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ class University {
};
}

@override
bool operator ==(Object other) =>
identical(this, other) || other is University && other.name == name;

@override
int get hashCode => name.hashCode;

Color hexToColor() {
return Color(int.parse(colorCode.replaceFirst('#', '0xff')));
}
Expand Down
50 changes: 50 additions & 0 deletions lib/providers/search_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'dart:convert';

import 'package:bobmoo/models/university.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

class SearchProvider extends ChangeNotifier {
List<University> _allItems = [];
String _keyword = "";

bool _isLoading = true;

// 1. 앱 시작 시 딱 한 번 호출해서 상태를 복원합니다.
Future<void> init() async {
_allItems = await _loadUniversities();

_isLoading = false;
notifyListeners();
}

Future<List<University>> _loadUniversities() async {
// universities.json 파일에서 대학목록을 불러옵니다.
// TODO: 나중에 학교 리스트 받는 API 구축하고 그 Repository를 "생성자"에서 받게끔
final String jsonString = await rootBundle.loadString(
'assets/data/universities.json',
);
final List<dynamic> jsonList = jsonDecode(jsonString);
return jsonList.map((json) => University.fromJson(json)).toList();
}

void updateKeyword(String keyword) {
_keyword = keyword;
notifyListeners();
}

bool get isLoading => _isLoading;

List<University> get filteredItems {
// 키워드가 비어있다면 그대로 반환
if (_keyword == "") return _allItems;

final normalizedKeyword = _keyword.replaceAll(" ", "").toLowerCase();

return _allItems.where((item) {
final itemName = item.name.replaceAll(" ", "").toLowerCase();

return itemName.contains(normalizedKeyword);
}).toList();
}
}
2 changes: 1 addition & 1 deletion lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:io';

import 'package:bobmoo/collections/meal_collection.dart';
import 'package:bobmoo/constants/app_colors.dart';
import 'package:bobmoo/ui/theme/app_colors.dart';
import 'package:bobmoo/locator.dart';
import 'package:bobmoo/models/all_cafeterias_widget_data.dart';
import 'package:bobmoo/models/meal_by_cafeteria.dart';
Expand Down
218 changes: 149 additions & 69 deletions lib/screens/select_school_screen.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'dart:convert';

import 'package:bobmoo/models/university.dart';
import 'package:bobmoo/screens/splash_screen.dart';
import 'package:bobmoo/providers/search_provider.dart';
import 'package:bobmoo/ui/components/buttons/primary_button.dart';
import 'package:bobmoo/ui/theme/app_colors.dart';
import 'package:bobmoo/ui/theme/app_shadow.dart';
import 'package:bobmoo/ui/theme/app_typography.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';

class SelectSchoolScreen extends StatefulWidget {
final bool allowBack;
Expand All @@ -19,87 +21,165 @@ class SelectSchoolScreen extends StatefulWidget {
}

class _SelectSchoolScreenState extends State<SelectSchoolScreen> {
List<University>? univs;
bool _isLoading = true;
University? _selectedUniv;

@override
void initState() {
super.initState();
_loadUniversities();
}

Future<void> _loadUniversities() async {
final universities = await loadUniversities();
if (mounted) {
setState(() {
univs = universities;
_isLoading = false;
});
}
}

Future<List<University>> loadUniversities() async {
// universities.json 파일에서 대학목록을 불러옵니다.
// TODO: 이후에 대학 목록 API와 연동시켜야함.
final String jsonString = await rootBundle.loadString(
'assets/data/universities.json',
PreferredSizeWidget _buildAppBar() {
return AppBar(
// Appbar의 기본 여백 제거
titleSpacing: 0,
backgroundColor: AppColors.colorGray4,
// 온보딩화면 -> false, 설정화면 -> true
automaticallyImplyLeading: widget.allowBack,
scrolledUnderElevation: 0,
title: Padding(
padding: EdgeInsets.only(left: 27.w, top: 10.h),
child: Text(
"학교찾기",
style: AppTypography.head.b30,
),
),
toolbarHeight: 98.h,
);
final List<dynamic> jsonList = jsonDecode(jsonString);
return jsonList.map((json) => University.fromJson(json)).toList();
}

@override
Widget build(BuildContext context) {
if (_isLoading) {
return const SplashScreen();
}
final univs = context.select((SearchProvider p) => p.filteredItems);

return PopScope(
canPop: widget.allowBack,
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
// Appbar의 기본 여백 제거
titleSpacing: 0,
backgroundColor: Colors.white,
// 온보딩화면 -> false, 설정화면 -> true
automaticallyImplyLeading: widget.allowBack,
scrolledUnderElevation: 0,
title: Padding(
padding: EdgeInsets.only(left: 36.w),
child: Text(
"학교찾기",
style: TextStyle(
fontSize: 30.sp,
fontWeight: FontWeight.w700,
// 자간 5% (픽셀 계산)
letterSpacing: 30.sp * 0.05,
// 행간 170%
height: 1.7,
backgroundColor: AppColors.colorGray4,
appBar: _buildAppBar(),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 23.w),
child: Column(
children: [
_buildSearchField(),
SizedBox(height: 25.h),

_buildSearchResult(univs),
SizedBox(height: 27.h),

if (_selectedUniv != null) ...[
PrimaryButton(
text: "선택완료",
onTap: () {
Navigator.of(context).pop(_selectedUniv);
},
),
SizedBox(height: 27.h),
],
],
),
),
),
);
}

Expanded _buildSearchResult(List<University> univs) {
return Expanded(
child: Container(
padding: EdgeInsets.symmetric(
vertical: 18.h,
horizontal: 17.w,
),
decoration: BoxDecoration(
color: AppColors.colorWhite,
borderRadius: BorderRadius.circular(15.r),
boxShadow: const [AppShadow.card],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"검색 결과 ${univs.length}",
style: AppTypography.search.b15,
),

SizedBox(height: 10.h),

Expanded(
child: ListView.separated(
physics: const BouncingScrollPhysics(),
itemCount: univs.length,
itemBuilder: (context, index) {
final university = univs[index];

return ListTile(
minTileHeight: 58.h,
trailing: _selectedUniv == university
? Icon(Icons.check, size: 25.h)
: null,
iconColor: AppColors.colorGray3,
title: Text(
university.name,
style: AppTypography.search.b17,
),
onTap: () {
setState(() {
_selectedUniv = _selectedUniv == university
? null
: university;
});
},
);
},
separatorBuilder: (context, index) => Divider(
// TODO: 두께 들쭉날쭉 관련 논의
thickness: 2.5,
color: AppColors.colorGray5,
),
),
),
),
toolbarHeight: 110.h,
],
),
body: ListView.separated(
physics: const BouncingScrollPhysics(),
itemCount: univs?.length ?? 0,
itemBuilder: (context, index) {
final university = univs![index];
),
);
}

return ListTile(
title: Text(university.name),
onTap: () {
Navigator.of(context).pop(university);
},
);
},
separatorBuilder: (context, index) => Divider(
thickness: 1.5,
color: Colors.black.withValues(alpha: 0.3),
Container _buildSearchField() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.r),
boxShadow: const [AppShadow.card],
),
height: 48.h,
child: TextField(
onChanged: (value) =>
context.read<SearchProvider>().updateKeyword(value),
autofocus: true,
style: AppTypography.search.sb15,
decoration: InputDecoration(
hintText: "학교를 검색해 주세요",
hintStyle: AppTypography.search.sb15.copyWith(
color: AppColors.colorGray3,
),
suffixIcon: Icon(
Icons.search,
size: 25.w,
weight: 2.w,
),
filled: true,
fillColor: AppColors.colorWhite,
contentPadding: EdgeInsets.symmetric(
horizontal: 15.w,
vertical: 13.h,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.r),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.r),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.r),
borderSide: BorderSide.none,
),
),
),
); //TODO: 학교찾기 구현(학교선택)
);
}
}
4 changes: 2 additions & 2 deletions lib/screens/settings_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:bobmoo/constants/app_colors.dart';
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';
Expand Down Expand Up @@ -145,7 +145,7 @@ class _SettingsScreenState extends State<SettingsScreen>
Future<void> _openSelectSchool() async {
final University? university = await Navigator.of(
context,
).pushNamed<University?>("/select_school", arguments: true);
).pushNamed<University?>("/select_school", arguments: false);

if (!mounted) return;

Expand Down
Loading