Skip to content

teamSANDOL/sandol_FE

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

136 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Sandori (μ‚°λŒμ΄)

ν•œκ΅­κ³΅ν•™λŒ€ν•™κ΅ 학생듀을 μœ„ν•œ 캠퍼슀 μƒν™œ 정보 μ•±


πŸ› οΈ 개발 μ•„ν‚€ν…μ²˜

μ•„ν‚€ν…μ²˜: Feature-First + 3-Layer

κΈ°λŠ₯별 λͺ¨λ“ˆ λ‹¨μœ„λ‘œ μ½”λ“œλ₯Ό λΆ„λ¦¬ν•˜κ³ , 각 κΈ°λŠ₯ μ•ˆμ—μ„œ data / domain / presentation 3계측을 μœ μ§€ν•©λ‹ˆλ‹€.

  • presentation β†’ domain μ°Έμ‘° ν—ˆμš©
  • presentation β†’ data (DTO 직접 μ°Έμ‘°) κΈˆμ§€
  • feature κ°„ 직접 μ°Έμ‘° κΈˆμ§€ β†’ 곡유 μ‹œ shared/둜 μΆ”μΆœ

μƒνƒœκ΄€λ¦¬: Riverpod (μ½”λ“œ 생성 방식)

@riverpod μ–΄λ…Έν…Œμ΄μ…˜λ§Œ μ‚¬μš©. StateProvider, StateNotifierProvider λ“± μˆ˜λ™ μ„ μ–Έ κΈˆμ§€.

λ„€νŠΈμ›Œν‚Ή: Retrofit + Dio

@RestApi μΈν„°νŽ˜μ΄μŠ€λ‘œ API μ„ μ–Έ. DioλŠ” keepAlive: true 싱글톀, 인터셉터 μˆœμ„œ: Auth β†’ Error β†’ Logging.

  • 메인 API (dio_provider.dart): http://127.0.0.1:3000
  • Static-Info API (static_info_dio_provider.dart): 둜컬 http://127.0.0.1:5600 / Docker http://127.0.0.1:8000

λΌμš°νŒ…: GoRouter

context.go() / context.push() 만 μ‚¬μš©. Navigator.push() 직접 μ‚¬μš© κΈˆμ§€.

μ½”λ“œ 생성 도ꡬ

μ—­ν•  νŒ¨ν‚€μ§€
JSON 직렬화 json_serializable + json_annotation
API ν΄λΌμ΄μ–ΈνŠΈ retrofit_generator
μƒνƒœκ΄€λ¦¬ riverpod_generator
λΆˆλ³€ λͺ¨λΈ freezed
dart run build_runner build --delete-conflicting-outputs

πŸ“‚ 디렉토리 ꡬ쑰

