298 lines
9.1 KiB
Dart
298 lines
9.1 KiB
Dart
// packages/feature_game_spider/lib/widgets/card_widget.dart
|
|
import 'dart:math' as math;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../models/spider_card.dart';
|
|
import '../controllers/spider_game_controller.dart';
|
|
|
|
class CardWidget extends StatelessWidget {
|
|
final SpiderCard card;
|
|
final double width;
|
|
final double height;
|
|
final bool isDraggable; // 👈 [추가]
|
|
|
|
const CardWidget({
|
|
super.key,
|
|
required this.card,
|
|
required this.width,
|
|
required this.height,
|
|
this.isDraggable = true, // 👈 [추가] 기본값은 true
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// 🔽 [수정] isDraggable이 false이면, 드래그 기능 없이 카드 앞면만 즉시 반환
|
|
if (!isDraggable) {
|
|
return _buildCardFace(context, card);
|
|
}
|
|
|
|
// --- (isDraggable이 true일 때만 아래 로직 실행) ---
|
|
final controller = Provider.of<SpiderGameController>(context, listen: false);
|
|
|
|
final List<SpiderCard> draggableStack = controller.getDraggableStack(card);
|
|
final bool canDrag = draggableStack.isNotEmpty;
|
|
|
|
return Draggable<List<SpiderCard>>(
|
|
data: draggableStack,
|
|
|
|
// 🔽 [수정] 겹침 높이 계산을 0.4로 수정
|
|
feedback: Opacity(
|
|
opacity: 0.8,
|
|
child: SizedBox(
|
|
width: width,
|
|
height: height + (draggableStack.length - 1) * (height * 0.4), // 👈 0.22 -> 0.4
|
|
child: Stack(
|
|
children: List.generate(draggableStack.length, (index) {
|
|
return Positioned(
|
|
top: index * (height * 0.4), // 👈 0.22 -> 0.4
|
|
left: 0,
|
|
// 🔽 [수정] 여기는 CardWidget이 아닌 _buildCardFace를 직접 호출 (중첩 Draggable 방지)
|
|
child: _buildCardFace(context, draggableStack[index]),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
|
|
childWhenDragging: _buildCardPlaceholder(context),
|
|
child: (card.isBeingDragged)
|
|
? _buildCardPlaceholder(context)
|
|
: _buildCardFace(context, card),
|
|
|
|
onDragStarted: () {
|
|
if (canDrag) {
|
|
controller.onDragStarted(draggableStack);
|
|
}
|
|
},
|
|
onDraggableCanceled: (velocity, offset) {
|
|
controller.onDragCancelled();
|
|
},
|
|
onDragEnd: (details) {
|
|
if (controller.draggedCards.isNotEmpty) {
|
|
controller.onDragCancelled();
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
// ( _buildCardFace, _buildRankText, _buildCenterSymbols 는 이전과 동일 )
|
|
Widget _buildCardFace(BuildContext context, SpiderCard card) {
|
|
return Container(
|
|
width: width,
|
|
height: height,
|
|
decoration: BoxDecoration(
|
|
color: card.isFaceUp ? Colors.white : Theme.of(context).primaryColor,
|
|
border: Border.all(color: Colors.black54, width: 0.5),
|
|
borderRadius: BorderRadius.circular(width * 0.08),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.2),
|
|
blurRadius: 2,
|
|
offset: const Offset(1, 1),
|
|
)
|
|
],
|
|
),
|
|
child: card.isFaceUp
|
|
? Stack(
|
|
children: [
|
|
_buildRankText(card, Alignment.topLeft),
|
|
_buildRankText(card, Alignment.bottomRight),
|
|
_buildCenterSymbols(card),
|
|
],
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
Widget _buildRankText(SpiderCard card, Alignment alignment) {
|
|
final bool isTopLeft = alignment == Alignment.topLeft;
|
|
final double fontSize = width * 0.4;
|
|
final double padding = width * 0.05;
|
|
Widget content = Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
card.rankText,
|
|
style: TextStyle(
|
|
color: card.isRed ? Colors.red : Colors.black,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: fontSize,
|
|
),
|
|
),
|
|
Text(
|
|
card.suitSymbol,
|
|
style: TextStyle(
|
|
color: card.isRed ? Colors.red : Colors.black,
|
|
fontSize: fontSize * 0.5,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
if (!isTopLeft) {
|
|
content = Transform.rotate(
|
|
angle: math.pi,
|
|
child: content,
|
|
);
|
|
}
|
|
return Positioned(
|
|
top: isTopLeft ? padding : null,
|
|
left: isTopLeft ? padding : null,
|
|
bottom: isTopLeft ? null : padding,
|
|
right: isTopLeft ? null : padding,
|
|
child: content,
|
|
);
|
|
}
|
|
Widget _buildCenterSymbols(SpiderCard card) {
|
|
final double symbolSize = width * 0.2;
|
|
final double bigSymbolSize = width * 0.7;
|
|
if (card.rank > 10) {
|
|
return Center(
|
|
child: Text(
|
|
card.suitSymbol,
|
|
style: TextStyle(
|
|
color: card.isRed ? Colors.red : Colors.black,
|
|
fontSize: bigSymbolSize,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
if (card.rank == 1) {
|
|
return Center(
|
|
child: Text(
|
|
card.suitSymbol,
|
|
style: TextStyle(
|
|
color: card.isRed ? Colors.red : Colors.black,
|
|
fontSize: bigSymbolSize * 0.8,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
List<Widget> symbols = [];
|
|
Widget symbol(Alignment align) {
|
|
return Align(
|
|
alignment: align,
|
|
child: Text(
|
|
card.suitSymbol,
|
|
style: TextStyle(
|
|
color: card.isRed ? Colors.red : Colors.black,
|
|
fontSize: symbolSize,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
Widget flippedSymbol(Alignment align) {
|
|
return Align(
|
|
alignment: align,
|
|
child: Transform.rotate(
|
|
angle: math.pi,
|
|
child: Text(
|
|
card.suitSymbol,
|
|
style: TextStyle(
|
|
color: card.isRed ? Colors.red : Colors.black,
|
|
fontSize: symbolSize,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
switch (card.rank) {
|
|
case 2:
|
|
symbols.add(symbol(Alignment.topCenter));
|
|
symbols.add(flippedSymbol(Alignment.bottomCenter));
|
|
break;
|
|
case 3:
|
|
symbols.add(symbol(Alignment.topCenter));
|
|
symbols.add(symbol(Alignment.center));
|
|
symbols.add(flippedSymbol(Alignment.bottomCenter));
|
|
break;
|
|
case 4:
|
|
symbols.add(symbol(Alignment.topLeft));
|
|
symbols.add(symbol(Alignment.topRight));
|
|
symbols.add(flippedSymbol(Alignment.bottomLeft));
|
|
symbols.add(flippedSymbol(Alignment.bottomRight));
|
|
break;
|
|
case 5:
|
|
symbols.addAll([
|
|
symbol(Alignment.topLeft),
|
|
symbol(Alignment.topRight),
|
|
symbol(Alignment.center),
|
|
flippedSymbol(Alignment.bottomLeft),
|
|
flippedSymbol(Alignment.bottomRight),
|
|
]);
|
|
break;
|
|
case 6:
|
|
symbols.addAll([
|
|
symbol(Alignment.topLeft),
|
|
symbol(Alignment.topRight),
|
|
symbol(Alignment.centerLeft),
|
|
symbol(Alignment.centerRight),
|
|
flippedSymbol(Alignment.bottomLeft),
|
|
flippedSymbol(Alignment.bottomRight),
|
|
]);
|
|
break;
|
|
case 7:
|
|
symbols.addAll([
|
|
symbol(Alignment.topLeft),
|
|
symbol(Alignment.topRight),
|
|
symbol(const Alignment(0.0, -0.25)),
|
|
symbol(Alignment.centerLeft),
|
|
symbol(Alignment.centerRight),
|
|
flippedSymbol(Alignment.bottomLeft),
|
|
flippedSymbol(Alignment.bottomRight),
|
|
]);
|
|
break;
|
|
case 8:
|
|
symbols.addAll([
|
|
symbol(Alignment.topLeft),
|
|
symbol(Alignment.topRight),
|
|
symbol(const Alignment(0.0, -0.25)),
|
|
symbol(Alignment.centerLeft),
|
|
symbol(Alignment.centerRight),
|
|
flippedSymbol(Alignment.bottomLeft),
|
|
flippedSymbol(Alignment.bottomRight),
|
|
flippedSymbol(const Alignment(0.0, 0.25)),
|
|
]);
|
|
break;
|
|
case 9:
|
|
symbols.addAll([
|
|
symbol(const Alignment(-0.8, -0.6)),
|
|
symbol(const Alignment(0.8, -0.6)),
|
|
symbol(const Alignment(-0.8, 0.0)),
|
|
symbol(const Alignment(0.8, 0.0)),
|
|
symbol(Alignment.center),
|
|
flippedSymbol(const Alignment(-0.8, 0.6)),
|
|
flippedSymbol(const Alignment(0.8, 0.6)),
|
|
symbol(const Alignment(0.0, -0.8)),
|
|
flippedSymbol(const Alignment(0.0, 0.8)),
|
|
]);
|
|
break;
|
|
case 10:
|
|
symbols.addAll([
|
|
symbol(const Alignment(-0.8, -0.7)),
|
|
symbol(const Alignment(0.8, -0.7)),
|
|
symbol(const Alignment(-0.8, -0.1)),
|
|
symbol(const Alignment(0.8, -0.1)),
|
|
symbol(const Alignment(0.0, -0.9)),
|
|
symbol(const Alignment(0.0, -0.4)),
|
|
flippedSymbol(const Alignment(-0.8, 0.7)),
|
|
flippedSymbol(const Alignment(0.8, 0.7)),
|
|
flippedSymbol(const Alignment(0.0, 0.9)),
|
|
flippedSymbol(const Alignment(0.0, 0.4)),
|
|
]);
|
|
break;
|
|
}
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: width * 0.2, vertical: height * 0.15),
|
|
child: Stack(children: symbols),
|
|
);
|
|
}
|
|
Widget _buildCardPlaceholder(BuildContext context) {
|
|
return Container(
|
|
width: width,
|
|
height: height,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
|
|
borderRadius: BorderRadius.circular(width * 0.08),
|
|
),
|
|
);
|
|
}
|
|
} |