feat(1.4-b3): '일기만' 정리 옵션·라디얼 FAB 가림 수정·1.4 전용 프로모션 — 빌드 3
- 데이터 정리·초기화에 '정리할 데이터' 축(기록과 일기/일기만) 추가 — 일기만은 기록·구조·양식 보존, 백업은 일기 PDF만, DataReset.deleteDiaries(전체/날짜까지) - 자가 검증 18→27건(일기만 9건: 기준일 포함 삭제·기록/행동/양식 보존·cascade) — 26·18.5 심 ALL PASS - 라디얼 모드 FAB가 목록 마지막 행을 가리던 실측 문제 수정 — 탭 루트(NavigationStack 안쪽) 하단 safeAreaInset 78pt, 26·18.5 시각 확인 - 도움말 '데이터 정리·초기화'에 일기만 문구, 신규 12키 en/ja 완역(카탈로그 missing/stale 0) - whats-new-1.4에 일기만 반영 + promotional-text-1.4.txt 신설(ko 154/en 169/ja 139자 — 이용자 3명 감사 멘트) - iOS 18.5 심층 QA: 자가 검증 106건 ALL PASS + 라디얼/위젯/초기화 화면 확인 - CURRENT_PROJECT_VERSION 2→3, Debug/Store/워치 3빌드 성공 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WtZwRRjbtM9pZJDM4FkTq6
This commit is contained in:
parent
22ed06f2a1
commit
16e8737976
File diff suppressed because one or more lines are too long
@ -601,7 +601,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -643,7 +643,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -686,7 +686,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -722,7 +722,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -758,7 +758,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
|
||||
@ -794,7 +794,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
|
||||
@ -827,7 +827,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
@ -853,7 +853,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
|
||||
@ -62,6 +62,25 @@ enum DataReset {
|
||||
return counts
|
||||
}
|
||||
|
||||
// MARK: 일기만 삭제 (1.4(2) — 기록·구조·양식은 보존)
|
||||
|
||||
/// 일기만 삭제한다. endDayKey가 nil이면 모든 일기, 지정하면 그 달력일(포함)까지.
|
||||
/// cascade로 페이지·요소·할 일·기분 사진까지 정리되고, 양식 라이브러리는 남긴다.
|
||||
@MainActor
|
||||
static func deleteDiaries(upToDayKey endDayKey: Date?, context: ModelContext) throws -> Counts {
|
||||
var counts = Counts()
|
||||
let descriptor: FetchDescriptor<DiaryEntry>
|
||||
if let endDayKey {
|
||||
descriptor = FetchDescriptor<DiaryEntry>(predicate: #Predicate { $0.dayKey <= endDayKey })
|
||||
} else {
|
||||
descriptor = FetchDescriptor<DiaryEntry>()
|
||||
}
|
||||
let entries = try context.fetch(descriptor)
|
||||
for entry in entries { context.delete(entry) }
|
||||
counts.diaryEntries = entries.count
|
||||
return counts
|
||||
}
|
||||
|
||||
// MARK: 전체 초기화
|
||||
|
||||
/// 모든 도메인 데이터 삭제 (cascade: 목표→다짐, 행동→기록, 일기→페이지·요소·할 일).
|
||||
@ -210,6 +229,43 @@ enum DataReset {
|
||||
expect("전체: 목표 0", (try? context.fetchCount(FetchDescriptor<Goal>())) ?? -1, 0)
|
||||
expect("전체: 일기 0", (try? context.fetchCount(FetchDescriptor<DiaryEntry>())) ?? -1, 0)
|
||||
expect("전체: 양식 0", (try? context.fetchCount(FetchDescriptor<DiaryTemplate>())) ?? -1, 0)
|
||||
|
||||
// ---- 2부: 일기만 삭제 (새 컨테이너 — 기록·구조 보존 확인) ----
|
||||
let container2 = try ModelContainer(
|
||||
for: DataStore.schema,
|
||||
configurations: [ModelConfiguration(isStoredInMemoryOnly: true)]
|
||||
)
|
||||
let context2 = container2.mainContext
|
||||
let action2 = Action(name: "일기검증행동", symbolName: "book.fill", trackingType: .time, sortOrder: 0)
|
||||
context2.insert(action2)
|
||||
context2.insert(TimeSession(action: action2,
|
||||
startAt: cutUpper.addingTimeInterval(-7200),
|
||||
endAt: cutUpper.addingTimeInterval(-3600)))
|
||||
let template2 = DiaryTemplate(name: "양식2", kind: .image)
|
||||
context2.insert(template2)
|
||||
for offset in [-1, 0, 1] {
|
||||
let entry = DiaryEntry(dayKey: cal.date(byAdding: .day, value: offset, to: cutKey)!)
|
||||
context2.insert(entry)
|
||||
let page = DiaryPage(index: 1)
|
||||
page.entry = entry
|
||||
context2.insert(page)
|
||||
}
|
||||
try context2.save()
|
||||
|
||||
let diaryRange = try DataReset.deleteDiaries(upToDayKey: cutKey, context: context2)
|
||||
try context2.save()
|
||||
expect("일기만(날짜까지): 삭제 수(기준일 포함)", diaryRange.diaryEntries, 2)
|
||||
expect("일기만(날짜까지): 남은 일기", (try? context2.fetchCount(FetchDescriptor<DiaryEntry>())) ?? -1, 1)
|
||||
expect("일기만(날짜까지): 페이지 cascade", (try? context2.fetchCount(FetchDescriptor<DiaryPage>())) ?? -1, 1)
|
||||
expect("일기만(날짜까지): 기록 보존", (try? context2.fetchCount(FetchDescriptor<TimeSession>())) ?? -1, 1)
|
||||
|
||||
let diaryAll = try DataReset.deleteDiaries(upToDayKey: nil, context: context2)
|
||||
try context2.save()
|
||||
expect("일기만(전체): 삭제 수", diaryAll.diaryEntries, 1)
|
||||
expect("일기만(전체): 일기 0", (try? context2.fetchCount(FetchDescriptor<DiaryEntry>())) ?? -1, 0)
|
||||
expect("일기만(전체): 기록 보존", (try? context2.fetchCount(FetchDescriptor<TimeSession>())) ?? -1, 1)
|
||||
expect("일기만(전체): 행동 보존", (try? context2.fetchCount(FetchDescriptor<Action>())) ?? -1, 1)
|
||||
expect("일기만(전체): 양식 보존", (try? context2.fetchCount(FetchDescriptor<DiaryTemplate>())) ?? -1, 1)
|
||||
} catch {
|
||||
failures += 1
|
||||
lines.append("FAIL 예외: \(error)")
|
||||
|
||||
@ -611,6 +611,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@까지의 일기를 삭제할까요?": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Delete diaries up to %@?"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@までの日記を削除しますか?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@요일": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -2861,6 +2877,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"기록과 일기": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Records & Diaries"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "記録と日記"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"기록에 메모 남기기": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -2893,18 +2925,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요.": {
|
||||
"기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. '일기만'을 고르면 기록은 그대로 두고 일기만 지울 수도 있어요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "If records have piled up, try Settings → Data → Data Cleanup & Reset. You can clear everything, or delete only records and diaries from the beginning through a chosen date — actions, tags, goals, quests, and diary templates remain, and sessions spanning the chosen date or still running are kept too. Before deleting, you can create a CSV backup plus a summary report (PDF/image) that sums up what will be removed at a glance — if backups are on, deletion proceeds only after the files are created. Removing past records breaks quest streaks. With iCloud sync on, deletion applies to your other devices too, so be sure to save the backups."
|
||||
"value": "If records have piled up, try Settings → Data → Data Cleanup & Reset. You can clear everything, or delete only records and diaries from the beginning through a chosen date — actions, tags, goals, quests, and diary templates remain, and sessions spanning the chosen date or still running are kept too. Choose 'Diaries only' to delete just the diaries while keeping records — handy for freeing up space taken by photos and handwriting. Before deleting, you can create a CSV backup plus a summary report (PDF/image) that sums up what will be removed at a glance — if backups are on, deletion proceeds only after the files are created. Removing past records breaks quest streaks. With iCloud sync on, deletion applies to your other devices too, so be sure to save the backups."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "記録がたまって整理したいときは、設定 → データ → 「データ整理・リセット」を試してください。全体を空にするか、最初から指定した日付までの記録・日記だけを削除できます — アクション・タグ・目標・クエストと日記テンプレートは残り、指定日をまたぐ計測や進行中の計測も残ります。削除前にCSVバックアップと、削除する記録をひと目でまとめた「整理レポート」(PDF・画像)を作成して保管できます — バックアップをオンにした場合は、ファイルを作成した後にのみ削除に進めます。過去の記録が消えるとクエストの連続達成は途切れます。iCloud同期がオンの場合は他のデバイスでも削除されるため、必ずバックアップを保存してください。"
|
||||
"value": "記録がたまって整理したいときは、設定 → データ → 「データ整理・リセット」を試してください。全体を空にするか、最初から指定した日付までの記録・日記だけを削除できます — アクション・タグ・目標・クエストと日記テンプレートは残り、指定日をまたぐ計測や進行中の計測も残ります。「日記のみ」を選ぶと、記録はそのままに日記だけを削除することもできます — 写真や手書きで大きくなった日記の容量だけを減らしたいときに便利です。削除前にCSVバックアップと、削除する記録をひと目でまとめた「整理レポート」(PDF・画像)を作成して保管できます — バックアップをオンにした場合は、ファイルを作成した後にのみ削除に進めます。過去の記録が消えるとクエストの連続達成は途切れます。iCloud同期がオンの場合は他のデバイスでも削除されるため、必ずバックアップを保存してください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5525,6 +5557,54 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"모든 일기 삭제…": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Delete All Diaries…"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "すべての日記を削除…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"모든 일기(필기·사진·할 일 포함)를 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Deletes every diary (handwriting, photos, and to-dos included). Time/count records, actions, goals, and diary templates remain — handy for freeing up space taken by photos and handwriting."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "すべての日記(手書き・写真・やることを含む)を削除します。時間・回数の記録とアクション・目標、日記テンプレートはそのまま残ります — 写真や手書きで大きくなった日記の容量だけを減らしたいときに便利です。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"모든 일기를 삭제할까요?": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Delete all diaries?"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "すべての日記を削除しますか?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"모든 캘린더": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -7181,6 +7261,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"삭제할 일기들을 PDF 하나로 만들어 보관할 수 있어요. 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "You can turn the diaries to be deleted into a single PDF for safekeeping. If the backup is on, you can delete only after creating and saving the file."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "削除する日記を1つのPDFにまとめて保管できます。バックアップをオンにした場合は、ファイルを作成・保存してから削除できます。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"상태": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -10802,6 +10898,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"일기만": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Diaries only"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "日記のみ"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"일기의 필기·사진·할 일이 함께 삭제돼요. 시간·횟수 기록과 행동·목표, 일기 양식은 남아요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "The diaries' handwriting, photos, and to-dos are deleted together. Time/count records, actions, goals, and diary templates remain. With iCloud sync on, they are deleted from other devices too."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "日記の手書き・写真・やることが一緒に削除されます。時間・回数の記録とアクション・目標、日記テンプレートは残ります。iCloud同期がオンの場合、他のデバイスからも削除されます。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"일정": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -11309,6 +11437,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"정리할 데이터": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Data to Clean Up"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "整理するデータ"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"정리할 범위": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -12237,6 +12381,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"지정한 날짜까지 일기 삭제…": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Delete Diaries Up to Date…"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "指定した日付まで日記を削除…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"지정한 날짜까지의 시간·횟수 기록과 일기가 삭제돼요. 과거 기록이 사라지면 다짐의 연속 달성 기록이 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -12429,6 +12589,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"처음부터 지정한 날짜(포함)까지의 일기만 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Deletes only diaries from the beginning through the chosen date (inclusive). Time/count records, actions, goals, and diary templates remain."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "最初から指定した日付(含む)までの日記だけを削除します。時間・回数の記録とアクション・目標、日記テンプレートはそのまま残ります。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"첫 페이지 타임테이블 카드 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 표시할 수 있어요(일정은 점선 테두리로 구분돼요). 필터 버튼을 누르면 그날 기록된 행동 중 타임테이블에 보여줄 것만 고를 수 있고, '이 날의 목표' 카드에도 필터 버튼이 있어 보고 싶은 목표만 골라 둘 수 있어요. 선택은 모두 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 반영돼요.\n매일 같은 선택을 반복하지 않으려면 달력 화면 오른쪽 위 '기본 표시 설정'을 써 보세요 — 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지를 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날짜는 그 선택이 우선하고, 필터의 '기본값으로 되돌리기'로 언제든 기본값으로 돌아갈 수 있어요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
|
||||
@ -28,6 +28,19 @@ struct DataResetView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// 정리 대상 (1.4(2)): 기록·일기 전부 vs 일기만.
|
||||
/// 일기만 모드는 사진·필기로 커진 일기 용량만 줄이고 기록·구조는 남긴다
|
||||
private enum ResetScope: String, CaseIterable, Identifiable {
|
||||
case recordsAndDiary, diaryOnly
|
||||
var id: String { rawValue }
|
||||
var label: String {
|
||||
switch self {
|
||||
case .recordsAndDiary: return String(localized: "기록과 일기")
|
||||
case .diaryOnly: return String(localized: "일기만")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ReportFormat: String, CaseIterable, Identifiable {
|
||||
case pdf, image
|
||||
var id: String { rawValue }
|
||||
@ -39,6 +52,7 @@ struct DataResetView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@State private var scope: ResetScope = .recordsAndDiary
|
||||
@State private var rangeMode: RangeMode = .all
|
||||
@State private var endDate: Date = Calendar.current.date(byAdding: .month, value: -3, to: .now) ?? .now
|
||||
@State private var includeCSV = true
|
||||
@ -63,10 +77,15 @@ struct DataResetView: View {
|
||||
rangeMode == .all ? nil : Date.distantPast..<upperBound
|
||||
}
|
||||
|
||||
/// 이 구성에서 만들 백업이 있는지 — 일기만 모드는 일기 PDF만 대상
|
||||
/// (CSV·정리 리포트는 기록 백업이라 일기만 지울 땐 만들지 않는다)
|
||||
private var wantsBackup: Bool {
|
||||
scope == .diaryOnly ? includeDiary : (includeCSV || includeDiary || includeReport)
|
||||
}
|
||||
|
||||
/// 백업을 하나도 안 만들거나, 만들었을 때만 삭제로 진행 가능
|
||||
private var canDelete: Bool {
|
||||
let wantsBackup = includeCSV || includeDiary || includeReport
|
||||
return (!wantsBackup || !backupURLs.isEmpty) && !isGenerating && !isDeleting
|
||||
(!wantsBackup || !backupURLs.isEmpty) && !isGenerating && !isDeleting
|
||||
&& deletedCounts == nil
|
||||
}
|
||||
|
||||
@ -133,6 +152,7 @@ struct DataResetView: View {
|
||||
.navigationTitle("데이터 정리·초기화")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar(.hidden, for: .tabBar)
|
||||
.onChange(of: scope) { backupURLs = [] }
|
||||
.onChange(of: rangeMode) { backupURLs = [] }
|
||||
.onChange(of: endDate) { backupURLs = [] }
|
||||
.onChange(of: includeCSV) { backupURLs = [] }
|
||||
@ -140,17 +160,13 @@ struct DataResetView: View {
|
||||
.onChange(of: includeReport) { backupURLs = [] }
|
||||
.onChange(of: reportFormat) { backupURLs = [] }
|
||||
.confirmationDialog(
|
||||
rangeMode == .all
|
||||
? String(localized: "모든 데이터를 삭제할까요?")
|
||||
: String(localized: "\(Format.fullDate(endKey))까지의 기록·일기를 삭제할까요?"),
|
||||
confirmTitle,
|
||||
isPresented: $confirmingDelete,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("삭제 진행", role: .destructive) { finalConfirm = true }
|
||||
} message: {
|
||||
Text(rangeMode == .all
|
||||
? "행동·꼬리표·목표·다짐·기록·일기·양식이 모두 삭제돼요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요."
|
||||
: "지정한 날짜까지의 시간·횟수 기록과 일기가 삭제돼요. 과거 기록이 사라지면 다짐의 연속 달성 기록이 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.")
|
||||
Text(confirmMessage)
|
||||
}
|
||||
.alert("정말 삭제할까요?", isPresented: $finalConfirm) {
|
||||
Button("취소", role: .cancel) {}
|
||||
@ -160,6 +176,10 @@ struct DataResetView: View {
|
||||
}
|
||||
#if DEBUG
|
||||
.onAppear {
|
||||
// 검증용: -dataResetScope diary → '일기만' 선택 상태로 시작
|
||||
if UserDefaults.standard.string(forKey: "dataResetScope") == "diary" {
|
||||
scope = .diaryOnly
|
||||
}
|
||||
// 검증용: -dataResetRangeDays <N> → '날짜 지정'(오늘-N일까지)로 시작
|
||||
let days = UserDefaults.standard.integer(forKey: "dataResetRangeDays")
|
||||
if days > 0 {
|
||||
@ -182,6 +202,12 @@ struct DataResetView: View {
|
||||
|
||||
private var rangeSection: some View {
|
||||
Section {
|
||||
Picker("정리할 데이터", selection: $scope.animation()) {
|
||||
ForEach(ResetScope.allCases) { scope in
|
||||
Text(scope.label).tag(scope)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
Picker("범위", selection: $rangeMode) {
|
||||
ForEach(RangeMode.allCases) { mode in
|
||||
Text(mode.label).tag(mode)
|
||||
@ -194,18 +220,27 @@ struct DataResetView: View {
|
||||
} header: {
|
||||
Text("정리할 범위")
|
||||
} footer: {
|
||||
Text(rangeMode == .all
|
||||
? "모든 행동·꼬리표·목표·다짐·기록·일기·양식을 삭제하고 처음 상태로 돌아가요. 앱 설정(테마·하루 시작 시간 등)과 프리미엄은 그대로예요."
|
||||
: "처음부터 지정한 날짜(포함)까지의 시간·횟수 기록과 일기만 삭제해요. 행동·꼬리표·목표·다짐과 양식 라이브러리는 남아요. 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정은 남아요.")
|
||||
switch (scope, rangeMode) {
|
||||
case (.recordsAndDiary, .all):
|
||||
Text("모든 행동·꼬리표·목표·다짐·기록·일기·양식을 삭제하고 처음 상태로 돌아가요. 앱 설정(테마·하루 시작 시간 등)과 프리미엄은 그대로예요.")
|
||||
case (.recordsAndDiary, .upTo):
|
||||
Text("처음부터 지정한 날짜(포함)까지의 시간·횟수 기록과 일기만 삭제해요. 행동·꼬리표·목표·다짐과 양식 라이브러리는 남아요. 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정은 남아요.")
|
||||
case (.diaryOnly, .all):
|
||||
Text("모든 일기(필기·사진·할 일 포함)를 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요.")
|
||||
case (.diaryOnly, .upTo):
|
||||
Text("처음부터 지정한 날짜(포함)까지의 일기만 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var targetSection: some View {
|
||||
Section("삭제 대상") {
|
||||
countRow("timer", String(localized: "시간 기록"), String(localized: "\(targetSessionCount)건"))
|
||||
countRow("number", String(localized: "횟수 기록"), String(localized: "\(targetEntryCount)건"))
|
||||
if scope == .recordsAndDiary {
|
||||
countRow("timer", String(localized: "시간 기록"), String(localized: "\(targetSessionCount)건"))
|
||||
countRow("number", String(localized: "횟수 기록"), String(localized: "\(targetEntryCount)건"))
|
||||
}
|
||||
countRow("book.closed.fill", String(localized: "일기"), String(localized: "\(targetDiaryCount)일"))
|
||||
if rangeMode == .all {
|
||||
if scope == .recordsAndDiary && rangeMode == .all {
|
||||
countRow("figure.walk", String(localized: "행동"), String(localized: "\(targetActionCount)개"))
|
||||
countRow("flag.checkered", String(localized: "목표"), String(localized: "\(targetGoalCount)개"))
|
||||
}
|
||||
@ -214,22 +249,27 @@ struct DataResetView: View {
|
||||
|
||||
private var backupSection: some View {
|
||||
Section {
|
||||
Toggle(isOn: $includeCSV) {
|
||||
Label("CSV 내보내기", systemImage: "tablecells")
|
||||
// 일기만 모드에서는 기록 백업(CSV·정리 리포트) 토글을 숨긴다 — 대상이 아님
|
||||
if scope == .recordsAndDiary {
|
||||
Toggle(isOn: $includeCSV) {
|
||||
Label("CSV 내보내기", systemImage: "tablecells")
|
||||
}
|
||||
}
|
||||
Toggle(isOn: $includeDiary) {
|
||||
Label("일기 PDF", systemImage: "book.closed")
|
||||
}
|
||||
Toggle(isOn: $includeReport) {
|
||||
Label("정리 리포트", systemImage: "doc.richtext")
|
||||
}
|
||||
if includeReport {
|
||||
Picker("리포트 형식", selection: $reportFormat) {
|
||||
ForEach(ReportFormat.allCases) { format in
|
||||
Text(format.label).tag(format)
|
||||
}
|
||||
if scope == .recordsAndDiary {
|
||||
Toggle(isOn: $includeReport) {
|
||||
Label("정리 리포트", systemImage: "doc.richtext")
|
||||
}
|
||||
if includeReport {
|
||||
Picker("리포트 형식", selection: $reportFormat) {
|
||||
ForEach(ReportFormat.allCases) { format in
|
||||
Text(format.label).tag(format)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
if isGenerating {
|
||||
HStack {
|
||||
@ -238,7 +278,7 @@ struct DataResetView: View {
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} else if backupURLs.isEmpty {
|
||||
if includeCSV || includeDiary || includeReport {
|
||||
if wantsBackup {
|
||||
Button {
|
||||
makeBackups()
|
||||
} label: {
|
||||
@ -259,7 +299,9 @@ struct DataResetView: View {
|
||||
} header: {
|
||||
Text("삭제 전 백업")
|
||||
} footer: {
|
||||
Text("CSV는 '데이터 가져오기'로 복원할 수 있는 원본 백업이에요. 정리 리포트는 삭제할 기록을 한눈에 정리한 문서예요(기간·행동별 누적·꼬리표·월별·목표 이력). 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요.")
|
||||
Text(scope == .diaryOnly
|
||||
? "삭제할 일기들을 PDF 하나로 만들어 보관할 수 있어요. 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요."
|
||||
: "CSV는 '데이터 가져오기'로 복원할 수 있는 원본 백업이에요. 정리 리포트는 삭제할 기록을 한눈에 정리한 문서예요(기간·행동별 누적·꼬리표·월별·목표 이력). 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,8 +317,7 @@ struct DataResetView: View {
|
||||
Button(role: .destructive) {
|
||||
confirmingDelete = true
|
||||
} label: {
|
||||
Label(rangeMode == .all ? "전체 데이터 삭제…" : "지정한 날짜까지 삭제…",
|
||||
systemImage: "trash")
|
||||
Label(deleteButtonTitle, systemImage: "trash")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.disabled(!canDelete)
|
||||
@ -297,8 +338,10 @@ struct DataResetView: View {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(AppTheme.green)
|
||||
}
|
||||
countRow("timer", String(localized: "시간 기록"), String(localized: "\(counts.sessions)건"))
|
||||
countRow("number", String(localized: "횟수 기록"), String(localized: "\(counts.entries)건"))
|
||||
if scope == .recordsAndDiary {
|
||||
countRow("timer", String(localized: "시간 기록"), String(localized: "\(counts.sessions)건"))
|
||||
countRow("number", String(localized: "횟수 기록"), String(localized: "\(counts.entries)건"))
|
||||
}
|
||||
countRow("book.closed.fill", String(localized: "일기"), String(localized: "\(counts.diaryEntries)일"))
|
||||
if counts.actions > 0 || counts.goals > 0 {
|
||||
countRow("figure.walk", String(localized: "행동"), String(localized: "\(counts.actions)개"))
|
||||
@ -311,6 +354,39 @@ struct DataResetView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var confirmTitle: String {
|
||||
switch (scope, rangeMode) {
|
||||
case (.recordsAndDiary, .all):
|
||||
return String(localized: "모든 데이터를 삭제할까요?")
|
||||
case (.recordsAndDiary, .upTo):
|
||||
return String(localized: "\(Format.fullDate(endKey))까지의 기록·일기를 삭제할까요?")
|
||||
case (.diaryOnly, .all):
|
||||
return String(localized: "모든 일기를 삭제할까요?")
|
||||
case (.diaryOnly, .upTo):
|
||||
return String(localized: "\(Format.fullDate(endKey))까지의 일기를 삭제할까요?")
|
||||
}
|
||||
}
|
||||
|
||||
private var confirmMessage: String {
|
||||
switch (scope, rangeMode) {
|
||||
case (.recordsAndDiary, .all):
|
||||
return String(localized: "행동·꼬리표·목표·다짐·기록·일기·양식이 모두 삭제돼요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.")
|
||||
case (.recordsAndDiary, .upTo):
|
||||
return String(localized: "지정한 날짜까지의 시간·횟수 기록과 일기가 삭제돼요. 과거 기록이 사라지면 다짐의 연속 달성 기록이 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.")
|
||||
case (.diaryOnly, _):
|
||||
return String(localized: "일기의 필기·사진·할 일이 함께 삭제돼요. 시간·횟수 기록과 행동·목표, 일기 양식은 남아요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.")
|
||||
}
|
||||
}
|
||||
|
||||
private var deleteButtonTitle: String {
|
||||
switch (scope, rangeMode) {
|
||||
case (.recordsAndDiary, .all): return String(localized: "전체 데이터 삭제…")
|
||||
case (.recordsAndDiary, .upTo): return String(localized: "지정한 날짜까지 삭제…")
|
||||
case (.diaryOnly, .all): return String(localized: "모든 일기 삭제…")
|
||||
case (.diaryOnly, .upTo): return String(localized: "지정한 날짜까지 일기 삭제…")
|
||||
}
|
||||
}
|
||||
|
||||
private func countRow(_ symbol: String, _ title: String, _ value: String) -> some View {
|
||||
HStack {
|
||||
Label {
|
||||
@ -356,7 +432,7 @@ struct DataResetView: View {
|
||||
}()
|
||||
let diaryEntries = try context.fetch(diaryDescriptor).filter(\.hasContent)
|
||||
|
||||
if includeCSV {
|
||||
if scope == .recordsAndDiary && includeCSV {
|
||||
// ⚠️ CSVExport.fileSuffix(for:)는 dayKeys 순회라 distantPast 범위 금지 —
|
||||
// '처음~까지'는 끝 날짜만 접미로 표기한다
|
||||
var suffix = ""
|
||||
@ -383,7 +459,7 @@ struct DataResetView: View {
|
||||
urls.append(url)
|
||||
}
|
||||
}
|
||||
if includeReport {
|
||||
if scope == .recordsAndDiary && includeReport {
|
||||
if let url = ArchiveReport.makeURL(
|
||||
format: reportFormat == .pdf ? .pdf : .image,
|
||||
actions: actions, sessions: sessions, entries: entries, goals: goals,
|
||||
@ -419,9 +495,14 @@ struct DataResetView: View {
|
||||
try? await Task.sleep(for: .milliseconds(60))
|
||||
do {
|
||||
let counts: DataReset.Counts
|
||||
if rangeMode == .all {
|
||||
switch (scope, rangeMode) {
|
||||
case (.diaryOnly, .all):
|
||||
counts = try DataReset.deleteDiaries(upToDayKey: nil, context: context)
|
||||
case (.diaryOnly, .upTo):
|
||||
counts = try DataReset.deleteDiaries(upToDayKey: endKey, context: context)
|
||||
case (.recordsAndDiary, .all):
|
||||
counts = try DataReset.deleteAll(context: context)
|
||||
} else {
|
||||
case (.recordsAndDiary, .upTo):
|
||||
counts = try DataReset.deleteRecords(upToDayKey: endKey, context: context, math: math)
|
||||
}
|
||||
// 저장 + 위젯·워치·LiveActivity 동기화 (진행 중 측정도 전체 삭제면 함께 정리됨)
|
||||
|
||||
@ -285,7 +285,7 @@ struct HelpView: View {
|
||||
HelpTopic(
|
||||
symbol: "trash",
|
||||
title: "데이터 정리·초기화",
|
||||
body: "기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요."
|
||||
body: "기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. '일기만'을 고르면 기록은 그대로 두고 일기만 지울 수도 있어요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요."
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
@ -56,7 +56,15 @@ struct RadialTabView: View {
|
||||
ForEach(AppTab.phoneCases) { tab in
|
||||
Tab(tab.label, systemImage: tab.symbol, value: tab.rawValue) {
|
||||
NavigationStack {
|
||||
// FAB(하단 42pt + 구슬 64pt)가 항상 떠 있으므로 스크롤 콘텐츠의
|
||||
// 끝이 그 위로 올라오게 하단 인셋을 준다 — 없으면 목록 마지막 행이
|
||||
// FAB에 가려지고 중앙 탭이 메뉴 열기로 새는 실측 문제(1.4(2) 검수).
|
||||
// ⚠️ NavigationStack '안쪽'에 붙일 것 — Tab 콘텐츠(스택 바깥)에
|
||||
// 붙이면 TabView가 safe area를 재설정해 무시된다(실측)
|
||||
tab.rootView
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
Color.clear.frame(height: 78)
|
||||
}
|
||||
}
|
||||
.toolbar(.hidden, for: .tabBar)
|
||||
}
|
||||
|
||||
14
myApp/HaruDanim/Marketing/AppStore/promotional-text-1.4.txt
Normal file
14
myApp/HaruDanim/Marketing/AppStore/promotional-text-1.4.txt
Normal file
@ -0,0 +1,14 @@
|
||||
# 프로모션 텍스트 — 1.4 전용 (로케일별 170자 이내, 심사 없이 수시 수정 가능)
|
||||
# ASC → 앱 → 해당 로케일 → '프로모션 텍스트' 란에 복사해 붙여넣기.
|
||||
# 2026-08-12 기준 이용자 3명(개발자 본인 포함) 감사 멘트 + "직접 쓰다 보니 아쉬워서
|
||||
# 벌써 1.4" 스토리 — 버전 전용 문구라, 다음 버전에서 상시 문구(promotional-text.txt)로
|
||||
# 되돌리거나 새로 쓰면 된다.
|
||||
|
||||
[ko] (154자)
|
||||
저를 포함해 세 분이 하루 다님과 함께하고 계세요. 어디선가 함께 걸어 주시는 두 분, 진심으로 감사해요. 매일 직접 쓰다 보니 아쉬운 곳이 보여 벌써 1.4로 돌아왔어요 — 지난 7일·30일 통계, 데이터 정리와 정리 리포트, 더 부드러운 펜슬 필기, iOS 18 지원까지.
|
||||
|
||||
[en] (169자)
|
||||
Three of us use Haru Danim — me included. To the two of you out there: thank you. 1.4 came quickly — last 7/30-day stats, data cleanup & report, smoother Pencil, iOS 18.
|
||||
|
||||
[ja] (139자)
|
||||
今日、私を含めて3人がHaru Danimと歩んでいます。どこかで一緒に歩いてくださるお二人、本当にありがとうございます。毎日使う中で気になる所があり、もう1.4になりました — 過去7日・30日の統計、データ整理と整理レポート、より滑らかなペンシル筆記、iOS 18対応まで。
|
||||
@ -3,7 +3,7 @@
|
||||
[ko]
|
||||
• 이제 iOS 18부터 쓸 수 있어요 — iPhone XS·XR과 구형 아이패드에서도 하루 다님을 만나 보세요
|
||||
• 통계에 '지난 7일/30일 단위로 보기'가 생겼어요 — 주초·월초에 그래프가 허전할 때, 오늘로 끝나는 최근 7일·30일로 볼 수 있어요. 통계 위젯에도 같은 기간이 추가됐어요
|
||||
• 데이터 정리·초기화가 생겼어요 — 오래 쌓인 기록을 전체 또는 지정한 날짜까지 비울 수 있어요. 삭제 전에 CSV 백업과, 그동안의 기록을 한눈에 담은 '정리 리포트'(PDF·이미지)를 만들어 보관하세요
|
||||
• 데이터 정리·초기화가 생겼어요 — 오래 쌓인 기록을 전체 또는 지정한 날짜까지 비울 수 있고, 사진·필기로 커진 일기만 골라 지울 수도 있어요. 삭제 전에 CSV 백업과, 그동안의 기록을 한눈에 담은 '정리 리포트'(PDF·이미지)를 만들어 보관하세요
|
||||
• 데이터 내보내기에 구간 지정이 생겼고, 일기도 내보낸 뒤 바로 삭제해 저장 공간을 줄일 수 있어요
|
||||
• 애플 펜슬 필기가 한층 부드러워졌어요 — 오래 쓴 페이지에서 획마다 걸리던 저장 부하를 없앴어요
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user