진행중 페이지까지 완료

This commit is contained in:
eld_master 2025-01-16 01:34:11 +09:00
parent 3cca3401ac
commit f416094820
5 changed files with 620 additions and 182 deletions

View File

@ -190,6 +190,15 @@ class _MainPageState extends State<MainPage> {
],
),
bottomNavigationBar: _isBannerReady && _bannerAd != null
? Container(
color: Colors.white,
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
)
: SizedBox.shrink(), // or
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -225,28 +234,6 @@ class _MainPageState extends State<MainPage> {
),
),
),
//
// () Container(...) _bannerAd
if (_isBannerReady && _bannerAd != null)
Container(
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
alignment: Alignment.center,
child: AdWidget(ad: _bannerAd!),
)
else
//
Container(
width: 300,
height: 50,
color: Colors.grey.shade400,
alignment: Alignment.center,
child: const Text(
'광고 로딩중',
style: TextStyle(color: Colors.black),
),
),
],
),
),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -9,6 +10,9 @@ import '../../dialogs/response_dialog.dart';
import '../../dialogs/score_edit_dialog.dart'; //
import '../../dialogs/user_info_basic_dialog.dart'; //
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../../config/config.dart';
class PlayingPrivatePage extends StatefulWidget {
final int roomSeq;
final String roomTitle;
@ -24,8 +28,20 @@ class PlayingPrivatePage extends StatefulWidget {
}
class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
// FRD
late DatabaseReference _roomRef;
Stream<DatabaseEvent>? _roomStream;
StreamSubscription<DatabaseEvent>? _roomStreamSubscription;
//
//
//
Timer? _countdownTimer;
Duration _remaining = const Duration(hours: 1); // 1
DateTime? _roomStartDt; // FRD의 roomInfo.room_start_dt
String _roomRunningTime = '0'; //
bool _roomTimeOut = false;
String _roomExitYn = 'N';
String roomMasterYn = 'N';
String roomTitle = '';
@ -34,11 +50,22 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
List<Map<String, dynamic>> _scoreList = [];
bool _isLoading = true;
//
bool _movedToFinishPage = false;
//
String scoreOpenRange = 'ALL';
String mySeq = '0';
// userListMap: { userSeq: true/false }
Map<String, bool> _userListMap = {};
/// (1)
BannerAd? _bannerAd;
bool _isBannerReady = false; //
String adUnitId = Config.testAdUnitId;
@override
void initState() {
super.initState();
@ -46,9 +73,40 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
FirebaseDatabase.instance.goOnline();
roomTitle = widget.roomTitle;
// (C)
_initBannerAd();
// (D)
_initFirebase();
}
@override
void dispose() {
_countdownTimer?.cancel();
_roomStreamSubscription?.cancel();
super.dispose();
}
///
void _initBannerAd() {
_bannerAd = BannerAd(
size: AdSize.banner, //
// adUnitId: 'ca-app-pub-3940256099942544/6300978111' ()
adUnitId: adUnitId, // / ID
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
setState(() => _isBannerReady = true);
},
onAdFailedToLoad: (Ad ad, LoadAdError err) {
ad.dispose();
},
),
request: const AdRequest(),
);
// load()
_bannerAd?.load();
}
Future<void> _initFirebase() async {
final prefs = await SharedPreferences.getInstance();
mySeq = prefs.getInt('my_user_seq')?.toString() ?? '0';
@ -83,15 +141,20 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
if (roomStatus == 'FINISH') {
//
if (mounted) {
Navigator.pushReplacement(
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => FinishPrivatePage(roomSeq: widget.roomSeq)),
(route) => false,
);
}
return;
}
setState(() {
//
_roomRunningTime = roomInfoData['running_time'] ?? '0';
//
scoreOpenRange = roomInfoData['score_open_range'] ?? 'ALL';
//
final masterSeq = roomInfoData['master_user_seq'];
roomMasterYn = (masterSeq != null && masterSeq.toString() == mySeq) ? 'Y' : 'N';
@ -111,6 +174,10 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
//
final List<Map<String, dynamic>> rawList = [];
userInfoData.forEach((uSeq, uData) {
//
if (uSeq.toString() == roomInfoData['master_user_seq'].toString()) uData['nickname'] = '' + (uData['nickname'] ?? '유저');
//
if ((uData['participant_type'] ?? '').toString().toUpperCase() == 'ADMIN') uData['nickname'] = '' + (uData['nickname'] ?? '유저');
rawList.add({
'user_seq': uSeq,
'participant_type': (uData['participant_type'] ?? '').toString().toUpperCase(),
@ -132,7 +199,7 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
}
// ADMIN
final playerList = rawList.where((u) => u['participant_type'] != 'ADMIN').toList();
final playerList = rawList.toList();
//
playerList.sort((a, b) {
final scoreA = a['score'] ?? 0;
@ -145,6 +212,33 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
_isLoading = false;
});
// =>
if (roomStatus == 'FINISH' && !_movedToFinishPage) {
_movedToFinishPage = true;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => FinishPrivatePage(
roomSeq: widget.roomSeq,
),
),
(route) => false,
);
return;
}
// room_start_dt
final roomStartDtStr = (roomInfoData['start_dt'] ?? '') as String;
if (roomStartDtStr.isNotEmpty) {
final dt = DateTime.tryParse(roomStartDtStr);
if (dt != null) {
setState(() {
_roomStartDt = dt;
});
_startCountdownTimer();
}
}
}, onError: (err) {
setState(() {
_isLoading = false;
@ -153,6 +247,43 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
});
}
//
void _startCountdownTimer() {
if (_countdownTimer != null && _countdownTimer!.isActive) {
return; //
}
if (_roomStartDt == null) return;
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// : roomStartDt +
final endTime = _roomStartDt!.add(Duration(hours: int.parse(_roomRunningTime)));
final now = DateTime.now();
final diff = endTime.difference(now);
if (diff.isNegative) {
// ->
timer.cancel();
_remaining = const Duration(seconds: 0);
_onAutoTimeout();
} else {
setState(() {
_remaining = diff;
});
}
});
}
//
void _onAutoTimeout() {
// => (leave API)
// =>
setState(() {
_roomTimeOut = true;
_roomExitYn = 'Y';
});
_requestFinish();
}
/// Finish API
Future<void> _requestFinish() async {
final reqBody = {
@ -258,14 +389,15 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
await userRef.set(false);
if (!mounted) return false;
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const MainPage()));
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const MainPage()), (route) => false);
return false;
}
///
Widget _buildScoreItem(Map<String, dynamic> user) {
final userSeq = user['user_seq'].toString();
final score = user['score'] ?? 0;
final tempScore = user['score'] ?? 0;
final score = (scoreOpenRange == 'ALL') ? tempScore : '-';
final nickname = user['nickname'] ?? '유저';
final bool isActive = _userListMap[userSeq] ?? true;
@ -342,8 +474,16 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
}
}
// ()
String _formatDuration(Duration d) {
final mm = d.inMinutes.remainder(60).toString().padLeft(2, '0');
final ss = d.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$mm:$ss';
}
@override
Widget build(BuildContext context) {
final countdownStr = _formatDuration(_remaining);
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
@ -355,9 +495,20 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
onPressed: () => _onWillPop(),
),
title: Text(
roomTitle.isNotEmpty ? roomTitle : '진행중 (개인전)',
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// :
Text(
roomTitle.isNotEmpty ? roomTitle : '방 제목',
style: const TextStyle(color: Colors.white),
),
// :
Text(
countdownStr, // : "10:23"
style: const TextStyle(color: Colors.white),
),
],
),
actions: [
if (roomMasterYn == 'Y')
@ -394,18 +545,16 @@ class _PlayingPrivatePageState extends State<PlayingPrivatePage> {
),
),
),
Container(
height: 50,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.black, width: 1),
),
child: const Center(
child: Text('구글 광고'),
),
),
],
),
bottomNavigationBar: _isBannerReady && _bannerAd != null
? Container(
color: Colors.white,
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
)
: SizedBox.shrink(), // or
),
);
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -9,6 +10,9 @@ import '../../dialogs/response_dialog.dart';
import '../../dialogs/score_edit_dialog.dart';
import '../../dialogs/user_info_basic_dialog.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../../config/config.dart';
class PlayingTeamPage extends StatefulWidget {
final int roomSeq;
final String roomTitle;
@ -24,8 +28,19 @@ class PlayingTeamPage extends StatefulWidget {
}
class _PlayingTeamPageState extends State<PlayingTeamPage> {
// FRD
late DatabaseReference _roomRef;
Stream<DatabaseEvent>? _roomStream;
StreamSubscription<DatabaseEvent>? _roomStreamSubscription;
//
//
//
Timer? _countdownTimer;
Duration _remaining = const Duration(hours: 1); // 1
DateTime? _roomStartDt; // FRD의 roomInfo.room_start_dt
String _roomRunningTime = '0'; // FRD의 roomInfo.running_time
bool _roomTimeOut = false;
String _roomExitYn = 'N';
String roomMasterYn = 'N';
String roomTitle = '';
@ -37,11 +52,22 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
Map<String, List<Map<String, dynamic>>> _teamMap = {};
bool _isLoading = true;
//
bool _movedToFinishPage = false;
String mySeq = '0';
// userListMap: { seq: true/false }
Map<String, bool> _userListMap = {};
//
String scoreOpenRange = 'ALL';
/// (1)
BannerAd? _bannerAd;
bool _isBannerReady = false; //
String adUnitId = Config.testAdUnitId;
@override
void initState() {
super.initState();
@ -49,9 +75,40 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
FirebaseDatabase.instance.goOnline();
roomTitle = widget.roomTitle;
// (C)
_initBannerAd();
// (D)
_initFirebase();
}
@override
void dispose() {
_countdownTimer?.cancel();
_roomStreamSubscription?.cancel();
super.dispose();
}
///
void _initBannerAd() {
_bannerAd = BannerAd(
size: AdSize.banner, //
// adUnitId: 'ca-app-pub-3940256099942544/6300978111' ()
adUnitId: adUnitId, // / ID
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
setState(() => _isBannerReady = true);
},
onAdFailedToLoad: (Ad ad, LoadAdError err) {
ad.dispose();
},
),
request: const AdRequest(),
);
// load()
_bannerAd?.load();
}
Future<void> _initFirebase() async {
final prefs = await SharedPreferences.getInstance();
mySeq = prefs.getInt('my_user_seq')?.toString() ?? '0';
@ -87,15 +144,22 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
if (roomStatus == 'FINISH') {
//
if (mounted) {
Navigator.pushReplacement(
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => FinishTeamPage(roomSeq: widget.roomSeq)),
(route) => false,
);
}
return;
}
setState(() {
//
_roomRunningTime = roomInfoData['running_time'] ?? '0';
//
scoreOpenRange = roomInfoData['score_open_range'] ?? 'ALL';
final masterSeq = roomInfoData['master_user_seq'];
roomMasterYn = (masterSeq != null && masterSeq.toString() == mySeq) ? 'Y' : 'N';
@ -112,6 +176,13 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
//
final List<Map<String, dynamic>> rawList = [];
userInfoData.forEach((uSeq, uData) {
//
if (uSeq.toString() == roomInfoData['master_user_seq'].toString()) {
uData['nickname'] = '' + (uData['nickname'] ?? '유저');
} else if ((uData['participant_type'] ?? '').toString().toUpperCase() == 'ADMIN') {
//
uData['nickname'] = '' + (uData['nickname'] ?? '유저');
}
rawList.add({
'user_seq': uSeq,
'participant_type': (uData['participant_type'] ?? '').toString().toUpperCase(),
@ -151,7 +222,7 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
for (var user in rawList) {
final pType = user['participant_type'];
final tName = (user['team_name'] ?? 'WAIT');
if (pType == 'ADMIN') continue;
// if (pType == 'ADMIN') continue;
if (tName == 'WAIT') continue;
tMap.putIfAbsent(tName, () => []);
@ -173,6 +244,33 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
_isLoading = false;
});
// =>
if (roomStatus == 'FINISH' && !_movedToFinishPage) {
_movedToFinishPage = true;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => FinishTeamPage(
roomSeq: widget.roomSeq,
),
),
(route) => false,
);
return;
}
// room_start_dt
final roomStartDtStr = (roomInfoData['start_dt'] ?? '') as String;
if (roomStartDtStr.isNotEmpty) {
final dt = DateTime.tryParse(roomStartDtStr);
if (dt != null) {
setState(() {
_roomStartDt = dt;
});
_startCountdownTimer();
}
}
}, onError: (err) {
setState(() {
_isLoading = false;
@ -181,6 +279,43 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
});
}
//
void _startCountdownTimer() {
if (_countdownTimer != null && _countdownTimer!.isActive) {
return; //
}
if (_roomStartDt == null) return;
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// : roomStartDt +
final endTime = _roomStartDt!.add(Duration(hours: int.parse(_roomRunningTime)));
final now = DateTime.now();
final diff = endTime.difference(now);
if (diff.isNegative) {
// ->
timer.cancel();
_remaining = const Duration(seconds: 0);
_onAutoTimeout();
} else {
setState(() {
_remaining = diff;
});
}
});
}
//
void _onAutoTimeout() {
// => (leave API)
// =>
setState(() {
_roomTimeOut = true;
_roomExitYn = 'Y';
});
_requestFinish();
}
///
Future<void> _requestFinish() async {
final body = {
@ -282,20 +417,39 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
await userRef.set(false);
if (!mounted) return false;
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const MainPage()));
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const MainPage()), (route) => false);
return false;
}
// ()
String _formatDuration(Duration d) {
final mm = d.inMinutes.remainder(60).toString().padLeft(2, '0');
final ss = d.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$mm:$ss';
}
@override
Widget build(BuildContext context) {
final countdownStr = _formatDuration(_remaining);
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(
roomTitle.isNotEmpty ? roomTitle : '진행중 (팀전)',
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// :
Text(
roomTitle.isNotEmpty ? roomTitle : '방 제목',
style: const TextStyle(color: Colors.white),
),
// :
Text(
countdownStr, // : "10:23"
style: const TextStyle(color: Colors.white),
),
],
),
backgroundColor: Colors.black,
elevation: 0,
@ -352,19 +506,16 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
),
),
),
Container(
height: 50,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.black, width: 1),
),
child: const Center(
child: Text('구글 광고', style: TextStyle(color: Colors.black)),
),
),
],
),
bottomNavigationBar: _isBannerReady && _bannerAd != null
? Container(
color: Colors.white,
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
)
: SizedBox.shrink(), // or
),
);
}
@ -372,7 +523,8 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
Widget _buildTeamSection(String teamName) {
final upperName = teamName.toUpperCase();
final members = _teamMap[upperName] ?? [];
final teamScore = _teamScoreMap[upperName] ?? 0;
final tempTeamScore = _teamScoreMap[upperName] ?? 0;
final teamScore = (scoreOpenRange == 'TEAM' || scoreOpenRange == 'ALL') ? tempTeamScore : '-';
return Container(
width: double.infinity,
@ -407,7 +559,8 @@ class _PlayingTeamPageState extends State<PlayingTeamPage> {
Widget _buildTeamMemberItem(Map<String, dynamic> userData) {
final userSeq = userData['user_seq'].toString();
final score = userData['score'] ?? 0;
final tempScore = userData['score'] ?? 0;
final score = (scoreOpenRange == 'ALL') ? tempScore : '-';
final nickname= userData['nickname'] ?? '유저';
final bool isActive = _userListMap[userSeq] ?? true;

View File

@ -11,6 +11,12 @@ import '../../dialogs/room_setting_dialog.dart';
import '../../dialogs/user_info_private_dialog.dart';
import 'playing_private_page.dart';
//
import 'package:google_mobile_ads/google_mobile_ads.dart';
//
import '../../config/config.dart';
class WaitingRoomPrivatePage extends StatefulWidget {
final int roomSeq;
final String roomType; // "private"
@ -62,13 +68,54 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
Timer? _countdownTimer;
Duration _remaining = const Duration(hours: 1); // 1
DateTime? _createDt; // FRD의 roomInfo.create_dt
bool _roomTimeOut = false;
String _roomExitYn = 'N';
/// (1)
BannerAd? _bannerAd;
bool _isBannerReady = false; //
String adUnitId = Config.testAdUnitId;
// SEQ
String _masterSeqString = '';
@override
void initState() {
super.initState();
// FRD
FirebaseDatabase.instance.goOnline();
// (B)
_initRoomRef();
// (C)
_initBannerAd();
}
@override
void dispose() {
_countdownTimer?.cancel();
_roomStreamSubscription?.cancel();
super.dispose();
}
///
void _initBannerAd() {
_bannerAd = BannerAd(
size: AdSize.banner, //
// adUnitId: 'ca-app-pub-3940256099942544/6300978111' ()
adUnitId: adUnitId, // / ID
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
setState(() => _isBannerReady = true);
},
onAdFailedToLoad: (Ad ad, LoadAdError err) {
ad.dispose();
},
),
request: const AdRequest(),
);
// load()
_bannerAd?.load();
}
Future<void> _initRoomRef() async {
@ -80,7 +127,9 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
// onDisconnect + connect_yn='Y'
final myUserRef = _roomRef.child('userInfo').child(mySeq);
if (_roomRef.child('userList').child(mySeq) == true) {
myUserRef.onDisconnect().update({'connect_yn': 'N'});
}
await myUserRef.update({'connect_yn': 'Y'});
_listenRoomData();
@ -96,15 +145,39 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
roomTitle = '방 정보 없음';
_userList = [];
});
_roomMasterLeave();
return;
}
final data = snapshot.value as Map<dynamic, dynamic>? ?? {};
final roomInfoData = data['roomInfo'] as Map<dynamic, dynamic>? ?? {};
final userInfoData = data['userInfo'] as Map<dynamic, dynamic>? ?? {};
final userInfoDynamic = data['userInfo']; // List일
final roomStatus = (roomInfoData['room_status'] ?? 'WAIT').toString().toUpperCase();
final tempList = <Map<String, dynamic>>[];
// userList
if (userInfoDynamic is Map) {
userInfoDynamic.forEach((key, val) {
if (val is Map) {
final tempUserSeq = val['user_seq'] ?? '0';
if (tempUserSeq != '0') {
tempList.add({
'user_seq': tempUserSeq,
'participant_type': val['participant_type'] ?? '',
'nickname': val['nickname'] ?? '유저',
'team_name': val['team_name'] ?? '',
'score': val['score'] ?? 0,
'profile_img': val['profile_img'] ?? '',
'introduce_myself': val['introduce_myself'] ?? '',
'ready_yn': (val['ready_yn'] ?? 'N').toString().toUpperCase(),
'connect_yn': (val['connect_yn'] ?? 'Y').toString().toUpperCase(),
});
}
}
});
}
// (A) roomInfo
setState(() {
roomTitle = (roomInfoData['room_title'] ?? '') as String;
@ -122,20 +195,12 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
roomMasterYn = 'Y';
}
// userList
final tempList = <Map<String, dynamic>>[];
userInfoData.forEach((userSeq, userMap) {
tempList.add({
'user_seq': userSeq,
'participant_type': userMap['participant_type'] ?? '',
'nickname': userMap['nickname'] ?? '유저',
'score': userMap['score'] ?? 0,
'profile_img': userMap['profile_img'] ?? '',
'introduce_myself': userMap['introduce_myself'] ?? '',
'ready_yn': (userMap['ready_yn'] ?? 'N').toString().toUpperCase(),
'connect_yn': (userMap['connect_yn'] ?? 'Y').toString().toUpperCase(),
});
});
if (masterSeq != null) {
_masterSeqString = masterSeq.toString();
} else {
_masterSeqString = '';
}
_userList = tempList;
_isLoading = false;
});
@ -143,7 +208,7 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
// (B) =>
if (roomStatus == 'RUNNING' && !_movedToRunningPage) {
_movedToRunningPage = true;
Navigator.pushReplacement(
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => PlayingPrivatePage(
@ -151,6 +216,7 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
roomTitle: roomTitle,
),
),
(route) => false,
);
return;
}
@ -158,10 +224,14 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
// (C) 1 create_dt
// : "2025-01-07T06:38:10.123456"
final createDtStr = (roomInfoData['create_dt'] ?? '') as String;
if (createDtStr.isNotEmpty && createDtStr.contains('T')) {
final dt = DateTime.tryParse(createDtStr);
if (createDtStr.isNotEmpty) {
final dotIndex = createDtStr.indexOf('.');
final isoStr = createDtStr.substring(0, dotIndex);
final dt = DateTime.tryParse(isoStr);
if (dt != null) {
setState(() {
_createDt = dt;
});
_startCountdownTimer();
}
}
@ -170,11 +240,25 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
final amIStillHere = _userList.any((u) => u['user_seq'].toString() == mySeq);
if (!amIStillHere && !_kickedOut && roomMasterYn != 'Y') {
_kickedOut = true;
if (_roomExitYn == 'N') {
showResponseDialog(context, '안내', '강퇴되었습니다.');
}
Future.delayed(Duration.zero, () async {
await showResponseDialog(context, '안내', '강퇴되었습니다.');
Navigator.pushReplacement(
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
(route) => false,
);
});
}
// (D)
if (_roomTimeOut) {
showResponseDialog(context, '안내', '방 제한시간이 종료되었습니다.');
Future.delayed(Duration.zero, () async {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
(route) => false,
);
});
}
@ -186,6 +270,18 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
});
}
//
void _roomMasterLeave() {
Future.delayed(Duration.zero, () async {
await showResponseDialog(context, '안내', '방장이 나갔습니다.');
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
(route) => false,
);
});
}
// 1
void _startCountdownTimer() {
if (_countdownTimer != null && _countdownTimer!.isActive) {
@ -216,11 +312,11 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
void _onAutoTimeout() {
// => (leave API)
// =>
if (roomMasterYn == 'Y') {
setState(() {
_roomTimeOut = true;
_roomExitYn = 'Y';
});
_requestLeaveRoom();
} else {
_requestLeaveRoom();
}
}
Future<void> _requestLeaveRoom() async {
@ -232,17 +328,10 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
//
}
if (mounted) {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const MainPage()));
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const MainPage()), (route) => false);
}
}
@override
void dispose() {
_countdownTimer?.cancel();
_roomStreamSubscription?.cancel();
super.dispose();
}
///
Future<void> _onLeaveRoom() async {
if (roomMasterYn == 'Y') {
@ -292,9 +381,11 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
if (confirm != true) return;
// leave API
setState(() {_roomExitYn = 'Y';});
await _requestLeaveRoom();
} else {
//
setState(() {_roomExitYn = 'Y';});
await _requestLeaveRoom();
}
}
@ -472,25 +563,34 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
backgroundColor: Colors.black,
elevation: 0,
// +
title: Text(
(roomTitle.isNotEmpty ? roomTitle : '방 제목') + ' [$countdownStr]',
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// :
Text(
roomTitle.isNotEmpty ? roomTitle : '방 제목',
style: const TextStyle(color: Colors.white),
),
// :
Text(
countdownStr, // : "10:23"
style: const TextStyle(color: Colors.white),
),
],
),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
onPressed: _onLeaveRoom,
),
),
bottomNavigationBar: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.grey.shade300,
border: Border.all(color: Colors.black, width: 1),
),
child: const Center(
child: Text('구글 광고', style: TextStyle(color: Colors.black)),
),
),
bottomNavigationBar: _isBannerReady && _bannerAd != null
? Container(
color: Colors.white,
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
)
: SizedBox.shrink(), // or
body: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
@ -502,10 +602,10 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
_buildTopButtons(),
const SizedBox(height: 20),
const Text('사회자', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
_buildAdminSection(),
const SizedBox(height: 20),
// const Text('사회자', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
// const SizedBox(height: 8),
// _buildAdminSection(),
// const SizedBox(height: 20),
const Text('참가자', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
@ -516,36 +616,38 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
);
}
Widget _buildAdminSection() {
final adminList = _userList.where((u) {
final t = (u['participant_type'] ?? '').toString().toUpperCase();
return t == 'ADMIN';
}).toList();
// Widget _buildAdminSection() {
// final adminList = _userList.where((u) {
// final t = (u['participant_type'] ?? '').toString().toUpperCase();
// return t == 'ADMIN';
// }).toList();
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(8),
),
child: adminList.isEmpty
? const Text('사회자가 없습니다.')
: Wrap(
spacing: 16,
runSpacing: 8,
children: adminList.map(_buildSeat).toList(),
),
);
}
// return Container(
// padding: const EdgeInsets.all(8),
// decoration: BoxDecoration(
// color: Colors.white,
// border: Border.all(color: Colors.black),
// borderRadius: BorderRadius.circular(8),
// ),
// child: adminList.isEmpty
// ? const Text('사회자가 없습니다.')
// : Wrap(
// spacing: 16,
// runSpacing: 8,
// children: adminList.map(_buildSeat).toList(),
// ),
// );
// }
Widget _buildPlayerSection() {
final playerList = _userList.where((u) {
final t = (u['participant_type'] ?? '').toString().toUpperCase();
return t != 'ADMIN';
final t = (u['user_seq'] ?? null);
return t != null;
}).toList();
// final playerList = _userList;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
@ -574,6 +676,24 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
final bool isDisconnected = (connectYn == 'N');
final bool isMaster = (roomMasterYn == 'Y');
// user가
final isRoomMasterUser = (userData['user_seq']?.toString() ?? '') == _masterSeqString;
// user가
final participantType = (userData['participant_type'] ?? '').toString().toUpperCase();
final isAdmin = (participantType == 'ADMIN');
//
String roleIcon = '';
if (isRoomMasterUser) {
//
roleIcon = '';
} else if (isAdmin) {
//
roleIcon = '';
}
final displayName = '$roleIcon$userName';
return GestureDetector(
onTap: () async {
final result = await showDialog(
@ -648,7 +768,7 @@ class _WaitingRoomPrivatePageState extends State<WaitingRoomPrivatePage> {
),
),
const SizedBox(height: 4),
Text(userName, style: const TextStyle(fontSize: 12)),
Text(displayName, style: const TextStyle(fontSize: 12, color: Colors.black)),
],
),
),