lib/
β”œβ”€β”€ main.dart                          # μ•± μ§„μž…μ , GetIt DI μ„€μ •, ProviderScope
β”‚
β”œβ”€β”€ const/
β”‚   └── colors.dart                    # μ•± 곡톡 색상 μƒμˆ˜
β”‚
β”œβ”€β”€ core/                              # μ•± μ „μ—­ 인프라
β”‚   β”œβ”€β”€ constants/
β”‚   β”‚   └── api_constants.dart         # baseUrl, staticInfoBaseUrl λ“± API μƒμˆ˜
β”‚   β”œβ”€β”€ network/
β”‚   β”‚   β”œβ”€β”€ dio_provider.dart          # 메인 API Dio 싱글톀 ν”„λ‘œλ°”μ΄λ”
β”‚   β”‚   └── static_info_dio_provider.dart  # Static-Info API μ „μš© Dio ν”„λ‘œλ°”μ΄λ” 
β”‚   β”œβ”€β”€ router/
β”‚   β”‚   β”œβ”€β”€ app_router.dart            # GoRouter μ„€μ •
β”‚   β”‚   └── route_paths.dart           # 경둜 μƒμˆ˜
β”‚   └── utils/
β”‚       └── date_formatter.dart        # ISO 8601 β†’ yyyy.MM.dd 포맷터
β”‚
β”œβ”€β”€ common/                            # feature κ°„ 곡톡 UI / 곡유 λ ˆμ΄μ–΄
β”‚   β”œβ”€β”€ layout/
β”‚   β”‚   β”œβ”€β”€ default_layout.dart        # 곡톡 Scaffold 래퍼 (AppBarΒ·SafeArea μ˜΅μ…˜)
β”‚   β”‚   └── root_tab.dart              # λ°”ν…€νƒ­ μ…Έ (IndexedStack + PopScope)
β”‚   β”œβ”€β”€ component/
β”‚   β”‚   β”œβ”€β”€ app_bottom_nav.dart        # λ°”ν…€ λ„€λΉ„κ²Œμ΄μ…˜ λ°”
β”‚   β”‚   β”œβ”€β”€ header_text.dart           # μ„Ήμ…˜ 헀더 ν…μŠ€νŠΈ + 더보기 λ²„νŠΌ
β”‚   β”‚   β”œβ”€β”€ selectable_icon_button.dart # μ„ νƒν˜• μ›ν˜• μ•„μ΄μ½˜ λ²„νŠΌ
β”‚   β”‚   └── top_bar.dart               # 상단바 (μ•± 이름, μ•Œλ¦Ό, ν”„λ‘œν•„)
β”‚   └── repository/
β”‚       └── static_repository.dart     # ν•™μ‹Β·λ²„μŠ€Β·λ°°λ„ˆ 정적 Mock 데이터
β”‚
β”œβ”€β”€ features/                          # κΈ°λŠ₯별 λͺ¨λ“ˆ
β”‚   β”‚
β”‚   β”œβ”€β”€ home/                          # ν™ˆ ν™”λ©΄
β”‚   β”‚   β”œβ”€β”€ component/
β”‚   β”‚   β”‚   └── banner_card_top.dart   # μžλ™ 슀크둀 λ°°λ„ˆ μΊλŸ¬μ…€
β”‚   β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”‚   └── banner_model.dart
β”‚   β”‚   └── screen/
β”‚   β”‚       β”œβ”€β”€ home_screen.dart       # ν™ˆ (ν•™μ‹Β·λΉˆκ°•μ˜μ‹€Β·λ²„μŠ€ μš”μ•½ + 쑰직도 μ§„μž… )
β”‚   β”‚       └── splash_screen.dart     # μŠ€ν”Œλž˜μ‹œ (2초 ν›„ gate 이동)
β”‚   β”‚
β”‚   β”œβ”€β”€ bus/                           # λ²„μŠ€ μ‹œκ°„ν‘œ
β”‚   β”‚   β”œβ”€β”€ component/
β”‚   β”‚   β”‚   └── bus_time_card.dart     # ν™ˆμš© λ²„μŠ€ 정보 μΉ΄λ“œ (ConsumerWidget, 동적 이미지 )
β”‚   β”‚   β”œβ”€β”€ data/                      # Static-Info API 연동
β”‚   β”‚   β”‚   β”œβ”€β”€ data_source/
β”‚   β”‚   β”‚   β”‚   └── bus_image_api.dart         # @RestApi BusImageApi
β”‚   β”‚   β”‚   β”œβ”€β”€ dto/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ bus_image_response.dart    # 단일 이미지 URL DTO
β”‚   β”‚   β”‚   β”‚   └── bus_images_response.dart   # 이미지 URL λͺ©λ‘ DTO
β”‚   β”‚   β”‚   └── repository/
β”‚   β”‚   β”‚       └── bus_image_repository_impl.dart
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”‚   └── repository/
β”‚   β”‚   β”‚       └── bus_image_repository.dart  # abstract Repository
β”‚   β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”‚   └── bus_model.dart
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”‚   └── provider/
β”‚   β”‚   β”‚       └── bus_image_provider.dart    # busImagesProvider (@riverpod) 
β”‚   β”‚   └── screen/
β”‚   β”‚       └── bus_time_detail_screen.dart    # λ²„μŠ€ 상세 + ꡬ글맡 (ConsumerStatefulWidget )
β”‚   β”‚
β”‚   β”œβ”€β”€ school_meal/                   # 학식·식당
β”‚   β”‚   β”œβ”€β”€ component/
β”‚   β”‚   β”‚   └── meal_card.dart         # ν™ˆμš© 학식 리슀트 μΉ΄λ“œ
β”‚   β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”‚   β”œβ”€β”€ meal_model.dart
β”‚   β”‚   β”‚   └── meals_ranking_model.dart
β”‚   β”‚   └── screen/
β”‚   β”‚       └── restaurant_detail_screen.dart  # 학식 상세 + 인기 학식 λž­ν‚Ή
β”‚   β”‚
β”‚   β”œβ”€β”€ empty_class/                   # 빈 κ°•μ˜μ‹€
β”‚   β”‚   β”œβ”€β”€ component/
β”‚   β”‚   β”‚   └── empty_class_card.dart  # ν™ˆμš© 빈 κ°•μ˜μ‹€ μΉ΄λ“œ
β”‚   β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”‚   └── class_model.dart       # EmptyClass
β”‚   β”‚   β”œβ”€β”€ repository/
β”‚   β”‚   β”‚   └── empty_class_repository.dart  # abstract + FakeMock κ΅¬ν˜„μ²΄
β”‚   β”‚   └── screen/
β”‚   β”‚       └── empty_detail_screen.dart     # ꡬ글맡 + SlidingUpPanel
β”‚   β”‚
β”‚   β”œβ”€β”€ notice/                        # 곡지사항 (3-Layer μ™„μ„±)
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   β”œβ”€β”€ data_source/
β”‚   β”‚   β”‚   β”‚   └── notice_api.dart            # @RestApi Retrofit μΈν„°νŽ˜μ΄μŠ€
β”‚   β”‚   β”‚   β”œβ”€β”€ dto/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ notice_item_response.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ paginated_notice_response.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ shuttle_item_response.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ paginated_shuttle_response.dart
β”‚   β”‚   β”‚   β”‚   └── shuttle_recent_response.dart
β”‚   β”‚   β”‚   └── repository/
β”‚   β”‚   β”‚       └── notice_repository_impl.dart
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ notice.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ shuttle.dart
β”‚   β”‚   β”‚   β”‚   └── shuttle_recent.dart
β”‚   β”‚   β”‚   └── repository/
β”‚   β”‚   β”‚       └── notice_repository.dart     # abstract Repository
β”‚   β”‚   └── presentation/
β”‚   β”‚       β”œβ”€β”€ page/
β”‚   β”‚       β”‚   β”œβ”€β”€ notice_page.dart           # νƒ­(일반/κΈ°μˆ™μ‚¬/μ…”ν‹€) λͺ©λ‘
β”‚   β”‚       β”‚   └── notice_detail_page.dart    # WebView 상세
β”‚   β”‚       β”œβ”€β”€ provider/
β”‚   β”‚       β”‚   └── notice_provider.dart       # @riverpod Notifier
β”‚   β”‚       └── widget/
β”‚   β”‚           β”œβ”€β”€ notice_card.dart
β”‚   β”‚           └── shuttle_card.dart
β”‚   β”‚
β”‚   β”œβ”€β”€ organization/                  # ν•™κ³ΌΒ·λΆ€μ„œ 쑰직도 
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   β”œβ”€β”€ data_source/
β”‚   β”‚   β”‚   β”‚   └── organization_api.dart      # @RestApi OrganizationApi
β”‚   β”‚   β”‚   β”œβ”€β”€ dto/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ organization_group_response.dart    # κ·Έλ£Ή λ…Έλ“œ DTO
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ organization_node_raw_response.dart # 톡합 DTO (group+unit)
β”‚   β”‚   β”‚   β”‚   └── organization_unit_response.dart     # λ‹¨μœ„ λ…Έλ“œ DTO
β”‚   β”‚   β”‚   └── repository/
β”‚   β”‚   β”‚       └── organization_repository_impl.dart
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”‚   β”‚   └── organization_node.dart     # sealed class (GroupNode / UnitNode)
β”‚   β”‚   β”‚   └── repository/
β”‚   β”‚   β”‚       └── organization_repository.dart  # abstract Repository
β”‚   β”‚   └── presentation/
β”‚   β”‚       β”œβ”€β”€ page/
β”‚   β”‚       β”‚   β”œβ”€β”€ organization_tree_page.dart   # 쑰직도 트리 (검색바 + ExpansionTile)
β”‚   β”‚       β”‚   └── organization_search_page.dart # 검색 κ²°κ³Ό νŽ˜μ΄μ§€
β”‚   β”‚       β”œβ”€β”€ provider/
β”‚   β”‚       β”‚   └── organization_provider.dart    # OrganizationTreeNotifier, SearchNotifier
β”‚   β”‚       └── widget/
β”‚   β”‚           └── organization_node_card.dart   # sealed class switch β†’ GroupTile / UnitTile
β”‚   β”‚
β”‚   └── auth/                          # 인증·둜그인
β”‚       └── screen/
β”‚           β”œβ”€β”€ sign_in_gate_screen.dart  # μ‹œμž‘ 게이트 ν™”λ©΄
β”‚           β”œβ”€β”€ login_screen.dart         # 둜그인
β”‚           └── signin_screen.dart        # νšŒμ›κ°€μž…
β”‚
└── shared/                            # feature κ°„ 곡유 λͺ¨λΈΒ·μœ„μ ―
    β”œβ”€β”€ model/
    β”‚   └── pagination_state.dart      # @freezed PaginationState<T>
    └── widget/
        └── full_screen_image_viewer.dart  # InteractiveViewer 전체화면 이미지

