광고 추가 보상 서버 시청 검증 · 사용자 퀘스트 승인 후 공개 · TIME ATTACK 모드 · 만보기 퀘스트 · 위치 퀘스트 지도 선택 · 운영 퀘스트 보상 재원
This commit is contained in:
@@ -232,12 +232,18 @@ class QuestCatalog {
|
||||
: (pool * feeRate).ceil();
|
||||
}
|
||||
|
||||
/// 위치 Quest 의 등록 장소 — `places/{placeId}`.
|
||||
/// 위치 Quest 의 장소 — 등록 장소(`places/{placeId}`) 또는 지도에 꽂은 핀(`id` 빈 값).
|
||||
class QuestPlace {
|
||||
const QuestPlace({required this.id, required this.name});
|
||||
const QuestPlace({required this.id, required this.name, this.lat, this.lng});
|
||||
|
||||
final String id;
|
||||
|
||||
/// 등록 장소 이름 또는 핀 주소.
|
||||
final String name;
|
||||
final double? lat;
|
||||
final double? lng;
|
||||
|
||||
bool get isPin => id.isEmpty;
|
||||
}
|
||||
|
||||
/// 링크 미리보기 — 서버 `fn-link-preview`(C-05). [status] 는 `ok` · `preview_failed` · `invalid`.
|
||||
@@ -272,10 +278,20 @@ class QuestSubmission {
|
||||
this.rejectReason,
|
||||
this.resubmitAllowed = false,
|
||||
this.summary = '',
|
||||
this.txId,
|
||||
this.paidAt,
|
||||
this.adClaimed = false,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String status;
|
||||
|
||||
/// 승인 지급의 기본 보상 원장 txId · 지급 시각 — 광고 추가 보상을 붙이는 기준(Q-UQ-03 A).
|
||||
final String? txId;
|
||||
final DateTime? paidAt;
|
||||
|
||||
/// 이 지급에 광고 추가 보상을 이미 받았는가(`users/{uid}/adBonuses/{txId}`).
|
||||
final bool adClaimed;
|
||||
final DateTime? submittedAt;
|
||||
final String? rejectReason;
|
||||
final bool resubmitAllowed;
|
||||
@@ -414,10 +430,19 @@ class QuestDetail {
|
||||
this.repeatMax = 1,
|
||||
this.submission,
|
||||
this.reviews = const [],
|
||||
this.publishState = '',
|
||||
this.moderationReason,
|
||||
});
|
||||
|
||||
final QuestCard card;
|
||||
|
||||
/// 사용자 생성 Quest 의 공개 상태(`quests.status`) — `review`(검수 대기) · `published` ·
|
||||
/// `rejected`(운영 반려) · `cancelled` · `ended`. 운영 Quest 는 빈 값.
|
||||
final String publishState;
|
||||
|
||||
/// 운영 반려 사유(`quests.moderation.reason`).
|
||||
final String? moderationReason;
|
||||
|
||||
/// 대표 이미지. 자산이 아직 없어 라벨로 둔다.
|
||||
final String imageLabel;
|
||||
|
||||
@@ -475,8 +500,16 @@ class QuestDraft {
|
||||
this.url,
|
||||
this.manualTitle,
|
||||
this.placeId,
|
||||
this.pin,
|
||||
this.goal,
|
||||
});
|
||||
|
||||
/// 지도에 꽂은 핀(등록 장소가 아닐 때) — 서버가 그 좌표로 장소를 만든다.
|
||||
final QuestPlace? pin;
|
||||
|
||||
/// 만보기 목표 걸음 수(`inputSchema` 의 `goal`).
|
||||
final int? goal;
|
||||
|
||||
/// 같은 화면의 재시도를 한 건으로 묶는 멱등 키.
|
||||
final String clientSeq;
|
||||
final String activityKey;
|
||||
@@ -529,6 +562,10 @@ abstract interface class QuestRepository {
|
||||
required String description,
|
||||
});
|
||||
|
||||
/// 생성자 취소(B-30) — 검수 중이면 예치 · 수수료 전액 반환, 공개 후면 수수료 비환불 ·
|
||||
/// 미배정 Pool 즉시 반환.
|
||||
Future<void> cancel(String questId);
|
||||
|
||||
/// 상태 전이. 서버가 확정한 상태를 돌려준다.
|
||||
Future<QuestStage> advance(String questId, QuestStage next);
|
||||
|
||||
@@ -565,12 +602,14 @@ abstract interface class QuestRepository {
|
||||
/// 보상형 광고 한 편을 불러온다. 불러오지 못하면 E-AD-01 이다.
|
||||
///
|
||||
/// 사용자가 추가 보상 모달에서 **직접 고른 뒤에만** 부른다(별첨 1 ⑥ No-Force ·
|
||||
/// 정책 원장 결정 8 「Reward Ad 는 명시적 옵트인」).
|
||||
Future<RewardedAdHandle> loadRewardedAd();
|
||||
/// 정책 원장 결정 8 「Reward Ad 는 명시적 옵트인」). [base] 는 광고를 붙일 기본 보상이다 —
|
||||
/// 서버측 시청 검증(AdMob SSV)에 그 txId 를 싣는다.
|
||||
Future<RewardedAdHandle> loadRewardedAd(QuestReward base);
|
||||
|
||||
/// 끝까지 본 광고의 추가 보상을 서버에 청구하고 지급액을 돌려준다. [base] 는 방금 받은
|
||||
/// 기본 보상이다 — 서버가 그 원장 행으로 증분을 계산한다(B-03 · C-02).
|
||||
/// 일일 한도를 넘으면 E-AD-02, 같은 노출의 재전송은 첫 지급 결과다.
|
||||
/// 지급 근거는 Google 이 서버로 보낸 SSV 기록이다(없으면 E-AD-03 — 증분 미지급 · 횟수 미차감).
|
||||
/// 일일 한도를 넘으면 E-AD-02, 같은 기본 보상의 재전송은 첫 지급 결과다.
|
||||
Future<int> claimAdReward(String impressionId, QuestReward base);
|
||||
}
|
||||
|
||||
@@ -730,10 +769,7 @@ class SampleQuestRepository implements QuestRepository {
|
||||
Future<QuestCatalog> catalog() async => _catalog;
|
||||
|
||||
@override
|
||||
Future<List<QuestPlace>> places() async => [
|
||||
for (final name in PlacePickerScreen.defaultPlaces)
|
||||
QuestPlace(id: name, name: name),
|
||||
];
|
||||
Future<List<QuestPlace>> places() async => PlacePickerScreen.defaultPlaces;
|
||||
|
||||
/// 표본은 네트워크를 쓰지 않는다 — 형식만 보고 조회 실패(기본 카드)로 돌려준다.
|
||||
@override
|
||||
@@ -766,6 +802,9 @@ class SampleQuestRepository implements QuestRepository {
|
||||
/// 이것이 없으면 보상을 받은 뒤 화면을 다시 읽을 때 또 「보상받기」로 보인다.
|
||||
static final _claimed = <String>{};
|
||||
|
||||
@override
|
||||
Future<void> cancel(String questId) async {}
|
||||
|
||||
@override
|
||||
Future<QuestStage> advance(String questId, QuestStage next) async => next;
|
||||
|
||||
@@ -802,7 +841,7 @@ class SampleQuestRepository implements QuestRepository {
|
||||
|
||||
/// 표본은 광고 SDK 를 부르지 않는다 — 잠깐 기다렸다가 끝까지 본 것으로 둔다.
|
||||
@override
|
||||
Future<RewardedAdHandle> loadRewardedAd() async {
|
||||
Future<RewardedAdHandle> loadRewardedAd(QuestReward base) async {
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
return const _SampleAd();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user