View File

@ -63,8 +63,6 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
bool _movedToRunningPage = false;
bool _kickedOut = false;
bool _roomTimeOut = false;
String _roomTimeOutMsg = '';
String mySeq = '0';
@ -72,6 +70,8 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
Timer? _countdownTimer;
Duration _remaining = const Duration(hours: 1);
DateTime? _createDt;
bool _roomTimeOut = false;
String _roomExitYn = 'N';
/// (1)
BannerAd? _bannerAd;
@ -100,7 +100,9 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
// onDisconnect + connect_yn='Y'
final myUserRef = _roomRef.child('userInfo').child(mySeq);
if (_roomRef.child('userList').child(mySeq) == true) {
myUserRef.onDisconnect().update({'connect_yn': 'N'});
}
await myUserRef.update({'connect_yn': 'Y'});
_listenRoomData();
@ -146,6 +148,7 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
roomTitle = '방 정보 없음';
_userList = [];
});
_roomMasterLeave();
return;
}
@ -154,10 +157,14 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
// final userInfoData = data['userInfo'] as Map<dynamic, dynamic>? ?? {};
final userInfoDynamic = data['userInfo']; // List일
final roomStatus = (roomInfoData['room_status'] ?? 'WAIT').toString().toUpperCase();
final tempList = <Map<String, dynamic>>[];
// userList
if (userInfoDynamic is Map) {
userInfoDynamic.forEach((key, val) {
final tempUserSeq = val['user_seq'] ?? '0';
if (tempUserSeq != '0') {
if (val is Map) {
tempList.add({
'user_seq': val['user_seq'].toString() ?? '0',
@ -171,11 +178,10 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
'connect_yn': (val['connect_yn'] ?? 'Y').toString().toUpperCase(),
});
}
}
});
}
final roomStatus = (roomInfoData['room_status'] ?? 'WAIT').toString().toUpperCase();
setState(() {
roomTitle = (roomInfoData['room_title'] ?? '') as String;
roomIntro = (roomInfoData['room_intro'] ?? '') as String;
@ -232,14 +238,7 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
if (createDtStr.isNotEmpty) {
final dotIndex = createDtStr.indexOf('.');
final isoStr = createDtStr.substring(0, dotIndex);
print('isoStr: $isoStr');
final dt1 = DateTime.tryParse(createDtStr);
print('dt1: $dt1');
final dt = DateTime.tryParse(isoStr);
print('dt: $dt');
final dt2 = DateTime.parse(isoStr);
print('dt2: $dt2');
if (dt != null) {
setState(() {
_createDt = dt;
@ -248,17 +247,25 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
}
}
//
// (D) =>
final amIStillHere = _userList.any((u) => u['user_seq'].toString() == mySeq);
if (!amIStillHere && !_kickedOut && roomMasterYn != 'Y') {
_kickedOut = true;
if (_roomTimeOut) {
_roomTimeOutMsg = '방장이 나갔습니다.';
} else if (_kickedOut) {
_roomTimeOutMsg = '강퇴되었습니다.';
if (_roomExitYn == 'N') {
showResponseDialog(context, '안내', '강퇴되었습니다.');
}
Future.delayed(Duration.zero, () async {
await showResponseDialog(context, '안내', _roomTimeOutMsg);
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
(route) => false,
);
});
}
// (D)
if (_roomTimeOut) {
showResponseDialog(context, '안내', '방 제한시간이 종료되었습니다.');
Future.delayed(Duration.zero, () async {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
@ -274,6 +281,18 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
});
}
//
void _roomMasterLeave() {
Future.delayed(Duration.zero, () async {
await showResponseDialog(context, '안내', '방장이 나갔습니다.');
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
(route) => false,
);
});
}
//
void _startCountdownTimer() {
if (_countdownTimer != null && _countdownTimer!.isActive) {
@ -282,8 +301,7 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
if (_createDt == null) return;
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// final endTime = _createDt!.add(const Duration(hours: 1));
final endTime = _createDt!.add(const Duration(minutes: 1));
final endTime = _createDt!.add(const Duration(hours: 1));
final now = DateTime.now();
final diff = endTime.difference(now);
@ -303,6 +321,7 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
// -> =(), =
setState(() {
_roomTimeOut = true;
_roomExitYn = 'Y';
});
_requestLeaveRoom();
}
@ -371,8 +390,11 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
);
if (confirm != true) return;
// leave API
setState(() {_roomExitYn = 'Y';});
await _requestLeaveRoom();
} else {
setState(() {_roomExitYn = 'Y';});
await _requestLeaveRoom();
}
}
@ -514,6 +536,13 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
setState(() => _isServerRequestLoading = false);
return;
}
//
final notTeam = _userList.any((u) => (u['team_name'] ?? '').toString().toUpperCase() == 'WAIT');
if (notTeam) {
showResponseDialog(context, '안내', '팀 배정이 안된 참가자가 있습니다.');
setState(() => _isServerRequestLoading = false);
return;
}
final requestBody = {
"room_seq": "${widget.roomSeq}",
@ -527,11 +556,11 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
print('게임 시작 요청 성공(팀전)');
} else {
//
showResponseDialog(context, '오류', '게임 시작 요청에 실패했습니다.');
showResponseDialog(context, response['response_info']['msg_title'], response['response_info']['msg_content']);
}
} else {
//
showResponseDialog(context, '오류', '게임 시작 요청에 실패했습니다.');
showResponseDialog(context, response['response_info']['msg_title'], response['response_info']['msg_content']);
}
} catch (e) {
//
@ -637,14 +666,15 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
// }
Widget _buildTeamSection() {
// final players = _userList.where((u) {
// final pType = (u['participant_type'] ?? '').toString().toUpperCase();
// return (pType != 'ADMIN');
// }).toList();
final players = _userList.toList();
final players = _userList.where((u) {
final t = (u['user_seq'] ?? null);
return t != null;
}).toList();
// final players = _userList.toList();
final Map<String, List<Map<String, dynamic>>> teamMap = {};
for (final tName in _teamNameList) {
if (tName == 'WAIT') continue;
teamMap[tName] = [];
}
@ -670,6 +700,7 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _teamNameList.map((teamName) {
if (teamName == 'WAIT') return const SizedBox();
final members = teamMap[teamName]!;
return Container(
@ -725,10 +756,8 @@ class _WaitingRoomTeamPageState extends State<WaitingRoomTeamPage> {
Widget _buildWaitSection() {
final waitList = _userList.where((u) {
final pType = (u['participant_type'] ?? '').toString().toUpperCase();
// if (pType == 'ADMIN') return false;
final tName = (u['team_name'] ?? '').toString().toUpperCase();
return (tName.isEmpty || tName == 'WAIT');
return tName == 'WAIT';
}).toList();
if (waitList.isEmpty) return const SizedBox();