성운 상세 실시간 기여 랭킹에 실제 기여자 표시
This commit is contained in:
@@ -11,8 +11,13 @@ import '../../core/i18n/app_locale.dart';
|
||||
|
||||
/// 수량·상한·중복 여부는 서버가 결정한다. 단말은 참여 의사와 결과만 전달한다.
|
||||
class HeroParticipationSheet extends StatefulWidget {
|
||||
const HeroParticipationSheet({super.key, required this.fandomId,
|
||||
required this.watch, required this.repository, this.accelerometer});
|
||||
const HeroParticipationSheet({
|
||||
super.key,
|
||||
required this.fandomId,
|
||||
required this.watch,
|
||||
required this.repository,
|
||||
this.accelerometer,
|
||||
});
|
||||
final String fandomId;
|
||||
final PlanetWatch watch;
|
||||
final PlanetRepository repository;
|
||||
@@ -39,10 +44,17 @@ class _HeroParticipationSheetState extends State<HeroParticipationSheet>
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_sensor = (widget.accelerometer ?? accelerometerEventStream()).listen((event) {
|
||||
final magnitude = math.sqrt(event.x * event.x + event.y * event.y + event.z * event.z);
|
||||
_sensor = (widget.accelerometer ?? accelerometerEventStream()).listen((
|
||||
event,
|
||||
) {
|
||||
final magnitude = math.sqrt(
|
||||
event.x * event.x + event.y * event.y + event.z * event.z,
|
||||
);
|
||||
final now = DateTime.now();
|
||||
if (!magnitude.isFinite || magnitude < 18 || now.difference(_lastShake).inMilliseconds < 220) return;
|
||||
if (!magnitude.isFinite ||
|
||||
magnitude < 18 ||
|
||||
now.difference(_lastShake).inMilliseconds < 220)
|
||||
return;
|
||||
_lastShake = now;
|
||||
if (!_failed) _claim();
|
||||
}, onError: (_) {}); // 센서가 없어도 동일한 서버 경로의 참여 버튼을 제공한다.
|
||||
@@ -62,21 +74,39 @@ class _HeroParticipationSheetState extends State<HeroParticipationSheet>
|
||||
|
||||
String? get _next {
|
||||
final state = _state;
|
||||
if (_accessDenied || state?.isHeroTime != true || state?.heroEndsAt?.isAfter(DateTime.now()) != true) return null;
|
||||
final ids = (state?.heroEventIds ?? {}).where((id) => !_received.containsKey(id) && !_unavailable.contains(id)).toList()..sort();
|
||||
if (_accessDenied ||
|
||||
state?.isHeroTime != true ||
|
||||
state?.heroEndsAt?.isAfter(DateTime.now()) != true)
|
||||
return null;
|
||||
final ids =
|
||||
(state?.heroEventIds ?? {})
|
||||
.where(
|
||||
(id) => !_received.containsKey(id) && !_unavailable.contains(id),
|
||||
)
|
||||
.toList()
|
||||
..sort();
|
||||
return ids.firstOrNull;
|
||||
}
|
||||
|
||||
Future<void> _claim() async {
|
||||
if (_sending || !_foreground || ModalRoute.of(context)?.isCurrent != true) return;
|
||||
if (_sending || !_foreground || ModalRoute.of(context)?.isCurrent != true)
|
||||
return;
|
||||
// 응답 유실 재확인은 이벤트 종료 뒤에도 같은 멱등키로 요청한다.
|
||||
final id = _pendingId ?? _next;
|
||||
if (id == null) return;
|
||||
setState(() { _sending = true; _failed = false; _notice = null; _pendingId = id; });
|
||||
setState(() {
|
||||
_sending = true;
|
||||
_failed = false;
|
||||
_notice = null;
|
||||
_pendingId = id;
|
||||
});
|
||||
try {
|
||||
final receipt = await widget.repository.claimHeroDrop(id);
|
||||
if (!mounted) return;
|
||||
setState(() { _received[id] = receipt; _pendingId = null; });
|
||||
setState(() {
|
||||
_received[id] = receipt;
|
||||
_pendingId = null;
|
||||
});
|
||||
if (_foreground) HapticFeedback.lightImpact();
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
@@ -109,28 +139,54 @@ class _HeroParticipationSheetState extends State<HeroParticipationSheet>
|
||||
_state = state;
|
||||
final available = _next != null;
|
||||
final total = _received.values.fold<int>(0, (sum, r) => sum + r.amount);
|
||||
return SafeArea(child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text('HERO TIME · ZAP+', style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
Text(context.l10n.heroGiftHowTo),
|
||||
Text(context.l10n.heroGiftPopNote),
|
||||
const SizedBox(height: 16),
|
||||
if (_received.isNotEmpty)
|
||||
Semantics(liveRegion: true, child: Text(context.l10n.heroGiftReceived('$total'), style: Theme.of(context).textTheme.titleMedium)),
|
||||
if (_notice case final notice?) Semantics(liveRegion: true, child: Text(notice)),
|
||||
if (_failed) Text(context.l10n.heroGiftUnknown),
|
||||
if (!available && !_sending && !_failed && _notice == null)
|
||||
Text(_received.isEmpty ? context.l10n.heroGiftNone : context.l10n.heroGiftConfirmed),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.icon(
|
||||
onPressed: _sending || (!available && !_failed) ? null : _claim,
|
||||
icon: const Icon(Icons.auto_awesome),
|
||||
label: Text(_sending ? context.l10n.heroGiftChecking : _failed ? context.l10n.heroGiftRetry : context.l10n.heroGiftTap),
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'HERO TIME · ZAP+',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(context.l10n.heroGiftHowTo),
|
||||
Text(context.l10n.heroGiftPopNote),
|
||||
const SizedBox(height: 16),
|
||||
if (_received.isNotEmpty)
|
||||
Semantics(
|
||||
liveRegion: true,
|
||||
child: Text(
|
||||
context.l10n.heroGiftReceived('$total'),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
if (_notice case final notice?)
|
||||
Semantics(liveRegion: true, child: Text(notice)),
|
||||
if (_failed) Text(context.l10n.heroGiftUnknown),
|
||||
if (!available && !_sending && !_failed && _notice == null)
|
||||
Text(
|
||||
_received.isEmpty
|
||||
? context.l10n.heroGiftNone
|
||||
: context.l10n.heroGiftConfirmed,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.icon(
|
||||
onPressed: _sending || (!available && !_failed) ? null : _claim,
|
||||
icon: const Icon(Icons.auto_awesome),
|
||||
label: Text(
|
||||
_sending
|
||||
? context.l10n.heroGiftChecking
|
||||
: _failed
|
||||
? context.l10n.heroGiftRetry
|
||||
: context.l10n.heroGiftTap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
));
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user