...
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
|
||||
class AdBannerWidget extends StatefulWidget {
|
||||
const AdBannerWidget({super.key});
|
||||
|
||||
@override
|
||||
State<AdBannerWidget> createState() => _AdBannerWidgetState();
|
||||
}
|
||||
|
||||
class _AdBannerWidgetState extends State<AdBannerWidget> {
|
||||
BannerAd? _bannerAd;
|
||||
bool _isAdLoaded = false;
|
||||
|
||||
// TODO: 릴리스 시 실제 Ad Unit ID로 교체하세요.
|
||||
final String _androidAdUnitId = 'ca-app-pub-3940256099942544/6300978111'; // 테스트 ID
|
||||
final String _iosAdUnitId = 'ca-app-pub-3940256099942544/2934735716'; // 테스트 ID
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAd();
|
||||
}
|
||||
|
||||
void _loadAd() {
|
||||
_bannerAd = BannerAd(
|
||||
adUnitId: Platform.isAndroid ? _androidAdUnitId : _iosAdUnitId,
|
||||
size: AdSize.banner,
|
||||
request: const AdRequest(),
|
||||
listener: BannerAdListener(
|
||||
onAdLoaded: (Ad ad) {
|
||||
setState(() {
|
||||
_isAdLoaded = true;
|
||||
});
|
||||
},
|
||||
onAdFailedToLoad: (Ad ad, LoadAdError error) {
|
||||
print('Ad failed to load: $error');
|
||||
ad.dispose();
|
||||
},
|
||||
),
|
||||
)..load();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_bannerAd?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isAdLoaded && _bannerAd != null) {
|
||||
// 광고가 로드되면 광고 위젯을 표시
|
||||
return Container(
|
||||
width: _bannerAd!.size.width.toDouble(),
|
||||
height: _bannerAd!.size.height.toDouble(),
|
||||
alignment: Alignment.center,
|
||||
child: AdWidget(ad: _bannerAd!),
|
||||
);
|
||||
} else {
|
||||
// 로드되지 않았으면, 광고 높이만큼의 빈 공간만 차지
|
||||
return Container(
|
||||
height: AdSize.banner.height.toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sudoku_app/models/sudoku_theme.dart';
|
||||
|
||||
class NumberPad extends StatelessWidget {
|
||||
final int blockSize;
|
||||
final SudokuTheme theme;
|
||||
final Map<int, int> numberCounts;
|
||||
final int? selectedNumber;
|
||||
final Function(int) onNumberTapped;
|
||||
final bool isLandscape; // 👈 [추가] 이 파라미터가 있어야 합니다
|
||||
|
||||
const NumberPad({
|
||||
super.key,
|
||||
required this.blockSize,
|
||||
required this.theme,
|
||||
required this.numberCounts,
|
||||
required this.selectedNumber,
|
||||
required this.onNumberTapped,
|
||||
required this.isLandscape, // 👈 [추가] 생성자에 추가
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final int gridSize = blockSize * blockSize;
|
||||
|
||||
// 1. 버튼 위젯 리스트 생성
|
||||
List<Widget> numberButtons = List.generate(gridSize, (index) {
|
||||
int numberValue = index + 1;
|
||||
String numberSymbol = theme.getSymbol(numberValue);
|
||||
bool isSelected = (numberValue == selectedNumber);
|
||||
bool isCompleted = (numberCounts[numberValue] ?? 0) >= gridSize;
|
||||
|
||||
Widget button = ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isSelected ? Colors.blue.shade300 : null,
|
||||
foregroundColor: isSelected ? Colors.white : null,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 0),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
|
||||
),
|
||||
onPressed: isCompleted
|
||||
? null
|
||||
: () => onNumberTapped(numberValue),
|
||||
child: Text(numberSymbol),
|
||||
);
|
||||
|
||||
// 가로 모드(Wrap)에서는 Flexible로 감싸고,
|
||||
// 세로 모드(Grid)에서는 감싸지 않음
|
||||
if (isLandscape) {
|
||||
// Flexible을 사용해 Wrap 내에서 버튼이 공간을 차지하도록 함
|
||||
return Flexible(child: button);
|
||||
} else {
|
||||
return button;
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 가로/세로 모드에 따라 다른 레이아웃 반환
|
||||
if (isLandscape) {
|
||||
// --- 가로 모드: Wrap 사용 (버튼이 가로로 흐름) ---
|
||||
return Wrap(
|
||||
runSpacing: 4.0, // 줄(세로) 간격
|
||||
spacing: 4.0, // 버튼(가로) 간격
|
||||
children: numberButtons,
|
||||
);
|
||||
} else {
|
||||
// --- 세로 모드: GridView 사용 (블록 모양) ---
|
||||
return GridView.count(
|
||||
crossAxisCount: blockSize, // 2x2, 3x3, 4x4, 5x5
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 4,
|
||||
children: numberButtons,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sudoku_app/models/sudoku_theme.dart'; // 👈 [추가]
|
||||
|
||||
class SudokuBoard extends StatelessWidget {
|
||||
final int blockSize;
|
||||
final SudokuTheme theme; // 👈 [추가]
|
||||
final List<int> cells; // 👈 [수정] List<String> -> List<int>
|
||||
final List<int> originalCells; // 👈 [수정] List<String> -> List<int>
|
||||
final int? selectedIndex;
|
||||
final int? selectedNumberPad;
|
||||
final Set<int> incorrectCells;
|
||||
final Function(int) onCellTapped;
|
||||
|
||||
const SudokuBoard({
|
||||
super.key,
|
||||
required this.blockSize,
|
||||
required this.theme, // 👈 [추가]
|
||||
required this.cells,
|
||||
required this.originalCells,
|
||||
required this.selectedIndex,
|
||||
required this.selectedNumberPad,
|
||||
required this.incorrectCells,
|
||||
required this.onCellTapped,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final int gridSize = blockSize * blockSize;
|
||||
final double fontSize = (gridSize > 9) ? (gridSize > 16 ? 12 : 16) : 24;
|
||||
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: GridView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: gridSize,
|
||||
),
|
||||
itemCount: gridSize * gridSize,
|
||||
itemBuilder: (context, index) {
|
||||
int row = index ~/ gridSize;
|
||||
int col = index % gridSize;
|
||||
|
||||
int cellValue = cells[index]; // 👈 [수정] 0, 1, 10...
|
||||
bool isEditable = (originalCells[index] == 0); // 👈 [수정] "0" -> 0
|
||||
bool isSelected = (index == selectedIndex);
|
||||
|
||||
bool isHighlighted = (cellValue != 0 && // 👈 [수정]
|
||||
selectedNumberPad != null &&
|
||||
cellValue == selectedNumberPad); // 👈 [수정] int == int 비교
|
||||
|
||||
bool isIncorrect = incorrectCells.contains(index);
|
||||
|
||||
BorderSide thickBorder = const BorderSide(color: Colors.black, width: 2.0);
|
||||
BorderSide thinBorder = const BorderSide(color: Colors.grey, width: 0.5);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => onCellTapped(index),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isIncorrect
|
||||
? Colors.red.shade100
|
||||
: isSelected
|
||||
? Colors.blue.shade100
|
||||
: isHighlighted
|
||||
? Colors.blue.shade200
|
||||
: isEditable
|
||||
? Colors.white
|
||||
: Colors.grey.shade200,
|
||||
border: Border(
|
||||
top: (row == 0) ? thickBorder : thinBorder,
|
||||
left: (col == 0) ? thickBorder : thinBorder,
|
||||
right: (col == gridSize - 1) ? thickBorder : (col % blockSize == blockSize - 1) ? thickBorder : thinBorder,
|
||||
bottom: (row == gridSize - 1) ? thickBorder : (row % blockSize == blockSize - 1) ? thickBorder : thinBorder,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
// 🔽 [수정] 0이면 비우고, 아니면 테마 기호("1", "A", "🍎") 표시
|
||||
cellValue == 0 ? '' : theme.getSymbol(cellValue),
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isIncorrect
|
||||
? Colors.red.shade900
|
||||
: isEditable
|
||||
? Colors.blue
|
||||
: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user