feat: Aufnahme und lokaler Speicher für Sprachnotizen (T7 L5 Task 17)
Recorder (webm/opus oder mp4, 24 kbit/s, Warnung bei 150 s, Stopp bei 180 s oder im Hintergrund; Mikrofon und Wake-Lock werden in jedem Fall freigegeben, auch bei Fehlern und ausgestecktem Mikrofon) und IndexedDB v6 mit Store pending-voice (Index ownerUserId); volle Speicherquote ergibt VoiceStorageFullError ohne Datenverlust; Abmelden löscht nur die eigenen Notizen. Refs #27 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
1dc39f7658
commit
ce472164b5
6 files changed
+829
-11
No files matched your search
@@ -95,6 +95,21 @@ export interface StoredPhoto {
|
||||
lastError?: QueueError;
|
||||
}
|
||||
|
||||
/** A voice note recorded on this device, waiting to be uploaded (Sprachnotiz). */
|
||||
export interface StoredVoiceNote {
|
||||
id: string;
|
||||
ownerUserId: string;
|
||||
blob: Blob;
|
||||
mime: string;
|
||||
durationSec: number;
|
||||
colonyId: string;
|
||||
inspectionDate: string;
|
||||
createdAt: string;
|
||||
uploadIdempotencyKey: string;
|
||||
state: "recorded" | "uploading";
|
||||
lastError?: QueueError;
|
||||
}
|
||||
|
||||
export interface StoredPack {
|
||||
apiaryId: string;
|
||||
ownerUserId: string;
|
||||
@@ -113,6 +128,7 @@ export interface OfflineDB extends DBSchema, RecordStores {
|
||||
value: StoredPhoto;
|
||||
indexes: { sessionId: string; inspectionIdempotencyKey: string; ownerUserId: string };
|
||||
};
|
||||
"pending-voice": { key: string; value: StoredVoiceNote; indexes: { ownerUserId: string } };
|
||||
"offline-packs": { key: string; value: StoredPack };
|
||||
meta: { key: string; value: unknown };
|
||||
}
|
||||
@@ -120,11 +136,12 @@ export interface OfflineDB extends DBSchema, RecordStores {
|
||||
const DB_NAME = "apicore-offline";
|
||||
// v1 only had "pending-inspections", v2 added the other five quick-capture
|
||||
// types, v3 adds bulk entries, photos, offline packs and meta, v4 adds plant
|
||||
// reports (Pflanzenentwicklung), v5 adds hive changes (Beute). Entries already
|
||||
// reports (Pflanzenentwicklung), v5 adds hive changes (Beute), v6 adds voice
|
||||
// notes (Sprache). Entries already
|
||||
// queued on a phone must survive every upgrade, so each store is only created
|
||||
// if it doesn't exist yet (guarded by oldVersion, per idb's upgrade()
|
||||
// convention), never recreated.
|
||||
const DB_VERSION = 5;
|
||||
const DB_VERSION = 6;
|
||||
|
||||
const RECORD_STORES: readonly RecordStoreName[] = [
|
||||
"pending-inspections",
|
||||
@@ -198,6 +215,10 @@ export function getDb(): Promise<IDBPDatabase<OfflineDB>> {
|
||||
if (oldVersion < 5) {
|
||||
db.createObjectStore("pending-hive-changes", { keyPath: "id", autoIncrement: true });
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
const voice = db.createObjectStore("pending-voice", { keyPath: "id" });
|
||||
voice.createIndex("ownerUserId", "ownerUserId");
|
||||
}
|
||||
},
|
||||
// A newer app version in another tab wants to upgrade: step aside
|
||||
// instead of blocking it. The next call here opens the new version.
|
||||
@@ -620,10 +641,10 @@ export async function discardQueueItem(item: QueueItem): Promise<void> {
|
||||
await tx.done;
|
||||
}
|
||||
|
||||
/** Deletes everything one account queued on this device, photos included. */
|
||||
/** Deletes everything one account queued on this device, photos and voice notes included. */
|
||||
export async function clearQueueOf(ownerUserId: string): Promise<void> {
|
||||
const db = await getDb();
|
||||
const tx = db.transaction([...QUEUE_STORES, "pending-photos"], "readwrite");
|
||||
const tx = db.transaction([...QUEUE_STORES, "pending-photos", "pending-voice"], "readwrite");
|
||||
const queues = QUEUE_STORES.map(async (store) => {
|
||||
const objectStore = tx.objectStore(store);
|
||||
const entries: QueuedValue[] = await objectStore.getAll();
|
||||
@@ -635,6 +656,12 @@ export async function clearQueueOf(ownerUserId: string): Promise<void> {
|
||||
});
|
||||
const photos = tx.objectStore("pending-photos");
|
||||
const photoIds = await photos.index("ownerUserId").getAllKeys(ownerUserId);
|
||||
await Promise.all([...queues, ...photoIds.map((photoId) => photos.delete(photoId))]);
|
||||
const voice = tx.objectStore("pending-voice");
|
||||
const voiceIds = await voice.index("ownerUserId").getAllKeys(ownerUserId);
|
||||
await Promise.all([
|
||||
...queues,
|
||||
...photoIds.map((photoId) => photos.delete(photoId)),
|
||||
...voiceIds.map((voiceId) => voice.delete(voiceId)),
|
||||
]);
|
||||
await tx.done;
|
||||
}
|
||||
Reference in new issue
Block a user