build(release): Android signing, iOS privacy manifest, store assets (Phase 7)
- Android: release keystore signing wired via gitignored key.properties (falls back to debug when absent). Verified: signed AAB built (signer CN=Block Seasons). - iOS: app PrivacyInfo.xcprivacy (ATT tracking flag, device-id/usage data types, UserDefaults+FileTimestamp required-reason APIs) registered in the Runner target. - Store: app-ads.txt (pub-5605900229781491), EN/KO listing copy, owner submission guide (privacy labels, app-ads hosting, upload/submit steps). Secrets (keystore, key.properties) are gitignored — owner backs them up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,3 +53,11 @@ CLAUDE.md
|
||||
# Firebase Hosting (CLI deploy cache + generated content payload)
|
||||
.firebase/
|
||||
/deploy/content/
|
||||
|
||||
# Android release signing — NEVER commit (owner backs these up out-of-band)
|
||||
android/key.properties
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Kotlin/Gradle build caches
|
||||
android/.kotlin/
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// START: FlutterFire Configuration
|
||||
@@ -8,6 +11,15 @@ plugins {
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
// Release signing is read from android/key.properties (gitignored). When that
|
||||
// file is absent (CI, a fresh clone, another machine) the release build falls
|
||||
// back to debug signing so `flutter build`/`flutter run --release` still works.
|
||||
val keystoreProperties = Properties()
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.airkjw.block_seasons"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
@@ -34,11 +46,24 @@ android {
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keyAlias = keystoreProperties["keyAlias"] as String
|
||||
keyPassword = keystoreProperties["keyPassword"] as String
|
||||
storeFile = file(keystoreProperties["storeFile"] as String)
|
||||
storePassword = keystoreProperties["storePassword"] as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
signingConfig = if (keystorePropertiesFile.exists()) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
google.com, pub-5605900229781491, DIRECT, f08c47fec0942fa0
|
||||
@@ -0,0 +1,110 @@
|
||||
# Phase 7 — 릴리스 & 스토어 제출 가이드 (오너용)
|
||||
|
||||
코드/빌드 쪽(서명, 프라이버시 매니페스트, app-ads.txt 내용, 스토어 카피)은 준비돼
|
||||
있습니다. 이 문서는 **오너가 콘솔/웹에서 할 일**과 **제출 순서**입니다. 헷갈리면
|
||||
Claude에게 화면 보여주면서 같이 하면 됩니다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 먼저 — 안드로이드 서명 키 백업 (가장 중요)
|
||||
|
||||
`android/app/upload-keystore.jks` 파일과 비밀번호는 **이 앱의 영구 서명 키**입니다.
|
||||
**잃어버리면 Play에서 이 앱을 영원히 업데이트할 수 없습니다.**
|
||||
- `upload-keystore.jks` 파일을 안전한 곳(클라우드 드라이브 + 외장 등 2곳 이상)에 백업.
|
||||
- 비밀번호도 비밀번호 관리자에 저장. (git에는 올라가지 않습니다 — gitignore됨)
|
||||
|
||||
> 참고: Play **앱 서명(Play App Signing)**을 쓰면 이 키는 "업로드 키"가 되고 분실 시
|
||||
> 재설정이 가능하지만, 그래도 백업은 필수입니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 개인정보처리방침(Privacy Policy) 페이지 — 양 스토어 필수
|
||||
|
||||
두 스토어 모두 **개인정보처리방침 URL**을 요구합니다. 무료로 GitHub Pages에 한 장
|
||||
올리면 됩니다. 내용에 최소한 다음을 명시:
|
||||
- 수집 항목: **광고 식별자(IDFA/광고 ID)**, **사용 데이터(앱 이용 통계)**, **기기 정보**.
|
||||
- 사용처: **광고 게재(AdMob)**, **분석(Firebase Analytics)**.
|
||||
- 제3자: Google AdMob, Google Firebase (각 정책 링크).
|
||||
- 추적: iOS에서 ATT 동의 시에만 맞춤 광고. 동의 거부해도 앱 정상 작동.
|
||||
- 구매: "광고 제거" 인앱 구입(비소모성).
|
||||
- 문의 이메일.
|
||||
|
||||
---
|
||||
|
||||
## 2. app-ads.txt 호스팅 — 광고 수익 인증
|
||||
|
||||
- 파일 내용은 `docs/store/app-ads.txt`에 있습니다:
|
||||
```
|
||||
google.com, pub-5605900229781491, DIRECT, f08c47fec0942fa0
|
||||
```
|
||||
- 이걸 **개발자 웹사이트 도메인 루트**에 올립니다 → `https://<도메인>/app-ads.txt`.
|
||||
- ⚠️ 그 도메인은 **스토어 리스팅의 "마케팅/개발자 웹사이트" URL과 정확히 같아야**
|
||||
AdMob이 인식합니다. (GitHub Pages 커스텀 도메인이면 그 도메인)
|
||||
- 올린 뒤 AdMob → 앱 → app-ads.txt 상태가 며칠 내 "발견됨"으로 바뀝니다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 개인정보 라벨 — 콘솔 입력값 (그대로 답하면 됨)
|
||||
|
||||
코드가 실제로 수집하는 것과 일치하는 답변입니다. 이 표대로 입력하세요.
|
||||
|
||||
### App Store Connect → 앱 개인정보 보호
|
||||
| 데이터 유형 | 수집? | 사용 목적 | 사용자 연결 | 추적에 사용 |
|
||||
|---|---|---|---|---|
|
||||
| 식별자 → 기기 ID(광고 식별자) | 예 | 제3자 광고 | 아니요 | **예** |
|
||||
| 사용 데이터 → 제품 상호작용 | 예 | 분석, 앱 기능 | 아니요 | 아니요 |
|
||||
| 구매 → 구매 내역 | (Apple이 IAP 자동 처리, 별도 수집 안 함) | — | — | — |
|
||||
|
||||
### Google Play → 데이터 보안(Data safety)
|
||||
- 데이터 수집함: **예**
|
||||
- 앱 활동(앱 상호작용) → 분석 목적, 사용자 연결 안 함.
|
||||
- 기기 또는 기타 ID(광고 ID) → 광고/마케팅 목적, **공유함(Google)**.
|
||||
- 전송 중 암호화: 예. 사용자가 삭제 요청 가능: 해당 시 명시.
|
||||
- ⚠️ **"아동 대상 앱" 아니오** (Everyone/4+, 아동 타겟 아님으로 정확히).
|
||||
|
||||
---
|
||||
|
||||
## 4. 안드로이드 — Play 업로드
|
||||
|
||||
1. **AAB 파일**: Claude가 빌드한 `build/app/outputs/bundle/release/app-release.aab`.
|
||||
(없거나 갱신 필요하면 Claude에게 "AAB 다시 빌드" 요청)
|
||||
2. Play Console → Block Seasons → **테스트 → 내부 테스트 → 새 버전 만들기**.
|
||||
3. AAB 업로드. (첫 업로드 시 **Play 앱 서명** 사용에 동의 — 권장)
|
||||
4. 업로드되면 **수익 창출 → 제품 → 인앱 상품**에서 `remove_ads` 상품 생성 가능해집니다
|
||||
(제품 ID는 **`remove_ads`** 정확히 — App Store와 동일).
|
||||
5. **라이선스 테스터**(설정)에 본인 계정 추가 → 샌드박스 결제 테스트.
|
||||
6. 데이터 보안·콘텐츠 등급·스토어 리스팅(아래 카피) 작성 → 내부 테스트 출시.
|
||||
|
||||
## 5. iOS — App Store Connect 업로드
|
||||
|
||||
1. **빌드 업로드**: Xcode로 `Runner.xcworkspace` 열기 → 기기를 "Any iOS Device" →
|
||||
Product ▸ Archive → Organizer에서 "Distribute App" → App Store Connect 업로드.
|
||||
(또는 `flutter build ipa` 후 Transporter 앱으로 업로드. 인증서/프로비저닝은
|
||||
Xcode 자동 서명으로 처리.)
|
||||
2. 업로드된 빌드가 App Store Connect → TestFlight에 나타납니다(처리 ~수십 분).
|
||||
3. **IAP 마무리**: `remove_ads` 상품에 **표시 이름/설명(한·영)** + **리뷰용 스크린샷**
|
||||
(설정 화면 캡처) 추가 → 상태가 "제출 준비됨"으로. 첫 IAP는 **앱 버전과 함께 제출**.
|
||||
4. **유료 앱 계약**(Business → 계약/세금/뱅킹) 완료해야 IAP 판매·심사 가능.
|
||||
5. 앱 개인정보(위 표) + ATT 사용 이유 + 스토어 카피 작성 → **심사 제출**.
|
||||
|
||||
## 6. 스토어 카피
|
||||
|
||||
`docs/store/store-listing.md`의 EN/KO 카피를 각 콘솔에 붙여넣으세요.
|
||||
스크린샷은 실기기/시뮬레이터에서 캡처(홈·시즌맵·플레이·승리·엔드리스). Claude가
|
||||
캡처를 도울 수 있습니다(시뮬레이터). iOS는 6.7"/6.5" 규격, Play는 폰 스크린샷.
|
||||
|
||||
## 7. AdMob — 앱 연결
|
||||
|
||||
앱이 스토어에 **출시된 뒤**, AdMob → 앱 → "앱 스토어에 연결"로 실제 스토어 앱과
|
||||
연결하면 실광고 게재가 승인됩니다. (출시 전까지는 미연결이 정상)
|
||||
|
||||
---
|
||||
|
||||
## 제출 전 최종 체크리스트
|
||||
- [ ] 키스토어 백업 완료
|
||||
- [ ] 개인정보처리방침 URL 게시
|
||||
- [ ] app-ads.txt 게시(개발자 웹사이트 도메인 = 스토어 URL)
|
||||
- [ ] App Store: 빌드 업로드 + IAP(현지화·스크린샷) + 개인정보 라벨 + ATT 문구 + 카피
|
||||
- [ ] Play: AAB(내부 테스트) + remove_ads 상품 + 데이터 보안 + 콘텐츠 등급 + 카피
|
||||
- [ ] 유료 앱 계약(Apple)·결제 프로필(Google) 완료
|
||||
- [ ] 양 스토어 심사 제출
|
||||
@@ -0,0 +1,89 @@
|
||||
# Store Listing Copy — Block Seasons
|
||||
|
||||
Paste these into App Store Connect (App Information / Version) and Google Play
|
||||
Console (Main store listing). Character limits noted; both EN and KO provided.
|
||||
|
||||
---
|
||||
|
||||
## App name
|
||||
- **EN:** `Block Seasons` (13)
|
||||
- **KO:** `블록 시즌즈` — or keep `Block Seasons` (영문 그대로도 무방)
|
||||
|
||||
## Subtitle (iOS, ≤30) / Short description (Play, ≤80)
|
||||
- **EN (iOS subtitle):** `Seasonal block puzzle bliss` (27)
|
||||
- **KO (iOS subtitle):** `시즌마다 새로워지는 블록 퍼즐` (16)
|
||||
- **EN (Play short, ≤80):** `Drop blocks, clear lines, and chase a fresh themed season every few weeks.` (73)
|
||||
- **KO (Play short, ≤80):** `블록을 놓아 줄을 지우고, 몇 주마다 새 테마 시즌을 즐기세요. 광고 강요 없는 편안한 퍼즐.` (47)
|
||||
|
||||
## Keywords (iOS, ≤100, comma-separated)
|
||||
- **EN:** `block,puzzle,blocks,brain,grid,tetris,blast,relax,season,line,casual,offline,jewel,combo`
|
||||
- **KO:** `블록,퍼즐,블럭,두뇌,그리드,테트리스,블라스트,힐링,시즌,라인,캐주얼,오프라인,콤보`
|
||||
|
||||
## Promotional text (iOS, ≤170)
|
||||
- **EN:** `New Season 2 "Summer Tide" is live — cool teal blocks and 30 fresh stages. No internet? No problem: Season 1 plays fully offline.` (131)
|
||||
- **KO:** `새 시즌 2 "여름 파도" 공개 — 시원한 청록 블록과 30개 새 스테이지. 인터넷이 없어도 시즌 1은 완전 오프라인으로 즐길 수 있어요.` (66)
|
||||
|
||||
## Description (long, ≤4000)
|
||||
|
||||
### EN
|
||||
```
|
||||
Block Seasons is a cozy, beautiful block puzzle you can actually relax with.
|
||||
|
||||
Drag three pieces at a time onto an 8×8 board, fill rows and columns, and watch
|
||||
them clear in a satisfying glossy burst. Easy to pick up, deep enough to chase
|
||||
"just one more" — chase combos, beat your best, and feel the board breathe.
|
||||
|
||||
WHAT MAKES IT DIFFERENT
|
||||
• Seasons — every few weeks a brand-new themed season arrives with fresh stages
|
||||
and its own look, no app update needed.
|
||||
• A journey, not a grid — wind your way up an illustrated map, one stage at a time.
|
||||
• Endless mode — no limits, no objectives, just you and your high score.
|
||||
• Glossy, hand-tuned visuals and a calm, drifting season backdrop.
|
||||
• Plays offline — Season 1 is built in, so you can play on a plane, a subway,
|
||||
anywhere.
|
||||
|
||||
FAIR BY DESIGN
|
||||
• No forced video before every move. Ads are spaced out, and rescue/continue is
|
||||
always your choice.
|
||||
• One-time "Remove Ads" if you'd rather go quiet — reward ads still work so you
|
||||
never lose a helping hand.
|
||||
|
||||
Whether you have two minutes or twenty, Block Seasons is the puzzle that's always
|
||||
in season. Download free and start your first season today.
|
||||
```
|
||||
|
||||
### KO
|
||||
```
|
||||
블록 시즌즈는 정말로 편안하게 즐길 수 있는, 예쁜 블록 퍼즐입니다.
|
||||
|
||||
한 번에 세 조각을 8×8 보드에 드래그해서 가로·세로 줄을 채우고, 반짝이는 글로시
|
||||
연출과 함께 줄이 사라지는 쾌감을 느껴보세요. 시작은 쉽지만 "한 판만 더"를 부르는
|
||||
깊이가 있습니다 — 콤보를 노리고, 최고 점수를 갱신하세요.
|
||||
|
||||
무엇이 다른가요
|
||||
• 시즌제 — 몇 주마다 새 테마의 시즌과 스테이지가 앱 업데이트 없이 도착합니다.
|
||||
• 그리드가 아닌 여정 — 일러스트 맵을 따라 한 스테이지씩 올라갑니다.
|
||||
• 엔드리스 모드 — 제한도 목표도 없이, 오직 최고 점수에 도전.
|
||||
• 손으로 다듬은 글로시 비주얼과 잔잔하게 흐르는 시즌 배경.
|
||||
• 오프라인 플레이 — 시즌 1이 내장돼 비행기·지하철 어디서든 즐길 수 있습니다.
|
||||
|
||||
설계부터 공정하게
|
||||
• 매 수마다 강제 영상 광고 없음. 광고는 충분히 띄엄띄엄, 구조/계속하기는 항상 선택.
|
||||
• 조용히 즐기고 싶다면 일회성 "광고 제거". 보상형 광고는 그대로라 도움은 늘 받을 수 있어요.
|
||||
|
||||
2분이든 20분이든, 블록 시즌즈는 언제나 제철인 퍼즐입니다. 무료로 받고 첫 시즌을
|
||||
시작하세요.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Category & age
|
||||
- Primary category: **Games → Puzzle** (iOS) / **Puzzle** (Play).
|
||||
- Age rating: **Everyone / 4+** — but answer the questionnaires honestly and DO
|
||||
NOT mark the app as "directed at children" (it is not — this protects ad rates
|
||||
and avoids child-privacy obligations).
|
||||
|
||||
## Required URLs (owner)
|
||||
- Support URL + Marketing URL: a simple page is fine (GitHub Pages works).
|
||||
- **Privacy Policy URL: required by both stores** — a hosted privacy policy page.
|
||||
See docs/store/phase7-submission-guide.md for what it must cover.
|
||||
@@ -15,6 +15,7 @@
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
A1624C49AABB61D3BB6EBA00 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F021B835BC4E346AE82B4C9 /* Pods_RunnerTests.framework */; };
|
||||
BC732790904D77939BB8C135 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FE18481AC23043B44AB64814 /* PrivacyInfo.xcprivacy */; };
|
||||
D444497F007A61A9102D174D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92F5ACA56D636C056F52DDE6 /* Pods_Runner.framework */; };
|
||||
E746073DDE80B82D8D3C9659 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 43C03408D7DF6E3F6C4EC9C7 /* GoogleService-Info.plist */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -67,6 +68,7 @@
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B4B2233E92790E4E03907BD2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
B9983A741CFB90A0857F31CD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
FE18481AC23043B44AB64814 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -155,6 +157,7 @@
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
FE18481AC23043B44AB64814 /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
@@ -269,6 +272,7 @@
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
E746073DDE80B82D8D3C9659 /* GoogleService-Info.plist in Resources */,
|
||||
BC732790904D77939BB8C135 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- The app uses data for tracking (personalized ads, gated by the ATT
|
||||
prompt). Google's AdMob/Firebase SDKs ship their own privacy manifests
|
||||
that declare the tracking domains; this app-level manifest declares the
|
||||
app's own collected data and required-reason API usage. -->
|
||||
<key>NSPrivacyTracking</key>
|
||||
<true/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<!-- shared_preferences reads/writes the app's own UserDefaults. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- Season content cache reads/writes + stats file timestamps in the
|
||||
app container (path_provider / ContentDownloader). -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user