97 lines
3.3 KiB
Dart
97 lines
3.3 KiB
Dart
// 全局昵称设置
|
||
// 持久化到 SharedPreferences / localStorage,所有工具共享同一个昵称
|
||
// Web 平台自动监听 localStorage 变化 → 跨标签页实时同步
|
||
import 'dart:async';
|
||
import 'dart:html' as html show window, StorageEvent;
|
||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
|
||
const _keyNickname = 'toolbox.nickname';
|
||
const _keyBrowserId = 'toolbox.browser_id';
|
||
|
||
class NicknameService {
|
||
NicknameService._();
|
||
static final NicknameService instance = NicknameService._();
|
||
|
||
SharedPreferences? _prefs;
|
||
|
||
/// 浏览器唯一 ID:同浏览器跨 tab 共享(localStorage 作用域),跨浏览器不同
|
||
/// 用于后端识别"同一个人",避免昵称冲突时自动改名
|
||
String? _browserId;
|
||
|
||
/// 昵称变化广播流:本地 setNickname 触发 + Web 上其他标签页修改也触发
|
||
final _streamController = StreamController<String>.broadcast();
|
||
Stream<String> get onChanged => _streamController.stream;
|
||
|
||
StreamSubscription? _webStorageSub;
|
||
|
||
Future<void> _ensureInit() async {
|
||
_prefs ??= await SharedPreferences.getInstance();
|
||
// 惰性生成 browserId:首次访问时若没有就生成 uuid 存起来
|
||
if (_browserId == null) {
|
||
final existing = _prefs!.getString(_keyBrowserId);
|
||
if (existing != null && existing.isNotEmpty) {
|
||
_browserId = existing;
|
||
} else {
|
||
_browserId = _genBrowserId();
|
||
await _prefs!.setString(_keyBrowserId, _browserId!);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 获取浏览器 ID(稳定值,同浏览器跨 tab 相同)
|
||
Future<String> get browserId async {
|
||
await _ensureInit();
|
||
return _browserId!;
|
||
}
|
||
|
||
/// 生成短 browserId:base62(0-9A-Za-z),去掉特殊字符方便 URL 使用
|
||
/// 12 字符足够唯一(62^12 ≈ 3e21 种可能)
|
||
String _genBrowserId() {
|
||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||
final rand = DateTime.now().microsecond.hashCode;
|
||
final buf = StringBuffer();
|
||
int seed = rand ^ DateTime.now().millisecondsSinceEpoch;
|
||
for (int i = 0; i < 12; i++) {
|
||
seed = (seed * 1103515245 + 12345) & 0x7FFFFFFF;
|
||
buf.write(chars[seed % chars.length]);
|
||
}
|
||
return buf.toString();
|
||
}
|
||
|
||
/// 开启跨标签页同步(仅 Web 生效;非 Web 空操作)
|
||
/// 在 widget 的 initState 里调用一次即可
|
||
void enableCrossTabSync() {
|
||
if (!kIsWeb || _webStorageSub != null) return;
|
||
_webStorageSub = html.window.onStorage.listen((html.StorageEvent event) async {
|
||
if (event.key == _keyNickname) {
|
||
final n = await nickname;
|
||
_streamController.add(n);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 获取当前昵称,未设置时返回默认昵称
|
||
Future<String> get nickname async {
|
||
await _ensureInit();
|
||
final n = _prefs!.getString(_keyNickname);
|
||
if (n != null && n.trim().isNotEmpty) return n.trim();
|
||
return '用户';
|
||
}
|
||
|
||
/// 设置昵称(1-32 字符,过短返回 false)
|
||
Future<bool> setNickname(String value) async {
|
||
final trimmed = value.trim();
|
||
if (trimmed.isEmpty || trimmed.length > 32) return false;
|
||
await _ensureInit();
|
||
await _prefs!.setString(_keyNickname, trimmed);
|
||
_streamController.add(trimmed);
|
||
return true;
|
||
}
|
||
|
||
void dispose() {
|
||
_webStorageSub?.cancel();
|
||
_webStorageSub = null;
|
||
}
|
||
}
|