이클립스 방어 횟수 즉시 표시·진동 · Music Shake 영상 재생 중 흔들기 판정 · 결과 공유

This commit is contained in:
Sn-Kinos
2026-10-05 21:39:22 +09:00
parent 65669b969e
commit 17c658b272
30 changed files with 1337 additions and 144 deletions
@@ -4,6 +4,7 @@ import 'dart:math' as math;
import 'dart:ui' show ImageFilter;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../core/data/backend.dart';
import '../../core/theme/tokens.dart';
@@ -56,6 +57,11 @@ class _EclipseDefendScreenState extends State<EclipseDefendScreen>
/// 서버가 받아들인 내 누적 참여량.
int _mine = 0;
/// 화면에 보이는 내 횟수 — 서버 확정값에 아직 보내지 않았거나 보내는 중인 흔들기를 더한다.
/// 흔드는 동안 서버 왕복(약 0.7초 묶음)을 기다리면 숫자가 늦게 올라 「얼마나 잘 흔들고
/// 있는지」 알 수 없었다. 확정값이 오면 그것으로 맞춰진다.
int get _shown => _mine + _pending + (_sending ? (_batch?.shakes ?? 0) : 0);
/// 상한에 걸려 깎인 적이 있는가. 한 번 알리고 다시 알리지 않는다.
bool _cappedNotified = false;
@@ -105,7 +111,15 @@ class _EclipseDefendScreenState extends State<EclipseDefendScreen>
_current?.canShake(DateTime.now()) != true ||
(_batch != null && !_sending))
return;
final qualify = _current?.qualifyShakes ?? 0;
final before = _shown;
setState(() => _pending += 1);
// 화면을 보지 않고 흔들어도 알 수 있게 — 한 번 셀 때마다 짧게, 인정 기준을 넘는 순간 길게.
if (qualify > 0 && before < qualify && _shown >= qualify) {
HapticFeedback.heavyImpact();
} else {
HapticFeedback.selectionClick();
}
_scheduleFlush();
}
@@ -256,7 +270,7 @@ class _EclipseDefendScreenState extends State<EclipseDefendScreen>
title: context.l10n.eclipseSuccessTitle,
success: true,
extra: [
if (_mine >= state.qualifyShakes) _Note(context.l10n.eclipseQualified),
if (_shown >= state.qualifyShakes) _Note(context.l10n.eclipseQualified),
if (_batch != null) _retryButton(),
],
);
@@ -283,7 +297,7 @@ class _EclipseDefendScreenState extends State<EclipseDefendScreen>
confirm: result != null,
extra: [
_Note(message),
if (_mine >= state.qualifyShakes) _Note(context.l10n.eclipseQualified),
if (_shown >= state.qualifyShakes) _Note(context.l10n.eclipseQualified),
if (_batch != null) _retryButton(),
],
);
@@ -424,16 +438,16 @@ class _EclipseDefendScreenState extends State<EclipseDefendScreen>
_Meter(
key: const ValueKey('eclipse-my-shakes'),
label:
'${context.l10n.eclipseMyShakes(formatPop(_mine), '${state.qualifyShakes}')}'
'${context.l10n.eclipseMyShakes(formatPop(_shown), '${state.qualifyShakes}')}'
'${_pending > 0
? context.l10n.eclipsePendingSuffix('$_pending')
: _sending
? context.l10n.eclipseSendingSuffix
: ''}'
'${_mine >= state.qualifyShakes ? ' · ${context.l10n.eclipseQualified}' : ''}',
'${_shown >= state.qualifyShakes ? ' · ${context.l10n.eclipseQualified}' : ''}',
value: state.qualifyShakes <= 0
? 0
: (_mine / state.qualifyShakes).clamp(0.0, 1.0),
: (_shown / state.qualifyShakes).clamp(0.0, 1.0),
color: AppColors.cosmicAccent,
),
const SizedBox(height: AppSpace.s3),