이클립스 방어 횟수 즉시 표시·진동 · Music Shake 영상 재생 중 흔들기 판정 · 결과 공유
This commit is contained in:
@@ -6,9 +6,11 @@
|
||||
/// - 퀘스트 생성 `523:18152`~`523:18188` · Admin 정의 `567:19531`
|
||||
library;
|
||||
|
||||
import '../../core/data/backend.dart' show BackendException;
|
||||
import '../../core/monetization/rewarded_ad.dart';
|
||||
import '../../core/i18n/app_locale.dart';
|
||||
import 'place_picker_screen.dart' show PlacePickerScreen;
|
||||
import 'quest_evidence.dart' show ShakeJudge, ShakeWindows;
|
||||
|
||||
/// 진행 상태 — 명세가 **다섯 가지**를 못박았다.
|
||||
///
|
||||
@@ -112,13 +114,14 @@ enum QuestType {
|
||||
int? rewardFrom(Map<String, dynamic> quest, Map<String, dynamic> policy) {
|
||||
// 시청 · 스트리밍은 무보상 참여다(C-04) — 문서 값과 무관하게 0.
|
||||
if (this == QuestType.streaming) return 0;
|
||||
// Music Shake 는 BPM 판정 성공 시 일반 POP(원장 B-24). 서버(`fn-quest-verify-music-shake`)가
|
||||
// 퀘스트의 `rewardPop` 을, 없으면 `config/policy.stepsPop` 을 기본액으로 쓰고 판정 등급
|
||||
// 배수를 곱한다. 화면은 기본액(C 등급)을 적는다 — 운영값이 둘 다 없을 때만 중립 문구.
|
||||
// Music Shake 는 BPM 판정 성공 시 일반 POP(원장 B-24 · 보상액 운영값). 서버
|
||||
// (`fn-quest-verify-music-shake`)가 퀘스트의 `rewardPop` 을, 없으면
|
||||
// `config/policy.musicShake.pop` 을 기본액으로 쓴다. 운영값이 둘 다 없을 때만 중립 문구.
|
||||
final shake = policy['musicShake'];
|
||||
final raw = this == QuestType.attend
|
||||
? policy['attendPop']
|
||||
: this == QuestType.musicShake
|
||||
? quest['rewardPop'] ?? policy['stepsPop']
|
||||
? quest['rewardPop'] ?? (shake is Map ? shake['pop'] : null)
|
||||
: quest['rewardPop'];
|
||||
return raw is num ? raw.round() : null;
|
||||
}
|
||||
@@ -130,8 +133,9 @@ enum QuestType {
|
||||
/// 내부 사유(질의 번호 등)는 화면에 내보내지 않는다 — 사용자에게는 한 문구다.
|
||||
bool get verifiable => this != QuestType.unknown;
|
||||
|
||||
/// 시작 뒤 진행도를 모아야 판정할 수 있는 유형 — 걸음(누적 걸음) · Music Shake(구간 판정).
|
||||
bool get gathers => this == QuestType.steps || this == QuestType.musicShake;
|
||||
/// 시작 뒤 진행도를 모아야 판정할 수 있는 유형 — 걸음(누적 걸음).
|
||||
/// Music Shake 는 영상을 보며 흔드는 별도 진행 화면(`MusicShakePlayScreen`)에서 모은다.
|
||||
bool get gathers => this == QuestType.steps;
|
||||
|
||||
/// 참여를 열지 않는 사유. 열려 있으면 null 이다.
|
||||
String? get blockedReason =>
|
||||
@@ -375,6 +379,30 @@ class QuestCard {
|
||||
}
|
||||
|
||||
/// 상세 화면이 필요로 하는 것.
|
||||
/// Music Shake 한 판의 준비값 — 퀘스트 문서와 운영값에서 읽는다(지어내지 않는다).
|
||||
class MusicShakeSetup {
|
||||
const MusicShakeSetup({
|
||||
required this.videoId,
|
||||
required this.title,
|
||||
required this.bpm,
|
||||
required this.segments,
|
||||
this.windows = const ShakeWindows(),
|
||||
});
|
||||
|
||||
/// 지정 공식 YouTube 영상(PRD-04 MVP 수용 기준) — `quests.link.url` 에서 꺼낸 ID.
|
||||
final String videoId;
|
||||
|
||||
/// 곡 제목 — `quests.link.title`(링크 원본 제목 · C-05).
|
||||
final String title;
|
||||
|
||||
/// `quests.bpm` — Figma 「목표BPM」. 🟡 의미 · 입력 주체 회신 대기(Q-MS-01 · Q-MS-02).
|
||||
final double bpm;
|
||||
|
||||
/// `quests.totalSegments` — 판정 박 수. 🟡 구간 패턴 정의 회신 대기(Q-MS-02).
|
||||
final int segments;
|
||||
final ShakeWindows windows;
|
||||
}
|
||||
|
||||
class QuestDetail {
|
||||
const QuestDetail({
|
||||
required this.card,
|
||||
@@ -517,6 +545,16 @@ abstract interface class QuestRepository {
|
||||
/// 센서가 없으면 오류로 끊는다.
|
||||
Stream<int> progressStream(QuestCard card);
|
||||
|
||||
/// Music Shake 한 판의 준비값 — 공식 YouTube 영상 · BPM · 구간 수 · 판정 창(운영값).
|
||||
/// 퀘스트에 값이 빠져 있으면 E-QST-01 이다(값을 지어내지 않는다).
|
||||
Future<MusicShakeSetup> musicShakeSetup(String questId);
|
||||
|
||||
/// 단말 흔들기 시각 스트림(가속도계).
|
||||
Stream<DateTime> shakeEvents();
|
||||
|
||||
/// 끝난 판의 판정표를 남긴다. 다음 [verify] 가 서버에 증거로 보낸다.
|
||||
void recordShake(ShakeJudge run);
|
||||
|
||||
/// 활동 증거를 서버에 보내 판정·지급을 받는다.
|
||||
///
|
||||
/// **판정도 지급도 서버가 한다.** 앱은 증거(걸음 수·좌표)만 싣고, 실패는
|
||||
@@ -742,6 +780,17 @@ class SampleQuestRepository implements QuestRepository {
|
||||
@override
|
||||
Stream<int> progressStream(QuestCard card) => const Stream<int>.empty();
|
||||
|
||||
/// 표본에는 영상 · BPM 이 없다 — 참여를 열지 않는다.
|
||||
@override
|
||||
Future<MusicShakeSetup> musicShakeSetup(String questId) async =>
|
||||
throw BackendException('E-QST-01', appL10n.questTypeUnverifiable);
|
||||
|
||||
@override
|
||||
Stream<DateTime> shakeEvents() => const Stream<DateTime>.empty();
|
||||
|
||||
@override
|
||||
void recordShake(ShakeJudge run) {}
|
||||
|
||||
@override
|
||||
Future<QuestReward> verify(QuestCard card) async {
|
||||
_claimed.add(card.id);
|
||||
|
||||
Reference in New Issue
Block a user