λ°”ν…€ λ„€λΉ„κ²Œμ΄μ…˜ νƒ­ μˆœμ„œ

인덱슀 νƒ­ ν™”λ©΄
0 λ²„μŠ€μ‹œκ°„ν‘œ BusTimeDetailScreen
1 학식 RestaurantDetailScreen
2 ν™ˆ (쀑앙) HomeScreen
3 곡지사항 NoticePage
4 빈 κ°•μ˜μ‹€ EmptyDetailScreen

라우트 λͺ©λ‘

경둜 ν™”λ©΄
/splash Splashscreen
/gate SignInGateScreen
/login Loginscreen
/login/sign-in Signinscreen
/main RootTab
/notice-detail NoticeDetailPage
/organization OrganizationTreePage
/organization/search OrganizationSearchPage

🌐 Static-Info API 연동

sandol-static-info-service (FastAPI) 와 μ—°λ™ν•©λ‹ˆλ‹€.

ν™˜κ²½ URL
둜컬 (uvicorn) http://127.0.0.1:5600
Docker http://127.0.0.1:8000

주의: 둜컬 μ‹€ν–‰ μ‹œ API κ²½λ‘œμ— /static-info/ prefix μ—†μŒ. Docker μ‚¬μš© μ‹œ api_constants.dart의 staticInfoBaseUrl을 http://127.0.0.1:8000으둜 λ³€κ²½ν•˜κ³  각 API 파일 κ²½λ‘œμ— /static-info/ prefixλ₯Ό μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μ—°λ™λœ μ—”λ“œν¬μΈνŠΈ

