feat: make core pages local-first offline

This commit is contained in:
2026-08-21 12:40:01 +08:00
parent 8828851631
commit fefa2d74e6
20 changed files with 921 additions and 188 deletions
+63 -2
View File
@@ -1,5 +1,9 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miaoji_zhang/shared/api/api_client.dart';
import 'package:miaoji_zhang/shared/api/backend_identity.dart';
import 'package:miaoji_zhang/shared/services/session_store.dart';
class BrandConfig {
@@ -41,6 +45,13 @@ class AvatarItem {
defaultName = j['defaultName'] as String,
speechTic = j['speechTic'] as String? ?? '',
imageUrl = j['imageUrl'] as String?;
Map<String, dynamic> toJson() => {
'key': key,
'defaultName': defaultName,
'speechTic': speechTic,
'imageUrl': imageUrl,
};
}
class PersonaItem {
@@ -50,6 +61,13 @@ class PersonaItem {
name = j['name'] as String,
description = j['description'] as String? ?? '',
sampleLine = j['sampleLine'] as String? ?? '';
Map<String, dynamic> toJson() => {
'key': key,
'name': name,
'description': description,
'sampleLine': sampleLine,
};
}
class CompanionDisplay {
@@ -145,16 +163,59 @@ class PublicConfigApi {
static Future<List<AvatarItem>> avatars() async {
final res = await _dio.get('/api/public/avatars');
return (res.data as List)
final values = (res.data as List)
.map((e) => AvatarItem.fromJson(e as Map<String, dynamic>))
.toList();
await _cacheCatalog(
_avatarCacheKey,
values.map((e) => e.toJson()).toList(),
);
return values;
}
static Future<List<PersonaItem>> personas() async {
final res = await _dio.get('/api/public/personas');
return (res.data as List)
final values = (res.data as List)
.map((e) => PersonaItem.fromJson(e as Map<String, dynamic>))
.toList();
await _cacheCatalog(
_personaCacheKey,
values.map((e) => e.toJson()).toList(),
);
return values;
}
static Future<List<AvatarItem>> cachedAvatars() async =>
(await _cachedCatalog(_avatarCacheKey)).map(AvatarItem.fromJson).toList();
static Future<List<PersonaItem>> cachedPersonas() async =>
(await _cachedCatalog(
_personaCacheKey,
)).map(PersonaItem.fromJson).toList();
static String get _avatarCacheKey =>
'public_avatars_${BackendIdentity.scope}';
static String get _personaCacheKey =>
'public_personas_${BackendIdentity.scope}';
static Future<void> _cacheCatalog(
String key,
List<Map<String, dynamic>> values,
) async {
await (await SharedPreferences.getInstance()).setString(
key,
jsonEncode(values),
);
}
static Future<List<Map<String, dynamic>>> _cachedCatalog(String key) async {
final value = (await SharedPreferences.getInstance()).getString(key);
if (value == null) return const [];
try {
return (jsonDecode(value) as List).cast<Map<String, dynamic>>().toList();
} catch (_) {
return const [];
}
}
static String? _normalizeName(String? value) {