227 lines
7.1 KiB
Dart
227 lines
7.1 KiB
Dart
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 {
|
|
final String appName, slogan, logoUrl;
|
|
final bool voiceEnabled;
|
|
final bool imageEnabled;
|
|
final bool screenshotEnabled;
|
|
final bool stickersEnabled;
|
|
final bool aiAutoBookEnabled;
|
|
|
|
BrandConfig.fromJson(Map<String, dynamic> j)
|
|
: appName = _appName(j['appName']),
|
|
slogan = j['slogan'] as String? ?? '',
|
|
logoUrl = j['logoUrl'] as String? ?? '',
|
|
voiceEnabled = _feature(j, 'voice'),
|
|
imageEnabled = _feature(j, 'image'),
|
|
screenshotEnabled = _feature(j, 'screenshot'),
|
|
stickersEnabled = _feature(j, 'stickers'),
|
|
aiAutoBookEnabled = _feature(j, 'aiAutoBook');
|
|
|
|
static String _appName(Object? value) {
|
|
final name = value?.toString().trim();
|
|
return name == null || name.isEmpty || name == '喵记账' || name == '喵记'
|
|
? '记之'
|
|
: name;
|
|
}
|
|
|
|
static bool _feature(Map<String, dynamic> json, String key) {
|
|
final features = json['features'];
|
|
return features is Map ? features[key] as bool? ?? true : true;
|
|
}
|
|
}
|
|
|
|
class AvatarItem {
|
|
final String key, defaultName, speechTic;
|
|
final String? imageUrl;
|
|
AvatarItem.fromJson(Map<String, dynamic> j)
|
|
: key = j['key'] as String,
|
|
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 {
|
|
final String key, name, description, sampleLine;
|
|
PersonaItem.fromJson(Map<String, dynamic> j)
|
|
: key = j['key'] as String,
|
|
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 {
|
|
final String name;
|
|
final String avatarKey;
|
|
|
|
const CompanionDisplay({required this.name, required this.avatarKey});
|
|
}
|
|
|
|
class PublicConfigApi {
|
|
static final _dio = ApiClient.instance.dio;
|
|
static BrandConfig? _cachedBrand;
|
|
|
|
static Future<void> init() async {
|
|
if (!SessionStore.instance.isGuest) {
|
|
try {
|
|
final b = await _dio.get('/api/public/brand');
|
|
_cachedBrand = BrandConfig.fromJson(b.data as Map<String, dynamic>);
|
|
} catch (_) {}
|
|
}
|
|
await refreshCompanion();
|
|
}
|
|
|
|
static String get appName => _cachedBrand?.appName ?? '记之';
|
|
static String get slogan => _cachedBrand?.slogan ?? '';
|
|
static bool get voiceEnabled => _cachedBrand?.voiceEnabled ?? true;
|
|
static bool get imageEnabled => _cachedBrand?.imageEnabled ?? true;
|
|
static bool get screenshotEnabled => _cachedBrand?.screenshotEnabled ?? true;
|
|
static bool get stickersEnabled => _cachedBrand?.stickersEnabled ?? true;
|
|
static bool get aiAutoBookEnabled => _cachedBrand?.aiAutoBookEnabled ?? true;
|
|
|
|
static final companionNotifier = ValueNotifier<CompanionDisplay>(
|
|
const CompanionDisplay(name: 'AI', avatarKey: 'cat'),
|
|
);
|
|
|
|
static String get companionName => companionNotifier.value.name;
|
|
static String get companionAvatarKey => companionNotifier.value.avatarKey;
|
|
|
|
static Future<void> refreshCompanion() async {
|
|
final session = SessionStore.instance;
|
|
if (!session.isAccount || session.shouldUseLocalOnly) {
|
|
companionNotifier.value = const CompanionDisplay(
|
|
name: 'AI',
|
|
avatarKey: 'cat',
|
|
);
|
|
return;
|
|
}
|
|
try {
|
|
final responses = await Future.wait([
|
|
_dio.get('/api/users/me'),
|
|
_dio.get('/api/public/avatars'),
|
|
]);
|
|
final profile = responses[0].data as Map<String, dynamic>;
|
|
final avatars = responses[1].data as List;
|
|
final companion = profile['aiCompanion'] as Map<String, dynamic>?;
|
|
final selectedAvatarKey = companion?['avatarKey'] as String?;
|
|
final customName = _normalizeName(companion?['customName'] as String?);
|
|
|
|
var avatarKey = selectedAvatarKey;
|
|
String? defaultName;
|
|
|
|
for (final item in avatars) {
|
|
final avatar = item as Map<String, dynamic>;
|
|
final key = avatar['key'] as String?;
|
|
if ((avatarKey == null || avatarKey.isEmpty) &&
|
|
key != null &&
|
|
key.isNotEmpty) {
|
|
avatarKey = key;
|
|
}
|
|
if (key == selectedAvatarKey) {
|
|
defaultName = _normalizeName(avatar['defaultName'] as String?);
|
|
break;
|
|
}
|
|
}
|
|
|
|
companionNotifier.value = CompanionDisplay(
|
|
name: customName ?? defaultName ?? '小账喵',
|
|
avatarKey: avatarKey ?? 'cat',
|
|
);
|
|
} catch (_) {
|
|
try {
|
|
final a = await _dio.get('/api/public/avatars');
|
|
final list = a.data as List;
|
|
if (list.isEmpty) return;
|
|
final first = list.first as Map<String, dynamic>;
|
|
companionNotifier.value = CompanionDisplay(
|
|
name: _normalizeName(first['defaultName'] as String?) ?? '小账喵',
|
|
avatarKey: first['key'] as String? ?? 'cat',
|
|
);
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
static Future<List<AvatarItem>> avatars() async {
|
|
final res = await _dio.get('/api/public/avatars');
|
|
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');
|
|
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) {
|
|
final text = value?.trim();
|
|
if (text == null || text.isEmpty) return null;
|
|
return text;
|
|
}
|
|
}
|