문서를 wiki 로 일원화: README·CLAUDE.md·server/README 는 "무조건 wiki 참조"만
- 기능 안내를 화면 지도 + 기능별 노트 12개로 나눔 - 규칙(00)·제품(01)·바꾸지 않는 기술 결정(02)·현재 상태(03)·모듈 표(04)·빌드·시험·저장소(05)로 옮김 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
parent
08641630c8
commit
e6813e39a4
78
CLAUDE.md
78
CLAUDE.md
|
|
@ -1,73 +1,7 @@
|
||||||
# Friendly Map — Claude 작업 지침 (인수인계)
|
# Friendly Map — Claude 작업 지침
|
||||||
|
|
||||||
## 0. 가장 먼저 (이 지시가 모든 것보다 최우선)
|
## 무조건 wiki 를 참조한다 (최우선)
|
||||||
- **모든 가이드·문서·결정·진행 기록·화면 점검은 프로젝트의 `wiki/` Obsidian 보관함에 저장하고 관리한다.** 새 내용은 wiki 에 먼저 쓰고, 다른 문서(README 등)는 wiki 를 가리키기만 한다.
|
- 모든 가이드·규칙·결정·구조·빌드 방법·진행 기록·화면 점검은 **`wiki/` (Obsidian 보관함)에만** 있다. 이 파일과 README 에는 내용을 두지 않는다.
|
||||||
- 세션을 시작하면 `wiki/홈.md` 와 `wiki/00 최우선 규칙.md` 부터 읽는다. 일할 때마다 `wiki/진행 로그/<날짜>.md`, `wiki/02 결정 기록.md` 를 갱신한다.
|
- 세션을 시작하면 반드시 `wiki/홈.md` → `wiki/00 최우선 규칙.md` 를 먼저 읽고, 필요한 노트(02 결정 기록, 04 구조와 모듈, 05 빌드 · 시험 · 저장소, 가이드/…)를 읽은 뒤 일한다.
|
||||||
|
- 새로 정한 것·한 일은 wiki 에 먼저 쓴다: `wiki/진행 로그/<날짜>.md`, `wiki/02 결정 기록.md`, 화면을 바꾸면 `wiki/06 화면 점검.md`.
|
||||||
이 파일은 이전 대화(claude.ai 프로젝트 "Frindly Map")에서 정한 것을 새 세션에 넘기는 문서다.
|
- wiki 와 다른 문서가 다르면 wiki 가 맞다.
|
||||||
세션을 시작하면 먼저 이 파일, wiki/홈.md, tool/check_modules.py 를 읽고, 작업 전에 아래 원칙을 한 번 읊고 시작한다.
|
|
||||||
|
|
||||||
## 최우선 규칙 (모든 규칙보다 위)
|
|
||||||
- 질문은 반드시 최종 화면이 담긴 설치 가능한 APK 를 먼저 건넨 뒤(사용자가 설치해 써 보며 답할 수 있게), 캡처도 함께 보여 주고, 그 화면을 기준으로 "이렇게 하면 / 저렇게 하면" 비교로만 묻는다. 화면 없는 질문 금지.
|
|
||||||
- 암호 비트 수·키 구조·함수 설계 같은 내부 기술 질문은 하지 않는다. 내가 정하고 결과에 적는다. 책임 회피성 질문 금지.
|
|
||||||
- 방법론: 화면 먼저 → 화면 보고 사용자가 답 → 그 답에 따라 내부 아키텍처를 바꾼다.
|
|
||||||
- 모든 기록(결정·진행 로그·로드맵·화면 점검)은 프로젝트의 `wiki/` Obsidian 보관함에 남긴다. 일할 때마다 갱신.
|
|
||||||
- 화면 캡처: `flutter test test_ux/shots_test.dart --update-goldens` → `test_ux/shots/*.png` (한글 글꼴 `build/ux/kr.ttf` 필요, 맥의 /Library/Fonts/Arial Unicode.ttf 복사).
|
|
||||||
|
|
||||||
## 사용자와 일하는 규칙 (1순위, 어기지 말 것)
|
|
||||||
- 모든 답변은 한글. 고친 파일은 일부 발췌가 아니라 처음부터 끝까지 완본으로 보여 준다.
|
|
||||||
- "목업"이라는 말은 쓰지 않는다 ("화면 설계"라고 한다).
|
|
||||||
- 나는 솔루션 개발자다: 구조가 기능보다 먼저다. 화면과 내부를 분리한다.
|
|
||||||
- 모듈화 필수. 모듈은 다른 모듈을 고치지 않고 빼고 끼울 수 있어야 한다. `tool/check_modules.py` 가 의존 방향을 검사한다(새 모듈은 ALLOW 표에 등록).
|
|
||||||
- 소스 파일은 300~400줄, 최대 700줄.
|
|
||||||
- 모듈별로 시험한다(`tool/test_module.sh <모듈>`). 시험이 끝난 모듈은 다시 만들지 않고 그 위에 쌓는다.
|
|
||||||
- 한 번에 한 플랫폼. 지금은 안드로이드만. 다른 플랫폼은 사용자가 지정할 때 `platform_<이름>` 모듈만 새로 붙인다.
|
|
||||||
- 단계마다 되묻지 말고 합리적으로 정해 진행하고, 결정한 내용은 결과에 적는다. 멈춰 서서 "대기"하지 않는다. (단, 되돌릴 수 없는 파괴적 작업은 먼저 묻는다.)
|
|
||||||
|
|
||||||
## 제품
|
|
||||||
Android 다크모드 인맥 앱(Flutter/Dart). 옛 "Family MAP" + 카카오톡식 대화. 목적별 명함(개인/회사/학교/그룹·여행/가족) 교환, "어디서 봤지?" 유추, 가계도, 연결 고리, 검색, 자동 연동 추천, 내 정보 숨기기, 가족 모임(촌수·통화 요약·생일/기일 알림).
|
|
||||||
|
|
||||||
## 이미 정한 기술 결정 (바꾸지 말 것)
|
|
||||||
- 모든 데이터는 기기에 암호화 저장(안드로이드 SQLCipher). 전화번호는 식별자일 뿐 키 재료가 아니다.
|
|
||||||
- 모든 기기가 서버 겸 클라이언트(P2P). 외부 DB(server/)는 암호화된 임시 우편함 + 기준 시각 제공뿐.
|
|
||||||
- (2026-10-26 사용자 결정으로 바뀜) 복구 코드·조각·보관 키 없음. 처음 시작은 이름·번호만, 키는 조용히 만든다. 폰을 잃으면 데이터는 끝. 새 폰에서 다시 시작하면 관계를 유지하는 사용자 각자가 승인(다시 연결) 또는 제명(그 번호 영구 차단). 탈퇴 후 재가입 없음. 사람 식별은 전화번호 1순위·이름 2순위.
|
|
||||||
- 대화는 영구 보관, 1MB/기기 사용률 80% 초과 시 별도 저장(조절 가능). 시각은 1/100초, 충돌 시 먼저 쓴 변경이 앞선다. 고친 명함은 "다시 보내기"에서만 보낸다. 그룹 대화 키는 돌리지 않는다.
|
|
||||||
- 가족 모임 데이터(가계도, 생일, 기일, 통화 주제)는 기기 안에만 저장하고 어디로도 보내지 않는다. 전화번호 없는 친척은 `local:<id>` 번호의 기기 안 전용 사람이다.
|
|
||||||
|
|
||||||
## 구조
|
|
||||||
- `lib/modules/<이름>/<이름>.dart` 가 모듈의 공개 입구(예외: platform_ui.dart, storage_sqlite.dart). 다른 모듈은 입구 파일만 import.
|
|
||||||
- 모듈: core, platform, platform_android, storage, keys, sharing, sync, transport, discovery, inference, layout, visibility, kinship, events, groups, work, app, ui.
|
|
||||||
- 조립은 모듈 밖: `lib/main.dart`, `lib/composition/android_bootstrap.dart`.
|
|
||||||
- 안드로이드 전용 플러그인은 platform_android 에서만 import (PLATFORM_ONLY 목록).
|
|
||||||
- 화면은 `AppScope.of(context).app`(FriendlyApp)의 서비스만 쓴다.
|
|
||||||
|
|
||||||
## 상태 (2026-10-26 기준)
|
|
||||||
- 맥 podman Flutter(stable) 컨테이너에서 `flutter analyze` 문제 없음, `flutter test` 307개 통과(widget_test.dart 포함), 서버(Python) 18개 통과, check_modules.py 통과.
|
|
||||||
- 2026-10-26 추가: groups 모듈(그룹 정의·비밀번호 잠금), 주소록 가져오기(flutter_contacts), 설정의 "그룹 · 주소록". 관계망 로드맵 1단계(그룹 계층: 학교 › 초등~대학원, 가족 › 친가·외가·처가·시가·촌수 모름, 회사 › 회사 › 팀) 끝. 2단계(채팅·사람 화면의 "관계 정하기": RelateService, 촌수 맞춰 친가·외가·처가, 촌수 모름 모음, 연결 고리) 끝. 3단계(work 모듈, WorkService: 팀 대화방, 📌 업무 맡기기·정리, 🚨 긴급 호출 즉시 알림) 끝. 4단계(CareerService: 받은 회사 명함 비교로 이직 알림, 확인하면 이전 회사 그룹에 이전 구성원으로 흐리게·새 회사 그룹으로 이동, 지우기·무시) 끝. 로드맵 1~4단계 완료.
|
|
||||||
- `flutter build apk --debug` 성공(app-debug.apk 약 190MB). 컨테이너마다 SDK 34·35, CMake 를 다시 내려받아 15분 넘게 걸린다. 빌드할 때는 시험을 같이 돌리지 않는다(podman 머신 8GB 에서 Gradle 데몬이 메모리 부족으로 꺼진 적 있음).
|
|
||||||
- 빌드 경고(아직 빌드를 막지는 않음): `nsd_android`, `flutter_contacts` 가 Kotlin Gradle Plugin 을 직접 적용한다. 앞으로 Flutter 판에서 빌드가 실패할 수 있으니, 지원 판이 나오면 nsd 버전을 올린다. geolocator_android 는 deprecated API 를 쓴다는 Note 만 나온다.
|
|
||||||
- 확인 안 된 곳: 실제 기기에서 화면 실행과 플러그인 동작(geolocator, call_log, url_launcher, flutter_local_notifications, timezone, sqflite_sqlcipher, nsd, mobile_scanner, sensors_plus, flutter_secure_storage).
|
|
||||||
- pubspec.lock 은 이 컨테이너(Flutter stable)에서 풀린 판을 기준으로 올린다(2026-10-26 flutter_contacts 추가 때부터).
|
|
||||||
- 빌드 캐시: `-v fm-gradle:/root/.gradle -v fm-pub:/root/.pub-cache` 를 붙이면 두 번째 빌드부터 빨라진다.
|
|
||||||
- 알려진 한계: 윤달 생일 미지원(기일만 지원), 음력 변환 1951~2049년, 통화 기록 읽기(READ_CALL_LOG)는 Play 스토어 제한, WebRTC·앱 잠금·복구 단어 재확인 알림·공유 이미지/PDF 저장·우편함 요청 서명은 미구현. 문자 인증은 서버에 `--sms-webhook` 연결이 필요.
|
|
||||||
|
|
||||||
## 빌드·시험 방법 (Mac Pro 2013, podman)
|
|
||||||
Flutter 는 직접 설치하지 않고 컨테이너로 쓴다 (프로젝트는 홈 폴더 안에 있어야 podman machine 이 공유한다):
|
|
||||||
```
|
|
||||||
podman machine start
|
|
||||||
podman run --rm -it -v "$PWD":/app -w /app ghcr.io/cirruslabs/flutter:stable bash
|
|
||||||
flutter create --platforms=android --org com.friendlymap --project-name friendly_map .
|
|
||||||
python3 tool/android_patch.py # 권한·수신기·desugaring·minSdk 패치 (여러 번 돌려도 같은 결과)
|
|
||||||
flutter pub get && flutter analyze && flutter test
|
|
||||||
flutter build apk --debug # build/app/outputs/flutter-apk/app-debug.apk
|
|
||||||
```
|
|
||||||
- `build/`, `.dart_tool/` 는 만들어지는 폴더라 저장소에 올리지 않는다(.gitignore). `android/` 는 Gitea 에는 올라가 있다.
|
|
||||||
- 서버 시험: `cd server && python3 -m unittest test_mailbox test_verify`.
|
|
||||||
- 모듈 경계 검사: `python3 tool/check_modules.py`.
|
|
||||||
- 저장소: http://192.168.88.15:3180/diadmin/frindlymap (Gitea, remote origin). 고친 뒤 커밋해서 Gitea 에 푸시한다.
|
|
||||||
- GitLab 에는 올리지 않는다. 단, 하루 한 번 메이저 업그레이드 때만 GitLab 에 올린다(원격은 아직 등록되지 않음).
|
|
||||||
|
|
||||||
## 작업 방식
|
|
||||||
- 고치면 해당 모듈 시험을 다시 돌리고, 마지막에 전체 시험과 check_modules.py 를 돌린다.
|
|
||||||
- 새 기능은 (1) 순수 모듈 또는 서비스 + 시험 → (2) 화면 순서로, 기존 시험이 끝난 모듈은 고치지 않는다.
|
|
||||||
- 끝낼 때 한글로: 무엇이 바뀌었나, 무엇을 돌려 확인했나, 확인 못 한 것은 무엇인가.
|
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -1,12 +1,5 @@
|
||||||
# Friendly Map
|
# Friendly Map
|
||||||
|
|
||||||
가족·친구·직장 관계를 채팅에서 시작해 이어 가는 안드로이드 앱 (Flutter).
|
**모든 가이드·문서·결정·진행 기록은 무조건 [`wiki/`](wiki/홈.md) 를 참조한다.** (Obsidian 에서 `wiki/` 폴더를 "보관함으로 열기")
|
||||||
|
|
||||||
**모든 가이드·문서·결정·진행 기록은 [`wiki/`](wiki/홈.md) (Obsidian 보관함)에서 관리한다.**
|
이 파일에는 내용을 두지 않는다. 시작은 [wiki/홈.md](wiki/홈.md).
|
||||||
|
|
||||||
- 기능 안내: [wiki/가이드/기능 안내.md](wiki/가이드/기능%20안내.md)
|
|
||||||
- 빌드·시험·저장소: [wiki/05 빌드 · 시험 · 저장소.md](wiki/05%20빌드%20·%20시험%20·%20저장소.md)
|
|
||||||
- 결정 기록: [wiki/02 결정 기록.md](wiki/02%20결정%20기록.md)
|
|
||||||
- 보관함 서버: [wiki/가이드/보관함 서버.md](wiki/가이드/보관함%20서버.md)
|
|
||||||
|
|
||||||
Obsidian 에서 `wiki/` 폴더를 "보관함으로 열기" 하면 된다.
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,3 @@
|
||||||
# 보관함 서버
|
# 보관함 서버
|
||||||
|
|
||||||
잠긴 봉투를 받는 사람이 접속할 때까지 잠시 맡아 두는 우체통입니다.
|
**무조건 wiki 를 참조한다:** [wiki/가이드/보관함 서버.md](../wiki/가이드/보관함%20서버.md)
|
||||||
봉투 속은 기기끼리만 풀 수 있어서 서버는 내용을 읽지 못합니다.
|
|
||||||
|
|
||||||
## 실행 (파이썬만 있으면 됨)
|
|
||||||
|
|
||||||
python3 mailbox_server.py --port 8080 --db mailbox.sqlite3
|
|
||||||
|
|
||||||
## 컨테이너로 실행
|
|
||||||
|
|
||||||
podman build -t fm-mailbox -f Containerfile .
|
|
||||||
podman run -d -p 8080:8080 -v fm-data:/data fm-mailbox
|
|
||||||
|
|
||||||
## 시험
|
|
||||||
|
|
||||||
python3 test_mailbox.py
|
|
||||||
|
|
||||||
## 알아둘 점
|
|
||||||
|
|
||||||
- 저장 방식은 `mailbox_store.py` 의 `MailboxStore` 약속만 지키면 바꿀 수 있습니다 (클라우드로 옮길 때 이 파일만 교체).
|
|
||||||
- 기본: 우편함은 처음 연 기기의 비밀값에 묶입니다(번호를 먼저 선점당할 수 있음).
|
|
||||||
- 문자 확인을 켜면(`--sms-webhook <주소>` + `--require-verify`) 문자로 번호를 확인한 기기만 우편함을 열 수 있어 선점이 막힙니다.
|
|
||||||
문자 발송은 웹훅 하나(`POST {"to":"+82...","text":"..."}`, 선택적으로 `--sms-webhook-token`)라 업체에 묶이지 않습니다.
|
|
||||||
`phone_verify.py` 의 `SmsSender` 를 바꾸면 다른 방식도 쓸 수 있습니다. 시험용 `--sms-log` 는 실서비스에서 쓰지 마세요.
|
|
||||||
- 인터넷에 열 때는 앞에 HTTPS 를 붙이세요 (Caddy, nginx 등).
|
|
||||||
- 봉투는 30일 안에 못 받아 가면 정리되고, 받는 사람 한 명당 5000통까지 맡습니다.
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,14 @@
|
||||||
-
|
-
|
||||||
|
|
||||||
## 일하는 규칙
|
## 일하는 규칙
|
||||||
- 모든 답변 한글. "목업" 대신 "화면 설계".
|
- 모든 답변 한글. 고친 파일은 일부 발췌가 아니라 처음부터 끝까지 완본으로 보여 준다. "목업" 대신 "화면 설계".
|
||||||
- 구조가 기능보다 먼저, 화면과 내부 분리, 모듈은 빼고 끼울 수 있게 (`tool/check_modules.py`).
|
- 사용자는 솔루션 개발자: 구조가 기능보다 먼저, 화면과 내부 분리.
|
||||||
- 파일 300~400줄, 최대 700줄. 모듈별 시험.
|
- 모듈화 필수. 모듈은 다른 모듈을 고치지 않고 빼고 끼울 수 있어야 한다. `tool/check_modules.py` 가 의존 방향 검사(새 모듈은 ALLOW 표에 등록).
|
||||||
- 한 번에 한 플랫폼(안드로이드).
|
- 소스 파일 300~400줄, 최대 700줄.
|
||||||
- 평소 Gitea 에 푸시, GitLab 은 하루 한 번 메이저 업그레이드 때만.
|
- 모듈별 시험(`tool/test_module.sh <모듈>`). 시험이 끝난 모듈은 다시 만들지 않고 그 위에 쌓는다.
|
||||||
- 모든 기록은 이 Obsidian 보관함에도 남긴다.
|
- 한 번에 한 플랫폼(지금은 안드로이드). 다른 플랫폼은 사용자가 지정할 때 `platform_<이름>` 모듈만 새로 붙인다.
|
||||||
|
- 단계마다 되묻지 말고 정해서 진행, 결정은 결과와 [[02 결정 기록]]에 적는다. 멈춰서 대기하지 않는다. (되돌릴 수 없는 파괴적 작업만 먼저 묻는다.)
|
||||||
|
- 고치면 해당 모듈 시험 → 마지막에 전체 시험과 check_modules.py.
|
||||||
|
- 새 기능은 (1) 순수 모듈 또는 서비스 + 시험 → (2) 화면. 화면을 바꾸면 캡처해서 [[06 화면 점검]]에 붙인다.
|
||||||
|
- 끝낼 때 한글로: 무엇이 바뀌었나, 무엇을 돌려 확인했나, 확인 못 한 것은 무엇인가. 그리고 [[진행 로그/2026-10-26|진행 로그]] 갱신.
|
||||||
|
- 저장소: 평소 Gitea 에 커밋·푸시, GitLab 은 하루 한 번 메이저 업그레이드 때만(원격 미등록).
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,18 @@
|
||||||
# 제품 개요
|
# 제품 개요
|
||||||
|
|
||||||
|
Android 다크모드 인맥 앱(Flutter/Dart). 옛 "Family MAP" + 카카오톡식 대화.
|
||||||
|
|
||||||
- 기본은 **채팅**. 채팅하다 관계를 알게 되면 그룹으로 넣는다.
|
- 기본은 **채팅**. 채팅하다 관계를 알게 되면 그룹으로 넣는다.
|
||||||
- 가족 → 촌수 맞춰 넣기(친가·외가·처가·시가), 모르면 "친척(촌수 모름)".
|
- 가족 → 촌수 맞춰 넣기(친가·외가·처가·시가), 모르면 "친척(촌수 모름)".
|
||||||
|
촌수에 맞게 나를 기준으로 좌우로 분리가혹 나의 위에는 위 촌사 아래는 아래 촌수 나의 옆에는 부부가 나오게 하고 아래에는 당연하게 자식이 나와야 함 결혼했으면 단영하게 와이프가 나오고 자식이 있으면 자식이 나와야하고 당연하게 생일등이 들어가고 생일 관련 알림 기능 남자를 기준으로 친가가 족보처럼 나오고 여자를 치군을 처가 족보가 조회되어 보여줘야지. 이때 자식을 선택하면 자식 기준으로 촌수 등이 일괄 변경해서 보여주고
|
||||||
- 학교 → 초등·중등·고등·대학·대학원 하위 그룹.
|
- 학교 → 초등·중등·고등·대학·대학원 하위 그룹.
|
||||||
|
나를 주면으로 초등 중등 고등 대학교 등이 뺑 둘려 보여주고 그옆에 가까운 사람이 보여주고 (많은 대화 전화등을 기준으로) 초등학교를 선택하면 나를 기준으로 초등학교 선택되고 원모양으로 가까운 사람이 부여주고 (동일하게 많은 대화 전화등의 기준으로) 선배 후배 가보여주기 1년차 2년차 도는 년도로 보여주고 선택하면 그 대상의 일괄 보여줘주던지 (또는 사람이 많을 경우 설정에서 반별로 보여주기 또는합처서 보기 등
|
||||||
|
긴급으로 동문 모임 동창 모임등을 일괄 선택후 그룹으로 보낼수 있어야함
|
||||||
- 회사 → 회사 › 프로젝트·팀, 팀 대화방, 업무 맡기기·정리, 긴급 호출.
|
- 회사 → 회사 › 프로젝트·팀, 팀 대화방, 업무 맡기기·정리, 긴급 호출.
|
||||||
|
학교와 동일 패턴이겟지 이부분은 전부 설정에게 가능해야됨
|
||||||
|
같은 프로젝트의 경우 내프로제긑 인력에대한 전체 메시지 기능 또는 하위직원 전체 메시지 상위 직원 전체 메시지 등이가능함
|
||||||
- 이직 → 상대가 회사 명함을 다시 보내면 "이직했습니다" 알림, 확인하면 이전 회사는 흐리게·새 회사로 이동·삭제.
|
- 이직 → 상대가 회사 명함을 다시 보내면 "이직했습니다" 알림, 확인하면 이전 회사는 흐리게·새 회사로 이동·삭제.
|
||||||
|
회사직했으면 이력 포함조회하면 그전에 있던 회사에 몇년부터 몇년까지 직원 또는 회시에 근무 등으로 보여주면 그사람을 선택하면 그사람의 주변 사람들(내 기록에 있는 사람 기준으로 보여줌 )
|
||||||
|
|
||||||
- 목적별 명함(개인/회사/학교/그룹·여행/가족), 공개 범위 표, "어디서 봤지?" 유추, 주소록 가져오기, 비밀번호 잠금 그룹.
|
- 목적별 명함(개인/회사/학교/그룹·여행/가족), 공개 범위 표, "어디서 봤지?" 유추, 주소록 가져오기, 비밀번호 잠금 그룹.
|
||||||
- 모든 데이터는 기기 안 암호화(SQLCipher), 기기끼리 P2P, 서버는 암호화 우편함뿐.
|
- 모든 데이터는 기기 안 암호화(SQLCipher), 기기끼리 P2P, 서버는 암호화 우편함뿐.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
# 결정 기록
|
# 결정 기록
|
||||||
|
|
||||||
|
## 바꾸지 않는 기술 결정
|
||||||
|
- 모든 데이터는 기기에 암호화 저장(안드로이드 SQLCipher). 전화번호는 식별자일 뿐 키 재료가 아니다.
|
||||||
|
- 모든 기기가 서버 겸 클라이언트(P2P). 외부 DB(server/)는 암호화된 임시 우편함 + 기준 시각 제공뿐.
|
||||||
|
- 복구 코드·조각·보관 키 없음(2026-10-26 변경). 처음 시작은 이름·번호만, 키는 조용히 만든다. 폰을 잃으면 데이터는 끝. 새 폰은 관계를 유지하는 사용자 각자의 승인(다시 연결) 또는 제명(번호 영구 차단). 탈퇴 후 재가입 없음.
|
||||||
|
- 사람 식별은 전화번호 1순위·이름 2순위.
|
||||||
|
- 대화는 영구 보관, 1MB/기기 사용률 80% 초과 시 별도 보관(조절 가능). 시각은 1/100초, 충돌 시 먼저 쓴 변경이 앞선다.
|
||||||
|
- 고친 명함은 "다시 보내기"에서만 보낸다. 그룹 대화 키는 돌리지 않는다.
|
||||||
|
- 가족 모임 데이터(가계도, 생일, 기일, 통화 주제)는 기기 안에만, 어디로도 보내지 않는다. 번호 없는 친척은 `local:<id>` 번호의 기기 안 전용 사람.
|
||||||
|
- 그룹·업무 목록·이직 알림·제명 목록도 기기 안에만.
|
||||||
|
|
||||||
|
## 날짜별 결정
|
||||||
|
|
||||||
| 날짜 | 결정 | 이유 |
|
| 날짜 | 결정 | 이유 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 2026-10-26 | 그룹·비밀번호 잠금(PBKDF2, 5번 틀리면 30초부터 두 배), 연 뒤 5분/앱 내리면 다시 잠금 | 사용자 요청: 비번 눌러야 열리는 그룹 |
|
| 2026-10-26 | 그룹·비밀번호 잠금(PBKDF2, 5번 틀리면 30초부터 두 배), 연 뒤 5분/앱 내리면 다시 잠금 | 사용자 요청: 비번 눌러야 열리는 그룹 |
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,10 @@
|
||||||
- [x] UX 전면 개편 1차 (캡처 [[06 화면 점검]])
|
- [x] UX 전면 개편 1차 (캡처 [[06 화면 점검]])
|
||||||
- [ ] APK 전달 → 사용자 설치·시험 → 화면 보며 피드백 반영
|
- [ ] APK 전달 → 사용자 설치·시험 → 화면 보며 피드백 반영
|
||||||
- [ ] 실제 기기 점검(주소록, 알림, 위치, QR, 같은 네트워크)
|
- [ ] 실제 기기 점검(주소록, 알림, 위치, QR, 같은 네트워크)
|
||||||
|
|
||||||
|
## 현재 상태 (2026-10-26)
|
||||||
|
- podman Flutter(stable)에서 `flutter analyze` 0건, `flutter test` 313개 통과, 서버(Python) 18개 통과, check_modules.py 통과.
|
||||||
|
- `flutter build apk --debug` 성공(약 190MB, 개편 화면).
|
||||||
|
- 확인 안 된 곳: 실제 기기의 화면 실행과 플러그인(geolocator, call_log, url_launcher, flutter_local_notifications, timezone, sqflite_sqlcipher, nsd, mobile_scanner, sensors_plus, flutter_secure_storage, flutter_contacts).
|
||||||
|
- 빌드 경고(막지는 않음): `nsd_android`, `flutter_contacts` 가 Kotlin Gradle Plugin 을 직접 적용 → 지원 판이 나오면 올린다. geolocator_android 는 deprecated API Note.
|
||||||
|
- 한계: [[가이드/알려진 한계]]
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,39 @@
|
||||||
# 구조와 모듈
|
# 구조와 모듈
|
||||||
|
|
||||||
`lib/modules/<이름>/<이름>.dart` 가 공개 입구. 조립은 `lib/main.dart`, `lib/composition/android_bootstrap.dart`.
|
- `lib/modules/<이름>/<이름>.dart` 가 모듈의 공개 입구(예외: platform_ui.dart, storage_sqlite.dart). 다른 모듈은 입구 파일만 import.
|
||||||
|
- 조립은 모듈 밖 `lib/main.dart`, `lib/composition/android_bootstrap.dart` 에서만. 암호·저장소·연결 방식을 바꾸려면 bootstrap 의 해당 줄만 바꾼다.
|
||||||
|
- 안드로이드 전용 플러그인은 platform_android 에서만 import (check_modules.py 의 PLATFORM_ONLY).
|
||||||
|
- 화면은 `AppScope.of(context).app`(FriendlyApp)의 서비스만 쓴다.
|
||||||
|
|
||||||
| 모듈 | 하는 일 |
|
## 모듈과 의존 방향 (`tool/check_modules.py` 가 검사)
|
||||||
|---|---|
|
|
||||||
| core | 모듈끼리의 약속(키·전송·암호·봉투·명함 항목) |
|
| 모듈 | 하는 일 | 쓸 수 있는 모듈 |
|
||||||
| platform / platform_android | 기기 기능 약속 / 안드로이드 구현(플러그인은 여기서만) |
|
| ---------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||||
| storage | 저장소(메모리·SQLCipher) |
|
| core | 모듈끼리 주고받는 약속(키·전송·암호·봉투·명함 항목 이름) | 없음 |
|
||||||
| keys | 키 파생·암호 (복구 코드 화면은 폐지, 모듈은 남김) |
|
| platform | 기기마다 다른 기능의 약속(보안 저장소, 저장 공간, 센서, QR, 발견, 위치, 통화 기록·전화, 알림, 주소록) | 없음 |
|
||||||
| sharing, sync, transport, discovery | 공유·동기화·전송·발견 |
|
| platform_android | platform 의 안드로이드 구현(플러그인은 여기서만) | core, platform, storage |
|
||||||
| inference, visibility, layout | 유추·공개 범위·배치 |
|
| storage | 저장소 약속 + 메모리/SQLite 구현 | platform |
|
||||||
| kinship, events | 촌수·쪽(친가/외가), 생일·기일 |
|
| keys | 키 파생, 둘만 아는 키 암호 (복구 코드·조각 기능은 화면에서 폐지, 코드는 남김) | core, platform |
|
||||||
| groups | 그룹 나무·잠금·이전 구성원 (순수) |
|
| sharing | 명함·대화방이 누구와 공유되는지 | storage |
|
||||||
| work | 📌 업무·🚨 긴급 표시, 업무 목록 (순수) |
|
| sync | 봉투 만들기·반영, 충돌 규칙, 시계 맞추기, 재전송 | core, storage |
|
||||||
| app | 서비스 묶음 FriendlyApp (groups, contacts, relate, work, career, blocks …) |
|
| transport | 메모리·직접 연결·외부 보관함·늦게 만드는 연결 | core |
|
||||||
| ui | 화면 |
|
| discovery | QR 내용, 흔들기 감지, 같은 네트워크 주소록 | platform |
|
||||||
|
| inference | "어디서 봤지?" 유추와 추천 | core |
|
||||||
|
| visibility | 공개 범위 표(정보 × 관계)와 명함 거르기. 순수 계산 | core |
|
||||||
|
| layout | 홈·명함 틀(템플릿) 순서/보이기/편집 | core |
|
||||||
|
| kinship | 가계도로 촌수·호칭 계산(순수) | 없음 |
|
||||||
|
| events | 생일·기일 일정, 알림 날짜 계획, 통화 기록 요약(순수) | 없음 |
|
||||||
|
| groups | 그룹 정의(이름·종류·순서·구성원·하위 그룹), 비밀번호 잠금, 여러 번 틀리면 기다리기(순수) | core |
|
||||||
|
| work | 대화 속 업무 표시(📌 업무·🚨 긴급 호출) 읽고 쓰기, 맡은 업무 목록(순수) | 없음 |
|
||||||
|
| app | 서비스 묶음(`FriendlyApp`). 화면이 쓰는 유일한 내부 입구 | 위 모듈들(키·전송 구현 제외) |
|
||||||
|
| ui | 화면. `app` 의 서비스만 쓴다 | app, core, platform, storage, discovery, inference, layout, visibility, kinship, events, groups, work |
|
||||||
|
|
||||||
|
모듈 폴더 밖의 `lib/main.dart`, `lib/composition/` 이 부품을 고르는 유일한 곳이다.
|
||||||
|
암호 구현·저장소·연결 방식을 바꾸려면 `lib/composition/android_bootstrap.dart` 의 해당 줄만 바꾼다.
|
||||||
|
|
||||||
|
## 화면(ui) 주요 파일
|
||||||
|
- 탭: chat_list · relations · tasks · card_wallet · me (home_shell.dart)
|
||||||
|
- 처음 시작: onboarding_screen.dart, startup_gate.dart
|
||||||
|
- 대화방: chat_room_screen.dart + widgets/message_bubble.dart
|
||||||
|
- 그룹: group_detail_screen.dart + widgets/group_menu.dart, pin_dialog.dart
|
||||||
|
- 요청: requests_screen.dart (다시 연결 승인·제명)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
# 빌드 · 시험 · 저장소
|
# 빌드 · 시험 · 저장소
|
||||||
|
|
||||||
|
Mac Pro 2013. Flutter 는 직접 설치하지 않고 podman 컨테이너로 쓴다(프로젝트는 홈 폴더 안에 있어야 podman machine 이 공유). 먼저 `podman machine start`.
|
||||||
|
|
||||||
```
|
```
|
||||||
podman run --rm -v "$PWD":/app -v fm-gradle:/root/.gradle -v fm-pub:/root/.pub-cache -w /app ghcr.io/cirruslabs/flutter:stable bash
|
podman run --rm -v "$PWD":/app -v fm-gradle:/root/.gradle -v fm-pub:/root/.pub-cache -w /app ghcr.io/cirruslabs/flutter:stable bash
|
||||||
python3 tool/android_patch.py
|
python3 tool/android_patch.py
|
||||||
|
|
@ -10,3 +12,26 @@ flutter test test_ux/shots_test.dart --update-goldens # 화면 캡처 test_ux/
|
||||||
- 빌드할 때 시험을 같이 돌리지 않는다(podman 머신 8GB, Gradle 데몬이 꺼진 적 있음).
|
- 빌드할 때 시험을 같이 돌리지 않는다(podman 머신 8GB, Gradle 데몬이 꺼진 적 있음).
|
||||||
- 푸시: `git push origin main` 단독 명령(샌드박스 제외 설정). Gitea 토큰은 키체인.
|
- 푸시: `git push origin main` 단독 명령(샌드박스 제외 설정). Gitea 토큰은 키체인.
|
||||||
- VS Code 터미널의 git 은 아이디를 창 맨 위 입력칸으로 묻는다.
|
- VS Code 터미널의 git 은 아이디를 창 맨 위 입력칸으로 묻는다.
|
||||||
|
|
||||||
|
## 처음 준비 (android/ 가 없을 때)
|
||||||
|
```
|
||||||
|
flutter create --platforms=android --org com.friendlymap --project-name friendly_map .
|
||||||
|
python3 tool/android_patch.py # 권한·수신기·desugaring·minSdk 23 (여러 번 돌려도 같음)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 시험
|
||||||
|
```
|
||||||
|
tool/test_module.sh # 모듈 목록
|
||||||
|
tool/test_module.sh groups # 모듈 하나만
|
||||||
|
flutter test # 전체
|
||||||
|
python3 tool/check_modules.py # 줄 수·import 방향
|
||||||
|
cd server && python3 -m unittest test_mailbox test_verify # 보관함 서버
|
||||||
|
```
|
||||||
|
- 보관함 서버(선택): [[보관함 서버]]. 서버 주소를 설정 `mailbox_url` 에 넣으면 같은 네트워크 밖에서도 주고받는다.
|
||||||
|
|
||||||
|
## 저장소 규칙
|
||||||
|
- `build/`, `.dart_tool/`, `.claude/settings.local.json`, `wiki/.obsidian/workspace*.json` 은 올리지 않는다(.gitignore). `android/` 는 Gitea 에 올라가 있다.
|
||||||
|
- pubspec.lock 은 이 컨테이너(Flutter stable)에서 풀린 판 기준.
|
||||||
|
- 빌드 캐시 `-v fm-gradle:/root/.gradle -v fm-pub:/root/.pub-cache`. 컨테이너마다 SDK 34·35·CMake 를 다시 받아 15분 넘게 걸린다.
|
||||||
|
- 저장소: http://192.168.88.15:3180/diadmin/frindlymap (Gitea, remote origin).
|
||||||
|
- 화면 캡처: `flutter test test_ux/shots_test.dart --update-goldens` → `test_ux/shots/*.png` (한글 글꼴 `build/ux/kr.ttf` 필요: 맥의 `/Library/Fonts/Arial Unicode.ttf` 복사).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 가족 · 가계도 · 생일
|
||||||
|
|
||||||
|
> 화면: 관계 탭 › 가계도, 프로필, 나 › 알림 · 저장 공간
|
||||||
|
|
||||||
|
- 관계 탭 › 가계도에서 가족을 한 분씩 추가한다. 번호가 없는 친척도 넣을 수 있다(이 기기 안에서만 쓰이고 친구 요청은 가지 않는다).
|
||||||
|
- "나의 부모/자녀/배우자"를 고르거나 부모·자녀·배우자를 이어 가계도를 만들면, 나와 내 자녀 각각의 눈으로 본 호칭과 촌수가 나온다(예: 내게는 친사촌 형 4촌, 내 아들에게는 당숙 5촌).
|
||||||
|
- 사람을 누르면 관계, 통화 횟수·시간·마지막 통화·통화 잘 되는 시간대, 주제 메모, 다가오는 생일/기일을 요약하고 전화·문자·대화 버튼이 있다.
|
||||||
|
- 돌아가신 날을 적으면 맨 위에 "故" 띠가 뜨고(설정에서 끌 수 있다) 생일 대신 기일을 알린다.
|
||||||
|
- 나 › 알림 · 저장 공간 › "생일·기일 알림"에 `7, 2, 1, 0` 처럼 적으면 그 날짜 전 오전 9시에 알림이 울린다(기본 1일 전·당일).
|
||||||
|
- 음력: 생일·기일마다 "음력"을 켤 수 있다(1951~2049년 변환표 내장, 기일은 윤달도 지원). 제사 알림이 음력 날짜로 잡힌다.
|
||||||
|
- 한계: 통화 "내용"은 기기에 남지 않으므로 주제는 직접 적는다. 통화 기록 읽기(READ_CALL_LOG)는 Play 스토어 심사 제한이 있다.
|
||||||
|
- 안드로이드: `call_log`, `url_launcher`, `flutter_local_notifications`, `timezone` 플러그인과 권한·수신기·desugaring 설정이 필요하다(`tool/android_patch.py` 가 맞춘다).
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# 공개 범위
|
||||||
|
|
||||||
|
> 화면: 나 › 공개 범위
|
||||||
|
|
||||||
|
- 나 › 공개 범위(학교 · 회사·직책 · 생일 · 연락처 × 가족 · 회사 · 동문 · 그룹)에서 칸을 눌러 공개/일부/숨김을 정한다.
|
||||||
|
- 명함을 보내거나 "다시 보내기"를 할 때 받는 사람의 관계(연결된 관계 + 보내는 명함 종류)에 맞게 걸러서 보낸다. 관계를 모르는 사람에게는 내가 고른 그대로 간다.
|
||||||
|
- 표를 고쳐도 이미 보낸 명함은 "다시 보내기" 전까지 그대로다.
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# 관계 정하기
|
||||||
|
|
||||||
|
> 화면: 대화방 ⋮ › 관계 정하기, 프로필 › 관계
|
||||||
|
|
||||||
|
- 대화방 ⋮ 메뉴 "관계 정하기", 또는 프로필의 "관계" 버튼에서 고른다: 가족·친척 / 학교 동문 / 회사·업무·팀 / 그룹·모임·여행.
|
||||||
|
- 가족: 나(또는 가계도에 있는 다른 가족)의 부모·자녀·배우자·형제자매를 고르면 가계도에 잇고 촌수·호칭을 계산한다. 아버지 쪽은 친가, 어머니 쪽은 외가, 배우자 쪽은 처가·시가 그룹에 들어간다. 모르면 "친척(촌수 모름)"에 모아 두고, 나중에 이으면 모음에서 빠진다.
|
||||||
|
- 형제자매를 고를 때 기준 사람의 부모가 가계도에 없으면 "부모(이름 모름)"를 이 기기 안에서만 쓰는 사람으로 만들어 잇는다.
|
||||||
|
- 학교·회사·모임: 그 종류의 그룹 나무에서 골라 넣거나, + 로 그 아래 새 그룹(예: 학교 › 고등학교 › 가람고 27회, 회사 › 한빛전자 › 결제팀)을 만들어 바로 넣는다.
|
||||||
|
- 넣으면 연결 고리(가족·동문·직장 동료·같은 모임)가 하나 남아, 공개 범위 표가 그 관계로 적용된다. 같은 종류의 그룹에서 모두 빠지면 연결 고리도 지운다.
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 그룹 · 잠금
|
||||||
|
|
||||||
|
> 화면: 관계 탭 › 그룹, 그룹 안, 나 › 잠긴 그룹 다시 잠그기
|
||||||
|
|
||||||
|
- 관계 탭. 처음 그룹은 가족·학교·회사·그룹·여행이고, 더하기·이름과 종류 고치기·순서 바꾸기·지우기가 된다(지워도 사람은 인맥에 남는다).
|
||||||
|
- 그룹 아래에 하위 그룹을 4단까지 만든다. 처음에는 학교 › 초등학교·중학교·고등학교·대학교·대학원, 가족 › 친가·외가·처가·시가·친척(촌수 모름)이 있고, 회사 › 회사 이름 › 프로젝트·팀은 직접 만든다. 같은 부모 아래에서만 이름이 겹치지 않으면 된다. 지우면 하위 그룹도 함께 지워진다(사람은 남는다).
|
||||||
|
- 그룹마다 비밀번호(4~16자)를 걸 수 있다. 잠긴 그룹은 열 때 비밀번호를 묻고, 닫혀 있으면 하위 그룹과 인맥 목록에서도 그 사람들을 숨긴다. 연 뒤 정한 시간(기본 5분, 1~60분)이 지나거나 앱을 내리면 다시 잠근다.
|
||||||
|
- 5번 틀리면 30초, 그 뒤로 틀릴 때마다 두 배(최대 1시간) 기다려야 한다. 비밀번호는 PBKDF2-SHA256 값만 남긴다. 잊으면 그 그룹은 지우고 다시 만들어야 한다.
|
||||||
|
- 그룹은 이 기기에만 저장한다. 안드로이드: `flutter_contacts` 플러그인과 READ_CONTACTS 권한이 필요하다(`tool/android_patch.py` 가 넣는다).
|
||||||
|
|
||||||
|
관련: [[관계 정하기]] · [[주소록 가져오기]]
|
||||||
|
|
@ -1,136 +1,30 @@
|
||||||
# 기능 안내
|
# 기능 안내
|
||||||
|
|
||||||
> 이 문서가 기준이다. 저장소의 README.md 는 이 문서를 가리키기만 한다.
|
> 기준 문서. 저장소 README.md 는 이 문서를 가리키기만 한다.
|
||||||
|
|
||||||
|
가족·친구·직장 관계를 채팅에서 시작해 이어 가는 안드로이드 앱. 모든 정보는 폰 안에 암호화되어 있고, 기기끼리 직접 주고받는다(외부 서버는 암호화된 임시 우편함뿐).
|
||||||
|
|
||||||
가족·친구·직장 관계를 한 앱에서. 새 친구가 "어디서 봤지?" 싶을 때 목적별 명함을 주고받아 관계를 유추한다.
|
## 화면 지도 (아래 메뉴)
|
||||||
안드로이드 우선, Flutter. 모든 기기가 서버 겸 클라이언트(P2P)이고, 외부 DB는 암호화된 임시 우편함일 뿐이다.
|
| 탭 | 들어 있는 것 |
|
||||||
|
|---|---|
|
||||||
|
| 채팅 (첫 화면) | 다시 연결 요청 띠, 이직 알림, 대화방 목록, 새 대화. 대화방: 날짜 구분, 업무 카드, 긴급 띠, + (업무 맡기기·긴급 호출), ⋮ (프로필·관계 정하기·이 방 업무·보관) |
|
||||||
|
| 관계 | 사람 찾기, 받은 요청, 모든 사람 · 가계도 · 주소록 · 흔들기, 그룹 나무, 이런 사이 아닐까요 |
|
||||||
|
| 업무 | 내 업무 / 전체, 끝난 것, 마감 D-day |
|
||||||
|
| 명함 | 목적별 명함, 공유, 다시 보내기 |
|
||||||
|
| 나 | 프로필, 번호 확인, 공개 범위, 친구 요청 받기, 모르는 사람 요청 막기, 위치 공유, 잠긴 그룹 다시 잠그기, 제명한 번호, 알림 · 저장 공간 |
|
||||||
|
|
||||||
## 원칙 (1순위)
|
화면 모습: [[06 화면 점검]]
|
||||||
|
|
||||||
1. 구조가 기능보다 먼저다. 화면과 내부를 분리한다.
|
## 기능별 안내
|
||||||
2. 모듈은 언제든 빼고 끼울 수 있다. 기능을 더하거나 빼도 다른 모듈을 고치지 않는다.
|
- [[처음 시작 · 다시 연결]]
|
||||||
3. 파일은 300~400줄, 최대 700줄.
|
- [[그룹 · 잠금]]
|
||||||
4. 모듈별로 시험한다. 시험이 끝난 모듈은 다시 만들지 않고 그 위에 쌓는다.
|
- [[주소록 가져오기]]
|
||||||
5. 한 번에 한 플랫폼. 지금은 안드로이드. 다른 플랫폼은 `platform_<이름>` 모듈만 새로 붙인다.
|
- [[관계 정하기]]
|
||||||
|
- [[가족 · 가계도 · 생일]]
|
||||||
## 모듈과 의존 방향 (`tool/check_modules.py` 가 검사)
|
- [[업무 · 팀 · 긴급 호출]]
|
||||||
|
- [[이직 알림]]
|
||||||
| 모듈 | 하는 일 | 쓸 수 있는 모듈 |
|
- [[공개 범위]]
|
||||||
|---|---|---|
|
- [[위치 공유]]
|
||||||
| core | 모듈끼리 주고받는 약속(키·전송·암호·봉투·명함 항목 이름) | 없음 |
|
- [[번호 확인]]
|
||||||
| platform | 기기마다 다른 기능의 약속(보안 저장소, 저장 공간, 센서, QR, 발견, 위치, 통화 기록·전화, 알림, 주소록) | 없음 |
|
- [[알려진 한계]]
|
||||||
| platform_android | platform 의 안드로이드 구현(플러그인은 여기서만) | core, platform, storage |
|
- [[보관함 서버]]
|
||||||
| storage | 저장소 약속 + 메모리/SQLite 구현 | platform |
|
|
||||||
| keys | 키 파생, 둘만 아는 키 암호 (복구 코드·조각 기능은 화면에서 폐지, 코드는 남김) | core, platform |
|
|
||||||
| sharing | 명함·대화방이 누구와 공유되는지 | storage |
|
|
||||||
| sync | 봉투 만들기·반영, 충돌 규칙, 시계 맞추기, 재전송 | core, storage |
|
|
||||||
| transport | 메모리·직접 연결·외부 보관함·늦게 만드는 연결 | core |
|
|
||||||
| discovery | QR 내용, 흔들기 감지, 같은 네트워크 주소록 | platform |
|
|
||||||
| inference | "어디서 봤지?" 유추와 추천 | core |
|
|
||||||
| visibility | 공개 범위 표(정보 × 관계)와 명함 거르기. 순수 계산 | core |
|
|
||||||
| layout | 홈·명함 틀(템플릿) 순서/보이기/편집 | core |
|
|
||||||
| kinship | 가계도로 촌수·호칭 계산(순수) | 없음 |
|
|
||||||
| events | 생일·기일 일정, 알림 날짜 계획, 통화 기록 요약(순수) | 없음 |
|
|
||||||
| groups | 그룹 정의(이름·종류·순서·구성원·하위 그룹), 비밀번호 잠금, 여러 번 틀리면 기다리기(순수) | core |
|
|
||||||
| work | 대화 속 업무 표시(📌 업무·🚨 긴급 호출) 읽고 쓰기, 맡은 업무 목록(순수) | 없음 |
|
|
||||||
| app | 서비스 묶음(`FriendlyApp`). 화면이 쓰는 유일한 내부 입구 | 위 모듈들(키·전송 구현 제외) |
|
|
||||||
| ui | 화면. `app` 의 서비스만 쓴다 | app, core, platform, storage, discovery, inference, layout, visibility, kinship, events, groups, work |
|
|
||||||
|
|
||||||
모듈 폴더 밖의 `lib/main.dart`, `lib/composition/` 이 부품을 고르는 유일한 곳이다.
|
|
||||||
암호 구현·저장소·연결 방식을 바꾸려면 `lib/composition/android_bootstrap.dart` 의 해당 줄만 바꾼다.
|
|
||||||
|
|
||||||
## 시험
|
|
||||||
|
|
||||||
```
|
|
||||||
tool/test_module.sh # 모듈 목록
|
|
||||||
tool/test_module.sh sync # 모듈 하나만
|
|
||||||
flutter test # 전체
|
|
||||||
python3 tool/check_modules.py # 줄 수·import 방향
|
|
||||||
```
|
|
||||||
|
|
||||||
## 시작
|
|
||||||
|
|
||||||
```
|
|
||||||
flutter create --platforms=android --org com.friendlymap --project-name friendly_map .
|
|
||||||
python3 tool/android_patch.py # 권한, 평문 통신, minSdk 23
|
|
||||||
flutter pub get
|
|
||||||
flutter analyze
|
|
||||||
flutter test
|
|
||||||
flutter run
|
|
||||||
```
|
|
||||||
|
|
||||||
보관함 서버(선택): `server/README.md`. 서버 주소를 설정 `mailbox_url` 에 넣으면 같은 네트워크 밖에서도 주고받는다.
|
|
||||||
|
|
||||||
## 알려진 한계
|
|
||||||
|
|
||||||
- 보관함의 우편함 토큰은 번호별 처음 쓴 기기에 묶인다(번호 선점 가능). 나중에 문자 인증으로 막는다.
|
|
||||||
- 그룹 대화방은 사람이 나가도 키를 바꾸지 않는다.
|
|
||||||
- 위치는 지도 타일 없이 레이더(내 위치 중심, 방위·거리)로 보여 준다. 지도 SDK는 붙이지 않았다.
|
|
||||||
- 문자 인증은 만들어 뒀지만 문자 발송 주소(`--sms-webhook`)를 정해 서버에 넣어야 켜진다. 켜기 전에는 처음 쓴 기기에 묶이는 방식(번호 선점 가능)이다.
|
|
||||||
|
|
||||||
## 가족 모임 (촌수 · 통화 요약 · 생일/기일 알림)
|
|
||||||
- 관계 탭 › 가계도에서 가족을 한 분씩 추가한다. 번호가 없는 친척도 넣을 수 있다(이 기기 안에서만 쓰이고 친구 요청은 가지 않는다).
|
|
||||||
- "나의 부모/자녀/배우자"를 고르거나 부모·자녀·배우자를 이어 가계도를 만들면, 나와 내 자녀 각각의 눈으로 본 호칭과 촌수가 나온다(예: 내게는 친사촌 형 4촌, 내 아들에게는 당숙 5촌).
|
|
||||||
- 사람을 누르면 관계, 통화 횟수·시간·마지막 통화·통화 잘 되는 시간대, 주제 메모, 다가오는 생일/기일을 요약하고 전화·문자·대화 버튼이 있다.
|
|
||||||
- 돌아가신 날을 적으면 맨 위에 "故" 띠가 뜨고(설정에서 끌 수 있다) 생일 대신 기일을 알린다.
|
|
||||||
- 나 › 알림 · 저장 공간 › "생일·기일 알림"에 `7, 2, 1, 0` 처럼 적으면 그 날짜 전 오전 9시에 알림이 울린다(기본 1일 전·당일).
|
|
||||||
- 음력: 생일·기일마다 "음력"을 켤 수 있다(1951~2049년 변환표 내장, 기일은 윤달도 지원). 제사 알림이 음력 날짜로 잡힌다.
|
|
||||||
- 한계: 통화 "내용"은 기기에 남지 않으므로 주제는 직접 적는다. 통화 기록 읽기(READ_CALL_LOG)는 Play 스토어 심사 제한이 있다.
|
|
||||||
- 안드로이드: `call_log`, `url_launcher`, `flutter_local_notifications`, `timezone` 플러그인과 권한·수신기·desugaring 설정이 필요하다(`tool/android_patch.py` 가 맞춘다).
|
|
||||||
|
|
||||||
## 처음 시작 · 다시 연결 (복구 없음)
|
|
||||||
|
|
||||||
- 처음 시작은 이름과 휴대폰 번호만 넣는다. 키는 이 폰 안에서 조용히 만들고, 복구 코드·조각·보관 키는 없다.
|
|
||||||
- 폰을 바꾸거나 잃으면 그 폰의 기록은 되살리지 않는다. 탈퇴 후 재가입도 없다.
|
|
||||||
- 새 폰에서 같은 번호로 시작하면, 관계를 유지하고 있는 사용자 각자에게 "새 기기로 다시 연결" 요청이 간다(채팅 맨 위 띠, 관계 탭). **승인**하면 이전 대화·그룹·명함 기록 그대로 새 키로 이어지고, **제명**하면 그 번호는 영구히 막힌다(나 › 제명한 번호에서 풀 수 있음).
|
|
||||||
|
|
||||||
## 공개 범위
|
|
||||||
|
|
||||||
- 나 › 공개 범위(학교 · 회사·직책 · 생일 · 연락처 × 가족 · 회사 · 동문 · 그룹)에서 칸을 눌러 공개/일부/숨김을 정한다.
|
|
||||||
- 명함을 보내거나 "다시 보내기"를 할 때 받는 사람의 관계(연결된 관계 + 보내는 명함 종류)에 맞게 걸러서 보낸다. 관계를 모르는 사람에게는 내가 고른 그대로 간다.
|
|
||||||
- 표를 고쳐도 이미 보낸 명함은 "다시 보내기" 전까지 그대로다.
|
|
||||||
|
|
||||||
## 위치 공유
|
|
||||||
|
|
||||||
- 나 › 위치 공유에서 켠다. 기본은 꺼짐.
|
|
||||||
- 켜면 1분마다, 공개 범위 표의 "실시간 위치"가 공개·일부인 관계의 친구에게만 보낸다. 관계를 모르는 친구에게는 보내지 않는다. "일부"는 약 1km 단위로 뭉개서 보낸다.
|
|
||||||
- 위치는 둘만 아는 키로 잠가서 보내고, 받은 쪽에는 마지막 위치 하나만 남는다.
|
|
||||||
- 안드로이드: `geolocator` 플러그인과 위치 권한이 필요하다 (`tool/android_patch.py` 가 권한을 넣는다).
|
|
||||||
|
|
||||||
## 번호 확인 (문자 인증)
|
|
||||||
|
|
||||||
- 나 › "내 번호 확인하기": 문자로 온 6자리를 넣으면 이 기기의 토큰이 내 번호에 묶인다. 폰을 바꾼 진짜 주인은 다시 확인하면 새 기기로 넘어오고, 이전 기기는 막힌다.
|
|
||||||
- 서버: `python3 mailbox_server.py --sms-webhook <주소> --require-verify`. 문자 발송은 `SmsSender` 하나(웹훅은 `{to,text}` 를 POST)라 업체를 바꿔도 서버 나머지는 그대로다.
|
|
||||||
- `--require-verify` 를 켜면 확인을 마친 기기만 우편함을 열 수 있다 (번호 선점 차단). 켜지 않으면 예전처럼 처음 쓴 기기에 묶인다.
|
|
||||||
- 5분 만료, 번호당 1시간 3번, 5번 오답이면 새로 요청. `--sms-log` 는 시험용(문자를 보내지 않고 기록)이라 실서비스에서 쓰면 안 된다.
|
|
||||||
- 서버 시험: `cd server && python3 -m unittest test_mailbox test_verify`
|
|
||||||
|
|
||||||
## 그룹 · 주소록 가져오기
|
|
||||||
- 관계 탭. 처음 그룹은 가족·학교·회사·그룹·여행이고, 더하기·이름과 종류 고치기·순서 바꾸기·지우기가 된다(지워도 사람은 인맥에 남는다).
|
|
||||||
- 그룹 아래에 하위 그룹을 4단까지 만든다. 처음에는 학교 › 초등학교·중학교·고등학교·대학교·대학원, 가족 › 친가·외가·처가·시가·친척(촌수 모름)이 있고, 회사 › 회사 이름 › 프로젝트·팀은 직접 만든다. 같은 부모 아래에서만 이름이 겹치지 않으면 된다. 지우면 하위 그룹도 함께 지워진다(사람은 남는다).
|
|
||||||
- 그룹마다 비밀번호(4~16자)를 걸 수 있다. 잠긴 그룹은 열 때 비밀번호를 묻고, 닫혀 있으면 하위 그룹과 인맥 목록에서도 그 사람들을 숨긴다. 연 뒤 정한 시간(기본 5분, 1~60분)이 지나거나 앱을 내리면 다시 잠근다.
|
|
||||||
- 5번 틀리면 30초, 그 뒤로 틀릴 때마다 두 배(최대 1시간) 기다려야 한다. 비밀번호는 PBKDF2-SHA256 값만 남긴다. 잊으면 그 그룹은 지우고 다시 만들어야 한다.
|
|
||||||
- 관계 탭 › 주소록 불러오기: 이름·초성(ㄱㅁㅅ)·번호로 찾고, 휴대폰 주소록 그룹(라벨)으로 거르고, 이미 있는 사람은 빼고 볼 수 있다. 한 명씩 또는 보이는 사람을 모두 고른 뒤, 넣을 그룹(여러 개)을 골라 가져온다.
|
|
||||||
- 번호가 여럿이면 휴대폰(010) 번호를 먼저 쓴다. 가져온 사람은 이 기기 인맥에만 들어가고, "친구 요청도 보내기"를 켰을 때만 상대에게 요청이 간다. 주소록은 읽기만 한다.
|
|
||||||
- 그룹은 이 기기에만 저장한다. 안드로이드: `flutter_contacts` 플러그인과 READ_CONTACTS 권한이 필요하다(`tool/android_patch.py` 가 넣는다).
|
|
||||||
|
|
||||||
## 관계 정하기 (채팅에서 시작하는 관계망)
|
|
||||||
- 대화방 ⋮ 메뉴 "관계 정하기", 또는 프로필의 "관계" 버튼에서 고른다: 가족·친척 / 학교 동문 / 회사·업무·팀 / 그룹·모임·여행.
|
|
||||||
- 가족: 나(또는 가계도에 있는 다른 가족)의 부모·자녀·배우자·형제자매를 고르면 가계도에 잇고 촌수·호칭을 계산한다. 아버지 쪽은 친가, 어머니 쪽은 외가, 배우자 쪽은 처가·시가 그룹에 들어간다. 모르면 "친척(촌수 모름)"에 모아 두고, 나중에 이으면 모음에서 빠진다.
|
|
||||||
- 형제자매를 고를 때 기준 사람의 부모가 가계도에 없으면 "부모(이름 모름)"를 이 기기 안에서만 쓰는 사람으로 만들어 잇는다.
|
|
||||||
- 학교·회사·모임: 그 종류의 그룹 나무에서 골라 넣거나, + 로 그 아래 새 그룹(예: 학교 › 고등학교 › 가람고 27회, 회사 › 한빛전자 › 결제팀)을 만들어 바로 넣는다.
|
|
||||||
- 넣으면 연결 고리(가족·동문·직장 동료·같은 모임)가 하나 남아, 공개 범위 표가 그 관계로 적용된다. 같은 종류의 그룹에서 모두 빠지면 연결 고리도 지운다.
|
|
||||||
|
|
||||||
## 업무 · 팀 · 긴급 호출
|
|
||||||
- 회사 종류 그룹(예: 회사 › 한빛전자 › 결제팀) 안의 "팀 대화방"을 누르면 구성원과 회사 맥락의 대화방이 생긴다. 회사 명함을 아직 공유하지 않은 사람이 있으면 그 이름을 알려 주고 만들지 않는다.
|
|
||||||
- 대화방 입력칸 왼쪽 + 버튼: "업무 맡기기"(할 일·담당·마감), "긴급 호출". 오른쪽 위 "업무 N"과 ⋮ 메뉴 "이 방 업무". 아래 메뉴 업무 탭은 모든 방의 업무(처음은 내 업무).
|
|
||||||
- 업무와 긴급 호출은 보통 대화로 간다(`📌 업무 [김민수] 결제 API 정리 (마감 10/30)`, `🚨 긴급 호출: …`). 받는 쪽 앱이 읽어서 업무 목록에 넣고(담당 이름이 내 이름이면 "내 업무"), 긴급 호출은 바로 알림을 띄운다. 새 봉투 형식은 없다.
|
|
||||||
- 지난 대화를 길게 누르면 "업무로 정리"(내 목록에만). 업무 목록은 이 기기에만 저장하고, 끝냄 표시·밀어서 지우기가 된다.
|
|
||||||
- 안드로이드: 긴급 호출 알림은 `flutter_local_notifications` 의 높은 중요도 채널(urgent)을 쓴다.
|
|
||||||
|
|
||||||
## 이직 알림
|
|
||||||
- 상대가 회사 명함을 고쳐 "다시 보내기" 하면, 이 기기가 알던 회사와 비교해 바뀌었으면 알림이 생긴다(채팅 맨 위, 프로필). 예: "김민수님이 한빛전자에서 누리소프트로 옮겼어요", "지금은 다니지 않음"이면 "…를 떠났어요".
|
|
||||||
- "확인 · 옮기기": 이전 회사 이름이 경로에 든 회사 그룹(예: 회사 › 한빛전자 › 결제팀)에서는 이전 구성원으로 흐리게 남기고, "회사 › 새 회사" 그룹(없으면 만든다)에 넣는다. 연결 고리는 '직장 동료'가 새 회사로 바뀌고 '전 직장 동료'(이전 경로)가 남는다.
|
|
||||||
- "이전 회사에서 지우기": 흐리게 남기지 않고 뺀다. "무시": 알림만 지운다. 이전 구성원은 그룹 안에서 ✕ 로 지울 수 있다.
|
|
||||||
- 처음 받는 회사 명함, 앱을 새로 연 뒤의 첫 비교는 알림을 만들지 않는다(알던 회사로만 기억). 알던 회사·알림은 이 기기에만 저장한다.
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# 번호 확인
|
||||||
|
|
||||||
|
> 화면: 나 › 내 번호 확인하기
|
||||||
|
|
||||||
|
- 나 › "내 번호 확인하기": 문자로 온 6자리를 넣으면 이 기기의 토큰이 내 번호에 묶인다. 폰을 바꾼 진짜 주인은 다시 확인하면 새 기기로 넘어오고, 이전 기기는 막힌다.
|
||||||
|
- 서버: `python3 mailbox_server.py --sms-webhook <주소> --require-verify`. 문자 발송은 `SmsSender` 하나(웹훅은 `{to,text}` 를 POST)라 업체를 바꿔도 서버 나머지는 그대로다.
|
||||||
|
- `--require-verify` 를 켜면 확인을 마친 기기만 우편함을 열 수 있다 (번호 선점 차단). 켜지 않으면 예전처럼 처음 쓴 기기에 묶인다.
|
||||||
|
- 5분 만료, 번호당 1시간 3번, 5번 오답이면 새로 요청. `--sms-log` 는 시험용(문자를 보내지 않고 기록)이라 실서비스에서 쓰면 안 된다.
|
||||||
|
- 서버 시험: `cd server && python3 -m unittest test_mailbox test_verify`
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# 알려진 한계
|
||||||
|
|
||||||
|
- 보관함의 우편함 토큰은 번호별 처음 쓴 기기에 묶인다(번호 선점 가능). 나중에 문자 인증으로 막는다.
|
||||||
|
- 그룹 대화방은 사람이 나가도 키를 바꾸지 않는다.
|
||||||
|
- 위치는 지도 타일 없이 레이더(내 위치 중심, 방위·거리)로 보여 준다. 지도 SDK는 붙이지 않았다.
|
||||||
|
- 문자 인증은 만들어 뒀지만 문자 발송 주소(`--sms-webhook`)를 정해 서버에 넣어야 켜진다. 켜기 전에는 처음 쓴 기기에 묶이는 방식(번호 선점 가능)이다.
|
||||||
|
- 음력 변환은 1951~2049년, 윤달 생일은 미지원(기일은 지원).
|
||||||
|
- WebRTC(음성·영상 통화), 앱 잠금, 공유 이미지/PDF 저장, 우편함 요청 서명은 아직 없다.
|
||||||
|
- 실제 기기 점검 전: 주소록 권한, 긴급 알림, 위치, QR, 같은 네트워크 발견.
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# 업무 · 팀 · 긴급 호출
|
||||||
|
|
||||||
|
> 화면: 대화방 +, 업무 탭, 회사 그룹 › 팀 대화방
|
||||||
|
|
||||||
|
- 회사 종류 그룹(예: 회사 › 한빛전자 › 결제팀) 안의 "팀 대화방"을 누르면 구성원과 회사 맥락의 대화방이 생긴다. 회사 명함을 아직 공유하지 않은 사람이 있으면 그 이름을 알려 주고 만들지 않는다.
|
||||||
|
- 대화방 입력칸 왼쪽 + 버튼: "업무 맡기기"(할 일·담당·마감), "긴급 호출". 오른쪽 위 "업무 N"과 ⋮ 메뉴 "이 방 업무". 아래 메뉴 업무 탭은 모든 방의 업무(처음은 내 업무).
|
||||||
|
- 업무와 긴급 호출은 보통 대화로 간다(`📌 업무 [김민수] 결제 API 정리 (마감 10/30)`, `🚨 긴급 호출: …`). 받는 쪽 앱이 읽어서 업무 목록에 넣고(담당 이름이 내 이름이면 "내 업무"), 긴급 호출은 바로 알림을 띄운다. 새 봉투 형식은 없다.
|
||||||
|
- 지난 대화를 길게 누르면 "업무로 정리"(내 목록에만). 업무 목록은 이 기기에만 저장하고, 끝냄 표시·밀어서 지우기가 된다.
|
||||||
|
- 안드로이드: 긴급 호출 알림은 `flutter_local_notifications` 의 높은 중요도 채널(urgent)을 쓴다.
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 위치 공유
|
||||||
|
|
||||||
|
> 화면: 나 › 위치 공유
|
||||||
|
|
||||||
|
- 나 › 위치 공유에서 켠다. 기본은 꺼짐.
|
||||||
|
- 켜면 1분마다, 공개 범위 표의 "실시간 위치"가 공개·일부인 관계의 친구에게만 보낸다. 관계를 모르는 친구에게는 보내지 않는다. "일부"는 약 1km 단위로 뭉개서 보낸다.
|
||||||
|
- 위치는 둘만 아는 키로 잠가서 보내고, 받은 쪽에는 마지막 위치 하나만 남는다.
|
||||||
|
- 안드로이드: `geolocator` 플러그인과 위치 권한이 필요하다 (`tool/android_patch.py` 가 권한을 넣는다).
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 이직 알림
|
||||||
|
|
||||||
|
> 화면: 채팅 맨 위, 프로필
|
||||||
|
|
||||||
|
- 상대가 회사 명함을 고쳐 "다시 보내기" 하면, 이 기기가 알던 회사와 비교해 바뀌었으면 알림이 생긴다(채팅 맨 위, 프로필). 예: "김민수님이 한빛전자에서 누리소프트로 옮겼어요", "지금은 다니지 않음"이면 "…를 떠났어요".
|
||||||
|
- "확인 · 옮기기": 이전 회사 이름이 경로에 든 회사 그룹(예: 회사 › 한빛전자 › 결제팀)에서는 이전 구성원으로 흐리게 남기고, "회사 › 새 회사" 그룹(없으면 만든다)에 넣는다. 연결 고리는 '직장 동료'가 새 회사로 바뀌고 '전 직장 동료'(이전 경로)가 남는다.
|
||||||
|
- "이전 회사에서 지우기": 흐리게 남기지 않고 뺀다. "무시": 알림만 지운다. 이전 구성원은 그룹 안에서 ✕ 로 지울 수 있다.
|
||||||
|
- 처음 받는 회사 명함, 앱을 새로 연 뒤의 첫 비교는 알림을 만들지 않는다(알던 회사로만 기억). 알던 회사·알림은 이 기기에만 저장한다.
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 주소록 가져오기
|
||||||
|
|
||||||
|
> 화면: 관계 탭 › 주소록 불러오기, 그룹 안 › 주소록에서, 빈 채팅 › 주소록에서
|
||||||
|
|
||||||
|
- 관계 탭 › 주소록 불러오기: 이름·초성(ㄱㅁㅅ)·번호로 찾고, 휴대폰 주소록 그룹(라벨)으로 거르고, 이미 있는 사람은 빼고 볼 수 있다. 한 명씩 또는 보이는 사람을 모두 고른 뒤, 넣을 그룹(여러 개)을 골라 가져온다.
|
||||||
|
- 번호가 여럿이면 휴대폰(010) 번호를 먼저 쓴다. 가져온 사람은 이 기기 인맥에만 들어가고, "친구 요청도 보내기"를 켰을 때만 상대에게 요청이 간다. 주소록은 읽기만 한다.
|
||||||
|
|
||||||
|
관련: [[그룹 · 잠금]]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# 처음 시작 · 다시 연결
|
||||||
|
|
||||||
|
> 화면: 처음 화면, 채팅 띠, 관계 탭 › 받은 요청, 나 › 제명한 번호
|
||||||
|
|
||||||
|
- 처음 시작은 이름과 휴대폰 번호만 넣는다. 키는 이 폰 안에서 조용히 만들고, 복구 코드·조각·보관 키는 없다.
|
||||||
|
- 폰을 바꾸거나 잃으면 그 폰의 기록은 되살리지 않는다. 탈퇴 후 재가입도 없다.
|
||||||
|
- 새 폰에서 같은 번호로 시작하면, 관계를 유지하고 있는 사용자 각자에게 "새 기기로 다시 연결" 요청이 간다(채팅 맨 위 띠, 관계 탭). **승인**하면 이전 대화·그룹·명함 기록 그대로 새 키로 이어지고, **제명**하면 그 번호는 영구히 막힌다(나 › 제명한 번호에서 풀 수 있음).
|
||||||
|
|
@ -12,3 +12,5 @@
|
||||||
- 시험 313개 통과, analyze 0건, 화면 캡처 14장 → [[06 화면 점검]]
|
- 시험 313개 통과, analyze 0건, 화면 캡처 14장 → [[06 화면 점검]]
|
||||||
- 최우선 규칙 추가: 질문은 APK·화면 먼저 / 모든 문서는 wiki
|
- 최우선 규칙 추가: 질문은 APK·화면 먼저 / 모든 문서는 wiki
|
||||||
- 20:38 개편 APK 빌드 성공 (app-debug.apk 약 190MB, 커밋 e7982c8) → 사용자 설치·시험 대기
|
- 20:38 개편 APK 빌드 성공 (app-debug.apk 약 190MB, 커밋 e7982c8) → 사용자 설치·시험 대기
|
||||||
|
- README 를 wiki 로 정리: 기능 안내를 화면 지도+기능별 노트 10개로 나누고, 모듈 표는 04, 시험·준비는 05, 원칙은 00 으로
|
||||||
|
- README·CLAUDE.md·server/README 의 내용을 wiki 로 모두 옮기고, 세 파일에는 "무조건 wiki 참조" 지시만 남김
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
- [[04 구조와 모듈]]
|
- [[04 구조와 모듈]]
|
||||||
- [[05 빌드 · 시험 · 저장소]]
|
- [[05 빌드 · 시험 · 저장소]]
|
||||||
- [[06 화면 점검]]
|
- [[06 화면 점검]]
|
||||||
- 가이드: [[가이드/기능 안내]] · [[가이드/보관함 서버]]
|
- 가이드: [[가이드/기능 안내]] (화면 지도 + 기능별 노트) · [[가이드/알려진 한계]] · [[가이드/보관함 서버]]
|
||||||
- 진행 로그: [[진행 로그/2026-10-26]]
|
- 진행 로그: [[진행 로그/2026-10-26]]
|
||||||
|
|
||||||
저장소: http://192.168.88.15:3180/diadmin/frindlymap (Gitea) · 소스: ~/friendly_map
|
저장소: http://192.168.88.15:3180/diadmin/frindlymap (Gitea) · 소스: ~/friendly_map
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue