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 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 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 j) : key = j['key'] as String, defaultName = j['defaultName'] as String, speechTic = j['speechTic'] as String? ?? '', imageUrl = j['imageUrl'] as String?; Map toJson() => { 'key': key, 'defaultName': defaultName, 'speechTic': speechTic, 'imageUrl': imageUrl, }; } class PersonaItem { final String key, name, description, sampleLine; PersonaItem.fromJson(Map j) : key = j['key'] as String, name = j['name'] as String, description = j['description'] as String? ?? '', sampleLine = j['sampleLine'] as String? ?? ''; Map 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 init() async { if (!SessionStore.instance.isGuest) { try { final b = await _dio.get('/api/public/brand'); _cachedBrand = BrandConfig.fromJson(b.data as Map); } 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( const CompanionDisplay(name: 'AI', avatarKey: 'cat'), ); static String get companionName => companionNotifier.value.name; static String get companionAvatarKey => companionNotifier.value.avatarKey; static Future 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; final avatars = responses[1].data as List; final companion = profile['aiCompanion'] as Map?; 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; 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; companionNotifier.value = CompanionDisplay( name: _normalizeName(first['defaultName'] as String?) ?? '小账喵', avatarKey: first['key'] as String? ?? 'cat', ); } catch (_) {} } } static Future> avatars() async { final res = await _dio.get('/api/public/avatars'); final values = (res.data as List) .map((e) => AvatarItem.fromJson(e as Map)) .toList(); await _cacheCatalog( _avatarCacheKey, values.map((e) => e.toJson()).toList(), ); return values; } static Future> personas() async { final res = await _dio.get('/api/public/personas'); final values = (res.data as List) .map((e) => PersonaItem.fromJson(e as Map)) .toList(); await _cacheCatalog( _personaCacheKey, values.map((e) => e.toJson()).toList(), ); return values; } static Future> cachedAvatars() async => (await _cachedCatalog(_avatarCacheKey)).map(AvatarItem.fromJson).toList(); static Future> 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 _cacheCatalog( String key, List> values, ) async { await (await SharedPreferences.getInstance()).setString( key, jsonEncode(values), ); } static Future>> _cachedCatalog(String key) async { final value = (await SharedPreferences.getInstance()).getString(key); if (value == null) return const []; try { return (jsonDecode(value) as List).cast>().toList(); } catch (_) { return const []; } } static String? _normalizeName(String? value) { final text = value?.trim(); if (text == null || text.isEmpty) return null; return text; } }