resource.dart 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:typed_data';
  5. import 'package:fis_common/helpers/http.dart';
  6. import 'package:fis_common/index.dart';
  7. import 'package:fis_common/logger/logger.dart';
  8. import 'package:flutter/foundation.dart';
  9. import 'package:flutter/services.dart';
  10. import 'package:path_provider/path_provider.dart';
  11. import 'consts.dart';
  12. import 'manifest.dart';
  13. import 'theme.dart';
  14. /// 资源来源
  15. enum FResourceFromType {
  16. local, // 本地
  17. network, // 网络
  18. }
  19. /// 资源配置信息实体
  20. class FResourceInfo {
  21. final String key;
  22. final FResourceFromType from;
  23. final String path;
  24. const FResourceInfo({
  25. required this.key,
  26. required this.from,
  27. required this.path,
  28. });
  29. factory FResourceInfo.fromJson(Map<String, dynamic> json) {
  30. return FResourceInfo(
  31. key: json['key'],
  32. from: FResourceFromType.values[json['from']],
  33. path: json['path'],
  34. );
  35. }
  36. }
  37. /// 资源索引键
  38. class FResourceKey {
  39. final String value;
  40. const FResourceKey(this.value);
  41. }
  42. /// Fis资源集
  43. abstract class FResourceBundle {
  44. static AssetBundle? assetBundle;
  45. late String _resourcePath;
  46. /// 资源路径
  47. String get resourcePath => _resourcePath;
  48. /// 设置资源路径
  49. void setResourcePath(String value) => _resourcePath = value;
  50. final Map<String, Future<String>> _stringCache = <String, Future<String>>{};
  51. final Map<String, Future<dynamic>> _structuredDataCache =
  52. <String, Future<dynamic>>{};
  53. Map<String, FResourceInfo> _resourceMap = Map<String, FResourceInfo>();
  54. /// 主题清单内存映射
  55. late FThemeManifestInfo _manifestInfoRefer;
  56. @protected
  57. Future<ByteData> _innerLoad(FResourceInfo info);
  58. /// 加载资源数据
  59. Future<ByteData> load(FResourceKey key) async {
  60. FResourceInfo? resourceInfo = _getInfo(key.value);
  61. if (resourceInfo == null) return ByteData(0);
  62. if (resourceInfo.from == FResourceFromType.network) {
  63. return await _loadFromNetwork(resourceInfo);
  64. }
  65. ByteData data;
  66. if (FTheme.ins.defaultName == _manifestInfoRefer.name) {
  67. var path =
  68. "packages/${FisUIConsts.themePackageName}/assets/${resourceInfo.path}";
  69. data = await (FResourceBundle.assetBundle ?? rootBundle).load(path);
  70. } else {
  71. data = await _innerLoad(resourceInfo);
  72. }
  73. return data;
  74. }
  75. Future<ByteData> _loadFromNetwork(FResourceInfo resourceInfo) async {
  76. try {
  77. var bytes = await FHttpHelper.downloadBytes(resourceInfo.path);
  78. if (bytes != null) {
  79. return bytes.buffer.asByteData();
  80. }
  81. } catch (e) {
  82. logger.e("Load resource-${resourceInfo.key} from network error.", e);
  83. }
  84. return ByteData(0);
  85. }
  86. /// 获取字符串
  87. Future<String> loadString(FResourceKey key, {bool cache = true}) {
  88. if (cache)
  89. return _stringCache.putIfAbsent(key.value, () => _innerLoadString(key));
  90. return _innerLoadString(key);
  91. }
  92. /// 获取结构化数据
  93. Future<T> loadStructuredData<T>(
  94. FResourceKey key, Future<T> parser(String value)) {
  95. String keyString = key.value;
  96. if (_structuredDataCache.containsKey(keyString))
  97. return _structuredDataCache[keyString]! as Future<T>;
  98. Completer<T>? completer;
  99. Future<T>? result;
  100. loadString(key, cache: false).then<T>(parser).then<void>((T value) {
  101. result = SynchronousFuture<T>(value);
  102. _structuredDataCache[keyString] = result!;
  103. if (completer != null) {
  104. completer.complete(value);
  105. }
  106. });
  107. if (result != null) {
  108. return result!;
  109. }
  110. completer = Completer<T>();
  111. _structuredDataCache[keyString] = completer.future;
  112. return completer.future;
  113. }
  114. /// 获取字节数组
  115. Future<Uint8List> loadAsBytes(FResourceKey key) async {
  116. var byteData = await load(key);
  117. return byteData.buffer.asUint8List();
  118. }
  119. /// 载入资源map和初始化
  120. void init(FThemeManifestInfo manifestInfo) {
  121. try {
  122. _manifestInfoRefer = manifestInfo;
  123. _resourceMap = Map.fromIterable(manifestInfo.resources,
  124. key: (item) => (item as FResourceInfo).key, value: (item) => item);
  125. clearCache();
  126. } catch (e) {}
  127. }
  128. /// 收回指定缓存
  129. void evict(FResourceKey key) {
  130. _stringCache.remove(key.value);
  131. _structuredDataCache.remove(key.value);
  132. }
  133. /// 清除缓存
  134. void clearCache() {
  135. _stringCache.clear();
  136. _structuredDataCache.clear();
  137. print('FResourceBundle clear cache!');
  138. }
  139. Future<String> _innerLoadString(FResourceKey key, {bool cache = true}) async {
  140. final ByteData data = await load(key);
  141. if (data.lengthInBytes < 50 * 1024) {
  142. return utf8.decode(data.buffer.asUint8List());
  143. }
  144. return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "$key"');
  145. }
  146. static String _utf8decode(ByteData data) {
  147. return utf8.decode(data.buffer.asUint8List());
  148. }
  149. @override
  150. String toString() => '${describeIdentity(this)}()';
  151. FResourceInfo? _getInfo(String key) {
  152. if (_resourceMap.containsKey(key)) {
  153. return _resourceMap[key];
  154. }
  155. return null;
  156. }
  157. }
  158. class _FResourceBundleNative extends FResourceBundle {
  159. Directory? _storageDirectory;
  160. @override
  161. Future<ByteData> _innerLoad(FResourceInfo info) async {
  162. try {
  163. final String storagePath = await _getStoragePath();
  164. final String themeName = super._manifestInfoRefer.name;
  165. final String filePath = "$storagePath/themes/$themeName/${info.path}";
  166. final Uint8List bytes = await File(filePath).readAsBytes();
  167. return bytes.buffer.asByteData();
  168. } catch (e) {
  169. logger.e("Load resource-${info.key} from file error.", e);
  170. }
  171. return ByteData(0);
  172. }
  173. Future<String> _getStoragePath() async {
  174. if (_storageDirectory == null) {
  175. _storageDirectory = await getApplicationDocumentsDirectory();
  176. }
  177. return _storageDirectory!.path;
  178. }
  179. }
  180. class _FResourceBundleWeb extends FResourceBundle {
  181. @override
  182. Future<ByteData> _innerLoad(FResourceInfo info) async {
  183. String url =
  184. "${_getUrlRoot()}/${super._manifestInfoRefer.name}/${info.path}";
  185. Uint8List? data = await FHttpHelper.downloadBytes(url);
  186. if (data != null) {
  187. return data.buffer.asByteData();
  188. }
  189. return ByteData(0);
  190. }
  191. @protected
  192. String _getUrlRoot() => resourcePath;
  193. }
  194. class _FResourceBundleShellWeb extends _FResourceBundleWeb {
  195. @override
  196. String _getUrlRoot() => "http://resource.fis.plus/themes";
  197. }
  198. FResourceBundle _createBundle() {
  199. switch (FPlatform.current) {
  200. case FPlatformEnum.android:
  201. case FPlatformEnum.iOS:
  202. return _FResourceBundleNative();
  203. case FPlatformEnum.web:
  204. return _FResourceBundleWeb();
  205. case FPlatformEnum.webOnWin:
  206. case FPlatformEnum.webOnMac:
  207. return _FResourceBundleShellWeb();
  208. }
  209. }
  210. final FResourceBundle fRootBundle = _createBundle();