끝난 이클립스 합류 문구 · HOME 헤더 POP · 다국어 HOME 레이아웃 · 캠페인 표시 수정
This commit is contained in:
@@ -2,6 +2,7 @@ package kr.co.fanuzu.app
|
|||||||
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
@@ -53,5 +54,10 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
.sortedWith(compareBy(collator) { it[1] })
|
.sortedWith(compareBy(collator) { it[1] })
|
||||||
result.success(pairs)
|
result.success(pairs)
|
||||||
}
|
}
|
||||||
|
// API 레벨 — Android 13+ 는 클립보드 복사를 시스템이 알리므로 앱 토스트를 생략한다(Dart `copyToClipboard`).
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "kr.co.fanuzu.app/device")
|
||||||
|
.setMethodCallHandler { call, result ->
|
||||||
|
if (call.method == "sdkInt") result.success(Build.VERSION.SDK_INT) else result.notImplemented()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,12 @@ extension SnapshotReader on Map<String, dynamic> {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 지갑(`users/{uid}/wallet/main`) 총 보유 POP — 일반 + 기여 + 보류(G-05 · 68f9b7a).
|
||||||
|
/// HOME 헤더(Figma `179:29099` 「보유 포인트 총합」) · 퀘스트 목록 헤더 · POP 내역이 같은 값을 쓴다.
|
||||||
|
/// 쓸 수 있는 잔액(기여 · 결제 가능액)과는 다르다 — 그쪽은 각 저장소가 따로 계산한다.
|
||||||
|
int get walletTotal =>
|
||||||
|
intOr('activityBalance') + intOr('supportBalance') + intOr('pendingActivity');
|
||||||
|
|
||||||
String strOr(String key, [String fallback = '']) {
|
String strOr(String key, [String fallback = '']) {
|
||||||
final value = this[key];
|
final value = this[key];
|
||||||
return value is String && value.isNotEmpty ? value : fallback;
|
return value is String && value.isNotEmpty ? value : fallback;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
const _deviceChannel = MethodChannel('kr.co.fanuzu.app/device');
|
||||||
|
|
||||||
|
/// Android API 레벨 — 한 번 읽어 둔다. Android 가 아니거나 채널이 없으면(위젯 테스트) null.
|
||||||
|
Future<int?>? _sdkInt;
|
||||||
|
|
||||||
|
/// 클립보드에 [text] 를 넣고, OS 가 복사를 직접 알리지 않을 때만 [toast] 를 부른다.
|
||||||
|
///
|
||||||
|
/// Android 13(API 33)+ 는 시스템이 「클립보드에 복사했어요」를 띄워 앱 토스트와 겹친다
|
||||||
|
/// (10/8 r11 3-8). iOS · Android 12 이하는 앱이 알린다.
|
||||||
|
Future<void> copyToClipboard(String text, VoidCallback toast) async {
|
||||||
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
|
if (defaultTargetPlatform == TargetPlatform.android &&
|
||||||
|
((await (_sdkInt ??= _readSdkInt())) ?? 0) >= 33) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int?> _readSdkInt() async {
|
||||||
|
try {
|
||||||
|
return await _deviceChannel.invokeMethod<int>('sdkInt');
|
||||||
|
} on Object {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
void resetClipboardSdkCache() => _sdkInt = null;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../core/ui/app_dialog.dart';
|
import '../../core/ui/app_dialog.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import '../../core/data/backend.dart' show BackendException;
|
import '../../core/data/backend.dart' show BackendException;
|
||||||
@@ -23,6 +22,7 @@ import 'widgets/campaign_rank.dart';
|
|||||||
import 'widgets/campaign_share_dialog.dart';
|
import 'widgets/campaign_share_dialog.dart';
|
||||||
import 'widgets/campaign_style.dart';
|
import 'widgets/campaign_style.dart';
|
||||||
import '../../core/ui/fx_intensity.dart';
|
import '../../core/ui/fx_intensity.dart';
|
||||||
|
import '../../core/ui/clipboard_copy.dart';
|
||||||
|
|
||||||
/// 캠페인 상세 — 화면 대조 원장 F5 · Figma `272:245738`.
|
/// 캠페인 상세 — 화면 대조 원장 F5 · Figma `272:245738`.
|
||||||
///
|
///
|
||||||
@@ -448,6 +448,10 @@ class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null)
|
: null)
|
||||||
|
// 모금 중이 아니면(달성 · 미달 환불 · 결과보고 · 취소) 기여할 수 없다 — Figma 는 종료 상태의
|
||||||
|
// 버튼을 정하지 않아(`523:18589` 「타인 캠페인에서만 표시」) 숨긴다(10/8 r11).
|
||||||
|
: card.status != CampaignStatus.funding
|
||||||
|
? null
|
||||||
: SafeArea(
|
: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
|
||||||
@@ -1070,8 +1074,9 @@ class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _copyText(CampaignCardData card) async {
|
Future<void> _copyText(CampaignCardData card) async {
|
||||||
await Clipboard.setData(ClipboardData(text: _shareBody(card)));
|
await copyToClipboard(_shareBody(card), () {
|
||||||
if (mounted) _toast(context.l10n.shareCopied);
|
if (mounted) _toast(context.l10n.shareCopied);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 캠페인 신고(`reports/campaign-{캠페인}-{신고자}`) · 개설자 차단.
|
/// 캠페인 신고(`reports/campaign-{캠페인}-{신고자}`) · 개설자 차단.
|
||||||
|
|||||||
@@ -21,6 +21,23 @@ String liveEventMessage(LiveEvent event) => switch (event.kind) {
|
|||||||
LiveEventKind.notice => '[LIVE] ${event.nickname}',
|
LiveEventKind.notice => '[LIVE] ${event.nickname}',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// 띠·섬광 배너에 올려도 되는 라이브 이벤트. 이클립스 방어 합류는 **그 회차가 진행 중일 때만** 싣는다 —
|
||||||
|
/// 끝난 회차의 합류 문구가 서버 TTL(1시간)까지 띠에 남던 결함(10/8 r11 2-5). [eclipse] 는 지금 진행 중인
|
||||||
|
/// 회차(`watch` 는 마감되면 null 을 낸다)이고, 회차 발생 전의 합류는 지난 회차의 것이다.
|
||||||
|
List<LiveEvent> liveEventsFor(
|
||||||
|
List<LiveEvent> events,
|
||||||
|
EclipseState? eclipse,
|
||||||
|
DateTime now,
|
||||||
|
) => [
|
||||||
|
for (final e in events)
|
||||||
|
if (e.kind != LiveEventKind.eclipseDefense ||
|
||||||
|
(eclipse != null &&
|
||||||
|
eclipse.result == null &&
|
||||||
|
!eclipse.isExpired(now) &&
|
||||||
|
!(eclipse.startAt?.isAfter(e.at) ?? false)))
|
||||||
|
e,
|
||||||
|
];
|
||||||
|
|
||||||
/// 홈 라이브 띠(전광판)에 올릴 한 줄. [hero] 면 띠가 HERO TIME 참여 진입이 된다.
|
/// 홈 라이브 띠(전광판)에 올릴 한 줄. [hero] 면 띠가 HERO TIME 참여 진입이 된다.
|
||||||
/// [push] 는 같은 사건을 알리는 Push 의 `kind` — 띠가 떠 있으면 앱 안 Push 를 겹쳐 띄우지 않는다(v1.5 §1.1).
|
/// [push] 는 같은 사건을 알리는 Push 의 `kind` — 띠가 떠 있으면 앱 안 Push 를 겹쳐 띄우지 않는다(v1.5 §1.1).
|
||||||
typedef TickerLine = ({String text, bool hero, String? push});
|
typedef TickerLine = ({String text, bool hero, String? push});
|
||||||
@@ -46,7 +63,8 @@ TickerLine? tickerLine({
|
|||||||
required DateTime now,
|
required DateTime now,
|
||||||
String? fallback,
|
String? fallback,
|
||||||
}) {
|
}) {
|
||||||
final event = events.isEmpty ? null : events.first;
|
final live = liveEventsFor(events, eclipse, now);
|
||||||
|
final event = live.isEmpty ? null : live.first;
|
||||||
if (eclipse != null && eclipse.result == null) {
|
if (eclipse != null && eclipse.result == null) {
|
||||||
final started = eclipse.startAt;
|
final started = eclipse.startAt;
|
||||||
if (event == null || started == null || event.at.isBefore(started)) {
|
if (event == null || started == null || event.at.isBefore(started)) {
|
||||||
|
|||||||
@@ -509,6 +509,7 @@ class _Map extends StatelessWidget {
|
|||||||
child: _LiveFlash(
|
child: _LiveFlash(
|
||||||
repository: eclipseRepository,
|
repository: eclipseRepository,
|
||||||
fandomId: eclipse?.fandomId ?? data.mine?.id,
|
fandomId: eclipse?.fandomId ?? data.mine?.id,
|
||||||
|
eclipse: eclipse,
|
||||||
fallback: data.event,
|
fallback: data.event,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1050,12 +1051,16 @@ class _LiveFlash extends StatelessWidget {
|
|||||||
const _LiveFlash({
|
const _LiveFlash({
|
||||||
required this.repository,
|
required this.repository,
|
||||||
required this.fandomId,
|
required this.fandomId,
|
||||||
|
required this.eclipse,
|
||||||
required this.fallback,
|
required this.fallback,
|
||||||
});
|
});
|
||||||
|
|
||||||
final EclipseRepository repository;
|
final EclipseRepository repository;
|
||||||
final String? fandomId;
|
final String? fandomId;
|
||||||
|
|
||||||
|
/// 진행 중인 회차. 끝난 회차의 방어 합류는 흘리지 않는다([liveEventsFor]).
|
||||||
|
final EclipseState? eclipse;
|
||||||
|
|
||||||
/// 라이브 이벤트가 없을 때 그릴 기존 `galaxyEvents` 한 건.
|
/// 라이브 이벤트가 없을 때 그릴 기존 `galaxyEvents` 한 건.
|
||||||
final GalaxyEvent? fallback;
|
final GalaxyEvent? fallback;
|
||||||
|
|
||||||
@@ -1073,8 +1078,9 @@ class _LiveFlash extends StatelessWidget {
|
|||||||
return LiveStreamBuilder<List<LiveEvent>>(
|
return LiveStreamBuilder<List<LiveEvent>>(
|
||||||
key: ValueKey('flash-$fandomId'),
|
key: ValueKey('flash-$fandomId'),
|
||||||
create: () => repository.liveEvents(fandomId: fandomId, limit: 5),
|
create: () => repository.liveEvents(fandomId: fandomId, limit: 5),
|
||||||
builder: (context, events) {
|
builder: (context, all) {
|
||||||
if (events == null || events.isEmpty) {
|
final events = liveEventsFor(all ?? const [], eclipse, DateTime.now());
|
||||||
|
if (events.isEmpty) {
|
||||||
return fallback == null
|
return fallback == null
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: _EventTicker(
|
: _EventTicker(
|
||||||
|
|||||||
@@ -20,8 +20,13 @@ class ZapFeedback extends StatefulWidget {
|
|||||||
required this.radius,
|
required this.radius,
|
||||||
this.fandomName,
|
this.fandomName,
|
||||||
this.quest = false,
|
this.quest = false,
|
||||||
|
this.messageAboveTarget = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// 안내 띠를 [target] 행성 상자 위쪽에 둔다(HOME). 기본 자리(아래 128)는 HOME 에서 행성 이름 ·
|
||||||
|
/// 성장 카드 말풍선을 덮는다(10/8 r11). 행성 상자 반 변 = [radius] / 0.4(HOME 이 넘기는 값).
|
||||||
|
final bool messageAboveTarget;
|
||||||
|
|
||||||
/// 퀘스트 보상 확정의 행성 반응인가 — 결과 문구를 퀘스트 반영으로 바꾼다.
|
/// 퀘스트 보상 확정의 행성 반응인가 — 결과 문구를 퀘스트 반영으로 바꾼다.
|
||||||
final bool quest;
|
final bool quest;
|
||||||
|
|
||||||
@@ -101,7 +106,12 @@ class _ZapFeedbackState extends State<ZapFeedback>
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
bottom: 128,
|
top: widget.messageAboveTarget && widget.target != Offset.zero
|
||||||
|
? math.max(0, widget.target.dy - widget.radius / 0.4 / 2)
|
||||||
|
: null,
|
||||||
|
bottom: widget.messageAboveTarget && widget.target != Offset.zero
|
||||||
|
? null
|
||||||
|
: 128,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
liveRegion: true,
|
liveRegion: true,
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ class HomeSnapshot {
|
|||||||
/// HOME 주간 정보(A-37 · HOME 정보위계 v1.0 §2 · §3). 소속 행성이 없으면 null.
|
/// HOME 주간 정보(A-37 · HOME 정보위계 v1.0 §2 · §3). 소속 행성이 없으면 null.
|
||||||
final HomeWeekly? weekly;
|
final HomeWeekly? weekly;
|
||||||
|
|
||||||
/// 활동 POP 잔액. 정책 설계서 §P-02 — 활동/지원 원장은 분리되어 있고
|
/// 총 보유 POP(일반 + 기여 + 보류) — Figma `179:29099` 「보유 포인트 총합」.
|
||||||
/// 홈 상단에는 활동 POP 을 띄운다.
|
/// 설정 · POP 내역의 총 보유와 같은 값이다(`walletTotal`).
|
||||||
final int popBalance;
|
final int popBalance;
|
||||||
|
|
||||||
/// 내 소속 팬덤. 이클립스 경보 배너와 라이브 띠가 **어느 팬덤을 구독할지**를
|
/// 내 소속 팬덤. 이클립스 경보 배너와 라이브 띠가 **어느 팬덤을 구독할지**를
|
||||||
@@ -499,7 +499,7 @@ abstract interface class HomeRepository {
|
|||||||
Future<HomeSnapshot> load();
|
Future<HomeSnapshot> load();
|
||||||
Stream<PlanetGrowth> watchGrowth(String fandomId);
|
Stream<PlanetGrowth> watchGrowth(String fandomId);
|
||||||
|
|
||||||
/// 보유 활동 POP — `users/{uid}/wallet/main.activityBalance` 구독. 퀘스트 목록 헤더
|
/// 총 보유 POP — `users/{uid}/wallet/main` 구독(`walletTotal`). 퀘스트 목록 헤더
|
||||||
/// (`QuestRepository.popBalance`)와 같은 문서라, 다른 화면에서 생긴 변동(현상금 예치 ·
|
/// (`QuestRepository.popBalance`)와 같은 문서라, 다른 화면에서 생긴 변동(현상금 예치 ·
|
||||||
/// 보상)이 HOME 헤더에도 바로 반영된다.
|
/// 보상)이 HOME 헤더에도 바로 반영된다.
|
||||||
Stream<int> watchPopBalance();
|
Stream<int> watchPopBalance();
|
||||||
|
|||||||
@@ -44,9 +44,7 @@ class FirestoreHomeRepository implements HomeRepository {
|
|||||||
Stream<int> watchPopBalance() => _backend.db
|
Stream<int> watchPopBalance() => _backend.db
|
||||||
.doc('users/${_backend.requireUid}/wallet/main')
|
.doc('users/${_backend.requireUid}/wallet/main')
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map(
|
.map((s) => (s.data() ?? const <String, dynamic>{}).walletTotal);
|
||||||
(s) => (s.data() ?? const <String, dynamic>{}).intOr('activityBalance'),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<({String fandomId, bool primaryChanged})> createNebula(
|
Future<({String fandomId, bool primaryChanged})> createNebula(
|
||||||
@@ -233,7 +231,7 @@ class FirestoreHomeRepository implements HomeRepository {
|
|||||||
|
|
||||||
return HomeSnapshot(
|
return HomeSnapshot(
|
||||||
weekly: weekly,
|
weekly: weekly,
|
||||||
popBalance: wallet.intOr('activityBalance'),
|
popBalance: wallet.walletTotal,
|
||||||
fandomId: fandomId,
|
fandomId: fandomId,
|
||||||
liveTicker: fandom.strOr('liveTicker').isEmpty
|
liveTicker: fandom.strOr('liveTicker').isEmpty
|
||||||
? null
|
? null
|
||||||
|
|||||||
@@ -684,7 +684,8 @@ class _HomeBody extends StatelessWidget {
|
|||||||
key: ValueKey(('pop', repository)),
|
key: ValueKey(('pop', repository)),
|
||||||
create: repository.watchPopBalance,
|
create: repository.watchPopBalance,
|
||||||
builder: (context, wallet) => HomeHeader(
|
builder: (context, wallet) => HomeHeader(
|
||||||
pop: wallet ?? zap?.balance ?? data.popBalance,
|
// Zap 영수증 잔액(일반 POP)은 섞지 않는다 — 헤더는 총 보유다(`179:29099`).
|
||||||
|
pop: wallet ?? data.popBalance,
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
items: data.items,
|
items: data.items,
|
||||||
fandomId: data.fandomId,
|
fandomId: data.fandomId,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'dart:ui' as ui;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import '../../../core/i18n/app_locale.dart';
|
import '../../../core/i18n/app_locale.dart';
|
||||||
@@ -10,6 +9,7 @@ import '../../../core/theme/tokens.dart';
|
|||||||
import '../../../core/ui/app_icons.dart';
|
import '../../../core/ui/app_icons.dart';
|
||||||
import '../../campaign/campaign_share.dart' show shareHashtags, shareLink, shareBaseUrl;
|
import '../../campaign/campaign_share.dart' show shareHashtags, shareLink, shareBaseUrl;
|
||||||
import '../home_data.dart';
|
import '../home_data.dart';
|
||||||
|
import '../../../core/ui/clipboard_copy.dart';
|
||||||
|
|
||||||
/// 브리핑 공유 — 「홈_브리핑 공유」 `266:90445` · 인스타 `272:240423` · 틱톡 `272:240430`.
|
/// 브리핑 공유 — 「홈_브리핑 공유」 `266:90445` · 인스타 `272:240423` · 틱톡 `272:240430`.
|
||||||
///
|
///
|
||||||
@@ -128,13 +128,16 @@ class _BriefingShareDialogState extends State<BriefingShareDialog> {
|
|||||||
setState(() => _busy = true);
|
setState(() => _busy = true);
|
||||||
try {
|
try {
|
||||||
if (index == _icons.length - 1) {
|
if (index == _icons.length - 1) {
|
||||||
await Clipboard.setData(ClipboardData(text: _text));
|
var announce = false;
|
||||||
|
await copyToClipboard(_text, () => announce = true);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
// 오버레이를 닫고 알린다 — 열린 채로 띄우면 스낵바가 장막 뒤에 가린다.
|
// 오버레이를 닫고 알린다 — 열린 채로 띄우면 스낵바가 장막 뒤에 가린다.
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
if (announce) {
|
||||||
toast(widget.link == null
|
toast(widget.link == null
|
||||||
? l10n.briefingShareTextCopied
|
? l10n.briefingShareTextCopied
|
||||||
: l10n.briefingShareCopied);
|
: l10n.briefingShareCopied);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final file = await _capture();
|
final file = await _capture();
|
||||||
|
|||||||
@@ -183,7 +183,19 @@ class _HomeHeroState extends State<HomeHero> {
|
|||||||
children: [
|
children: [
|
||||||
// 첫 사용자 홈(`343:212939`)은 아바타가 y124, 나머지 상태는 y130.
|
// 첫 사용자 홈(`343:212939`)은 아바타가 y124, 나머지 상태는 y130.
|
||||||
if (!widget.unjoined) const SizedBox(height: 6),
|
if (!widget.unjoined) const SizedBox(height: 6),
|
||||||
_Contribution(growth: growth, joined: !widget.unjoined),
|
// 미니맵(오른쪽 위 [HomeHero.miniMapSize])과 같은 높이라 그 폭만큼 비운다 — 긴 언어(ja ·
|
||||||
|
// es)의 제목이 미니맵 아래로 들어가 잘리지 않고 줄바꿈된다(10/8 r11).
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
right: widget.miniMap == null
|
||||||
|
? 0
|
||||||
|
: HomeHero.miniMapSize - AppSpace.s4 + AppSpace.s2,
|
||||||
|
),
|
||||||
|
child: _Contribution(
|
||||||
|
growth: growth,
|
||||||
|
joined: !widget.unjoined,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: AppSpace.s7),
|
const SizedBox(height: AppSpace.s7),
|
||||||
// HERO TIME 안내·큰 글자처럼 아래가 길어지면 행성이 먼저 줄어든다.
|
// HERO TIME 안내·큰 글자처럼 아래가 길어지면 행성이 먼저 줄어든다.
|
||||||
Flexible(
|
Flexible(
|
||||||
@@ -216,13 +228,16 @@ class _HomeHeroState extends State<HomeHero> {
|
|||||||
child: _TapShake(
|
child: _TapShake(
|
||||||
onTap: widget.onTapPlanet,
|
onTap: widget.onTapPlanet,
|
||||||
child: LivePlanet25D(
|
child: LivePlanet25D(
|
||||||
onOpenHistory: widget.onOpenHistory,
|
onOpenHistory:
|
||||||
|
widget.onOpenHistory,
|
||||||
key: _planetKey,
|
key: _planetKey,
|
||||||
fandomId: fandomId,
|
fandomId: fandomId,
|
||||||
watch: watchPlanet,
|
watch: watchPlanet,
|
||||||
stage: growth.stage,
|
stage: growth.stage,
|
||||||
// 형태 없는 옛 행성은 다른 화면과 같은 그림(10/6 C-5).
|
// 형태 없는 옛 행성은 다른 화면과 같은 그림(10/6 C-5).
|
||||||
image: planetFallbackImage(fandomId),
|
image: planetFallbackImage(
|
||||||
|
fandomId,
|
||||||
|
),
|
||||||
size: math.min(
|
size: math.min(
|
||||||
planet,
|
planet,
|
||||||
box.maxHeight,
|
box.maxHeight,
|
||||||
@@ -276,6 +291,7 @@ class _HomeHeroState extends State<HomeHero> {
|
|||||||
child: ZapFeedback(
|
child: ZapFeedback(
|
||||||
fandomName: widget.growth.fandomName,
|
fandomName: widget.growth.fandomName,
|
||||||
quest: zap.questReaction,
|
quest: zap.questReaction,
|
||||||
|
messageAboveTarget: true,
|
||||||
touchSerial: zap.touchSerial,
|
touchSerial: zap.touchSerial,
|
||||||
pending: zap.pending > 0 || zap.sending,
|
pending: zap.pending > 0 || zap.sending,
|
||||||
result: zap.result,
|
result: zap.result,
|
||||||
@@ -312,8 +328,19 @@ class _Contribution extends StatelessWidget {
|
|||||||
/// 소속 팬덤이 있는가. 없으면 첫 사용자 문구(`343:212939`)를 쓴다.
|
/// 소속 팬덤이 있는가. 없으면 첫 사용자 문구(`343:212939`)를 쓴다.
|
||||||
final bool joined;
|
final bool joined;
|
||||||
|
|
||||||
|
static const _ownerAffix = TextStyle(
|
||||||
|
fontFamily: AppFonts.body,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 1.3,
|
||||||
|
letterSpacing: -0.3,
|
||||||
|
color: AppColors.onCosmicVariant,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Column(
|
Widget build(BuildContext context) {
|
||||||
|
final owner = context.l10n.heroOwner('\u0000').split('\u0000');
|
||||||
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 「{닉네임} 님의」는 로그인 사용자의 인사 줄이다(설명 프레임 `2097:16391`).
|
// 「{닉네임} 님의」는 로그인 사용자의 인사 줄이다(설명 프레임 `2097:16391`).
|
||||||
@@ -354,8 +381,14 @@ class _Contribution extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 9),
|
const SizedBox(width: 9),
|
||||||
Text(
|
// 「님의」는 언어마다 이름 앞(es 「De」) 또는 뒤(ko 「님의」 · ja 「さんの」)에 붙는다 — 문구
|
||||||
|
// 하나({name} 자리표시)를 이름 자리에서 갈라 앞뒤를 흐린 색으로 그린다(10/8 r11 es 오역).
|
||||||
|
if (owner.first.isNotEmpty) Text(owner.first, style: _ownerAffix),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
growth.contributorName,
|
growth.contributorName,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: AppFonts.display,
|
fontFamily: AppFonts.display,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -364,18 +397,8 @@ class _Contribution extends StatelessWidget {
|
|||||||
color: AppColors.onCosmic,
|
color: AppColors.onCosmic,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 2),
|
|
||||||
Text(
|
|
||||||
context.l10n.heroOwnerSuffix,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontFamily: AppFonts.body,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1.3,
|
|
||||||
letterSpacing: -0.3,
|
|
||||||
color: AppColors.onCosmicVariant,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
if (owner.last.isNotEmpty) Text(owner.last, style: _ownerAffix),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 3),
|
const SizedBox(height: 3),
|
||||||
@@ -403,6 +426,7 @@ class _Contribution extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 성장 카드 — Figma 「홈」 `Frame 1984081073`(343×151 · `#040304` 40% · r12 · 패딩 12/16 ·
|
/// 성장 카드 — Figma 「홈」 `Frame 1984081073`(343×151 · `#040304` 40% · r12 · 패딩 12/16 ·
|
||||||
/// 간격 12 · 유리 흐림 4): 「BTS ›」(ABeeZee 32 · chevron 24 #777777) / 「행성 성장률 67%」
|
/// 간격 12 · 유리 흐림 4): 「BTS ›」(ABeeZee 32 · chevron 24 #777777) / 「행성 성장률 67%」
|
||||||
@@ -425,8 +449,21 @@ class _GrowthOverlayCard extends StatelessWidget {
|
|||||||
color: AppColors.onCosmic,
|
color: AppColors.onCosmic,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 말풍선 가운데의 최소 x — 「행성 성장률 100%」 줄(약 103)과 겹치지 않게 한다.
|
/// 말풍선과 성장률 글자 사이 최소 간격.
|
||||||
static const _bubbleMinCenter = 196.0;
|
static const _bubbleGap = 8.0;
|
||||||
|
|
||||||
|
static double _textWidth(
|
||||||
|
BuildContext context,
|
||||||
|
String text,
|
||||||
|
TextStyle style,
|
||||||
|
) =>
|
||||||
|
(TextPainter(
|
||||||
|
text: TextSpan(text: text, style: style),
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
textScaler: MediaQuery.textScalerOf(context),
|
||||||
|
maxLines: 1,
|
||||||
|
)..layout()).width.ceilToDouble() +
|
||||||
|
1;
|
||||||
|
|
||||||
String _value(num v) =>
|
String _value(num v) =>
|
||||||
v == v.roundToDouble() ? formatPop(v.toInt()) : v.toStringAsFixed(1);
|
v == v.roundToDouble() ? formatPop(v.toInt()) : v.toStringAsFixed(1);
|
||||||
@@ -464,6 +501,9 @@ class _GrowthOverlayCard extends StatelessWidget {
|
|||||||
final low = growth.bottleneck;
|
final low = growth.bottleneck;
|
||||||
final ratio = low?.ratio;
|
final ratio = low?.ratio;
|
||||||
final percent = ratio == null ? '—' : '${(ratio * 100).round()}';
|
final percent = ratio == null ? '—' : '${(ratio * 100).round()}';
|
||||||
|
final rate = growth.isNebula
|
||||||
|
? context.l10n.heroNebulaGrowthRate(percent)
|
||||||
|
: context.l10n.heroGrowthRate(percent);
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
@@ -520,12 +560,7 @@ class _GrowthOverlayCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(rate, style: _white14),
|
||||||
growth.isNebula
|
|
||||||
? context.l10n.heroNebulaGrowthRate(percent)
|
|
||||||
: context.l10n.heroGrowthRate(percent),
|
|
||||||
style: _white14,
|
|
||||||
),
|
|
||||||
const SizedBox(height: AppSpace.s3),
|
const SizedBox(height: AppSpace.s3),
|
||||||
_Bar(ratio: ratio ?? 0),
|
_Bar(ratio: ratio ?? 0),
|
||||||
const SizedBox(height: AppSpace.s3),
|
const SizedBox(height: AppSpace.s3),
|
||||||
@@ -541,17 +576,30 @@ class _GrowthOverlayCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
// 말풍선 — 사본 y566.5(카드 안 31.5) · 꼬리 끝이 바 위에 닿는다. 가운데는 채움 끝
|
// 말풍선 — 사본 y566.5(카드 안 31.5) · 꼬리 끝이 바 위에 닿는다. 가운데는 채움 끝
|
||||||
// (사본 x238 = 채움 206)을 따르되 왼쪽 성장률 글자와 겹치지 않게 민다.
|
// (사본 x238 = 채움 206)을 따르되 왼쪽 성장률 글자와 겹치지 않게 민다. 두 글자 폭은
|
||||||
|
// 언어마다 달라 재서 정한다 — 남는 폭이 모자라면 말풍선 글자를 줄인다(10/8 r11 es).
|
||||||
// 성운 카드(`344:213809`)에는 말풍선이 없다.
|
// 성운 카드(`344:213809`)에는 말풍선이 없다.
|
||||||
if (!growth.isNebula)
|
if (!growth.isNebula)
|
||||||
Positioned(
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final left =
|
||||||
|
_textWidth(context, rate, _white14) + _bubbleGap;
|
||||||
|
final width = math.min(
|
||||||
|
_textWidth(
|
||||||
|
context,
|
||||||
|
growth.growthBubble,
|
||||||
|
_SpeechBubble.style,
|
||||||
|
) +
|
||||||
|
_SpeechBubble.padding * 2,
|
||||||
|
math.max(0.0, box.maxWidth - left),
|
||||||
|
);
|
||||||
|
return Positioned(
|
||||||
top: 31.5,
|
top: 31.5,
|
||||||
left: (box.maxWidth * (ratio ?? 0)).clamp(
|
left: (box.maxWidth * (ratio ?? 0) - width / 2).clamp(
|
||||||
math.min(_bubbleMinCenter, box.maxWidth - 84),
|
math.min(left, box.maxWidth - width),
|
||||||
box.maxWidth - 84,
|
box.maxWidth - width,
|
||||||
),
|
),
|
||||||
child: FractionalTranslation(
|
width: width,
|
||||||
translation: const Offset(-0.5, 0),
|
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
button: true,
|
button: true,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@@ -559,7 +607,8 @@ class _GrowthOverlayCard extends StatelessWidget {
|
|||||||
child: _SpeechBubble(text: growth.growthBubble),
|
child: _SpeechBubble(text: growth.growthBubble),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -606,13 +655,23 @@ class _SpeechBubble extends StatelessWidget {
|
|||||||
|
|
||||||
static const _fill = Color(0xCC000000);
|
static const _fill = Color(0xCC000000);
|
||||||
|
|
||||||
|
static const padding = 10.0;
|
||||||
|
|
||||||
|
static const style = TextStyle(
|
||||||
|
fontFamily: AppFonts.body,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 1,
|
||||||
|
color: AppColors.onCosmic,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Column(
|
Widget build(BuildContext context) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
height: 30,
|
height: 30,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: padding),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _fill,
|
color: _fill,
|
||||||
@@ -620,13 +679,9 @@ class _SpeechBubble extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: const TextStyle(
|
maxLines: 1,
|
||||||
fontFamily: AppFonts.body,
|
overflow: TextOverflow.ellipsis,
|
||||||
fontSize: 12,
|
style: style,
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1,
|
|
||||||
color: AppColors.onCosmic,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
@@ -1049,7 +1104,6 @@ class HomeZap extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 행성 터치 반응 — Figma `2024:20758` 「행성 1회 터치시 흔들림 1번」 · UX·FX v1.5 §3 Light Haptic.
|
/// 행성 터치 반응 — Figma `2024:20758` 「행성 1회 터치시 흔들림 1번」 · UX·FX v1.5 §3 Light Haptic.
|
||||||
/// 흔들림이 끝나면 [onTap](커뮤니티 이동)을 부른다. [onTap] 이 없으면 아무 반응도 하지 않는다.
|
/// 흔들림이 끝나면 [onTap](커뮤니티 이동)을 부른다. [onTap] 이 없으면 아무 반응도 하지 않는다.
|
||||||
class _TapShake extends StatefulWidget {
|
class _TapShake extends StatefulWidget {
|
||||||
@@ -1062,8 +1116,12 @@ class _TapShake extends StatefulWidget {
|
|||||||
State<_TapShake> createState() => _TapShakeState();
|
State<_TapShake> createState() => _TapShakeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TapShakeState extends State<_TapShake> with SingleTickerProviderStateMixin {
|
class _TapShakeState extends State<_TapShake>
|
||||||
late final _shake = AnimationController(vsync: this, duration: const Duration(milliseconds: 320));
|
with SingleTickerProviderStateMixin {
|
||||||
|
late final _shake = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 320),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -1087,7 +1145,10 @@ class _TapShakeState extends State<_TapShake> with SingleTickerProviderStateMixi
|
|||||||
animation: _shake,
|
animation: _shake,
|
||||||
// 한 번 좌우로 흔들리고 제자리 — 진폭 6 · 감쇠.
|
// 한 번 좌우로 흔들리고 제자리 — 진폭 6 · 감쇠.
|
||||||
builder: (context, child) => Transform.translate(
|
builder: (context, child) => Transform.translate(
|
||||||
offset: Offset(math.sin(_shake.value * math.pi * 4) * 6 * (1 - _shake.value), 0),
|
offset: Offset(
|
||||||
|
math.sin(_shake.value * math.pi * 4) * 6 * (1 - _shake.value),
|
||||||
|
0,
|
||||||
|
),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
|
|||||||
@@ -37,16 +37,22 @@ class HomeWeeklyCard extends StatelessWidget {
|
|||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
final w = weekly;
|
final w = weekly;
|
||||||
final change = w.rankChange;
|
final change = w.rankChange;
|
||||||
|
// 라벨 2 : 값 3 으로 나눈다 — 값이 폭을 다 차지하면 긴 언어(es)에서 라벨이 0 폭이 되어
|
||||||
|
// 한 글자씩 줄바꿈된다(10/8 r11 「Mis POP」). 넘치면 둘 다 단어 단위로 줄바꿈한다.
|
||||||
Widget row(String label, Widget value) => Padding(
|
Widget row(String label, Widget value) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: AppSpace.s2),
|
padding: const EdgeInsets.symmetric(vertical: AppSpace.s2),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text(label, style: _label)),
|
Expanded(flex: 2, child: Text(label, style: _label)),
|
||||||
value,
|
const SizedBox(width: AppSpace.s2),
|
||||||
|
Flexible(
|
||||||
|
flex: 3,
|
||||||
|
child: Align(alignment: Alignment.centerRight, child: value),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Text text(String s) => Text(s, style: _value);
|
Text text(String s) => Text(s, style: _value, textAlign: TextAlign.end);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class FirestoreMyRepository extends SampleMyRepository {
|
|||||||
userExp: user.intOr('userExp'),
|
userExp: user.intOr('userExp'),
|
||||||
userLevel: user.intOr('userLevel').clamp(1, 1 << 31),
|
userLevel: user.intOr('userLevel').clamp(1, 1 << 31),
|
||||||
passportTier: primaryTier,
|
passportTier: primaryTier,
|
||||||
balancePop: wallet.intOr('activityBalance') + wallet.intOr('supportBalance') + wallet.intOr('pendingActivity'),
|
balancePop: wallet.walletTotal,
|
||||||
reservedPop: wallet.intOr('pendingActivity'),
|
reservedPop: wallet.intOr('pendingActivity'),
|
||||||
steps: user.intOr('steps'),
|
steps: user.intOr('steps'),
|
||||||
completedQuests: await completedF,
|
completedQuests: await completedF,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import '../../../core/format.dart';
|
import '../../../core/format.dart';
|
||||||
@@ -8,6 +7,7 @@ import '../../../core/ui/app_icons.dart';
|
|||||||
import '../my_data.dart';
|
import '../my_data.dart';
|
||||||
import 'my_cards.dart';
|
import 'my_cards.dart';
|
||||||
import '../../../core/i18n/app_locale.dart';
|
import '../../../core/i18n/app_locale.dart';
|
||||||
|
import '../../../core/ui/clipboard_copy.dart';
|
||||||
import '../../campaign/campaign_share.dart' show shareHashtags;
|
import '../../campaign/campaign_share.dart' show shareHashtags;
|
||||||
|
|
||||||
/// 프로필 카드 오버레이 — 시안 `374:15738`(명세 `549:22058`).
|
/// 프로필 카드 오버레이 — 시안 `374:15738`(명세 `549:22058`).
|
||||||
@@ -123,7 +123,8 @@ String profileShareText(MyProfile profile, [AppLocalizations? l10n]) => [
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
/// 프로필 카드 공유. 앱별 전용 SDK 없이 OS 공유 시트로 보내고(사용자가 그 안에서
|
/// 프로필 카드 공유. 앱별 전용 SDK 없이 OS 공유 시트로 보내고(사용자가 그 안에서
|
||||||
/// 인스타 · 틱톡 · X 를 고른다), 마지막 칸은 문구를 복사한다. 복사했으면 true.
|
/// 인스타 · 틱톡 · X 를 고른다), 마지막 칸은 문구를 복사한다. 복사했고 앱이 알려야 하면 true
|
||||||
|
/// (Android 13+ 는 시스템이 알린다 — [copyToClipboard]).
|
||||||
Future<bool> shareProfile(
|
Future<bool> shareProfile(
|
||||||
MyProfile profile,
|
MyProfile profile,
|
||||||
String target, {
|
String target, {
|
||||||
@@ -131,8 +132,9 @@ Future<bool> shareProfile(
|
|||||||
}) async {
|
}) async {
|
||||||
final text = profileShareText(profile);
|
final text = profileShareText(profile);
|
||||||
if (target == ProfileCard.shareTargets.last) {
|
if (target == ProfileCard.shareTargets.last) {
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
var announce = false;
|
||||||
return true;
|
await copyToClipboard(text, () => announce = true);
|
||||||
|
return announce;
|
||||||
}
|
}
|
||||||
await (share ?? SharePlus.instance.share)(ShareParams(text: text));
|
await (share ?? SharePlus.instance.share)(ShareParams(text: text));
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ class AppNotification {
|
|||||||
/// 없으면 종류 이름([NotificationKind.label])으로 물러난다.
|
/// 없으면 종류 이름([NotificationKind.label])으로 물러난다.
|
||||||
final String? source;
|
final String? source;
|
||||||
|
|
||||||
String get sourceLabel => source ?? kind.label;
|
String get sourceLabel =>
|
||||||
|
source ??
|
||||||
|
(kind == NotificationKind.campaign ? campaignNotificationLabel(id) : null) ??
|
||||||
|
kind.label;
|
||||||
|
|
||||||
/// 눌렀을 때 열 대상 문서 경로(`quests/{id}` · `campaigns/{id}` 등). 서버가 싣는다.
|
/// 눌렀을 때 열 대상 문서 경로(`quests/{id}` · `campaigns/{id}` 등). 서버가 싣는다.
|
||||||
/// 명세가 *이동 X* 로 정한 종류(이클립스 · 아이템)는 경로가 있어도 쓰지 않는다.
|
/// 명세가 *이동 X* 로 정한 종류(이클립스 · 아이템)는 경로가 있어도 쓰지 않는다.
|
||||||
@@ -125,6 +128,17 @@ class AppNotification {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 캠페인 알림의 세부 종류 이름. 서버(`campaign/settle.ts` `notifyOnce`)는 `eventType` 을 모두
|
||||||
|
/// `campaign` 으로 싣고 문서 ID 앞머리로 가른다 — `campaignSuccess_` · `campaignRefund_`(목표 미달 ·
|
||||||
|
/// 취소 환불) · `campaignCancel_`(개설자 등록비 반환) · `campaignReport_`. 모르는 앞머리는 null
|
||||||
|
/// (종류 이름 「캠페인 달성」으로 물러난다).
|
||||||
|
String? campaignNotificationLabel(String id) => switch (id.split('_').first) {
|
||||||
|
'campaignRefund' => appL10n.notifKindCampaignRefund,
|
||||||
|
'campaignCancel' => appL10n.notifKindCampaignCancel,
|
||||||
|
'campaignReport' => appL10n.notifKindCampaignReport,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
/// 날짜 머리글. 명세 — *오늘 및 어제를 제외한 과거는 yyyy.mm.dd 로 표시*.
|
/// 날짜 머리글. 명세 — *오늘 및 어제를 제외한 과거는 yyyy.mm.dd 로 표시*.
|
||||||
String notificationDayLabel(DateTime at, {DateTime? now}) {
|
String notificationDayLabel(DateTime at, {DateTime? now}) {
|
||||||
final today = _dateOnly(now ?? DateTime.now());
|
final today = _dateOnly(now ?? DateTime.now());
|
||||||
|
|||||||
@@ -740,7 +740,7 @@ abstract interface class QuestRepository {
|
|||||||
|
|
||||||
Future<String> create(QuestDraft draft);
|
Future<String> create(QuestDraft draft);
|
||||||
|
|
||||||
/// 보유 일반 POP(`wallet/main.activityBalance`) — 생성 · 보상 뒤 목록 헤더를 다시 맞춘다.
|
/// 총 보유 POP(`wallet/main` 일반 + 기여 + 보류, HOME 헤더와 같은 값) — 생성 · 보상 뒤 목록 헤더를 다시 맞춘다.
|
||||||
/// 모르면 null(헤더는 받은 값을 유지한다).
|
/// 모르면 null(헤더는 받은 값을 유지한다).
|
||||||
Future<int?> popBalance();
|
Future<int?> popBalance();
|
||||||
|
|
||||||
|
|||||||
@@ -649,7 +649,7 @@ class FirestoreQuestRepository implements QuestRepository {
|
|||||||
@override
|
@override
|
||||||
Future<int?> popBalance() async {
|
Future<int?> popBalance() async {
|
||||||
final wallet = await _backend.db.doc('users/${_backend.requireUid}/wallet/main').get();
|
final wallet = await _backend.db.doc('users/${_backend.requireUid}/wallet/main').get();
|
||||||
return wallet.data()?.intOr('activityBalance');
|
return wallet.data()?.walletTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -272,7 +272,7 @@
|
|||||||
"campaignView": "View campaign",
|
"campaignView": "View campaign",
|
||||||
"growthFinalReached": "You've reached the final stage",
|
"growthFinalReached": "You've reached the final stage",
|
||||||
"zapRetrySame": "Recheck same contribution",
|
"zapRetrySame": "Recheck same contribution",
|
||||||
"heroOwnerSuffix": "'s",
|
"heroOwner": "{name}'s",
|
||||||
"heroStartPrompt": "Create or join a planet\nand start your FANUZU journey!",
|
"heroStartPrompt": "Create or join a planet\nand start your FANUZU journey!",
|
||||||
"heroNebulaPrompt": "Use the POP you earn from activities\nto contribute to the nebula!",
|
"heroNebulaPrompt": "Use the POP you earn from activities\nto contribute to the nebula!",
|
||||||
"heroRecentContribution": "Growing steadily with\n{pop} POP contributed.",
|
"heroRecentContribution": "Growing steadily with\n{pop} POP contributed.",
|
||||||
@@ -450,6 +450,9 @@
|
|||||||
"notifKindCampaign": "Campaign goal",
|
"notifKindCampaign": "Campaign goal",
|
||||||
"notifKindQuest": "Quest complete",
|
"notifKindQuest": "Quest complete",
|
||||||
"notifKindEvolution": "Planet evolution",
|
"notifKindEvolution": "Planet evolution",
|
||||||
|
"notifKindCampaignRefund": "Campaign refund",
|
||||||
|
"notifKindCampaignCancel": "Campaign cancelled",
|
||||||
|
"notifKindCampaignReport": "Result report",
|
||||||
"notifTargetPost": "post",
|
"notifTargetPost": "post",
|
||||||
"notifTargetCampaign": "campaign",
|
"notifTargetCampaign": "campaign",
|
||||||
"notifTargetQuest": "quest details",
|
"notifTargetQuest": "quest details",
|
||||||
@@ -827,7 +830,7 @@
|
|||||||
"heroNebulaGrowthRate": "Nebula growth {percent}%",
|
"heroNebulaGrowthRate": "Nebula growth {percent}%",
|
||||||
"homeIncubator": "Fandom Planet Incubator",
|
"homeIncubator": "Fandom Planet Incubator",
|
||||||
"homeGrowTogether": "Grow together",
|
"homeGrowTogether": "Grow together",
|
||||||
"homeArtistNews": "Artist News",
|
"homeArtistNews": "AI Daily Briefing",
|
||||||
"growthConditionsTitle": "Growth conditions",
|
"growthConditionsTitle": "Growth conditions",
|
||||||
"eclipseButtonSoon": "Eclipse in {time}!\nShake your phone to stop it!",
|
"eclipseButtonSoon": "Eclipse in {time}!\nShake your phone to stop it!",
|
||||||
"eclipseButtonActive": "The eclipse has started!\nShake your phone within {seconds}s to stop it!",
|
"eclipseButtonActive": "The eclipse has started!\nShake your phone within {seconds}s to stop it!",
|
||||||
|
|||||||
@@ -241,7 +241,7 @@
|
|||||||
"growthNebulaGoalPassed": "El plazo objetivo ha pasado · Cumple las condiciones y nacerá el planeta",
|
"growthNebulaGoalPassed": "El plazo objetivo ha pasado · Cumple las condiciones y nacerá el planeta",
|
||||||
"growthFinalStage": "Etapa final",
|
"growthFinalStage": "Etapa final",
|
||||||
"growthPromotionPending": "Ascenso pendiente",
|
"growthPromotionPending": "Ascenso pendiente",
|
||||||
"growthCheckConditions": "Ver condiciones de crecimiento",
|
"growthCheckConditions": "Ver condiciones",
|
||||||
"growthConditionChecking": "Comprobando condición de {name}",
|
"growthConditionChecking": "Comprobando condición de {name}",
|
||||||
"growthConditionMet": "Condición de {name} cumplida",
|
"growthConditionMet": "Condición de {name} cumplida",
|
||||||
"growthConditionUnmet": "Condición de {name} no cumplida",
|
"growthConditionUnmet": "Condición de {name} no cumplida",
|
||||||
@@ -272,7 +272,7 @@
|
|||||||
"campaignView": "Ver campaña",
|
"campaignView": "Ver campaña",
|
||||||
"growthFinalReached": "Has llegado a la etapa final",
|
"growthFinalReached": "Has llegado a la etapa final",
|
||||||
"zapRetrySame": "Volver a comprobar la contribución",
|
"zapRetrySame": "Volver a comprobar la contribución",
|
||||||
"heroOwnerSuffix": "aporta:",
|
"heroOwner": "De {name}",
|
||||||
"heroStartPrompt": "¡Crea o únete a un planeta\ny empieza en FANUZU!",
|
"heroStartPrompt": "¡Crea o únete a un planeta\ny empieza en FANUZU!",
|
||||||
"heroNebulaPrompt": "¡Usa el POP que ganas con actividades\npara contribuir a la nebulosa!",
|
"heroNebulaPrompt": "¡Usa el POP que ganas con actividades\npara contribuir a la nebulosa!",
|
||||||
"heroRecentContribution": "Creciendo sin parar con\n{pop} POP aportados.",
|
"heroRecentContribution": "Creciendo sin parar con\n{pop} POP aportados.",
|
||||||
@@ -450,6 +450,9 @@
|
|||||||
"notifKindCampaign": "Meta de campaña",
|
"notifKindCampaign": "Meta de campaña",
|
||||||
"notifKindQuest": "Misión completada",
|
"notifKindQuest": "Misión completada",
|
||||||
"notifKindEvolution": "Evolución del planeta",
|
"notifKindEvolution": "Evolución del planeta",
|
||||||
|
"notifKindCampaignRefund": "Reembolso de campaña",
|
||||||
|
"notifKindCampaignCancel": "Campaña cancelada",
|
||||||
|
"notifKindCampaignReport": "Informe de resultados",
|
||||||
"notifTargetPost": "la publicación",
|
"notifTargetPost": "la publicación",
|
||||||
"notifTargetCampaign": "la campaña",
|
"notifTargetCampaign": "la campaña",
|
||||||
"notifTargetQuest": "el detalle de la misión",
|
"notifTargetQuest": "el detalle de la misión",
|
||||||
@@ -827,7 +830,7 @@
|
|||||||
"heroNebulaGrowthRate": "Crecimiento de la nebulosa {percent}%",
|
"heroNebulaGrowthRate": "Crecimiento de la nebulosa {percent}%",
|
||||||
"homeIncubator": "Incubadora de planetas fandom",
|
"homeIncubator": "Incubadora de planetas fandom",
|
||||||
"homeGrowTogether": "Crecer juntos",
|
"homeGrowTogether": "Crecer juntos",
|
||||||
"homeArtistNews": "Noticias del artista",
|
"homeArtistNews": "AI Daily Briefing",
|
||||||
"growthConditionsTitle": "Condiciones de crecimiento",
|
"growthConditionsTitle": "Condiciones de crecimiento",
|
||||||
"eclipseButtonSoon": "¡Eclipse en {time}!\n¡Agita tu móvil para detenerlo!",
|
"eclipseButtonSoon": "¡Eclipse en {time}!\n¡Agita tu móvil para detenerlo!",
|
||||||
"eclipseButtonActive": "¡El eclipse ha comenzado!\n¡Agita el teléfono en {seconds} s para detenerlo!",
|
"eclipseButtonActive": "¡El eclipse ha comenzado!\n¡Agita el teléfono en {seconds} s para detenerlo!",
|
||||||
|
|||||||
@@ -272,7 +272,7 @@
|
|||||||
"campaignView": "キャンペーンを見る",
|
"campaignView": "キャンペーンを見る",
|
||||||
"growthFinalReached": "最終ステージに到達しました",
|
"growthFinalReached": "最終ステージに到達しました",
|
||||||
"zapRetrySame": "同じ貢献を再確認",
|
"zapRetrySame": "同じ貢献を再確認",
|
||||||
"heroOwnerSuffix": "さんの",
|
"heroOwner": "{name} さんの",
|
||||||
"heroStartPrompt": "惑星を作成または参加して\nファン宇宙を始めよう!",
|
"heroStartPrompt": "惑星を作成または参加して\nファン宇宙を始めよう!",
|
||||||
"heroNebulaPrompt": "活動で獲得したPOPで\n星雲に貢献しよう!",
|
"heroNebulaPrompt": "活動で獲得したPOPで\n星雲に貢献しよう!",
|
||||||
"heroRecentContribution": "{pop} POPの貢献で\n着実に成長しています。",
|
"heroRecentContribution": "{pop} POPの貢献で\n着実に成長しています。",
|
||||||
@@ -450,6 +450,9 @@
|
|||||||
"notifKindCampaign": "キャンペーン達成",
|
"notifKindCampaign": "キャンペーン達成",
|
||||||
"notifKindQuest": "クエスト完了",
|
"notifKindQuest": "クエスト完了",
|
||||||
"notifKindEvolution": "惑星の進化",
|
"notifKindEvolution": "惑星の進化",
|
||||||
|
"notifKindCampaignRefund": "キャンペーン返金",
|
||||||
|
"notifKindCampaignCancel": "キャンペーン中止",
|
||||||
|
"notifKindCampaignReport": "結果報告",
|
||||||
"notifTargetPost": "投稿",
|
"notifTargetPost": "投稿",
|
||||||
"notifTargetCampaign": "キャンペーン",
|
"notifTargetCampaign": "キャンペーン",
|
||||||
"notifTargetQuest": "クエスト詳細",
|
"notifTargetQuest": "クエスト詳細",
|
||||||
@@ -827,7 +830,7 @@
|
|||||||
"heroNebulaGrowthRate": "星雲成長率 {percent}%",
|
"heroNebulaGrowthRate": "星雲成長率 {percent}%",
|
||||||
"homeIncubator": "ファンダム惑星インキュベーター",
|
"homeIncubator": "ファンダム惑星インキュベーター",
|
||||||
"homeGrowTogether": "一緒に成長する",
|
"homeGrowTogether": "一緒に成長する",
|
||||||
"homeArtistNews": "アーティストニュース",
|
"homeArtistNews": "AI Daily Briefing",
|
||||||
"growthConditionsTitle": "成長条件",
|
"growthConditionsTitle": "成長条件",
|
||||||
"eclipseButtonSoon": "{time}後にイクリプスが発生します!\nスマホを振ってイクリプスを防ぎましょう!",
|
"eclipseButtonSoon": "{time}後にイクリプスが発生します!\nスマホを振ってイクリプスを防ぎましょう!",
|
||||||
"eclipseButtonActive": "イクリプスが発生しました!\n{seconds}秒以内にスマホを振って防ぎましょう!",
|
"eclipseButtonActive": "イクリプスが発生しました!\n{seconds}秒以内にスマホを振って防ぎましょう!",
|
||||||
|
|||||||
+12
-2
@@ -633,7 +633,14 @@
|
|||||||
"campaignView": "캠페인 보기",
|
"campaignView": "캠페인 보기",
|
||||||
"growthFinalReached": "최종 Stage에 도달했어요",
|
"growthFinalReached": "최종 Stage에 도달했어요",
|
||||||
"zapRetrySame": "같은 기여 다시 확인",
|
"zapRetrySame": "같은 기여 다시 확인",
|
||||||
"heroOwnerSuffix": "님의",
|
"heroOwner": "{name} 님의",
|
||||||
|
"@heroOwner": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"heroStartPrompt": "행성을 생성 또는 가입하고\n팬우주를 시작해 보세요!",
|
"heroStartPrompt": "행성을 생성 또는 가입하고\n팬우주를 시작해 보세요!",
|
||||||
"heroNebulaPrompt": "활동을 통해 얻은 POP을 통해\n성운에 기여해 보세요!",
|
"heroNebulaPrompt": "활동을 통해 얻은 POP을 통해\n성운에 기여해 보세요!",
|
||||||
"heroRecentContribution": "{pop} POP 기여로\n꾸준하게 성장하고 있어요.",
|
"heroRecentContribution": "{pop} POP 기여로\n꾸준하게 성장하고 있어요.",
|
||||||
@@ -898,6 +905,9 @@
|
|||||||
"notifKindCampaign": "캠페인 달성",
|
"notifKindCampaign": "캠페인 달성",
|
||||||
"notifKindQuest": "퀘스트 완료",
|
"notifKindQuest": "퀘스트 완료",
|
||||||
"notifKindEvolution": "행성 진화",
|
"notifKindEvolution": "행성 진화",
|
||||||
|
"notifKindCampaignRefund": "캠페인 환불",
|
||||||
|
"notifKindCampaignCancel": "캠페인 취소",
|
||||||
|
"notifKindCampaignReport": "결과 보고",
|
||||||
"notifTargetPost": "게시물",
|
"notifTargetPost": "게시물",
|
||||||
"notifTargetCampaign": "캠페인",
|
"notifTargetCampaign": "캠페인",
|
||||||
"notifTargetQuest": "퀘스트 상세",
|
"notifTargetQuest": "퀘스트 상세",
|
||||||
@@ -1698,7 +1708,7 @@
|
|||||||
},
|
},
|
||||||
"homeIncubator": "팬덤행성 인큐베이터",
|
"homeIncubator": "팬덤행성 인큐베이터",
|
||||||
"homeGrowTogether": "함께 성장하기",
|
"homeGrowTogether": "함께 성장하기",
|
||||||
"homeArtistNews": "아티스트 소식",
|
"homeArtistNews": "AI Daily Briefing",
|
||||||
"growthConditionsTitle": "성장 조건",
|
"growthConditionsTitle": "성장 조건",
|
||||||
"eclipseButtonSoon": "{time} 후 이클립스가 발생해요!\n휴대폰을 흔들어 이클립스를 막으세요!",
|
"eclipseButtonSoon": "{time} 후 이클립스가 발생해요!\n휴대폰을 흔들어 이클립스를 막으세요!",
|
||||||
"@eclipseButtonSoon": {
|
"@eclipseButtonSoon": {
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ import 'app_localizations_zh.dart';
|
|||||||
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||||
/// property.
|
/// property.
|
||||||
abstract class AppLocalizations {
|
abstract class AppLocalizations {
|
||||||
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
AppLocalizations(String locale)
|
||||||
|
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||||
|
|
||||||
final String localeName;
|
final String localeName;
|
||||||
|
|
||||||
@@ -73,7 +74,8 @@ abstract class AppLocalizations {
|
|||||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
|
static const LocalizationsDelegate<AppLocalizations> delegate =
|
||||||
|
_AppLocalizationsDelegate();
|
||||||
|
|
||||||
/// A list of this localizations delegate along with the default localizations
|
/// A list of this localizations delegate along with the default localizations
|
||||||
/// delegates.
|
/// delegates.
|
||||||
@@ -85,7 +87,8 @@ abstract class AppLocalizations {
|
|||||||
/// Additional delegates can be added by appending to this list in
|
/// Additional delegates can be added by appending to this list in
|
||||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||||
/// of delegates is preferred or required.
|
/// of delegates is preferred or required.
|
||||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
|
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||||
|
<LocalizationsDelegate<dynamic>>[
|
||||||
delegate,
|
delegate,
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
@@ -98,7 +101,7 @@ abstract class AppLocalizations {
|
|||||||
Locale('es'),
|
Locale('es'),
|
||||||
Locale('ja'),
|
Locale('ja'),
|
||||||
Locale('ko'),
|
Locale('ko'),
|
||||||
Locale('zh')
|
Locale('zh'),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// No description provided for @commonConfirm.
|
/// No description provided for @commonConfirm.
|
||||||
@@ -1733,11 +1736,11 @@ abstract class AppLocalizations {
|
|||||||
/// **'같은 기여 다시 확인'**
|
/// **'같은 기여 다시 확인'**
|
||||||
String get zapRetrySame;
|
String get zapRetrySame;
|
||||||
|
|
||||||
/// No description provided for @heroOwnerSuffix.
|
/// No description provided for @heroOwner.
|
||||||
///
|
///
|
||||||
/// In ko, this message translates to:
|
/// In ko, this message translates to:
|
||||||
/// **'님의'**
|
/// **'{name} 님의'**
|
||||||
String get heroOwnerSuffix;
|
String heroOwner(String name);
|
||||||
|
|
||||||
/// No description provided for @heroStartPrompt.
|
/// No description provided for @heroStartPrompt.
|
||||||
///
|
///
|
||||||
@@ -2801,6 +2804,24 @@ abstract class AppLocalizations {
|
|||||||
/// **'행성 진화'**
|
/// **'행성 진화'**
|
||||||
String get notifKindEvolution;
|
String get notifKindEvolution;
|
||||||
|
|
||||||
|
/// No description provided for @notifKindCampaignRefund.
|
||||||
|
///
|
||||||
|
/// In ko, this message translates to:
|
||||||
|
/// **'캠페인 환불'**
|
||||||
|
String get notifKindCampaignRefund;
|
||||||
|
|
||||||
|
/// No description provided for @notifKindCampaignCancel.
|
||||||
|
///
|
||||||
|
/// In ko, this message translates to:
|
||||||
|
/// **'캠페인 취소'**
|
||||||
|
String get notifKindCampaignCancel;
|
||||||
|
|
||||||
|
/// No description provided for @notifKindCampaignReport.
|
||||||
|
///
|
||||||
|
/// In ko, this message translates to:
|
||||||
|
/// **'결과 보고'**
|
||||||
|
String get notifKindCampaignReport;
|
||||||
|
|
||||||
/// No description provided for @notifTargetPost.
|
/// No description provided for @notifTargetPost.
|
||||||
///
|
///
|
||||||
/// In ko, this message translates to:
|
/// In ko, this message translates to:
|
||||||
@@ -4353,7 +4374,12 @@ abstract class AppLocalizations {
|
|||||||
///
|
///
|
||||||
/// In ko, this message translates to:
|
/// In ko, this message translates to:
|
||||||
/// **'{startMonth}월 {startDay}일 ~ {endMonth}월 {endDay}일'**
|
/// **'{startMonth}월 {startDay}일 ~ {endMonth}월 {endDay}일'**
|
||||||
String campaignPeriodRange(String startMonth, String startDay, String endMonth, String endDay);
|
String campaignPeriodRange(
|
||||||
|
String startMonth,
|
||||||
|
String startDay,
|
||||||
|
String endMonth,
|
||||||
|
String endDay,
|
||||||
|
);
|
||||||
|
|
||||||
/// No description provided for @campaignPeriodSelect.
|
/// No description provided for @campaignPeriodSelect.
|
||||||
///
|
///
|
||||||
@@ -4815,7 +4841,14 @@ abstract class AppLocalizations {
|
|||||||
///
|
///
|
||||||
/// In ko, this message translates to:
|
/// In ko, this message translates to:
|
||||||
/// **'{title}\n보상 {reward} POP × {count}명 = {pool} POP\n수수료 {fee} POP (공개 시 소멸)\n총 {total} POP 이 차감됩니다.'**
|
/// **'{title}\n보상 {reward} POP × {count}명 = {pool} POP\n수수료 {fee} POP (공개 시 소멸)\n총 {total} POP 이 차감됩니다.'**
|
||||||
String questCreateCost(String title, String reward, String count, String pool, String fee, String total);
|
String questCreateCost(
|
||||||
|
String title,
|
||||||
|
String reward,
|
||||||
|
String count,
|
||||||
|
String pool,
|
||||||
|
String fee,
|
||||||
|
String total,
|
||||||
|
);
|
||||||
|
|
||||||
/// No description provided for @questNoReward.
|
/// No description provided for @questNoReward.
|
||||||
///
|
///
|
||||||
@@ -5066,7 +5099,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @homeArtistNews.
|
/// No description provided for @homeArtistNews.
|
||||||
///
|
///
|
||||||
/// In ko, this message translates to:
|
/// In ko, this message translates to:
|
||||||
/// **'아티스트 소식'**
|
/// **'AI Daily Briefing'**
|
||||||
String get homeArtistNews;
|
String get homeArtistNews;
|
||||||
|
|
||||||
/// No description provided for @growthConditionsTitle.
|
/// No description provided for @growthConditionsTitle.
|
||||||
@@ -5538,7 +5571,8 @@ abstract class AppLocalizations {
|
|||||||
String questReflected(String fandom);
|
String questReflected(String fandom);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
class _AppLocalizationsDelegate
|
||||||
|
extends LocalizationsDelegate<AppLocalizations> {
|
||||||
const _AppLocalizationsDelegate();
|
const _AppLocalizationsDelegate();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -5547,28 +5581,32 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isSupported(Locale locale) => <String>['en', 'es', 'ja', 'ko', 'zh'].contains(locale.languageCode);
|
bool isSupported(Locale locale) =>
|
||||||
|
<String>['en', 'es', 'ja', 'ko', 'zh'].contains(locale.languageCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||||
|
|
||||||
|
|
||||||
// Lookup logic when only language code is specified.
|
// Lookup logic when only language code is specified.
|
||||||
switch (locale.languageCode) {
|
switch (locale.languageCode) {
|
||||||
case 'en': return AppLocalizationsEn();
|
case 'en':
|
||||||
case 'es': return AppLocalizationsEs();
|
return AppLocalizationsEn();
|
||||||
case 'ja': return AppLocalizationsJa();
|
case 'es':
|
||||||
case 'ko': return AppLocalizationsKo();
|
return AppLocalizationsEs();
|
||||||
case 'zh': return AppLocalizationsZh();
|
case 'ja':
|
||||||
|
return AppLocalizationsJa();
|
||||||
|
case 'ko':
|
||||||
|
return AppLocalizationsKo();
|
||||||
|
case 'zh':
|
||||||
|
return AppLocalizationsZh();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw FlutterError(
|
throw FlutterError(
|
||||||
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||||
'an issue with the localizations generation tool. Please file an issue '
|
'an issue with the localizations generation tool. Please file an issue '
|
||||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||||
'that was used.'
|
'that was used.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -63,31 +63,25 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderLogin(String provider) {
|
String authProviderLogin(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'apple': 'Appleでログイン',
|
'apple': 'Appleでログイン',
|
||||||
'google': 'Googleでログイン',
|
'google': 'Googleでログイン',
|
||||||
'kakao': 'Kakaoでログイン',
|
'kakao': 'Kakaoでログイン',
|
||||||
'line': 'LINEでログイン',
|
'line': 'LINEでログイン',
|
||||||
'other': 'メールでログイン',
|
'other': 'メールでログイン',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderSignup(String provider) {
|
String authProviderSignup(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'apple': 'Appleで登録',
|
'apple': 'Appleで登録',
|
||||||
'google': 'Googleで登録',
|
'google': 'Googleで登録',
|
||||||
'kakao': 'Kakaoで登録',
|
'kakao': 'Kakaoで登録',
|
||||||
'line': 'LINEで登録',
|
'line': 'LINEで登録',
|
||||||
'other': 'メールで登録',
|
'other': 'メールで登録',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,31 +519,25 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderStart(String provider) {
|
String authProviderStart(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'email': 'メールではじめる',
|
'email': 'メールではじめる',
|
||||||
'kakao': 'Kakaoではじめる',
|
'kakao': 'Kakaoではじめる',
|
||||||
'line': 'LINEではじめる',
|
'line': 'LINEではじめる',
|
||||||
'apple': 'Appleではじめる',
|
'apple': 'Appleではじめる',
|
||||||
'other': 'Googleではじめる',
|
'other': 'Googleではじめる',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderName(String provider) {
|
String authProviderName(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'email': 'メール',
|
'email': 'メール',
|
||||||
'kakao': 'Kakao',
|
'kakao': 'Kakao',
|
||||||
'line': 'LINE',
|
'line': 'LINE',
|
||||||
'apple': 'Apple',
|
'apple': 'Apple',
|
||||||
'other': 'Google',
|
'other': 'Google',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,7 +869,8 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get nebulaNameTaken => 'すでに使用されている名前です。';
|
String get nebulaNameTaken => 'すでに使用されている名前です。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get nebulaNameInvalid => '2〜20文字のハングル・英字・数字で入力してください(記号・絵文字・連続/前後の空白は不可)。';
|
String get nebulaNameInvalid =>
|
||||||
|
'2〜20文字のハングル・英字・数字で入力してください(記号・絵文字・連続/前後の空白は不可)。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get nebulaNameHint => 'ファンダム名';
|
String get nebulaNameHint => 'ファンダム名';
|
||||||
@@ -959,7 +948,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get zapRetrySame => '同じ貢献を再確認';
|
String get zapRetrySame => '同じ貢献を再確認';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroOwnerSuffix => 'さんの';
|
String heroOwner(String name) {
|
||||||
|
return '$name さんの';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroStartPrompt => '惑星を作成または参加して\nファン宇宙を始めよう!';
|
String get heroStartPrompt => '惑星を作成または参加して\nファン宇宙を始めよう!';
|
||||||
@@ -1198,7 +1189,8 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get pushTopicEssentialDesc => 'アカウント・決済・メンテナンスのお知らせです。オフにできません。';
|
String get pushTopicEssentialDesc => 'アカウント・決済・メンテナンスのお知らせです。オフにできません。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get pushTopicFandomEventDesc => 'イクリプス・キャンペーン・クエスト・アーティストホットタイムのお知らせです。';
|
String get pushTopicFandomEventDesc =>
|
||||||
|
'イクリプス・キャンペーン・クエスト・アーティストホットタイムのお知らせです。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get pushTopicBriefingDesc => '惑星の一日をまとめたブリーフィングカードをお届けします。';
|
String get pushTopicBriefingDesc => '惑星の一日をまとめたブリーフィングカードをお届けします。';
|
||||||
@@ -1304,7 +1296,8 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get payEmpty => '決済履歴はありません。';
|
String get payEmpty => '決済履歴はありません。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get payRefundGuide => '消費型商品:購入後7日以内かつ未使用の商品は返金できます。一部でも使用した商品は返金対象外です。\nサブスク商品:決済後24時間以内に特典(追加報酬など)を未使用の場合、返金できます。\n返金承認後7〜14日以内に返金が完了します。';
|
String get payRefundGuide =>
|
||||||
|
'消費型商品:購入後7日以内かつ未使用の商品は返金できます。一部でも使用した商品は返金対象外です。\nサブスク商品:決済後24時間以内に特典(追加報酬など)を未使用の場合、返金できます。\n返金承認後7〜14日以内に返金が完了します。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get payDetailTitle => '決済履歴の詳細';
|
String get payDetailTitle => '決済履歴の詳細';
|
||||||
@@ -1516,6 +1509,15 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get notifKindEvolution => '惑星の進化';
|
String get notifKindEvolution => '惑星の進化';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignRefund => 'キャンペーン返金';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignCancel => 'キャンペーン中止';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignReport => '結果報告';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notifTargetPost => '投稿';
|
String get notifTargetPost => '投稿';
|
||||||
|
|
||||||
@@ -1843,9 +1845,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String countryName(String code) {
|
String countryName(String code) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(code, {
|
||||||
code,
|
|
||||||
{
|
|
||||||
'KR': '韓国',
|
'KR': '韓国',
|
||||||
'JP': '日本',
|
'JP': '日本',
|
||||||
'US': 'アメリカ',
|
'US': 'アメリカ',
|
||||||
@@ -1869,8 +1869,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
'AU': 'オーストラリア',
|
'AU': 'オーストラリア',
|
||||||
'ZZ': '未指定',
|
'ZZ': '未指定',
|
||||||
'other': '-',
|
'other': '-',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2066,7 +2065,8 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get adWarpBefore => 'Warping...ファンダムとアーティストにもっと貢献できるよう、ぴったりの広告を探しています。';
|
String get adWarpBefore =>
|
||||||
|
'Warping...ファンダムとアーティストにもっと貢献できるよう、ぴったりの広告を探しています。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get adWarpAfter => 'Warping...あなたの貢献への報酬を準備しています。';
|
String get adWarpAfter => 'Warping...あなたの貢献への報酬を準備しています。';
|
||||||
@@ -2219,7 +2219,8 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get nebulaAlreadyToday => '今日はすでに星雲を作成しました。';
|
String get nebulaAlreadyToday => '今日はすでに星雲を作成しました。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get nebulaCreatedKeepPrimary => '星雲を作成しました。メイン惑星は前回の変更から30日経過後に変更できるため、そのままです。';
|
String get nebulaCreatedKeepPrimary =>
|
||||||
|
'星雲を作成しました。メイン惑星は前回の変更から30日経過後に変更できるため、そのままです。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planetLeave => '脱退する';
|
String get planetLeave => '脱退する';
|
||||||
@@ -2385,7 +2386,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get campaignWeekdays => '日,月,火,水,木,金,土';
|
String get campaignWeekdays => '日,月,火,水,木,金,土';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String campaignPeriodRange(String startMonth, String startDay, String endMonth, String endDay) {
|
String campaignPeriodRange(
|
||||||
|
String startMonth,
|
||||||
|
String startDay,
|
||||||
|
String endMonth,
|
||||||
|
String endDay,
|
||||||
|
) {
|
||||||
return '$startMonth月$startDay日 ~ $endMonth月$endDay日';
|
return '$startMonth月$startDay日 ~ $endMonth月$endDay日';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2429,7 +2435,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get campaignSubmitTitle => 'この内容で投稿しますか?';
|
String get campaignSubmitTitle => 'この内容で投稿しますか?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String campaignSubmitBody(String title, String goal, String days, String fee) {
|
String campaignSubmitBody(
|
||||||
|
String title,
|
||||||
|
String goal,
|
||||||
|
String days,
|
||||||
|
String fee,
|
||||||
|
) {
|
||||||
return '$title · 目標 $goal POP · $days日\n登録料 $fee POP が差し引かれ、運営の審査後に支援が始まります。';
|
return '$title · 目標 $goal POP · $days日\n登録料 $fee POP が差し引かれ、運営の審査後に支援が始まります。';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2636,7 +2647,14 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String questCreateCost(String title, String reward, String count, String pool, String fee, String total) {
|
String questCreateCost(
|
||||||
|
String title,
|
||||||
|
String reward,
|
||||||
|
String count,
|
||||||
|
String pool,
|
||||||
|
String fee,
|
||||||
|
String total,
|
||||||
|
) {
|
||||||
return '$title\n報酬 $reward POP × $count人 = $pool POP\n手数料 $fee POP(公開時に消滅)\n合計 $total POP が差し引かれます。';
|
return '$title\n報酬 $reward POP × $count人 = $pool POP\n手数料 $fee POP(公開時に消滅)\n合計 $total POP が差し引かれます。';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2778,7 +2796,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get homeGrowTogether => '一緒に成長する';
|
String get homeGrowTogether => '一緒に成長する';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeArtistNews => 'アーティストニュース';
|
String get homeArtistNews => 'AI Daily Briefing';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get growthConditionsTitle => '成長条件';
|
String get growthConditionsTitle => '成長条件';
|
||||||
@@ -2845,7 +2863,8 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get questCancelInReview => '審査中に取り消すと、預けた報酬POPと手数料はすべて返還されます。';
|
String get questCancelInReview => '審査中に取り消すと、預けた報酬POPと手数料はすべて返還されます。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get questCancelPublished => '公開中のクエストを取り消すと参加はすぐ締め切られ、残りの報酬POPは返還されます。手数料は返還されません。';
|
String get questCancelPublished =>
|
||||||
|
'公開中のクエストを取り消すと参加はすぐ締め切られ、残りの報酬POPは返還されます。手数料は返還されません。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get questUnderReview => '運営の審査中です。承認されると公開されます。';
|
String get questUnderReview => '運営の審査中です。承認されると公開されます。';
|
||||||
|
|||||||
@@ -63,31 +63,25 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderLogin(String provider) {
|
String authProviderLogin(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'apple': 'Apple로 로그인',
|
'apple': 'Apple로 로그인',
|
||||||
'google': 'Google로 로그인',
|
'google': 'Google로 로그인',
|
||||||
'kakao': 'Kakao로 로그인',
|
'kakao': 'Kakao로 로그인',
|
||||||
'line': 'LINE으로 로그인',
|
'line': 'LINE으로 로그인',
|
||||||
'other': '이메일로 로그인',
|
'other': '이메일로 로그인',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderSignup(String provider) {
|
String authProviderSignup(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'apple': 'Apple로 가입하기',
|
'apple': 'Apple로 가입하기',
|
||||||
'google': 'Google로 가입하기',
|
'google': 'Google로 가입하기',
|
||||||
'kakao': 'Kakao로 가입하기',
|
'kakao': 'Kakao로 가입하기',
|
||||||
'line': 'LINE으로 가입하기',
|
'line': 'LINE으로 가입하기',
|
||||||
'other': '이메일로 가입하기',
|
'other': '이메일로 가입하기',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,31 +519,25 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderStart(String provider) {
|
String authProviderStart(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'email': '이메일로 시작하기',
|
'email': '이메일로 시작하기',
|
||||||
'kakao': '카카오로 시작하기',
|
'kakao': '카카오로 시작하기',
|
||||||
'line': 'LINE으로 시작하기',
|
'line': 'LINE으로 시작하기',
|
||||||
'apple': 'Apple로 시작하기',
|
'apple': 'Apple로 시작하기',
|
||||||
'other': 'Google로 시작하기',
|
'other': 'Google로 시작하기',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderName(String provider) {
|
String authProviderName(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'email': '이메일',
|
'email': '이메일',
|
||||||
'kakao': '카카오',
|
'kakao': '카카오',
|
||||||
'line': 'LINE',
|
'line': 'LINE',
|
||||||
'apple': 'Apple',
|
'apple': 'Apple',
|
||||||
'other': 'Google',
|
'other': 'Google',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,7 +869,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get nebulaNameTaken => '이미 사용중인 이름입니다.';
|
String get nebulaNameTaken => '이미 사용중인 이름입니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get nebulaNameInvalid => '2~20자 한글·영문·숫자로 입력해 주세요(특수문자·이모지·연속/앞뒤 공백 불가).';
|
String get nebulaNameInvalid =>
|
||||||
|
'2~20자 한글·영문·숫자로 입력해 주세요(특수문자·이모지·연속/앞뒤 공백 불가).';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get nebulaNameHint => '팬덤 이름';
|
String get nebulaNameHint => '팬덤 이름';
|
||||||
@@ -959,7 +948,9 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get zapRetrySame => '같은 기여 다시 확인';
|
String get zapRetrySame => '같은 기여 다시 확인';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroOwnerSuffix => '님의';
|
String heroOwner(String name) {
|
||||||
|
return '$name 님의';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroStartPrompt => '행성을 생성 또는 가입하고\n팬우주를 시작해 보세요!';
|
String get heroStartPrompt => '행성을 생성 또는 가입하고\n팬우주를 시작해 보세요!';
|
||||||
@@ -1304,7 +1295,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get payEmpty => '결제 내역이 없습니다.';
|
String get payEmpty => '결제 내역이 없습니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get payRefundGuide => '소비형 상품 : 구매 후 7일 이내이며 사용하지 않은 상품은 환불 가능합니다. 일부라도 사용된 상품은 환불 대상에서 제외됩니다.\n구독형 상품 : 결제 후 24시간 이내 혜택 미사용(추가 보상 등) 시 환불 가능합니다.\n환불 승인 후 7-14 이내 환불 완료 처리가 됩니다.';
|
String get payRefundGuide =>
|
||||||
|
'소비형 상품 : 구매 후 7일 이내이며 사용하지 않은 상품은 환불 가능합니다. 일부라도 사용된 상품은 환불 대상에서 제외됩니다.\n구독형 상품 : 결제 후 24시간 이내 혜택 미사용(추가 보상 등) 시 환불 가능합니다.\n환불 승인 후 7-14 이내 환불 완료 처리가 됩니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get payDetailTitle => '결제 내역 상세';
|
String get payDetailTitle => '결제 내역 상세';
|
||||||
@@ -1490,7 +1482,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get tierPromptSuffix => '를 올려보세요';
|
String get tierPromptSuffix => '를 올려보세요';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get tierPassportNote => 'Passport Tier 는 레벨과 별개인 카드 외형 등급이며 내려가지 않습니다.';
|
String get tierPassportNote =>
|
||||||
|
'Passport Tier 는 레벨과 별개인 카드 외형 등급이며 내려가지 않습니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get tierTotalContribution => '누적 기여';
|
String get tierTotalContribution => '누적 기여';
|
||||||
@@ -1516,6 +1509,15 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get notifKindEvolution => '행성 진화';
|
String get notifKindEvolution => '행성 진화';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignRefund => '캠페인 환불';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignCancel => '캠페인 취소';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignReport => '결과 보고';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notifTargetPost => '게시물';
|
String get notifTargetPost => '게시물';
|
||||||
|
|
||||||
@@ -1594,7 +1596,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get stationReason => '추천 이유 (선택)';
|
String get stationReason => '추천 이유 (선택)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get stationSuggestNote => '제안하신 링크는 운영 검토를 거쳐 등록됩니다. 검토 결과는 알림으로 알려 드립니다.';
|
String get stationSuggestNote =>
|
||||||
|
'제안하신 링크는 운영 검토를 거쳐 등록됩니다. 검토 결과는 알림으로 알려 드립니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroGiftUnavailable => '이 선물은 현재 수령할 수 없습니다.';
|
String get heroGiftUnavailable => '이 선물은 현재 수령할 수 없습니다.';
|
||||||
@@ -1843,9 +1846,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String countryName(String code) {
|
String countryName(String code) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(code, {
|
||||||
code,
|
|
||||||
{
|
|
||||||
'KR': '대한민국',
|
'KR': '대한민국',
|
||||||
'JP': '일본',
|
'JP': '일본',
|
||||||
'US': '미국',
|
'US': '미국',
|
||||||
@@ -1869,8 +1870,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
'AU': '호주',
|
'AU': '호주',
|
||||||
'ZZ': '미지정',
|
'ZZ': '미지정',
|
||||||
'other': '-',
|
'other': '-',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2066,7 +2066,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get adWarpBefore => 'Warping...우리 팬덤과 아티스트에 더 많은 기여를 위해 적합한 광고를 열심히 찾고 있어요.';
|
String get adWarpBefore =>
|
||||||
|
'Warping...우리 팬덤과 아티스트에 더 많은 기여를 위해 적합한 광고를 열심히 찾고 있어요.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get adWarpAfter => 'Warping...당신의 기여에 대한 보상을 준비하고 있어요.';
|
String get adWarpAfter => 'Warping...당신의 기여에 대한 보상을 준비하고 있어요.';
|
||||||
@@ -2219,7 +2220,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get nebulaAlreadyToday => '이미 오늘 성운을 생성하셨습니다.';
|
String get nebulaAlreadyToday => '이미 오늘 성운을 생성하셨습니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get nebulaCreatedKeepPrimary => '성운을 만들었습니다. 기본 행성은 최근 변경 후 30일이 지나야 바꿀 수 있어 그대로 유지됩니다.';
|
String get nebulaCreatedKeepPrimary =>
|
||||||
|
'성운을 만들었습니다. 기본 행성은 최근 변경 후 30일이 지나야 바꿀 수 있어 그대로 유지됩니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planetLeave => '탈퇴하기';
|
String get planetLeave => '탈퇴하기';
|
||||||
@@ -2246,7 +2248,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get campaignStatusWithdrawn => '개설자가 철회한 캠페인입니다.';
|
String get campaignStatusWithdrawn => '개설자가 철회한 캠페인입니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get campaignStatusCancelled => '운영 사유로 취소되었습니다. 등록비와 후원 POP 을 돌려드렸습니다.';
|
String get campaignStatusCancelled =>
|
||||||
|
'운영 사유로 취소되었습니다. 등록비와 후원 POP 을 돌려드렸습니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rankWindowDaily => '일간';
|
String get rankWindowDaily => '일간';
|
||||||
@@ -2385,7 +2388,12 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get campaignWeekdays => '일,월,화,수,목,금,토';
|
String get campaignWeekdays => '일,월,화,수,목,금,토';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String campaignPeriodRange(String startMonth, String startDay, String endMonth, String endDay) {
|
String campaignPeriodRange(
|
||||||
|
String startMonth,
|
||||||
|
String startDay,
|
||||||
|
String endMonth,
|
||||||
|
String endDay,
|
||||||
|
) {
|
||||||
return '$startMonth월 $startDay일 ~ $endMonth월 $endDay일';
|
return '$startMonth월 $startDay일 ~ $endMonth월 $endDay일';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2429,7 +2437,12 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get campaignSubmitTitle => '이 내용으로 올릴까요?';
|
String get campaignSubmitTitle => '이 내용으로 올릴까요?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String campaignSubmitBody(String title, String goal, String days, String fee) {
|
String campaignSubmitBody(
|
||||||
|
String title,
|
||||||
|
String goal,
|
||||||
|
String days,
|
||||||
|
String fee,
|
||||||
|
) {
|
||||||
return '$title · 목표 $goal POP · $days일\n등록비 $fee POP 이 빠지고 운영 검수 뒤 모금이 열립니다.';
|
return '$title · 목표 $goal POP · $days일\n등록비 $fee POP 이 빠지고 운영 검수 뒤 모금이 열립니다.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2636,7 +2649,14 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String questCreateCost(String title, String reward, String count, String pool, String fee, String total) {
|
String questCreateCost(
|
||||||
|
String title,
|
||||||
|
String reward,
|
||||||
|
String count,
|
||||||
|
String pool,
|
||||||
|
String fee,
|
||||||
|
String total,
|
||||||
|
) {
|
||||||
return '$title\n보상 $reward POP × $count명 = $pool POP\n수수료 $fee POP (공개 시 소멸)\n총 $total POP 이 차감됩니다.';
|
return '$title\n보상 $reward POP × $count명 = $pool POP\n수수료 $fee POP (공개 시 소멸)\n총 $total POP 이 차감됩니다.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2778,7 +2798,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get homeGrowTogether => '함께 성장하기';
|
String get homeGrowTogether => '함께 성장하기';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeArtistNews => '아티스트 소식';
|
String get homeArtistNews => 'AI Daily Briefing';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get growthConditionsTitle => '성장 조건';
|
String get growthConditionsTitle => '성장 조건';
|
||||||
@@ -2842,10 +2862,12 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get questCancel => '퀘스트 취소';
|
String get questCancel => '퀘스트 취소';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get questCancelInReview => '검수 중인 퀘스트를 취소하면 예치한 보상 POP 과 수수료를 모두 돌려드려요.';
|
String get questCancelInReview =>
|
||||||
|
'검수 중인 퀘스트를 취소하면 예치한 보상 POP 과 수수료를 모두 돌려드려요.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get questCancelPublished => '공개된 퀘스트를 취소하면 참여가 바로 마감되고 남은 보상 POP 은 돌려드려요. 수수료는 돌려드리지 않아요.';
|
String get questCancelPublished =>
|
||||||
|
'공개된 퀘스트를 취소하면 참여가 바로 마감되고 남은 보상 POP 은 돌려드려요. 수수료는 돌려드리지 않아요.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get questUnderReview => '운영 검수 중이에요. 승인되면 공개됩니다.';
|
String get questUnderReview => '운영 검수 중이에요. 승인되면 공개됩니다.';
|
||||||
@@ -2966,7 +2988,8 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get onbPermLocation => '위치';
|
String get onbPermLocation => '위치';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onbPermLocationDesc => '위치 퀘스트의 장소 도착을 확인합니다. 위치 퀘스트를 처음 시작할 때 요청해요.';
|
String get onbPermLocationDesc =>
|
||||||
|
'위치 퀘스트의 장소 도착을 확인합니다. 위치 퀘스트를 처음 시작할 때 요청해요.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onbPermContinue => '계속';
|
String get onbPermContinue => '계속';
|
||||||
|
|||||||
@@ -63,31 +63,25 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderLogin(String provider) {
|
String authProviderLogin(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'apple': '通过Apple登录',
|
'apple': '通过Apple登录',
|
||||||
'google': '通过Google登录',
|
'google': '通过Google登录',
|
||||||
'kakao': '通过Kakao登录',
|
'kakao': '通过Kakao登录',
|
||||||
'line': '通过LINE登录',
|
'line': '通过LINE登录',
|
||||||
'other': '通过邮箱登录',
|
'other': '通过邮箱登录',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderSignup(String provider) {
|
String authProviderSignup(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'apple': '通过Apple注册',
|
'apple': '通过Apple注册',
|
||||||
'google': '通过Google注册',
|
'google': '通过Google注册',
|
||||||
'kakao': '通过Kakao注册',
|
'kakao': '通过Kakao注册',
|
||||||
'line': '通过LINE注册',
|
'line': '通过LINE注册',
|
||||||
'other': '通过邮箱注册',
|
'other': '通过邮箱注册',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,31 +519,25 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderStart(String provider) {
|
String authProviderStart(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'email': '使用邮箱开始',
|
'email': '使用邮箱开始',
|
||||||
'kakao': '使用 Kakao 开始',
|
'kakao': '使用 Kakao 开始',
|
||||||
'line': '使用 LINE 开始',
|
'line': '使用 LINE 开始',
|
||||||
'apple': '使用 Apple 开始',
|
'apple': '使用 Apple 开始',
|
||||||
'other': '使用 Google 开始',
|
'other': '使用 Google 开始',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String authProviderName(String provider) {
|
String authProviderName(String provider) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(provider, {
|
||||||
provider,
|
|
||||||
{
|
|
||||||
'email': '邮箱',
|
'email': '邮箱',
|
||||||
'kakao': 'Kakao',
|
'kakao': 'Kakao',
|
||||||
'line': 'LINE',
|
'line': 'LINE',
|
||||||
'apple': 'Apple',
|
'apple': 'Apple',
|
||||||
'other': 'Google',
|
'other': 'Google',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -959,7 +947,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get zapRetrySame => '重新确认本次贡献';
|
String get zapRetrySame => '重新确认本次贡献';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroOwnerSuffix => '的';
|
String heroOwner(String name) {
|
||||||
|
return '$name 的';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroStartPrompt => '创建或加入星球,\n开启粉丝宇宙吧!';
|
String get heroStartPrompt => '创建或加入星球,\n开启粉丝宇宙吧!';
|
||||||
@@ -1304,7 +1294,8 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get payEmpty => '暂无支付记录。';
|
String get payEmpty => '暂无支付记录。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get payRefundGuide => '消耗型商品:购买后 7 天内且未使用可退款,已部分使用的商品不予退款。\n订阅型商品:支付后 24 小时内未使用权益(如额外奖励)可退款。\n退款批准后 7–14 天内完成退款。';
|
String get payRefundGuide =>
|
||||||
|
'消耗型商品:购买后 7 天内且未使用可退款,已部分使用的商品不予退款。\n订阅型商品:支付后 24 小时内未使用权益(如额外奖励)可退款。\n退款批准后 7–14 天内完成退款。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get payDetailTitle => '支付详情';
|
String get payDetailTitle => '支付详情';
|
||||||
@@ -1516,6 +1507,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get notifKindEvolution => '星球进化';
|
String get notifKindEvolution => '星球进化';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignRefund => '活动退款';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignCancel => '活动取消';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignReport => '结果报告';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notifTargetPost => '帖子';
|
String get notifTargetPost => '帖子';
|
||||||
|
|
||||||
@@ -1843,9 +1843,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String countryName(String code) {
|
String countryName(String code) {
|
||||||
String _temp0 = intl.Intl.selectLogic(
|
String _temp0 = intl.Intl.selectLogic(code, {
|
||||||
code,
|
|
||||||
{
|
|
||||||
'KR': '韩国',
|
'KR': '韩国',
|
||||||
'JP': '日本',
|
'JP': '日本',
|
||||||
'US': '美国',
|
'US': '美国',
|
||||||
@@ -1869,8 +1867,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
'AU': '澳大利亚',
|
'AU': '澳大利亚',
|
||||||
'ZZ': '未指定',
|
'ZZ': '未指定',
|
||||||
'other': '-',
|
'other': '-',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2385,7 +2382,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get campaignWeekdays => '日,一,二,三,四,五,六';
|
String get campaignWeekdays => '日,一,二,三,四,五,六';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String campaignPeriodRange(String startMonth, String startDay, String endMonth, String endDay) {
|
String campaignPeriodRange(
|
||||||
|
String startMonth,
|
||||||
|
String startDay,
|
||||||
|
String endMonth,
|
||||||
|
String endDay,
|
||||||
|
) {
|
||||||
return '$startMonth月$startDay日 ~ $endMonth月$endDay日';
|
return '$startMonth月$startDay日 ~ $endMonth月$endDay日';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2429,7 +2431,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get campaignSubmitTitle => '按此内容提交吗?';
|
String get campaignSubmitTitle => '按此内容提交吗?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String campaignSubmitBody(String title, String goal, String days, String fee) {
|
String campaignSubmitBody(
|
||||||
|
String title,
|
||||||
|
String goal,
|
||||||
|
String days,
|
||||||
|
String fee,
|
||||||
|
) {
|
||||||
return '$title · 目标 $goal POP · $days天\n将扣除 $fee POP 登记费,运营审核后开放筹集。';
|
return '$title · 目标 $goal POP · $days天\n将扣除 $fee POP 登记费,运营审核后开放筹集。';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2636,7 +2643,14 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String questCreateCost(String title, String reward, String count, String pool, String fee, String total) {
|
String questCreateCost(
|
||||||
|
String title,
|
||||||
|
String reward,
|
||||||
|
String count,
|
||||||
|
String pool,
|
||||||
|
String fee,
|
||||||
|
String total,
|
||||||
|
) {
|
||||||
return '$title\n奖励 $reward POP × $count人 = $pool POP\n手续费 $fee POP(发布时销毁)\n共扣除 $total POP。';
|
return '$title\n奖励 $reward POP × $count人 = $pool POP\n手续费 $fee POP(发布时销毁)\n共扣除 $total POP。';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2778,7 +2792,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get homeGrowTogether => '一起成长';
|
String get homeGrowTogether => '一起成长';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeArtistNews => '艺人动态';
|
String get homeArtistNews => 'AI Daily Briefing';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get growthConditionsTitle => '成长条件';
|
String get growthConditionsTitle => '成长条件';
|
||||||
|
|||||||
@@ -272,7 +272,7 @@
|
|||||||
"campaignView": "查看活动",
|
"campaignView": "查看活动",
|
||||||
"growthFinalReached": "已到达最终阶段",
|
"growthFinalReached": "已到达最终阶段",
|
||||||
"zapRetrySame": "重新确认本次贡献",
|
"zapRetrySame": "重新确认本次贡献",
|
||||||
"heroOwnerSuffix": "的",
|
"heroOwner": "{name} 的",
|
||||||
"heroStartPrompt": "创建或加入星球,\n开启粉丝宇宙吧!",
|
"heroStartPrompt": "创建或加入星球,\n开启粉丝宇宙吧!",
|
||||||
"heroNebulaPrompt": "用活动获得的 POP\n为星云做贡献吧!",
|
"heroNebulaPrompt": "用活动获得的 POP\n为星云做贡献吧!",
|
||||||
"heroRecentContribution": "凭借 {pop} POP 的贡献\n正在稳步成长。",
|
"heroRecentContribution": "凭借 {pop} POP 的贡献\n正在稳步成长。",
|
||||||
@@ -450,6 +450,9 @@
|
|||||||
"notifKindCampaign": "活动达成",
|
"notifKindCampaign": "活动达成",
|
||||||
"notifKindQuest": "任务完成",
|
"notifKindQuest": "任务完成",
|
||||||
"notifKindEvolution": "星球进化",
|
"notifKindEvolution": "星球进化",
|
||||||
|
"notifKindCampaignRefund": "活动退款",
|
||||||
|
"notifKindCampaignCancel": "活动取消",
|
||||||
|
"notifKindCampaignReport": "结果报告",
|
||||||
"notifTargetPost": "帖子",
|
"notifTargetPost": "帖子",
|
||||||
"notifTargetCampaign": "活动",
|
"notifTargetCampaign": "活动",
|
||||||
"notifTargetQuest": "任务详情",
|
"notifTargetQuest": "任务详情",
|
||||||
@@ -827,7 +830,7 @@
|
|||||||
"heroNebulaGrowthRate": "星云成长率 {percent}%",
|
"heroNebulaGrowthRate": "星云成长率 {percent}%",
|
||||||
"homeIncubator": "粉丝星球孵化器",
|
"homeIncubator": "粉丝星球孵化器",
|
||||||
"homeGrowTogether": "一起成长",
|
"homeGrowTogether": "一起成长",
|
||||||
"homeArtistNews": "艺人动态",
|
"homeArtistNews": "AI Daily Briefing",
|
||||||
"growthConditionsTitle": "成长条件",
|
"growthConditionsTitle": "成长条件",
|
||||||
"eclipseButtonSoon": "{time}后将发生日食!\n摇动手机来阻止日食!",
|
"eclipseButtonSoon": "{time}后将发生日食!\n摇动手机来阻止日食!",
|
||||||
"eclipseButtonActive": "日食已发生!\n请在{seconds}秒内摇动手机来阻止!",
|
"eclipseButtonActive": "日食已发生!\n请在{seconds}秒内摇动手机来阻止!",
|
||||||
|
|||||||
@@ -887,7 +887,7 @@ class RemoteAppLocalizations extends AppLocalizations {
|
|||||||
String get heroNextEvolution => _remote['heroNextEvolution'] ?? _base.heroNextEvolution;
|
String get heroNextEvolution => _remote['heroNextEvolution'] ?? _base.heroNextEvolution;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroOwnerSuffix => _remote['heroOwnerSuffix'] ?? _base.heroOwnerSuffix;
|
String heroOwner(String name) => _fill('heroOwner', {'name': name}) ?? _base.heroOwner(name);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get heroParticipate => _remote['heroParticipate'] ?? _base.heroParticipate;
|
String get heroParticipate => _remote['heroParticipate'] ?? _base.heroParticipate;
|
||||||
@@ -1357,6 +1357,15 @@ class RemoteAppLocalizations extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get notifKindCampaign => _remote['notifKindCampaign'] ?? _base.notifKindCampaign;
|
String get notifKindCampaign => _remote['notifKindCampaign'] ?? _base.notifKindCampaign;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignCancel => _remote['notifKindCampaignCancel'] ?? _base.notifKindCampaignCancel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignRefund => _remote['notifKindCampaignRefund'] ?? _base.notifKindCampaignRefund;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notifKindCampaignReport => _remote['notifKindCampaignReport'] ?? _base.notifKindCampaignReport;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notifKindComment => _remote['notifKindComment'] ?? _base.notifKindComment;
|
String get notifKindComment => _remote['notifKindComment'] ?? _base.notifKindComment;
|
||||||
|
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ void main() {
|
|||||||
final db = await _seed();
|
final db = await _seed();
|
||||||
final snapshot = await FirestoreHomeRepository(_backend(db)).load();
|
final snapshot = await FirestoreHomeRepository(_backend(db)).load();
|
||||||
|
|
||||||
expect(snapshot.popBalance, 12540);
|
expect(snapshot.popBalance, 12840, reason: '총 보유 = 일반 12,540 + 기여 300');
|
||||||
expect(snapshot.liveTicker, contains('버프'));
|
expect(snapshot.liveTicker, contains('버프'));
|
||||||
expect(snapshot.growth.fandomName, 'BTS');
|
expect(snapshot.growth.fandomName, 'BTS');
|
||||||
// 설명 프레임 `2097:16391` — 인사 줄은 로그인 사용자 기준. 팬덤의 직전 기여자가 아니다.
|
// 설명 프레임 `2097:16391` — 인사 줄은 로그인 사용자 기준. 팬덤의 직전 기여자가 아니다.
|
||||||
@@ -734,6 +734,21 @@ void main() {
|
|||||||
expect(again.first.title, '글 0');
|
expect(again.first.title, '글 0');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('HOME 헤더 POP 은 총 보유(일반 + 기여 + 보류) — POP 내역 · 퀘스트 목록과 같은 값(179:29099 · 10/8 r11)', () async {
|
||||||
|
final db = await _seed();
|
||||||
|
await db.doc('users/$_uid/wallet/main').set({
|
||||||
|
'activityBalance': 12130,
|
||||||
|
'supportBalance': 330,
|
||||||
|
'pendingActivity': 20,
|
||||||
|
});
|
||||||
|
final backend = _backend(db);
|
||||||
|
final home = FirestoreHomeRepository(backend);
|
||||||
|
expect((await home.load()).popBalance, 12480);
|
||||||
|
expect(await home.watchPopBalance().first, 12480);
|
||||||
|
expect(await FirestoreQuestRepository(backend).popBalance(), 12480);
|
||||||
|
expect((await FirestoreMyRepository(backend).profile()).balancePop, 12480);
|
||||||
|
});
|
||||||
|
|
||||||
test('다른 팬덤 글은 섞이지 않는다', () async {
|
test('다른 팬덤 글은 섞이지 않는다', () async {
|
||||||
final db = await seedPlanet();
|
final db = await seedPlanet();
|
||||||
await db.collection('posts').add({
|
await db.collection('posts').add({
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:fancake/core/ui/clipboard_copy.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
/// 링크 복사 — Android 13+ 는 시스템이 「클립보드에 복사했어요」를 띄우므로 앱 토스트를 생략한다(10/8 r11 3-8).
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
const channel = MethodChannel('kr.co.fanuzu.app/device');
|
||||||
|
final messenger =
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
messenger.setMockMethodCallHandler(channel, null);
|
||||||
|
resetClipboardSdkCache();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (final (sdk, toasts) in [(33, 0), (34, 0), (32, 1), (null, 1)]) {
|
||||||
|
test('Android API $sdk → 앱 토스트 $toasts 회', () async {
|
||||||
|
messenger.setMockMethodCallHandler(channel, (_) async => sdk);
|
||||||
|
var count = 0;
|
||||||
|
await copyToClipboard('https://fanuzu.app/c/1', () => count++);
|
||||||
|
expect(count, toasts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -275,7 +275,10 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('홈 라이브 띠가 라이브 이벤트를 그린다', (tester) async {
|
testWidgets('홈 라이브 띠가 라이브 이벤트를 그린다', (tester) async {
|
||||||
final repo = FakeEclipseRepository();
|
// 방어 합류는 진행 중인 회차에서만 띠에 오른다(r11 2-5).
|
||||||
|
final repo = FakeEclipseRepository(
|
||||||
|
state: eclipseFixture(startAt: DateTime(2026, 9, 13), defense: 1),
|
||||||
|
);
|
||||||
addTearDown(repo.dispose);
|
addTearDown(repo.dispose);
|
||||||
tester.view.physicalSize = const Size(1080, 2400);
|
tester.view.physicalSize = const Size(1080, 2400);
|
||||||
tester.view.devicePixelRatio = 2;
|
tester.view.devicePixelRatio = 2;
|
||||||
@@ -293,16 +296,16 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// 이벤트가 없으면 스냅샷의 폴백 문구를 그린다.
|
// 합류 이벤트가 아직 없으면 발생 소식을 그린다.
|
||||||
expect(find.textContaining('아이템 사용으로 버프가 발동'), findsOneWidget);
|
expect(find.text('BTS 행성에 이클립스가 발생했어요!'), findsOneWidget);
|
||||||
|
|
||||||
repo.pushEvents([
|
repo.pushEvents([
|
||||||
LiveEvent(id: 'e1', nickname: '무민', defense: 20, at: DateTime(2026, 9, 14)),
|
LiveEvent(id: 'e1', nickname: '무민', defense: 20, at: DateTime(2026, 9, 14)),
|
||||||
]);
|
]);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// 이벤트가 오면 그 문구가 폴백을 대체한다.
|
// 이벤트가 오면 그 문구가 발생 소식을 대체한다.
|
||||||
expect(find.textContaining('아이템 사용으로 버프가 발동'), findsNothing);
|
expect(find.text('BTS 행성에 이클립스가 발생했어요!'), findsNothing);
|
||||||
expect(
|
expect(
|
||||||
find.text('[LIVE] 무민님이 이클립스 방어에 합류했습니다!'),
|
find.text('[LIVE] 무민님이 이클립스 방어에 합류했습니다!'),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
|
|||||||
@@ -83,6 +83,20 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('캠페인 알림은 문서 ID 앞머리로 달성 · 환불 · 취소 · 결과보고를 가른다(10/8 r11)', () {
|
||||||
|
String label(String id) => AppNotification(
|
||||||
|
id: id,
|
||||||
|
kind: NotificationKind.campaign,
|
||||||
|
title: 't',
|
||||||
|
at: DateTime(2026, 10, 8),
|
||||||
|
).sourceLabel;
|
||||||
|
expect(label('campaignSuccess_c1'), '캠페인 달성');
|
||||||
|
expect(label('campaignRefund_tx1'), '캠페인 환불');
|
||||||
|
expect(label('campaignCancel_c1'), '캠페인 취소');
|
||||||
|
expect(label('campaignReport_c1_r1'), '결과 보고');
|
||||||
|
expect(label('n1'), '캠페인 달성', reason: '모르는 앞머리는 종류 이름');
|
||||||
|
});
|
||||||
|
|
||||||
group('알림 화면 (F0 · 229:11265)', () {
|
group('알림 화면 (F0 · 229:11265)', () {
|
||||||
testWidgets('날짜 머리글과 항목을 함께 보여 준다', (tester) async {
|
testWidgets('날짜 머리글과 항목을 함께 보여 준다', (tester) async {
|
||||||
// 표본은 DateTime.now() 기준이라 자정 전후로 「오늘/어제」가 갈린다.
|
// 표본은 DateTime.now() 기준이라 자정 전후로 「오늘/어제」가 갈린다.
|
||||||
|
|||||||
@@ -267,6 +267,28 @@ void main() {
|
|||||||
expect(find.widgetWithText(FilledButton, '기여하기'), findsNothing);
|
expect(find.widgetWithText(FilledButton, '기여하기'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('모금 중이 아닌 타인 캠페인(달성 · 미달 환불 · 결과보고 · 취소)에는 기여하기 버튼이 없다(10/8 r11)', (tester) async {
|
||||||
|
for (final (status, shown) in [
|
||||||
|
(CampaignStatus.funding, findsOneWidget),
|
||||||
|
(CampaignStatus.success, findsNothing),
|
||||||
|
(CampaignStatus.failed, findsNothing),
|
||||||
|
(CampaignStatus.reported, findsNothing),
|
||||||
|
(CampaignStatus.cancelled, findsNothing),
|
||||||
|
]) {
|
||||||
|
await pumpWide(
|
||||||
|
tester,
|
||||||
|
CampaignDetailScreen(
|
||||||
|
key: ValueKey(status),
|
||||||
|
campaignId: 'c1',
|
||||||
|
repository: _Mine(status, supporters: 1, owned: false),
|
||||||
|
myPop: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.widgetWithText(FilledButton, '기여하기'), shown, reason: status.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('타인 캠페인에는 기여하기 버튼이 있다', (tester) async {
|
testWidgets('타인 캠페인에는 기여하기 버튼이 있다', (tester) async {
|
||||||
await pumpWide(tester, CampaignDetailScreen(campaignId: 'c1'));
|
await pumpWide(tester, CampaignDetailScreen(campaignId: 'c1'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@@ -529,10 +551,11 @@ void main() {
|
|||||||
|
|
||||||
/// 내 캠페인 상세 — 상태 · 후원 수를 바꿔 철회 버튼을 본다(B-58).
|
/// 내 캠페인 상세 — 상태 · 후원 수를 바꿔 철회 버튼을 본다(B-58).
|
||||||
class _Mine extends SampleCampaignRepository {
|
class _Mine extends SampleCampaignRepository {
|
||||||
_Mine(this.status, {this.supporters = 0});
|
_Mine(this.status, {this.supporters = 0, this.owned = true});
|
||||||
|
|
||||||
final CampaignStatus status;
|
final CampaignStatus status;
|
||||||
final int supporters;
|
final int supporters;
|
||||||
|
final bool owned;
|
||||||
final withdrawn = <String>[];
|
final withdrawn = <String>[];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -545,7 +568,7 @@ class _Mine extends SampleCampaignRepository {
|
|||||||
createdAt: c.createdAt,
|
createdAt: c.createdAt,
|
||||||
status: withdrawn.isEmpty ? status : CampaignStatus.withdrawn,
|
status: withdrawn.isEmpty ? status : CampaignStatus.withdrawn,
|
||||||
goalPop: c.goalPop, raisedPop: supporters == 0 ? 0 : 10,
|
goalPop: c.goalPop, raisedPop: supporters == 0 ? 0 : 10,
|
||||||
supporters: supporters, mine: true,
|
supporters: supporters, mine: owned,
|
||||||
likes: 0, comments: 0, views: 0, shares: 0,
|
likes: 0, comments: 0, views: 0, shares: 0,
|
||||||
),
|
),
|
||||||
mediaLabels: base.mediaLabels,
|
mediaLabels: base.mediaLabels,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:fancake/core/ui/clipboard_copy.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
@@ -240,8 +241,18 @@ void main() {
|
|||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(await shareProfile(profile, '문구 복사'), isTrue);
|
// Android 12 이하는 앱이 알리고(true), 13+ 는 시스템이 알려 앱 토스트를 생략한다(false).
|
||||||
|
const device = MethodChannel('kr.co.fanuzu.app/device');
|
||||||
|
addTearDown(() {
|
||||||
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(device, null);
|
||||||
|
resetClipboardSdkCache();
|
||||||
|
});
|
||||||
|
for (final (sdk, announce) in [(32, true), (33, false)]) {
|
||||||
|
resetClipboardSdkCache();
|
||||||
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(device, (_) async => sdk);
|
||||||
|
expect(await shareProfile(profile, '문구 복사'), announce);
|
||||||
expect(clip, profileShareText(profile));
|
expect(clip, profileShareText(profile));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('티어 카드를 누르면 행성별 티어로 넘어간다', (tester) async {
|
testWidgets('티어 카드를 누르면 행성별 티어로 넘어간다', (tester) async {
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import 'package:fancake/core/i18n/app_locale.dart';
|
||||||
|
import 'package:fancake/core/theme/app_theme.dart';
|
||||||
|
import 'package:fancake/features/home/home_data.dart';
|
||||||
|
import 'package:fancake/features/home/widgets/home_hero.dart';
|
||||||
|
import 'package:fancake/features/home/widgets/home_weekly.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
/// 긴 언어(es · ja)에서 HOME 이 깨지지 않는다 — 10/8 r11 실기기(Flip3) 관찰.
|
||||||
|
void main() {
|
||||||
|
tearDown(() => appL10n = koL10n);
|
||||||
|
|
||||||
|
Future<void> pump(WidgetTester tester, Locale locale, Widget child,
|
||||||
|
{double width = 360}) async {
|
||||||
|
tester.view.physicalSize = Size(width * 3, 2400);
|
||||||
|
tester.view.devicePixelRatio = 3;
|
||||||
|
addTearDown(tester.view.reset);
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: buildAppTheme(),
|
||||||
|
locale: locale,
|
||||||
|
supportedLocales: appLocales,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
RemoteL10nDelegate({}),
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
home: Scaffold(body: child),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
const growth = PlanetGrowth(
|
||||||
|
fandomName: '오로라',
|
||||||
|
contributorName: 'Kinos',
|
||||||
|
stage: 2,
|
||||||
|
contributedPop: 0,
|
||||||
|
growthPercent: 1,
|
||||||
|
currentPop: 189,
|
||||||
|
requiredPop: 16575,
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('es 주간 카드 「Mis POP」 라벨이 한 글자씩 줄바꿈되지 않는다', (tester) async {
|
||||||
|
final weekly = HomeWeekly.from(
|
||||||
|
fandomId: 'f',
|
||||||
|
snapshots: const [],
|
||||||
|
fandom: const {},
|
||||||
|
myWeekPop: 250,
|
||||||
|
wallet: const {'activityBalance': 12130, 'supportBalance': 330},
|
||||||
|
);
|
||||||
|
await pump(tester, const Locale('es'), HomeWeeklyCard(weekly: weekly));
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
// 한 줄 = 글자 13 × 행간 18/13.
|
||||||
|
expect(tester.getSize(find.text('Mis POP')).height, 18);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('es 인사 줄은 「De Kinos」 — 「aporta:」 오역이 아니다', (tester) async {
|
||||||
|
await pump(tester, const Locale('es'), const HomeHero(growth: growth));
|
||||||
|
expect(find.text('De '), findsOneWidget);
|
||||||
|
expect(find.text('Kinos'), findsOneWidget);
|
||||||
|
expect(find.textContaining('aporta'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('es 성장 말풍선이 성장률 글자를 가리지 않는다', (tester) async {
|
||||||
|
await pump(tester, const Locale('es'), const HomeHero(growth: growth),
|
||||||
|
width: 540);
|
||||||
|
final rate = tester.getRect(find.text('Crecimiento del planeta 1%'));
|
||||||
|
final bubble = tester.getRect(find.text('Ver condiciones'));
|
||||||
|
expect(bubble.left, greaterThanOrEqualTo(rate.right));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ja 제목이 미니맵 아래로 들어가지 않는다', (tester) async {
|
||||||
|
await pump(
|
||||||
|
tester,
|
||||||
|
const Locale('ja'),
|
||||||
|
const HomeHero(
|
||||||
|
growth: growth,
|
||||||
|
miniMap: SizedBox(width: HomeHero.miniMapSize, height: 90),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
final title = tester.getRect(find.text('みんなで育てるファンダム惑星'));
|
||||||
|
expect(title.right, lessThanOrEqualTo(360 - HomeHero.miniMapSize));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -166,7 +166,7 @@ void main() {
|
|||||||
for (final title in [
|
for (final title in [
|
||||||
'TODAY’S QUEST',
|
'TODAY’S QUEST',
|
||||||
'팬덤행성 인큐베이터',
|
'팬덤행성 인큐베이터',
|
||||||
'아티스트 소식',
|
'AI Daily Briefing',
|
||||||
'HOT ARCHIVE',
|
'HOT ARCHIVE',
|
||||||
'인기 K-INSIDE',
|
'인기 K-INSIDE',
|
||||||
]) {
|
]) {
|
||||||
@@ -296,7 +296,7 @@ void main() {
|
|||||||
/// *문구도 노출하지 않음)*. 서버가 당일 게시분을 0 건으로 주는 상황이다.
|
/// *문구도 노출하지 않음)*. 서버가 당일 게시분을 0 건으로 주는 상황이다.
|
||||||
testWidgets('브리핑이 0 건이면 구획 제목까지 숨긴다', (tester) async {
|
testWidgets('브리핑이 0 건이면 구획 제목까지 숨긴다', (tester) async {
|
||||||
await _pump(tester, repo: const _BriefingRepo([]));
|
await _pump(tester, repo: const _BriefingRepo([]));
|
||||||
expect(find.text('아티스트 소식'), findsNothing);
|
expect(find.text('AI Daily Briefing'), findsNothing);
|
||||||
// 빈 상태 문구도 두지 않는다 — 아래 구획은 그대로 있어야 한다.
|
// 빈 상태 문구도 두지 않는다 — 아래 구획은 그대로 있어야 한다.
|
||||||
await tester.scrollUntilVisible(
|
await tester.scrollUntilVisible(
|
||||||
find.text('HOT ARCHIVE'),
|
find.text('HOT ARCHIVE'),
|
||||||
@@ -309,7 +309,7 @@ void main() {
|
|||||||
testWidgets('브리핑 헤더의 「공유」는 브리핑 공유 카드를 연다(F8 · 별첨 1 ③-5)', (tester) async {
|
testWidgets('브리핑 헤더의 「공유」는 브리핑 공유 카드를 연다(F8 · 별첨 1 ③-5)', (tester) async {
|
||||||
await _pump(tester, repo: _BriefingRepo([_brief('공유할 한 줄')]));
|
await _pump(tester, repo: _BriefingRepo([_brief('공유할 한 줄')]));
|
||||||
final header = find.byWidgetPredicate(
|
final header = find.byWidgetPredicate(
|
||||||
(w) => w is HomeSectionHeader && w.title == '아티스트 소식',
|
(w) => w is HomeSectionHeader && w.title == 'AI Daily Briefing',
|
||||||
);
|
);
|
||||||
await tester.scrollUntilVisible(find.text('공유할 한 줄'), 220,
|
await tester.scrollUntilVisible(find.text('공유할 한 줄'), 220,
|
||||||
scrollable: find.byType(Scrollable).first);
|
scrollable: find.byType(Scrollable).first);
|
||||||
@@ -328,7 +328,7 @@ void main() {
|
|||||||
220,
|
220,
|
||||||
scrollable: find.byType(Scrollable).first,
|
scrollable: find.byType(Scrollable).first,
|
||||||
);
|
);
|
||||||
expect(find.text('아티스트 소식'), findsOneWidget);
|
expect(find.text('AI Daily Briefing'), findsOneWidget);
|
||||||
expect(find.text('오늘 게시된 한 줄'), findsOneWidget);
|
expect(find.text('오늘 게시된 한 줄'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -298,5 +298,43 @@ void main() {
|
|||||||
expect(zap.balance, 100);
|
expect(zap.balance, 100);
|
||||||
await tester.pump(const Duration(seconds: 2));
|
await tester.pump(const Duration(seconds: 2));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('HOME 반영 문구는 행성 상자 위쪽 — 아래 성장 카드(행성 이름 · 말풍선)를 덮지 않는다(10/8 r11)', (tester) async {
|
||||||
|
final zap = ZapController(SampleGalaxyRepository(), onFailure: (_) {});
|
||||||
|
addTearDown(zap.dispose);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: ListenableBuilder(
|
||||||
|
listenable: zap,
|
||||||
|
builder: (_, _) => ZapFeedback(
|
||||||
|
fandomName: 'BTS',
|
||||||
|
quest: zap.questReaction,
|
||||||
|
touchSerial: zap.touchSerial,
|
||||||
|
pending: zap.pending > 0 || zap.sending,
|
||||||
|
result: zap.result,
|
||||||
|
source: Offset.zero,
|
||||||
|
target: const Offset(200, 400),
|
||||||
|
radius: 80,
|
||||||
|
messageAboveTarget: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final reacting = zap.react();
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
await reacting;
|
||||||
|
await tester.pump();
|
||||||
|
final text = find.text('퀘스트 완료가 BTS 성장에 반영되었습니다');
|
||||||
|
expect(text, findsOneWidget);
|
||||||
|
// 행성 상자(반 변 80 / 0.4 / 2 = 100) 윗변 y300 에서 시작해 행성 중심(y400) 위에서 끝난다.
|
||||||
|
final box = tester.getRect(
|
||||||
|
find.ancestor(of: text, matching: find.byType(DecoratedBox)).first,
|
||||||
|
);
|
||||||
|
expect(box.top, 300);
|
||||||
|
expect(box.bottom, lessThan(400));
|
||||||
|
await tester.pump(const Duration(seconds: 2));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,35 @@ void main() {
|
|||||||
final l = line(eclipse: eclipseFixture(startAt: start), hero: true)!;
|
final l = line(eclipse: eclipseFixture(startAt: start), hero: true)!;
|
||||||
expect(l.hero, isFalse);
|
expect(l.hero, isFalse);
|
||||||
});
|
});
|
||||||
|
test('끝난 회차의 방어 합류는 띠에 남지 않는다 (r11 2-5)', () {
|
||||||
|
expect(line(events: [join])!.text, 'fallback', reason: '진행 중 회차 없음');
|
||||||
|
expect(
|
||||||
|
line(
|
||||||
|
eclipse: eclipseFixture(
|
||||||
|
startAt: start,
|
||||||
|
defense: 1,
|
||||||
|
result: EclipseResult.failure,
|
||||||
|
),
|
||||||
|
events: [join],
|
||||||
|
)!.text,
|
||||||
|
'fallback',
|
||||||
|
reason: '판정 끝난 회차',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
line(
|
||||||
|
eclipse: eclipseFixture(startAt: DateTime.now(), defense: 1),
|
||||||
|
events: [
|
||||||
|
LiveEvent(
|
||||||
|
id: 'old',
|
||||||
|
nickname: '팬',
|
||||||
|
at: DateTime.now().subtract(const Duration(minutes: 40)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)!.text,
|
||||||
|
'BTS 행성에 이클립스가 발생했어요!',
|
||||||
|
reason: '지난 회차의 합류',
|
||||||
|
);
|
||||||
|
});
|
||||||
test('아무것도 없으면 폴백', () {
|
test('아무것도 없으면 폴백', () {
|
||||||
expect(line()!.text, 'fallback');
|
expect(line()!.text, 'fallback');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user