feat(app): add vworld geocoding and native ads placeholders

This commit is contained in:
JiWoong Sul
2025-12-03 14:30:20 +09:00
parent d101f7d0dc
commit 3ff9e5f837
23 changed files with 1108 additions and 540 deletions

View File

@@ -13,6 +13,7 @@ import '../../providers/location_provider.dart';
import '../../providers/recommendation_provider.dart';
import '../../providers/restaurant_provider.dart';
import '../../providers/weather_provider.dart';
import '../../widgets/native_ad_placeholder.dart';
import 'widgets/recommendation_result_dialog.dart';
class RandomSelectionScreen extends ConsumerStatefulWidget {
@@ -51,64 +52,84 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 맛집 리스트 현황 카드
// 상단 요약 바 (높이 최소화)
Card(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
elevation: 2,
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
child: Row(
children: [
const Icon(
Icons.restaurant,
size: 48,
color: AppColors.lightPrimary,
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: AppColors.lightPrimary.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.restaurant,
size: 20,
color: AppColors.lightPrimary,
),
),
const SizedBox(height: 12),
Consumer(
builder: (context, ref, child) {
final restaurantsAsync = ref.watch(
restaurantListProvider,
);
return restaurantsAsync.when(
data: (restaurants) => Text(
'${restaurants.length}',
style: AppTypography.heading1(
isDark,
).copyWith(color: AppColors.lightPrimary),
),
loading: () => const CircularProgressIndicator(
color: AppColors.lightPrimary,
),
error: (_, __) => Text(
'0개',
style: AppTypography.heading1(
isDark,
).copyWith(color: AppColors.lightPrimary),
),
);
},
const SizedBox(width: 10),
Expanded(
child: Consumer(
builder: (context, ref, child) {
final restaurantsAsync = ref.watch(
restaurantListProvider,
);
return restaurantsAsync.when(
data: (restaurants) => Text(
'등록된 맛집 ${restaurants.length}',
style: AppTypography.heading2(
isDark,
).copyWith(fontSize: 18),
),
loading: () => const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.lightPrimary,
),
),
error: (_, __) => Text(
'등록된 맛집 0개',
style: AppTypography.heading2(
isDark,
).copyWith(fontSize: 18),
),
);
},
),
),
Text('등록된 맛집', style: AppTypography.body2(isDark)),
],
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 12),
// 날씨 정보 카드
Card(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
elevation: 2,
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 14,
),
child: Consumer(
builder: (context, ref, child) {
final weatherAsync = ref.watch(weatherProvider);
@@ -164,22 +185,22 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
),
),
const SizedBox(height: 16),
const SizedBox(height: 12),
// 카테고리 선택 카드
Card(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
elevation: 2,
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.fromLTRB(12, 14, 12, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('카테고리', style: AppTypography.heading2(isDark)),
const SizedBox(height: 12),
const SizedBox(height: 10),
Consumer(
builder: (context, ref, child) {
final categoriesAsync = ref.watch(categoriesProvider);
@@ -204,7 +225,7 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
.toList();
return Wrap(
spacing: 8,
runSpacing: 8,
runSpacing: 10,
children: categories.isEmpty
? [const Text('카테고리 없음')]
: [
@@ -227,22 +248,22 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
),
),
const SizedBox(height: 16),
const SizedBox(height: 12),
// 거리 설정 카드
Card(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
elevation: 2,
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.fromLTRB(12, 14, 12, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('최대 거리', style: AppTypography.heading2(isDark)),
const SizedBox(height: 12),
const SizedBox(height: 10),
Row(
children: [
Expanded(
@@ -274,7 +295,7 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
),
],
),
const SizedBox(height: 8),
const SizedBox(height: 6),
Consumer(
builder: (context, ref, child) {
final locationAsync = ref.watch(
@@ -322,7 +343,7 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
),
),
const SizedBox(height: 24),
const SizedBox(height: 16),
// 추천받기 버튼
ElevatedButton(
@@ -362,6 +383,11 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
],
),
),
const SizedBox(height: 16),
const NativeAdPlaceholder(
margin: EdgeInsets.symmetric(vertical: 8),
),
],
),
),