fix(ad): 스크린샷 모드에서 네이티브 광고 비활성화

This commit is contained in:
JiWoong Sul
2025-12-04 16:29:32 +09:00
parent 04b1c3e987
commit bcc26f5e79
11 changed files with 1037 additions and 230 deletions

View File

@@ -12,6 +12,7 @@ import '../../providers/ad_provider.dart';
import '../../providers/location_provider.dart';
import '../../providers/recommendation_provider.dart';
import '../../providers/restaurant_provider.dart';
import '../../providers/settings_provider.dart';
import '../../providers/weather_provider.dart';
import 'widgets/recommendation_result_dialog.dart';
@@ -33,6 +34,18 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final locationAsync = ref.watch(currentLocationWithFallbackProvider);
final restaurantsAsync = ref.watch(restaurantListProvider);
final categoriesAsync = ref.watch(categoriesProvider);
final screenshotModeEnabled = ref
.watch(screenshotModeEnabledProvider)
.maybeWhen(data: (value) => value, orElse: () => false);
final readiness = _evaluateRecommendationReadiness(
locationAsync: locationAsync,
restaurantsAsync: restaurantsAsync,
categoriesAsync: categoriesAsync,
screenshotModeEnabled: screenshotModeEnabled,
);
return Scaffold(
backgroundColor: isDark
@@ -165,29 +178,31 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
),
error: (_, __) => SizedBox(
height: sectionHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildWeatherInfo(
'지금',
Icons.wb_sunny,
'맑음',
20,
isDark,
const Icon(
Icons.cloud_off,
color: AppColors.lightWarning,
size: 28,
),
Container(
width: 1,
height: 50,
color: isDark
? AppColors.darkDivider
: AppColors.lightDivider,
const SizedBox(height: 8),
Text(
'날씨 정보를 불러오지 못했습니다.',
style: AppTypography.body2(isDark),
textAlign: TextAlign.center,
),
_buildWeatherInfo(
'1시간 후',
Icons.wb_sunny,
'맑음',
22,
isDark,
const SizedBox(height: 4),
Text(
'네트워크/위치 권한을 확인한 뒤 다시 시도해 주세요.',
style: AppTypography.caption(isDark),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
TextButton.icon(
onPressed: () => ref.refresh(weatherProvider),
icon: const Icon(Icons.refresh),
label: const Text('다시 시도'),
),
],
),
@@ -360,7 +375,7 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
// 추천받기 버튼
ElevatedButton(
onPressed: !_isProcessingRecommendation && _canRecommend()
onPressed: !_isProcessingRecommendation && readiness.canRecommend
? () => _startRecommendation()
: null,
style: ElevatedButton.styleFrom(
@@ -397,6 +412,30 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
),
),
if (readiness.message != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
readiness.icon,
size: 18,
color: _readinessColor(readiness, isDark),
),
const SizedBox(width: 8),
Expanded(
child: Text(
readiness.message!,
style: AppTypography.caption(
isDark,
).copyWith(color: _readinessColor(readiness, isDark)),
),
),
],
),
),
const SizedBox(height: 16),
// const NativeAdPlaceholder(
// margin: EdgeInsets.symmetric(vertical: 8),
@@ -429,30 +468,6 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
);
}
Widget _buildWeatherInfo(
String label,
IconData icon,
String description,
int temperature,
bool isDark,
) {
return Column(
children: [
Text(label, style: AppTypography.caption(isDark)),
const SizedBox(height: 8),
Icon(icon, color: Colors.orange, size: 32),
const SizedBox(height: 4),
Text(
'$temperature°C',
style: AppTypography.body1(
isDark,
).copyWith(fontWeight: FontWeight.bold),
),
Text(description, style: AppTypography.caption(isDark)),
],
);
}
Widget _buildAllCategoryChip(
bool isDark,
bool isSelected,
@@ -609,33 +624,113 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
}).length;
}
bool _canRecommend() {
final locationAsync = ref.read(currentLocationWithFallbackProvider);
final restaurantsAsync = ref.read(restaurantListProvider);
final categories = ref
.read(categoriesProvider)
.maybeWhen(data: (list) => list, orElse: () => const <String>[]);
final location = locationAsync.maybeWhen(
data: (pos) => pos,
orElse: () => null,
);
final restaurants = restaurantsAsync.maybeWhen(
data: (list) => list,
orElse: () => null,
);
if (location == null || restaurants == null || restaurants.isEmpty) {
return false;
_RecommendationReadiness _evaluateRecommendationReadiness({
required AsyncValue<Position> locationAsync,
required AsyncValue<List<Restaurant>> restaurantsAsync,
required AsyncValue<List<String>> categoriesAsync,
bool screenshotModeEnabled = false,
}) {
if (screenshotModeEnabled) {
return const _RecommendationReadiness(canRecommend: true);
}
if (locationAsync.isLoading) {
return const _RecommendationReadiness(
canRecommend: false,
message: '위치 정보를 불러오는 중입니다. 잠시만 기다려 주세요.',
icon: Icons.location_searching,
isWarning: true,
);
}
if (locationAsync.hasError) {
return const _RecommendationReadiness(
canRecommend: false,
message: '위치 권한 또는 설정을 확인해 주세요.',
icon: Icons.gps_off,
isError: true,
);
}
final location = locationAsync.value;
if (location == null) {
return const _RecommendationReadiness(
canRecommend: false,
message: '위치 정보를 확인할 수 없습니다.',
icon: Icons.location_disabled,
isError: true,
);
}
if (restaurantsAsync.isLoading) {
return const _RecommendationReadiness(
canRecommend: false,
message: '맛집 목록을 불러오는 중입니다.',
icon: Icons.restaurant_menu,
isWarning: true,
);
}
final restaurants = restaurantsAsync.value;
if (restaurants == null || restaurants.isEmpty) {
return const _RecommendationReadiness(
canRecommend: false,
message: '등록된 맛집이 없습니다. 맛집을 추가해 주세요.',
icon: Icons.playlist_remove,
isError: true,
);
}
if (categoriesAsync.isLoading) {
return const _RecommendationReadiness(
canRecommend: false,
message: '카테고리를 불러오는 중입니다.',
icon: Icons.category_outlined,
isWarning: true,
);
}
final categories = categoriesAsync.value ?? const <String>[];
final count = _getRestaurantCountInRange(
restaurants,
location,
_distanceValue,
_effectiveSelectedCategories(categories),
);
return count > 0;
if (count == 0) {
return const _RecommendationReadiness(
canRecommend: false,
message: '선택한 거리/카테고리에 해당하는 맛집이 없습니다. 범위를 넓혀 보세요.',
icon: Icons.travel_explore,
isWarning: true,
);
}
final usesFallback = _isFallbackPosition(location);
return _RecommendationReadiness(
canRecommend: true,
message: usesFallback
? '기본 위치(서울 시청) 기준으로 추천 중입니다. 위치 권한을 허용하면 더 정확해요.'
: null,
icon: usesFallback ? Icons.gps_not_fixed : Icons.check_circle,
isWarning: usesFallback,
);
}
bool _isFallbackPosition(Position position) {
final fallback = defaultPosition();
return position.latitude == fallback.latitude &&
position.longitude == fallback.longitude;
}
Color _readinessColor(_RecommendationReadiness readiness, bool isDark) {
if (readiness.isError) {
return isDark ? AppColors.darkError : AppColors.lightError;
}
if (readiness.isWarning) {
return isDark ? AppColors.darkWarning : AppColors.lightWarning;
}
return isDark ? AppColors.darkTextSecondary : AppColors.lightTextSecondary;
}
Future<void> _startRecommendation({
@@ -845,4 +940,20 @@ class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
}
}
class _RecommendationReadiness {
final bool canRecommend;
final String? message;
final IconData icon;
final bool isWarning;
final bool isError;
const _RecommendationReadiness({
required this.canRecommend,
this.message,
this.icon = Icons.info_outline,
this.isWarning = false,
this.isError = false,
});
}
enum _SnackType { info, warning, error, success }