μ—”λ“œν¬μΈνŠΈ κΈ°λŠ₯
GET /bus/images λ²„μŠ€ μ•„μ΄μ½˜ 이미지 URL λͺ©λ‘
GET /bus/image/{index} νŠΉμ • 인덱슀 λ²„μŠ€ 이미지
GET /organization/tree 전체 쑰직도 트리
GET /organization/search/{name} 쑰직 이름 검색
GET /organization/{path} νŠΉμ • 경둜 쑰직 쑰회
GET /organization/{path}/children ν•˜μœ„ 쑰직 λͺ©λ‘


πŸ“± μŠ€ν¬λ¦°μƒ·

cap1
ν™ˆ
cap2
식단
cap3
빈 κ°•μ˜μ‹€
cap4
빈 κ°•μ˜μ‹€ 상세
cap5
λ²„μŠ€ μ‹œκ°„ν‘œ
cap6
곡지사항

🎨 색상 νŒ”λ ˆνŠΈ

μš©λ„ 색상값
메인 포인트 Color(0xFF00C4F9)
μ„œλΈŒ 포인트 Color(0xFF95E0F4)
λ°°κ²½ Colors.white
μ„œλΈŒ λ°°κ²½ Color(0xFFFAFAFA)
ν…μŠ€νŠΈ κΈ°λ³Έ Colors.black / Colors.black87
ν…μŠ€νŠΈ 보쑰 Colors.grey

Flutter κΈ°λ³Έ 보라/νŒŒλž‘ 계열(Colors.blue, Colors.purple λ“±) μ‚¬μš© κΈˆμ§€. CircularProgressIndicator, TabBar, ElevatedButton λ“± 기본값이 보라색인 μœ„μ ―μ€ λ°˜λ“œμ‹œ 색상 μ§€μ •.


πŸ“¦ μ£Όμš” μ˜μ‘΄μ„±

dependencies:
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5
  dio: ^5.9.2
  retrofit: ">=4.6.0 <4.9.0"
  json_annotation: ^4.9.0
  freezed_annotation: ^2.4.4
  go_router: ^17.1.0
  flutter_secure_storage: ^10.0.0
  get_it: ^9.2.1
  google_maps_flutter: ^2.15.0
  geolocator: ^14.0.2
  sliding_up_panel: ^2.0.0+1
  webview_flutter: ^4.13.1

dev_dependencies:
  build_runner: ^2.4.13
  riverpod_generator: ^2.4.3
  retrofit_generator: ^9.0.0
  json_serializable: ^6.9.4
  freezed: ^2.5.7

πŸš€ μ„€μΉ˜ & μ‹€ν–‰

# μ €μž₯μ†Œ 클둠
git clone https://github.com/SongsBy/Sandori.git

# νŒ¨ν‚€μ§€ μ„€μΉ˜
flutter pub get

# μ½”λ“œ 생성
dart run build_runner build --delete-conflicting-outputs

# iOS μ‹€ν–‰
flutter run -d ios

# Android μ‹€ν–‰
flutter run -d android

πŸ—ΊοΈ ν–₯ν›„ 개발 κ³„νš

  • μ†Œμ…œ 둜그인 (Kakao / Google / Apple) β€” PKCE + state νŒŒλΌλ―Έν„° 적용
  • μ‹€μ‹œκ°„ API 연동 (학식 메뉴, λ²„μŠ€ μ‹œκ°„ν‘œ)
  • home/, school_meal/, empty_class/ β†’ notice처럼 data/domain/presentation 3-Layer둜 점진적 μ „ν™˜
  • ν‘Έμ‹œ μ•Œλ¦Ό (곡지사항, μ…”ν‹€ 좜발 μ•Œλ¦Ό)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors