갤럭시 검색 결과에서 행성 상세로 이동
This commit is contained in:
@@ -96,8 +96,7 @@ class GalaxySnapshot {
|
|||||||
/// 명세 규칙 — *행성/성운 가입 또는 생성한 유저에게만 노출* (Zap 버튼).
|
/// 명세 규칙 — *행성/성운 가입 또는 생성한 유저에게만 노출* (Zap 버튼).
|
||||||
final bool joined;
|
final bool joined;
|
||||||
|
|
||||||
PlanetNode? get mine =>
|
PlanetNode? get mine => planets.where((p) => p.isMine).firstOrNull;
|
||||||
planets.where((p) => p.isMine).firstOrNull;
|
|
||||||
|
|
||||||
/// Ranking View — 원장 B-21 「Ranking View 는 FPI 상위 중심」. FPI 1위 행성을 가운데
|
/// Ranking View — 원장 B-21 「Ranking View 는 FPI 상위 중심」. FPI 1위 행성을 가운데
|
||||||
/// 두고 나머지는 기본 보기와 같이 순위 궤도를 돈다. Stage0 성운은 랭킹 모집단이 아니므로
|
/// 두고 나머지는 기본 보기와 같이 순위 궤도를 돈다. Stage0 성운은 랭킹 모집단이 아니므로
|
||||||
@@ -133,7 +132,11 @@ class GalaxySnapshot {
|
|||||||
|
|
||||||
/// ZAP 한 번(또는 연타 묶음)의 결과. 서버가 확정한 잔액만 담는다.
|
/// ZAP 한 번(또는 연타 묶음)의 결과. 서버가 확정한 잔액만 담는다.
|
||||||
class ZapResult {
|
class ZapResult {
|
||||||
const ZapResult({required this.txId, required this.balanceAfter, this.contributedPop});
|
const ZapResult({
|
||||||
|
required this.txId,
|
||||||
|
required this.balanceAfter,
|
||||||
|
this.contributedPop,
|
||||||
|
});
|
||||||
|
|
||||||
final String txId;
|
final String txId;
|
||||||
|
|
||||||
@@ -203,16 +206,73 @@ class SampleGalaxyRepository implements GalaxyRepository {
|
|||||||
online: 1204,
|
online: 1204,
|
||||||
isMine: true,
|
isMine: true,
|
||||||
),
|
),
|
||||||
PlanetNode(id: 'newjeans', name: 'NewJeans', rank: 2, fpi: 271905, members: 11230, online: 980),
|
PlanetNode(
|
||||||
PlanetNode(id: 'seventeen', name: 'SEVENTEEN', rank: 3, fpi: 254120, members: 10450, online: 872),
|
id: 'newjeans',
|
||||||
PlanetNode(id: 'ive', name: 'IVE', rank: 4, fpi: 198740, members: 8320, online: 611),
|
name: 'NewJeans',
|
||||||
PlanetNode(id: 'aespa', name: 'aespa', rank: 5, fpi: 176250, members: 7640, online: 540),
|
rank: 2,
|
||||||
PlanetNode(id: 'lesserafim', name: 'LE SSERAFIM', rank: 6, fpi: 152080, members: 6910, online: 402),
|
fpi: 271905,
|
||||||
|
members: 11230,
|
||||||
|
online: 980,
|
||||||
|
),
|
||||||
|
PlanetNode(
|
||||||
|
id: 'seventeen',
|
||||||
|
name: 'SEVENTEEN',
|
||||||
|
rank: 3,
|
||||||
|
fpi: 254120,
|
||||||
|
members: 10450,
|
||||||
|
online: 872,
|
||||||
|
),
|
||||||
|
PlanetNode(
|
||||||
|
id: 'ive',
|
||||||
|
name: 'IVE',
|
||||||
|
rank: 4,
|
||||||
|
fpi: 198740,
|
||||||
|
members: 8320,
|
||||||
|
online: 611,
|
||||||
|
),
|
||||||
|
PlanetNode(
|
||||||
|
id: 'aespa',
|
||||||
|
name: 'aespa',
|
||||||
|
rank: 5,
|
||||||
|
fpi: 176250,
|
||||||
|
members: 7640,
|
||||||
|
online: 540,
|
||||||
|
),
|
||||||
|
PlanetNode(
|
||||||
|
id: 'lesserafim',
|
||||||
|
name: 'LE SSERAFIM',
|
||||||
|
rank: 6,
|
||||||
|
fpi: 152080,
|
||||||
|
members: 6910,
|
||||||
|
online: 402,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
memberships: const [
|
memberships: const [
|
||||||
PlanetNode(id: 'bts', name: 'BTS', rank: 1, fpi: 284310, members: 12840, online: 1204, isMine: true),
|
PlanetNode(
|
||||||
PlanetNode(id: 'newjeans', name: 'NewJeans', rank: 2, fpi: 271905, members: 11230, online: 980),
|
id: 'bts',
|
||||||
PlanetNode(id: 'seventeen', name: 'SEVENTEEN', rank: 3, fpi: 254120, members: 10450, online: 872),
|
name: 'BTS',
|
||||||
|
rank: 1,
|
||||||
|
fpi: 284310,
|
||||||
|
members: 12840,
|
||||||
|
online: 1204,
|
||||||
|
isMine: true,
|
||||||
|
),
|
||||||
|
PlanetNode(
|
||||||
|
id: 'newjeans',
|
||||||
|
name: 'NewJeans',
|
||||||
|
rank: 2,
|
||||||
|
fpi: 271905,
|
||||||
|
members: 11230,
|
||||||
|
online: 980,
|
||||||
|
),
|
||||||
|
PlanetNode(
|
||||||
|
id: 'seventeen',
|
||||||
|
name: 'SEVENTEEN',
|
||||||
|
rank: 3,
|
||||||
|
fpi: 254120,
|
||||||
|
members: 10450,
|
||||||
|
online: 872,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,10 @@ class _GalaxyHomeScreenState extends State<GalaxyHomeScreen> {
|
|||||||
if (!ok || !mounted) return;
|
if (!ok || !mounted) return;
|
||||||
try {
|
try {
|
||||||
await widget.repository.setPrimary(p.id);
|
await widget.repository.setPrimary(p.id);
|
||||||
if (mounted) setState(() { _snapshot = widget.repository.load(); });
|
if (mounted)
|
||||||
|
setState(() {
|
||||||
|
_snapshot = widget.repository.load();
|
||||||
|
});
|
||||||
} on BackendException catch (e) {
|
} on BackendException catch (e) {
|
||||||
// 30일 대기(E-FDM-04) 등 서버 사유를 그대로 알린다.
|
// 30일 대기(E-FDM-04) 등 서버 사유를 그대로 알린다.
|
||||||
if (mounted) await showAlertDialog(context, e.message);
|
if (mounted) await showAlertDialog(context, e.message);
|
||||||
@@ -101,11 +104,14 @@ class _GalaxyHomeScreenState extends State<GalaxyHomeScreen> {
|
|||||||
final myPop = (await _snapshot).myPop;
|
final myPop = (await _snapshot).myPop;
|
||||||
final fandomId = await navigator.push(
|
final fandomId = await navigator.push(
|
||||||
MaterialPageRoute<String>(
|
MaterialPageRoute<String>(
|
||||||
builder: (_) => NebulaCreateScreen(repository: widget.home, myPop: myPop),
|
builder: (_) =>
|
||||||
|
NebulaCreateScreen(repository: widget.home, myPop: myPop),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (fandomId == null || !mounted) return;
|
if (fandomId == null || !mounted) return;
|
||||||
setState(() { _snapshot = widget.repository.load(); });
|
setState(() {
|
||||||
|
_snapshot = widget.repository.load();
|
||||||
|
});
|
||||||
await navigator.push(
|
await navigator.push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => PlanetDetailScreen(
|
builder: (_) => PlanetDetailScreen(
|
||||||
@@ -182,6 +188,22 @@ class _GalaxyHomeScreenState extends State<GalaxyHomeScreen> {
|
|||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => GalaxySearchScreen(
|
builder: (_) => GalaxySearchScreen(
|
||||||
repository: widget.repository,
|
repository: widget.repository,
|
||||||
|
onOpen: (p) =>
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
rootNavigator: true,
|
||||||
|
).push(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
builder: (_) =>
|
||||||
|
PlanetDetailScreen(
|
||||||
|
repository: widget
|
||||||
|
.planetRepository(p.id),
|
||||||
|
watchPlanet:
|
||||||
|
widget.watchPlanet,
|
||||||
|
eclipse: widget.eclipse,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -278,9 +300,7 @@ class _GalaxyHomeScreenState extends State<GalaxyHomeScreen> {
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
child: Center(
|
child: Center(child: _NebulaPill(onTap: _createNebula)),
|
||||||
child: _NebulaPill(onTap: _createNebula),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -132,8 +132,10 @@ class GalaxyClock extends ChangeNotifier {
|
|||||||
if (_dragged != 0) {
|
if (_dragged != 0) {
|
||||||
// 손이 닿아 있는 동안의 각속도는 **드래그 거리가 아니라 드래그 속도**를
|
// 손이 닿아 있는 동안의 각속도는 **드래그 거리가 아니라 드래그 속도**를
|
||||||
// 따른다. 거리로 누적하면 천천히 오래 끌기만 해도 팽이처럼 돌아 버린다.
|
// 따른다. 거리로 누적하면 천천히 오래 끌기만 해도 팽이처럼 돌아 버린다.
|
||||||
_velocity = (_dragged / dt * GalaxyMotion.dragSpinGain)
|
_velocity = (_dragged / dt * GalaxyMotion.dragSpinGain).clamp(
|
||||||
.clamp(-GalaxyMotion.maxSpin, GalaxyMotion.maxSpin);
|
-GalaxyMotion.maxSpin,
|
||||||
|
GalaxyMotion.maxSpin,
|
||||||
|
);
|
||||||
_dragged = 0;
|
_dragged = 0;
|
||||||
} else {
|
} else {
|
||||||
// 지수 감쇠 — 프레임 간격이 흔들려도 같은 시간에 같은 만큼 준다.
|
// 지수 감쇠 — 프레임 간격이 흔들려도 같은 시간에 같은 만큼 준다.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart' show QueryDocumentSnapshot;
|
import 'package:cloud_firestore/cloud_firestore.dart'
|
||||||
|
show QueryDocumentSnapshot;
|
||||||
|
|
||||||
import '../../core/data/backend.dart';
|
import '../../core/data/backend.dart';
|
||||||
import '../planet/planet_data.dart' show rankWithTies;
|
import '../planet/planet_data.dart' show rankWithTies;
|
||||||
@@ -58,7 +59,8 @@ class FirestoreGalaxyRepository implements GalaxyRepository {
|
|||||||
// fandomId 오름차순으로 고정한다 — 쿼리 순서(동점은 ID 내림차순)를 그대로 쓰지 않는다.
|
// fandomId 오름차순으로 고정한다 — 쿼리 순서(동점은 ID 내림차순)를 그대로 쓰지 않는다.
|
||||||
// Stage0 성운은 순위를 매기기 **전에** 뺀다(B-08 · B-17) — 성운이 끼면 정식 행성의
|
// Stage0 성운은 순위를 매기기 **전에** 뺀다(B-08 · B-17) — 성운이 끼면 정식 행성의
|
||||||
// 순위 번호가 밀린다. 성운은 맨 바깥 궤도 자리(정식 수 + 1)에 둔다.
|
// 순위 번호가 밀린다. 성운은 맨 바깥 궤도 자리(정식 수 + 1)에 둔다.
|
||||||
bool isNebula(QueryDocumentSnapshot<Map<String, dynamic>> d) => d.data()['stage'] == 0;
|
bool isNebula(QueryDocumentSnapshot<Map<String, dynamic>> d) =>
|
||||||
|
d.data()['stage'] == 0;
|
||||||
final formal = fandoms.where((d) => !isNebula(d)).toList();
|
final formal = fandoms.where((d) => !isNebula(d)).toList();
|
||||||
final planets = <PlanetNode>[
|
final planets = <PlanetNode>[
|
||||||
for (final (doc, rank) in rankWithTies(
|
for (final (doc, rank) in rankWithTies(
|
||||||
@@ -79,7 +81,8 @@ class FirestoreGalaxyRepository implements GalaxyRepository {
|
|||||||
]);
|
]);
|
||||||
for (final doc in missing) {
|
for (final doc in missing) {
|
||||||
final data = doc.data();
|
final data = doc.data();
|
||||||
if (data != null) byId[doc.id] = _node(doc.id, data, formal.length + 1, myFandomId);
|
if (data != null)
|
||||||
|
byId[doc.id] = _node(doc.id, data, formal.length + 1, myFandomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GalaxySnapshot(
|
return GalaxySnapshot(
|
||||||
@@ -93,7 +96,12 @@ class FirestoreGalaxyRepository implements GalaxyRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlanetNode _node(String id, Map<String, dynamic> data, int rank, String myFandomId) => PlanetNode(
|
PlanetNode _node(
|
||||||
|
String id,
|
||||||
|
Map<String, dynamic> data,
|
||||||
|
int rank,
|
||||||
|
String myFandomId,
|
||||||
|
) => PlanetNode(
|
||||||
id: id,
|
id: id,
|
||||||
stage: switch (data['stage']) {
|
stage: switch (data['stage']) {
|
||||||
int n when n >= 0 && n <= 6 => n,
|
int n when n >= 0 && n <= 6 => n,
|
||||||
@@ -124,7 +132,10 @@ class FirestoreGalaxyRepository implements GalaxyRepository {
|
|||||||
required String clientSeq,
|
required String clientSeq,
|
||||||
}) async {
|
}) async {
|
||||||
final snapshot = await _backend.db.doc('config/policy').get();
|
final snapshot = await _backend.db.doc('config/policy').get();
|
||||||
final zapPop = (snapshot.data() ?? const <String, dynamic>{}).intOr('zapPop', 10);
|
final zapPop = (snapshot.data() ?? const <String, dynamic>{}).intOr(
|
||||||
|
'zapPop',
|
||||||
|
10,
|
||||||
|
);
|
||||||
final data = await _backend.call('fnPopSpend', {
|
final data = await _backend.call('fnPopSpend', {
|
||||||
'popType': 'activity',
|
'popType': 'activity',
|
||||||
'amount': zapPop * count,
|
'amount': zapPop * count,
|
||||||
@@ -135,9 +146,17 @@ class FirestoreGalaxyRepository implements GalaxyRepository {
|
|||||||
'refPath': 'fandoms/$fandomId',
|
'refPath': 'fandoms/$fandomId',
|
||||||
});
|
});
|
||||||
final lots = data['lotRefs'];
|
final lots = data['lotRefs'];
|
||||||
final contributedPop = lots is List && lots.isNotEmpty && lots.every(
|
final contributedPop =
|
||||||
(lot) => lot is Map && lot['delta'] is int && (lot['delta'] as int) < 0,
|
lots is List &&
|
||||||
) ? lots.fold<int>(0, (sum, lot) => sum - (lot['delta'] as int)) : null;
|
lots.isNotEmpty &&
|
||||||
|
lots.every(
|
||||||
|
(lot) =>
|
||||||
|
lot is Map &&
|
||||||
|
lot['delta'] is int &&
|
||||||
|
(lot['delta'] as int) < 0,
|
||||||
|
)
|
||||||
|
? lots.fold<int>(0, (sum, lot) => sum - (lot['delta'] as int))
|
||||||
|
: null;
|
||||||
return ZapResult(
|
return ZapResult(
|
||||||
contributedPop: contributedPop,
|
contributedPop: contributedPop,
|
||||||
txId: data['txId'] as String? ?? '',
|
txId: data['txId'] as String? ?? '',
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ class _GalaxyScreenState extends State<GalaxyScreen>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_snapshot = widget.repository.load();
|
_snapshot = widget.repository.load();
|
||||||
_zap = ZapController(widget.repository, onFailure: _notify)..addListener(_onZapChanged);
|
_zap = ZapController(widget.repository, onFailure: _notify)
|
||||||
|
..addListener(_onZapChanged);
|
||||||
_fly = AnimationController(vsync: this, duration: GalaxyMotion.recenter)
|
_fly = AnimationController(vsync: this, duration: GalaxyMotion.recenter)
|
||||||
..addListener(_onFly);
|
..addListener(_onFly);
|
||||||
_ticker = createTicker(_tick);
|
_ticker = createTicker(_tick);
|
||||||
@@ -182,12 +183,18 @@ class _GalaxyScreenState extends State<GalaxyScreen>
|
|||||||
void _follow() {
|
void _follow() {
|
||||||
final p = _selected;
|
final p = _selected;
|
||||||
if (p == null || _viewport.isEmpty || _fly.isAnimating) return;
|
if (p == null || _viewport.isEmpty || _fly.isAnimating) return;
|
||||||
final at = GalaxyField.positionOf(p, _selectedIndex, _clock.spin, _outerRank);
|
final at = GalaxyField.positionOf(
|
||||||
|
p,
|
||||||
|
_selectedIndex,
|
||||||
|
_clock.spin,
|
||||||
|
_outerRank,
|
||||||
|
);
|
||||||
final s = _view.value.getMaxScaleOnAxis();
|
final s = _view.value.getMaxScaleOnAxis();
|
||||||
final tx = _viewport.width / 2 - at.dx * s;
|
final tx = _viewport.width / 2 - at.dx * s;
|
||||||
final ty = _viewport.height / 2 - at.dy * s;
|
final ty = _viewport.height / 2 - at.dy * s;
|
||||||
final m = _view.value;
|
final m = _view.value;
|
||||||
if ((m.storage[12] - tx).abs() < 0.01 && (m.storage[13] - ty).abs() < 0.01) {
|
if ((m.storage[12] - tx).abs() < 0.01 &&
|
||||||
|
(m.storage[13] - ty).abs() < 0.01) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_view.value = m.clone()
|
_view.value = m.clone()
|
||||||
@@ -258,50 +265,57 @@ class _GalaxyScreenState extends State<GalaxyScreen>
|
|||||||
return LiveStreamBuilder<EclipseState?>(
|
return LiveStreamBuilder<EclipseState?>(
|
||||||
key: ValueKey('eclipse-${myFandomId ?? 'mine'}'),
|
key: ValueKey('eclipse-${myFandomId ?? 'mine'}'),
|
||||||
create: () => widget.eclipse.watch(fandomId: myFandomId),
|
create: () => widget.eclipse.watch(fandomId: myFandomId),
|
||||||
builder: (context, eclipse) => LiveStreamBuilder<PlanetVisualState>(
|
builder: (context, eclipse) =>
|
||||||
key: ValueKey(('galaxy-orbit', (_selected ?? data.mine)?.id)),
|
LiveStreamBuilder<PlanetVisualState>(
|
||||||
create: () => widget.watchPlanet((_selected ?? data.mine)?.id ?? ''),
|
key: ValueKey((
|
||||||
builder: (context, state) {
|
'galaxy-orbit',
|
||||||
_clock.idleSpeedScale = state?.isHibernating == true
|
(_selected ?? data.mine)?.id,
|
||||||
? 0.25
|
)),
|
||||||
: state?.isEclipsed == true
|
create: () =>
|
||||||
? 0.5
|
widget.watchPlanet((_selected ?? data.mine)?.id ?? ''),
|
||||||
: state?.isHeroTime == true
|
builder: (context, state) {
|
||||||
? 1.5
|
_clock.idleSpeedScale = state?.isHibernating == true
|
||||||
: 1;
|
? 0.25
|
||||||
return _Map(
|
: state?.isEclipsed == true
|
||||||
data: data,
|
? 0.5
|
||||||
watchPlanet: widget.watchPlanet,
|
: state?.isHeroTime == true
|
||||||
eclipse: eclipse,
|
? 1.5
|
||||||
eclipseRepository: widget.eclipse,
|
: 1;
|
||||||
repository: widget.repository,
|
return _Map(
|
||||||
planetRepository: widget.planetRepository,
|
data: data,
|
||||||
clock: _clock,
|
watchPlanet: widget.watchPlanet,
|
||||||
view: _view,
|
eclipse: eclipse,
|
||||||
selected: _selected ?? data.mine,
|
eclipseRepository: widget.eclipse,
|
||||||
selectedIndex: _selected == null
|
repository: widget.repository,
|
||||||
? data.planets.indexWhere((p) => p.isMine).clamp(0, 1 << 30)
|
planetRepository: widget.planetRepository,
|
||||||
: _selectedIndex,
|
clock: _clock,
|
||||||
balance: _zap.balance,
|
view: _view,
|
||||||
pending: _zap.pending,
|
selected: _selected ?? data.mine,
|
||||||
sending: _zap.sending,
|
selectedIndex: _selected == null
|
||||||
touchSerial: _zap.touchSerial,
|
? data.planets
|
||||||
zapResult: _zap.result,
|
.indexWhere((p) => p.isMine)
|
||||||
retryZap: _zap.needsRetry ? _zap.retry : null,
|
.clamp(0, 1 << 30)
|
||||||
outerRank: _outerRank,
|
: _selectedIndex,
|
||||||
ranking: _ranking,
|
balance: _zap.balance,
|
||||||
onToggleRanking: () => _toggleRanking(snap.data!),
|
pending: _zap.pending,
|
||||||
fly: _fly,
|
sending: _zap.sending,
|
||||||
onSelect: _center,
|
touchSerial: _zap.touchSerial,
|
||||||
onDrag: _clock.drag,
|
zapResult: _zap.result,
|
||||||
onGrab: () {
|
retryZap: _zap.needsRetry ? _zap.retry : null,
|
||||||
// 날아가는 도중에 손을 대면 사용자가 이긴다.
|
outerRank: _outerRank,
|
||||||
if (_fly.isAnimating) _fly.stop();
|
ranking: _ranking,
|
||||||
|
onToggleRanking: () => _toggleRanking(snap.data!),
|
||||||
|
fly: _fly,
|
||||||
|
onSelect: _center,
|
||||||
|
onDrag: _clock.drag,
|
||||||
|
onGrab: () {
|
||||||
|
// 날아가는 도중에 손을 대면 사용자가 이긴다.
|
||||||
|
if (_fly.isAnimating) _fly.stop();
|
||||||
|
},
|
||||||
|
onZap: _zap.tap,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onZap: _zap.tap,
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -433,9 +447,14 @@ class _Map extends StatelessWidget {
|
|||||||
child: AnimatedBuilder(
|
child: AnimatedBuilder(
|
||||||
animation: clock,
|
animation: clock,
|
||||||
builder: (context, _) => GalaxyField(
|
builder: (context, _) => GalaxyField(
|
||||||
onOpenHistory: (planet) => Navigator.of(context).push(MaterialPageRoute<void>(
|
onOpenHistory: (planet) => Navigator.of(context).push(
|
||||||
builder: (_) => PlanetHistoryScreen(repository: planetRepository(planet.id), fandomName: planet.name),
|
MaterialPageRoute<void>(
|
||||||
)),
|
builder: (_) => PlanetHistoryScreen(
|
||||||
|
repository: planetRepository(planet.id),
|
||||||
|
fandomName: planet.name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
planets: data.planets,
|
planets: data.planets,
|
||||||
watchPlanet: watchPlanet,
|
watchPlanet: watchPlanet,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
@@ -522,7 +541,19 @@ class _Map extends StatelessWidget {
|
|||||||
tooltip: context.l10n.commonSearch,
|
tooltip: context.l10n.commonSearch,
|
||||||
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => GalaxySearchScreen(repository: repository),
|
builder: (_) => GalaxySearchScreen(
|
||||||
|
repository: repository,
|
||||||
|
onOpen: (p) =>
|
||||||
|
Navigator.of(context, rootNavigator: true).push(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
builder: (_) => PlanetDetailScreen(
|
||||||
|
repository: planetRepository(p.id),
|
||||||
|
watchPlanet: watchPlanet,
|
||||||
|
eclipse: eclipseRepository,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.search),
|
icon: const Icon(Icons.search),
|
||||||
@@ -540,24 +571,31 @@ class _Map extends StatelessWidget {
|
|||||||
if (eclipse case final e?)
|
if (eclipse case final e?)
|
||||||
EclipseBannerCard(
|
EclipseBannerCard(
|
||||||
state: e,
|
state: e,
|
||||||
onTap: () => Navigator.of(context, rootNavigator: true).push(
|
onTap: () =>
|
||||||
MaterialPageRoute<void>(builder: (_) => EclipseDefendScreen(
|
Navigator.of(context, rootNavigator: true).push(
|
||||||
repository: eclipseRepository, fandomId: e.fandomId,
|
MaterialPageRoute<void>(
|
||||||
)),
|
builder: (_) => EclipseDefendScreen(
|
||||||
),
|
repository: eclipseRepository,
|
||||||
|
fandomId: e.fandomId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (selected case final target?)
|
if (selected case final target?)
|
||||||
HeroTimeBillboard(
|
HeroTimeBillboard(
|
||||||
fandomId: target.id,
|
fandomId: target.id,
|
||||||
fandomName: target.name,
|
fandomName: target.name,
|
||||||
watch: watchPlanet,
|
watch: watchPlanet,
|
||||||
onOpen: () => Navigator.of(context, rootNavigator: true).push(
|
onOpen: () =>
|
||||||
MaterialPageRoute<void>(builder: (_) => PlanetDetailScreen(
|
Navigator.of(context, rootNavigator: true).push(
|
||||||
repository: planetRepository(target.id),
|
MaterialPageRoute<void>(
|
||||||
watchPlanet: watchPlanet,
|
builder: (_) => PlanetDetailScreen(
|
||||||
eclipse: eclipseRepository,
|
repository: planetRepository(target.id),
|
||||||
)),
|
watchPlanet: watchPlanet,
|
||||||
),
|
eclipse: eclipseRepository,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -618,48 +656,54 @@ class _Map extends StatelessWidget {
|
|||||||
key: ValueKey(('zap-mode', data.mine?.id)),
|
key: ValueKey(('zap-mode', data.mine?.id)),
|
||||||
create: () => watchPlanet(data.mine?.id ?? ''),
|
create: () => watchPlanet(data.mine?.id ?? ''),
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final hero = state?.isHeroTime == true &&
|
final hero =
|
||||||
|
state?.isHeroTime == true &&
|
||||||
state?.heroEndsAt?.isAfter(DateTime.now()) == true &&
|
state?.heroEndsAt?.isAfter(DateTime.now()) == true &&
|
||||||
pending == 0 && !sending && retryZap == null;
|
pending == 0 &&
|
||||||
|
!sending &&
|
||||||
|
retryZap == null;
|
||||||
return ZapButton(
|
return ZapButton(
|
||||||
isHeroTime: hero,
|
isHeroTime: hero,
|
||||||
// 단가는 `config/policy.zapPop` 이다 — Figma 「Zap 버튼」이
|
// 단가는 `config/policy.zapPop` 이다 — Figma 「Zap 버튼」이
|
||||||
// 어드민 설정값이라 못 박았다.
|
// 어드민 설정값이라 못 박았다.
|
||||||
zapPop: data.zapPop,
|
zapPop: data.zapPop,
|
||||||
// 표시 잔액 = 서버 확정값(없으면 스냅샷) − 아직 안 보낸 연타분.
|
// 표시 잔액 = 서버 확정값(없으면 스냅샷) − 아직 안 보낸 연타분.
|
||||||
myPop: (balance ?? data.myPop) - pending * data.zapPop,
|
myPop: (balance ?? data.myPop) - pending * data.zapPop,
|
||||||
// **이클립스와 무관하다.** ZAP 은 행성 성장 기여이며(B-23), 방어는
|
// **이클립스와 무관하다.** ZAP 은 행성 성장 기여이며(B-23), 방어는
|
||||||
// 「흔들어 방어」 화면에서 POP 없이 참여한다.
|
// 「흔들어 방어」 화면에서 POP 없이 참여한다.
|
||||||
blockedReason: sending
|
blockedReason: sending
|
||||||
? context.l10n.zapApplying
|
? context.l10n.zapApplying
|
||||||
: retryZap != null
|
: retryZap != null
|
||||||
? context.l10n.zapNeedsCheck
|
? context.l10n.zapNeedsCheck
|
||||||
: data.mine == null
|
: data.mine == null
|
||||||
? context.l10n.zapNoPlanet
|
? context.l10n.zapNoPlanet
|
||||||
: null,
|
: null,
|
||||||
onZap: () {
|
onZap: () {
|
||||||
if (data.mine case final mine?) {
|
if (data.mine case final mine?) {
|
||||||
if (hero) {
|
if (hero) {
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context, isScrollControlled: true,
|
context: context,
|
||||||
builder: (_) => HeroParticipationSheet(
|
isScrollControlled: true,
|
||||||
fandomId: mine.id, watch: watchPlanet,
|
builder: (_) => HeroParticipationSheet(
|
||||||
repository: planetRepository(mine.id),
|
fandomId: mine.id,
|
||||||
),
|
watch: watchPlanet,
|
||||||
);
|
repository: planetRepository(mine.id),
|
||||||
return;
|
),
|
||||||
}
|
);
|
||||||
onSelect(
|
return;
|
||||||
mine,
|
}
|
||||||
data.planets.indexOf(mine),
|
onSelect(
|
||||||
viewport,
|
mine,
|
||||||
animate: !MediaQuery.disableAnimationsOf(context),
|
data.planets.indexOf(mine),
|
||||||
);
|
viewport,
|
||||||
onZap(mine.id);
|
animate: !MediaQuery.disableAnimationsOf(context),
|
||||||
}
|
);
|
||||||
},
|
onZap(mine.id);
|
||||||
onContribute: () => _todo(context, context.l10n.commonContribute),
|
}
|
||||||
onItem: () => _todo(context, context.l10n.commonItem),
|
},
|
||||||
|
onContribute: () =>
|
||||||
|
_todo(context, context.l10n.commonContribute),
|
||||||
|
onItem: () => _todo(context, context.l10n.commonItem),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -866,7 +910,10 @@ class _HudCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Stage0 성운은 랭킹 모집단이 아니다(B-17).
|
// Stage0 성운은 랭킹 모집단이 아니다(B-17).
|
||||||
_Metric(label: 'RANK', value: planet.stage == 0 ? '-' : '${planet.rank}'),
|
_Metric(
|
||||||
|
label: 'RANK',
|
||||||
|
value: planet.stage == 0 ? '-' : '${planet.rank}',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: AppSpace.s1),
|
const SizedBox(height: AppSpace.s1),
|
||||||
|
|||||||
@@ -16,10 +16,14 @@ class GalaxySearchScreen extends StatefulWidget {
|
|||||||
const GalaxySearchScreen({
|
const GalaxySearchScreen({
|
||||||
super.key,
|
super.key,
|
||||||
this.repository = const SampleGalaxyRepository(),
|
this.repository = const SampleGalaxyRepository(),
|
||||||
|
this.onOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
final GalaxyRepository repository;
|
final GalaxyRepository repository;
|
||||||
|
|
||||||
|
/// 결과 줄을 누르면 그 행성 상세로 — 상세 화면 저장소는 부르는 쪽이 안다.
|
||||||
|
final void Function(PlanetNode planet)? onOpen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GalaxySearchScreen> createState() => _GalaxySearchScreenState();
|
State<GalaxySearchScreen> createState() => _GalaxySearchScreenState();
|
||||||
}
|
}
|
||||||
@@ -153,7 +157,12 @@ class _GalaxySearchScreenState extends State<GalaxySearchScreen> {
|
|||||||
padding: const EdgeInsets.all(AppSpace.s4),
|
padding: const EdgeInsets.all(AppSpace.s4),
|
||||||
itemCount: hits.length,
|
itemCount: hits.length,
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: AppSpace.s2),
|
separatorBuilder: (_, _) => const SizedBox(height: AppSpace.s2),
|
||||||
itemBuilder: (context, i) => _Hit(planet: hits[i]),
|
itemBuilder: (context, i) => _Hit(
|
||||||
|
planet: hits[i],
|
||||||
|
onTap: widget.onOpen == null
|
||||||
|
? null
|
||||||
|
: () => widget.onOpen!(hits[i]),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -257,40 +266,45 @@ class _NoHit extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _Hit extends StatelessWidget {
|
class _Hit extends StatelessWidget {
|
||||||
const _Hit({required this.planet});
|
const _Hit({required this.planet, this.onTap});
|
||||||
|
|
||||||
final PlanetNode planet;
|
final PlanetNode planet;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Theme.of(context).textTheme;
|
final t = Theme.of(context).textTheme;
|
||||||
return Container(
|
return GestureDetector(
|
||||||
padding: const EdgeInsets.all(AppSpace.s4),
|
behavior: HitTestBehavior.opaque,
|
||||||
decoration: BoxDecoration(
|
onTap: onTap,
|
||||||
color: AppColors.cosmicSurfaceContainer,
|
child: Container(
|
||||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
padding: const EdgeInsets.all(AppSpace.s4),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Row(
|
color: AppColors.cosmicSurfaceContainer,
|
||||||
children: [
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||||
Text(
|
),
|
||||||
'${planet.rank}',
|
child: Row(
|
||||||
style: t.titleMedium?.copyWith(color: AppColors.cosmicAccent),
|
children: [
|
||||||
),
|
Text(
|
||||||
const SizedBox(width: AppSpace.s4),
|
'${planet.rank}',
|
||||||
Expanded(
|
style: t.titleMedium?.copyWith(color: AppColors.cosmicAccent),
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(planet.name, style: t.bodyMedium),
|
|
||||||
Text(
|
|
||||||
context.l10n.galaxyMembers(formatPop(planet.members)),
|
|
||||||
style: t.labelSmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: AppSpace.s4),
|
||||||
Text('FPI ${formatPop(planet.fpi)}', style: t.bodySmall),
|
Expanded(
|
||||||
],
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(planet.name, style: t.bodyMedium),
|
||||||
|
Text(
|
||||||
|
context.l10n.galaxyMembers(formatPop(planet.members)),
|
||||||
|
style: t.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('FPI ${formatPop(planet.fpi)}', style: t.bodySmall),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,12 +73,20 @@ class GalaxyField extends StatelessWidget {
|
|||||||
/// 90개가 사라진다. 바깥 궤도가 [edgeMargin] 안쪽에 서도록 간격을 나눈다.
|
/// 90개가 사라진다. 바깥 궤도가 [edgeMargin] 안쪽에 서도록 간격을 나눈다.
|
||||||
static double ringOf(int rank, int outerRank) {
|
static double ringOf(int rank, int outerRank) {
|
||||||
final rings = math.max(1, outerRank - 1);
|
final rings = math.max(1, outerRank - 1);
|
||||||
final gap = math.min(ringGap, (extent / 2 - innerRing - edgeMargin) / rings);
|
final gap = math.min(
|
||||||
|
ringGap,
|
||||||
|
(extent / 2 - innerRing - edgeMargin) / rings,
|
||||||
|
);
|
||||||
return innerRing + (rank - 1) * gap;
|
return innerRing + (rank - 1) * gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 행성의 논리 좌표. 그리기·히트 테스트·HUD 가 같은 식을 써야 어긋나지 않는다.
|
/// 행성의 논리 좌표. 그리기·히트 테스트·HUD 가 같은 식을 써야 어긋나지 않는다.
|
||||||
static Offset positionOf(PlanetNode p, int index, double spin, int outerRank) {
|
static Offset positionOf(
|
||||||
|
PlanetNode p,
|
||||||
|
int index,
|
||||||
|
double spin,
|
||||||
|
int outerRank,
|
||||||
|
) {
|
||||||
if (p.isCenter) return const Offset(extent / 2, extent / 2);
|
if (p.isCenter) return const Offset(extent / 2, extent / 2);
|
||||||
// 순위가 낮을수록 멀어진다. 중심 행성을 뺀 나머지를 고리에 흩는다.
|
// 순위가 낮을수록 멀어진다. 중심 행성을 뺀 나머지를 고리에 흩는다.
|
||||||
final ring = ringOf(p.rank, outerRank);
|
final ring = ringOf(p.rank, outerRank);
|
||||||
@@ -101,7 +109,10 @@ class GalaxyField extends StatelessWidget {
|
|||||||
|
|
||||||
/// -1(뒤·화면 위) ~ +1(앞·화면 아래) 의 깊이.
|
/// -1(뒤·화면 위) ~ +1(앞·화면 아래) 의 깊이.
|
||||||
static double depthOf(Offset at) =>
|
static double depthOf(Offset at) =>
|
||||||
((at.dy - extent / 2) / (extent / 2 * GalaxyMotion.tilt)).clamp(-1.0, 1.0);
|
((at.dy - extent / 2) / (extent / 2 * GalaxyMotion.tilt)).clamp(
|
||||||
|
-1.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
|
||||||
/// 2.5D 깊이 배율. 궤도 앞쪽일수록 크다.
|
/// 2.5D 깊이 배율. 궤도 앞쪽일수록 크다.
|
||||||
static double depthScaleOf(Offset at) =>
|
static double depthScaleOf(Offset at) =>
|
||||||
@@ -125,7 +136,9 @@ class GalaxyField extends StatelessWidget {
|
|||||||
/// 행성 일러스트. 내 행성은 홈과 같은 그림, 나머지는 시안 행성 2종을 돌려 쓴다.
|
/// 행성 일러스트. 내 행성은 홈과 같은 그림, 나머지는 시안 행성 2종을 돌려 쓴다.
|
||||||
static String imageOf(PlanetNode p, int index) => p.isMine
|
static String imageOf(PlanetNode p, int index) => p.isMine
|
||||||
? 'assets/figma/planet-home.png'
|
? 'assets/figma/planet-home.png'
|
||||||
: (index.isEven ? 'assets/figma/planet-1.png' : 'assets/figma/planet-2.png');
|
: (index.isEven
|
||||||
|
? 'assets/figma/planet-1.png'
|
||||||
|
: 'assets/figma/planet-2.png');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -147,7 +160,10 @@ class GalaxyField extends StatelessWidget {
|
|||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: _BackdropPainter(planets: planets, outerRank: outerRank),
|
painter: _BackdropPainter(
|
||||||
|
planets: planets,
|
||||||
|
outerRank: outerRank,
|
||||||
|
),
|
||||||
isComplex: true,
|
isComplex: true,
|
||||||
willChange: false,
|
willChange: false,
|
||||||
),
|
),
|
||||||
@@ -169,7 +185,9 @@ class GalaxyField extends StatelessWidget {
|
|||||||
opacity: depthOpacityOf(at),
|
opacity: depthOpacityOf(at),
|
||||||
selected: selected?.id == p.id,
|
selected: selected?.id == p.id,
|
||||||
onTap: () => onSelect(p),
|
onTap: () => onSelect(p),
|
||||||
onOpenHistory: onOpenHistory == null ? null : () => onOpenHistory!(p),
|
onOpenHistory: onOpenHistory == null
|
||||||
|
? null
|
||||||
|
: () => onOpenHistory!(p),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -243,8 +261,17 @@ class _PlanetHit extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 3~5. 본체 일러스트(표면 장식·구름 포함).
|
// 3~5. 본체 일러스트(표면 장식·구름 포함).
|
||||||
LivePlanet25D(onOpenHistory: onOpenHistory, fandomId: planet.id, watch: watchPlanet, image: image, size: side, stage: planet.stage, glow: glow,
|
LivePlanet25D(
|
||||||
animate: selected, lowEffects: !selected),
|
onOpenHistory: onOpenHistory,
|
||||||
|
fandomId: planet.id,
|
||||||
|
watch: watchPlanet,
|
||||||
|
image: image,
|
||||||
|
size: side,
|
||||||
|
stage: planet.stage,
|
||||||
|
glow: glow,
|
||||||
|
animate: selected,
|
||||||
|
lowEffects: !selected,
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: -4,
|
bottom: -4,
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -257,7 +284,9 @@ class _PlanetHit extends StatelessWidget {
|
|||||||
fontWeight: planet.isMine
|
fontWeight: planet.isMine
|
||||||
? FontWeight.w600
|
? FontWeight.w600
|
||||||
: FontWeight.w400,
|
: FontWeight.w400,
|
||||||
shadows: const [Shadow(color: Colors.black, blurRadius: 6)],
|
shadows: const [
|
||||||
|
Shadow(color: Colors.black, blurRadius: 6),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -309,7 +338,8 @@ class _BackdropPainter extends CustomPainter {
|
|||||||
|
|
||||||
static bool _sameRanks(List<PlanetNode> a, List<PlanetNode> b) {
|
static bool _sameRanks(List<PlanetNode> a, List<PlanetNode> b) {
|
||||||
for (var i = 0; i < a.length; i++) {
|
for (var i = 0; i < a.length; i++) {
|
||||||
if (a[i].rank != b[i].rank || a[i].isCenter != b[i].isCenter) return false;
|
if (a[i].rank != b[i].rank || a[i].isCenter != b[i].isCenter)
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,8 @@ class _GalaxyWebViewState extends State<GalaxyWebView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get _visible {
|
bool get _visible {
|
||||||
if (!mounted || !_foreground || !TickerMode.valuesOf(context).enabled) return false;
|
if (!mounted || !_foreground || !TickerMode.valuesOf(context).enabled)
|
||||||
|
return false;
|
||||||
if (ModalRoute.of(context)?.isCurrent == false) return false;
|
if (ModalRoute.of(context)?.isCurrent == false) return false;
|
||||||
final box = context.findRenderObject();
|
final box = context.findRenderObject();
|
||||||
if (box is! RenderBox || !box.attached || !box.hasSize) return true;
|
if (box is! RenderBox || !box.attached || !box.hasSize) return true;
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ class _ZapButtonState extends State<ZapButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get _canZap =>
|
bool get _canZap =>
|
||||||
widget.blockedReason == null && (widget.isHeroTime || widget.myPop >= widget.zapPop);
|
widget.blockedReason == null &&
|
||||||
|
(widget.isHeroTime || widget.myPop >= widget.zapPop);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => AnimatedSize(
|
Widget build(BuildContext context) => AnimatedSize(
|
||||||
@@ -180,7 +181,9 @@ class _ZapButtonState extends State<ZapButton> {
|
|||||||
icon: const Icon(Icons.bolt),
|
icon: const Icon(Icons.bolt),
|
||||||
label: Text(
|
label: Text(
|
||||||
_canZap
|
_canZap
|
||||||
? widget.isHeroTime ? 'ZAP+ · HERO TIME' : 'ZAP ${widget.zapPop} POP'
|
? widget.isHeroTime
|
||||||
|
? 'ZAP+ · HERO TIME'
|
||||||
|
: 'ZAP ${widget.zapPop} POP'
|
||||||
: (widget.blockedReason ?? context.l10n.zapNotEnoughPop),
|
: (widget.blockedReason ?? context.l10n.zapNotEnoughPop),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -447,6 +447,24 @@ class _HomeBody extends StatelessWidget {
|
|||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => GalaxySearchScreen(
|
builder: (_) => GalaxySearchScreen(
|
||||||
repository: zap!.repository,
|
repository: zap!.repository,
|
||||||
|
onOpen: (p) =>
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
rootNavigator: true,
|
||||||
|
).push(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
builder: (_) =>
|
||||||
|
PlanetDetailScreen(
|
||||||
|
repository:
|
||||||
|
planetRepository!(
|
||||||
|
p.id,
|
||||||
|
),
|
||||||
|
watchPlanet:
|
||||||
|
watchPlanet,
|
||||||
|
eclipse: eclipse,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -471,6 +471,15 @@ void main() {
|
|||||||
expect(find.text('전체 삭제'), findsOneWidget);
|
expect(find.text('전체 삭제'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('결과 줄을 누르면 그 행성 상세로 간다(10/5 실기기)', (tester) async {
|
||||||
|
PlanetNode? opened;
|
||||||
|
await _pump(tester, GalaxySearchScreen(onOpen: (p) => opened = p));
|
||||||
|
await tester.enterText(find.byType(TextField), 'BTS');
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.textContaining('멤버 12,840명'));
|
||||||
|
expect(opened?.name, 'BTS');
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('없는 이름이면 사유를 알린다', (tester) async {
|
testWidgets('없는 이름이면 사유를 알린다', (tester) async {
|
||||||
await _pump(tester, const GalaxySearchScreen());
|
await _pump(tester, const GalaxySearchScreen());
|
||||||
await tester.enterText(find.byType(TextField), 'zzzz');
|
await tester.enterText(find.byType(TextField), 'zzzz');
|
||||||
|
|||||||
Reference in New Issue
Block a user