theme_loader.dart 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:archive/archive.dart';
  4. import 'package:dio/adapter.dart';
  5. import 'package:dio/dio.dart';
  6. import 'package:fis_common/env/env.dart';
  7. import 'package:fis_common/helpers/http.dart';
  8. import 'package:fis_common/logger/logger.dart';
  9. import 'package:fis_jsonrpc/exception.dart';
  10. import 'package:fis_jsonrpc/request.dart';
  11. import 'package:flutter/foundation.dart';
  12. import 'package:flutter/services.dart';
  13. import 'package:path_provider/path_provider.dart';
  14. import 'theme.dart';
  15. import 'consts.dart';
  16. abstract class FThemeLoader {
  17. /// 主题名称
  18. final String name;
  19. FThemeLoader(this.name);
  20. /// 加载主题清单
  21. Future<FThemeManifestInfo?> load() async {
  22. bool isDefault = FTheme.ins.defaultName == this.name;
  23. if (!isDefault) {
  24. bool pkgLoaded = await _loadPackage();
  25. if (!pkgLoaded) {
  26. logger.w("加载主题包失败");
  27. return null;
  28. }
  29. }
  30. String? manifestJson =
  31. isDefault ? await _getDefaultManifestJson() : await _getManifestJson();
  32. if (manifestJson == null) {
  33. logger.w("主题清单json为空");
  34. return null;
  35. }
  36. try {
  37. Map<String, dynamic> json = jsonDecode(manifestJson);
  38. FThemeManifestInfo manifestInfo = FThemeManifestInfo.fromJson(json);
  39. return manifestInfo;
  40. } catch (e) {
  41. logger.e("解析主题清单Json失败", e);
  42. }
  43. return null;
  44. }
  45. /// 加载主题包
  46. Future<bool> _loadPackage();
  47. /// 获取清单Json字符串
  48. Future<String?> _getManifestJson();
  49. /// 获取默认主题清单Json字符串
  50. Future<String?> _getDefaultManifestJson() async {
  51. try {
  52. String assetPath =
  53. "packages/${FisUIConsts.themePackageName}/assets/${FisUIConsts.manifestFileName}";
  54. String jsonString = await (FResourceBundle.assetBundle ?? rootBundle)
  55. .loadString(assetPath, cache: false);
  56. return jsonString;
  57. } catch (e) {
  58. logger.e("读取默认主题清单Json失败", e);
  59. }
  60. return null;
  61. }
  62. }
  63. /// 原生平台(iOS/Android)主题加载器
  64. class FThemeLoaderNative extends FThemeLoader {
  65. FThemeLoaderNative(String name) : super(name);
  66. Directory? _storageDirectory;
  67. @override
  68. Future<bool> _loadPackage() async {
  69. try {
  70. final String storagePath = await _getStoragePath();
  71. final String pkgFileName = "$storagePath/themes/${this.name}.zip";
  72. if (!await File(pkgFileName).exists()) {
  73. final String url = _getPackageUrl();
  74. final Dio dio = Dio(BaseOptions(connectTimeout: 10 * 1000));
  75. (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
  76. (client) {
  77. // handle for cors
  78. client.badCertificateCallback =
  79. (X509Certificate cert, String host, int port) => true;
  80. };
  81. var response = await dio.download(url, pkgFileName);
  82. if (response.statusCode == null ||
  83. response.statusCode != HttpStatus.ok) {
  84. logger.w("下载主题包失败, Http code - ${response.statusCode}");
  85. return false;
  86. }
  87. }
  88. final zipBytes = await File(pkgFileName).readAsBytes();
  89. final archive = ZipDecoder().decodeBytes(zipBytes);
  90. for (final file in archive) {
  91. final filePath = "$storagePath/themes/${this.name}/${file.name}";
  92. if (file.isFile) {
  93. final data = file.content as List<int>;
  94. File fileInfo = File(filePath);
  95. await fileInfo.create(recursive: true);
  96. await fileInfo.writeAsBytes(data);
  97. } else {
  98. await Directory(filePath).create(recursive: true);
  99. }
  100. }
  101. return true;
  102. } catch (e) {
  103. print(e);
  104. logger.e("加载主题包异常", e);
  105. }
  106. return false;
  107. }
  108. @override
  109. Future<String?> _getManifestJson() async {
  110. try {
  111. final String storagePath = await _getStoragePath();
  112. String filePath =
  113. "$storagePath/themes/${this.name}/${FisUIConsts.manifestFileName}";
  114. String json = await File(filePath).readAsString();
  115. return json;
  116. } catch (e) {
  117. logger.e("读取主题清单Json异常", e);
  118. }
  119. return null;
  120. }
  121. /// 获取主题文件存储路径
  122. Future<String> _getStoragePath() async {
  123. if (_storageDirectory == null) {
  124. _storageDirectory = await getApplicationDocumentsDirectory();
  125. }
  126. return _storageDirectory!.path;
  127. }
  128. /// 获取主题包链接地址
  129. String _getPackageUrl() => "${fRootBundle.resourcePath}/${this.name}.zip";
  130. }
  131. /// Web主题加载器
  132. class FThemeLoaderWeb extends FThemeLoader {
  133. FThemeLoaderWeb(String name) : super(name);
  134. @override
  135. Future<String?> _getManifestJson() async {
  136. try {
  137. String url =
  138. "${getUrlRoot()}/${this.name}/${FisUIConsts.manifestFileName}";
  139. String? json = await FHttpHelper.downloadString(url);
  140. return json;
  141. } catch (e) {
  142. logger.e("读取主题清单Json异常", e);
  143. }
  144. return null;
  145. }
  146. @override
  147. Future<bool> _loadPackage() async => true;
  148. /// 获取链接头
  149. @protected
  150. String getUrlRoot() => fRootBundle.resourcePath;
  151. }
  152. /// 套壳Web(Win/Mac)主题加载器
  153. class FThemeLoaderShellWeb extends FThemeLoaderWeb {
  154. FThemeLoaderShellWeb(String name) : super(name);
  155. @override
  156. Future<bool> _loadPackage() async {
  157. try {
  158. final url = "http://platform.fis.plus/IPlatformService";
  159. final rpcRequest = JsonRpcRequest("LoadTheme", name);
  160. final String package = jsonEncode(rpcRequest.toJson());
  161. final dio = Dio();
  162. dio.options.sendTimeout = 30 * 1000;
  163. dio.options.contentType = Headers.jsonContentType;
  164. dio.options.responseType = ResponseType.json;
  165. final response = await dio.post(url, data: package);
  166. if (response.statusCode == HttpStatus.ok && response.data != null) {
  167. var resp = response.data;
  168. if (resp.containsKey('error')) {
  169. throw JsonRpcServerError.fromJson(resp['error']);
  170. }
  171. return resp['result'];
  172. }
  173. } catch (e) {
  174. logger.e("加载主题包异常", e);
  175. }
  176. return false;
  177. }
  178. @override
  179. String getUrlRoot() => "http://resource.fis.plus/themes";
  180. }
  181. /// 获取主题加载器
  182. ///
  183. /// [name]主题名称
  184. FThemeLoader getThemeLoader(String name) {
  185. switch (FPlatform.current) {
  186. case FPlatformEnum.android:
  187. case FPlatformEnum.iOS:
  188. return FThemeLoaderNative(name);
  189. case FPlatformEnum.web:
  190. return FThemeLoaderWeb(name);
  191. case FPlatformEnum.webOnWin:
  192. case FPlatformEnum.webOnMac:
  193. return FThemeLoaderShellWeb(name);
  194. }
  195. }