/// 캠페인 공유 문구 조립 — 「홈/HOT ARCHIVE/공유」 · Figma `480:14594`. /// /// 조립을 화면 밖에 둔 이유는 정책 원장 **B-78** 이다. 해시태그를 콘텐츠에 따라 /// 동적으로 구성하라고 확정했고 검증 기준이 *고정 3종 하드코딩 0건* 이므로, /// 규칙을 한 곳에 두고 테스트로 고정한다. 화면 안에 문자열을 이어 붙이면 /// 캠페인과 퀘스트에서 규칙이 갈린다. library; import '../../core/data/invite_code.dart'; import '../../core/i18n/app_locale.dart'; const String shareBrand = 'FANUZU'; /// 웹 브릿지 주소 — 빌드 설정(`--dart-define=FANUZU_SHARE_BASE_URL=https://…`). /// /// 도메인은 해석 확인 요청 「공유 · 웹 브릿지」 3-1 회신 뒤 정해진다. 설정이 없으면 링크 없이 /// 문구만 보낸다 — 살아 있지 않은 주소를 사용자 손에 쥐여 주지 않는다. const String shareBaseUrl = String.fromEnvironment('FANUZU_SHARE_BASE_URL'); /// 공유 링크 한 곳 — 경로는 프로토타입 구조(`/b/<팬덤>/<날짜>` · `/c/<캠페인>`)를 따른다. /// 브리핑 · 캠페인(HOT ARCHIVE) 공유가 모두 여기를 지난다. 내 초대 코드([InviteCode])를 /// 받아 두었으면 `?ref=<코드>` 를 붙이고, 없으면 붙이지 않는다(공유를 막지 않는다). String? shareLink( List segments, { String base = shareBaseUrl, String? ref, }) { if (base.isEmpty || segments.any((s) => s.isEmpty)) return null; final root = base.endsWith('/') ? base.substring(0, base.length - 1) : base; final code = ref ?? InviteCode.current; final query = code == null || code.isEmpty ? '' : '?ref=${Uri.encodeQueryComponent(code)}'; return '$root/${segments.map(Uri.encodeComponent).join('/')}$query'; } /// 캠페인 상세로 돌아오는 링크 `/c/`. String? campaignShareLink(String campaignId, {String base = shareBaseUrl}) => shareLink(['c', campaignId], base: base); /// 해시태그를 조립한다. 최대 [maxHashtags] 개다. /// /// B-78 — *브랜드 · Artist/Fandom · Campaign/Quest 등을 콘텐츠에 따라 최대 약 /// 3 개로 동적 구성합니다.* 세 자리를 채우는 것이 아니라 **있는 것만** 넣는다. /// 팬덤을 모르는 캠페인은 두 개가 되고, 그것이 「고정 3종」이 아니라는 증거다. List shareHashtags({ String brand = shareBrand, String? fandomName, String? topic, int maxHashtags = 3, }) { final tags = [ for (final value in [brand, fandomName, topic]) if (value != null && value.trim().isNotEmpty) '#${_tag(value)}', ]; return tags.take(maxHashtags).toList(); } /// 해시태그 한 조각. 공백과 해시는 태그를 끊으므로 지운다. String _tag(String value) => value.trim().replaceAll(RegExp(r'[\s#]+'), ''); /// 캠페인 공유 문구. /// /// 제목과 한 줄 소개, 행성명, 링크(있을 때), 해시태그로 끝난다. 소개가 비어 있으면 그 줄을 /// 빼고, 행성명을 모르면 그 줄과 팬덤 해시태그가 함께 빠진다. String campaignShareText({ required String title, String? summary, String? fandomName, String? topic, String? link, }) { topic ??= appL10n.shareTopicCampaign; final lines = [ title.trim(), if (summary != null && summary.trim().isNotEmpty) summary.trim(), if (fandomName != null && fandomName.trim().isNotEmpty) appL10n.sharePlanetLine(shareBrand, fandomName.trim()), ?link, shareHashtags(fandomName: fandomName, topic: topic).join(' '), ]; return lines.where((l) => l.isNotEmpty).join('\n'); }