melon.yin пре 3 година
комит
a7043dd4e6

+ 75 - 0
.gitignore

@@ -0,0 +1,75 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3

+ 10 - 0
.metadata

@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: f4abaa0735eba4dfd8f33f73363911d63931fe03
+  channel: stable
+
+project_type: package

+ 3 - 0
CHANGELOG.md

@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.

+ 1 - 0
LICENSE

@@ -0,0 +1 @@
+TODO: Add your license here.

+ 14 - 0
README.md

@@ -0,0 +1,14 @@
+# fistheme
+
+A new Flutter package project.
+
+## Getting Started
+
+This project is a starting point for a Dart
+[package](https://flutter.dev/developing-packages/),
+a library module containing code that can be shared easily across
+multiple Flutter or Dart projects.
+
+For help getting started with Flutter, view our 
+[online documentation](https://flutter.dev/docs), which offers tutorials, 
+samples, guidance on mobile development, and a full API reference.

+ 7 - 0
lib/consts.dart

@@ -0,0 +1,7 @@
+class FisUIConsts {
+  /// 主题清单文件名
+  static final String manifestFileName = "manifest.json";
+
+  /// 主题资源包Package名称
+  static final String themePackageName = "fisresource";
+}

+ 25 - 0
lib/font_loader.dart

@@ -0,0 +1,25 @@
+import 'dart:collection';
+
+import 'package:fiscommon/logger/logger.dart';
+import 'package:flutter/services.dart';
+import 'resource.dart';
+
+class FFontLoader {
+  static final HashSet<String> _loadedFonts = HashSet();
+
+  /// 加载字体
+  static Future<bool> load(String fontFamily, FResourceKey resourceKey) async {
+    if (_loadedFonts.contains(fontFamily)) return true;
+
+    var fontLoader = FontLoader(fontFamily);
+    fontLoader.addFont(fRootBundle.load(resourceKey));
+    try {
+      await fontLoader.load();
+    } catch (e) {
+      logger.e("FFontLoader load [$fontFamily] error.", e);
+      return false;
+    }
+    _loadedFonts.add(fontFamily);
+    return true;
+  }
+}

+ 46 - 0
lib/locale_setting.dart

@@ -0,0 +1,46 @@
+class FThemeLocaleSettingItem {
+  FThemeLocaleSettingItem({
+    required this.fontFamily,
+    required this.fontFamilySource,
+  });
+
+  final String fontFamily;
+  final String fontFamilySource;
+
+  factory FThemeLocaleSettingItem.fromJson(Map<String, dynamic> map) {
+    return FThemeLocaleSettingItem(
+      fontFamily: map['fontFamily'],
+      fontFamilySource: map['fontFamilySource'],
+    );
+  }
+}
+
+class FThemeLocaleSettings {
+  FThemeLocaleSettings.raw({
+    required this.chinese,
+    required this.english,
+  });
+
+  final FThemeLocaleSettingItem chinese;
+  final FThemeLocaleSettingItem english;
+
+  factory FThemeLocaleSettings({
+    FThemeLocaleSettingItem? chinese,
+    FThemeLocaleSettingItem? english,
+  }) {
+    return FThemeLocaleSettings.raw(
+      chinese: FThemeLocaleSettingItem(
+          fontFamily: "QNYouYuan", fontFamilySource: "font.default.zh"),
+      english: FThemeLocaleSettingItem(
+          fontFamily: "CalibriR", fontFamilySource: "font.default.en"),
+    );
+  }
+
+  factory FThemeLocaleSettings.fromJson(Map<String, dynamic>? map) {
+    if (map == null) return FThemeLocaleSettings();
+    return FThemeLocaleSettings.raw(
+      chinese: FThemeLocaleSettingItem.fromJson(map['chinese']),
+      english: FThemeLocaleSettingItem.fromJson(map['english']),
+    );
+  }
+}

+ 84 - 0
lib/manifest.dart

@@ -0,0 +1,84 @@
+import 'locale_setting.dart';
+import 'resource.dart';
+import 'text_size_scheme.dart';
+import 'theme_color_scheme.dart';
+
+/// 主题资源清单
+class FThemeManifestInfo {
+  /// 主题名称
+  final String name;
+
+  /// 描述
+  final String description;
+
+  /// 作者
+  final String author;
+
+  /// 版本号
+  final String version;
+
+  /// 版本类型
+  final String versionType;
+
+  /// 客户端适配最小版本号限制
+  final String clientVersionMinimum;
+
+  /// 适配平台集合
+  final List<String> supportedPlatforms;
+
+  /// 资源信息集合
+  final List<FResourceInfo> resources;
+
+  /// 语言配置
+  final FThemeLocaleSettings localeSettings;
+
+  /// 配色信息
+  final FThemeColorScheme colorScheme;
+
+  /// 配色(深色)信息
+  final FThemeColorScheme? darkColorScheme;
+
+  /// 文字尺寸组合
+  final FThemeTextSizeScheme textSizeScheme;
+
+  const FThemeManifestInfo({
+    required this.name,
+    this.description = "",
+    this.author = "",
+    required this.version,
+    this.versionType = "flyinsono",
+    this.clientVersionMinimum = "1.0.0",
+    this.supportedPlatforms = const [],
+    this.resources = const [],
+    required this.localeSettings,
+    required this.colorScheme,
+    this.darkColorScheme,
+    required this.textSizeScheme,
+  });
+
+  factory FThemeManifestInfo.fromJson(Map<String, dynamic> json) {
+    List<dynamic> resourceJsonList = json['resources'];
+    var resources =
+        resourceJsonList.map((json) => FResourceInfo.fromJson(json)).toList();
+    List<dynamic> supportedPlatformJsonList = json['supportedPlatforms'];
+    List<String> supportedPlatformList =
+        supportedPlatformJsonList.cast<String>();
+
+    return FThemeManifestInfo(
+      name: json['name'],
+      description: json['description'],
+      author: json['author'],
+      version: json['version'],
+      versionType: json['versionType'],
+      clientVersionMinimum: json['clientVersionMinimum'],
+      supportedPlatforms: supportedPlatformList,
+      resources: resources,
+      localeSettings: FThemeLocaleSettings.fromJson(json['localeSettings']),
+      colorScheme: FThemeColorScheme.fromJson(json['colorScheme']),
+      darkColorScheme: json['darkColorScheme'] != null
+          ? FThemeColorScheme.fromJson(json['darkColorScheme'])
+          : null,
+      textSizeScheme: FThemeTextSizeScheme.fromJson(json['textSizeScheme']),
+    );
+  }
+}

+ 242 - 0
lib/resource.dart

@@ -0,0 +1,242 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:fiscommon/helpers/http.dart';
+import 'package:fiscommon/index.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:path_provider/path_provider.dart';
+
+import 'manifest.dart';
+import 'theme.dart';
+
+/// 资源来源
+enum FResourceFromType {
+  local, // 本地
+  network, // 网络
+}
+
+/// 资源配置信息实体
+class FResourceInfo {
+  final String key;
+  final FResourceFromType from;
+  final String path;
+
+  const FResourceInfo({
+    required this.key,
+    required this.from,
+    required this.path,
+  });
+
+  factory FResourceInfo.fromJson(Map<String, dynamic> json) {
+    return FResourceInfo(
+      key: json['key'],
+      from: FResourceFromType.values[json['from']],
+      path: json['path'],
+    );
+  }
+}
+
+/// 资源索引键
+class FResourceKey {
+  final String value;
+  const FResourceKey(this.value);
+}
+
+/// 主题资源包Package名称
+const String themePackageName = "fisresource";
+
+/// Fis资源集
+abstract class FResourceBundle {
+  late String _resourcePath;
+
+  /// 资源路径
+  String get resourcePath => _resourcePath;
+
+  /// 设置资源路径
+  void setResourcePath(String value) => _resourcePath = value;
+
+  final Map<String, Future<String>> _stringCache = <String, Future<String>>{};
+  final Map<String, Future<dynamic>> _structuredDataCache =
+      <String, Future<dynamic>>{};
+  Map<String, FResourceInfo> _resourceMap = Map<String, FResourceInfo>();
+
+  /// 主题清单内存映射
+  late FThemeManifestInfo _manifestInfoRefer;
+
+  @protected
+  Future<ByteData> _innerLoad(FResourceInfo info);
+
+  /// 加载资源数据
+  Future<ByteData> load(FResourceKey key) async {
+    FResourceInfo? resourceInfo = _getInfo(key.value);
+    if (resourceInfo == null) return ByteData(0);
+    if (resourceInfo.from == FResourceFromType.network) {
+      return await _loadFromNetwork(resourceInfo);
+    }
+    ByteData data;
+    if (FTheme.ins.defaultName == _manifestInfoRefer.name) {
+      var path = "packages/$themePackageName/assets/${resourceInfo.path}";
+      data = await rootBundle.load(path);
+    } else {
+      data = await _innerLoad(resourceInfo);
+    }
+    return data;
+  }
+
+  Future<ByteData> _loadFromNetwork(FResourceInfo resourceInfo) async {
+    try {
+      var bytes = await FHttpHelper.downloadBytes(resourceInfo.path);
+      if (bytes != null) {
+        return bytes.buffer.asByteData();
+      }
+    } catch (e) {
+      // TODO: add log
+    }
+    return ByteData(0);
+  }
+
+  /// 获取字符串
+  Future<String> loadString(FResourceKey key, {bool cache = true}) {
+    if (cache)
+      return _stringCache.putIfAbsent(key.value, () => _innerLoadString(key));
+    return _innerLoadString(key);
+  }
+
+  /// 获取结构化数据
+  Future<T> loadStructuredData<T>(
+      FResourceKey key, Future<T> parser(String value)) {
+    String keyString = key.value;
+    if (_structuredDataCache.containsKey(keyString))
+      return _structuredDataCache[keyString]! as Future<T>;
+    Completer<T>? completer;
+    Future<T>? result;
+    loadString(key, cache: false).then<T>(parser).then<void>((T value) {
+      result = SynchronousFuture<T>(value);
+      _structuredDataCache[keyString] = result!;
+      if (completer != null) {
+        completer.complete(value);
+      }
+    });
+    if (result != null) {
+      return result!;
+    }
+    completer = Completer<T>();
+    _structuredDataCache[keyString] = completer.future;
+    return completer.future;
+  }
+
+  /// 获取字节数组
+  Future<Uint8List> loadAsBytes(FResourceKey key) async {
+    var byteData = await load(key);
+    return byteData.buffer.asUint8List();
+  }
+
+  /// 载入资源map和初始化
+  void init(FThemeManifestInfo manifestInfo) {
+    try {
+      _manifestInfoRefer = manifestInfo;
+      _resourceMap = Map.fromIterable(manifestInfo.resources,
+          key: (item) => (item as FResourceInfo).key, value: (item) => item);
+      clearCache();
+    } catch (e) {}
+  }
+
+  /// 收回指定缓存
+  void evict(FResourceKey key) {
+    _stringCache.remove(key.value);
+    _structuredDataCache.remove(key.value);
+  }
+
+  /// 清除缓存
+  void clearCache() {
+    _stringCache.clear();
+    _structuredDataCache.clear();
+    print('FResourceBundle clear cache!');
+  }
+
+  Future<String> _innerLoadString(FResourceKey key, {bool cache = true}) async {
+    final ByteData data = await load(key);
+    if (data.lengthInBytes < 50 * 1024) {
+      return utf8.decode(data.buffer.asUint8List());
+    }
+    return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "$key"');
+  }
+
+  static String _utf8decode(ByteData data) {
+    return utf8.decode(data.buffer.asUint8List());
+  }
+
+  @override
+  String toString() => '${describeIdentity(this)}()';
+
+  FResourceInfo? _getInfo(String key) {
+    if (_resourceMap.containsKey(key)) {
+      return _resourceMap[key];
+    }
+    return null;
+  }
+}
+
+class _FResourceBundleNative extends FResourceBundle {
+  Directory? _storageDirectory;
+
+  @override
+  Future<ByteData> _innerLoad(FResourceInfo info) async {
+    try {
+      final String storagePath = await _getStoragePath();
+      final String themeName = super._manifestInfoRefer.name;
+      final String filePath = "$storagePath/themes/$themeName/${info.path}";
+      final Uint8List bytes = await File(filePath).readAsBytes();
+      return bytes.buffer.asByteData();
+    } catch (e) {
+      // TODO: add log
+    }
+    return ByteData(0);
+  }
+
+  Future<String> _getStoragePath() async {
+    if (_storageDirectory == null) {
+      _storageDirectory = await getApplicationDocumentsDirectory();
+    }
+    return _storageDirectory!.path;
+  }
+}
+
+class _FResourceBundleWeb extends FResourceBundle {
+  @override
+  Future<ByteData> _innerLoad(FResourceInfo info) async {
+    String url =
+        "${_getUrlRoot()}/${super._manifestInfoRefer.name}/${info.path}";
+    Uint8List? data = await FHttpHelper.downloadBytes(url);
+    if (data != null) {
+      return data.buffer.asByteData();
+    }
+    return ByteData(0);
+  }
+
+  @protected
+  String _getUrlRoot() => resourcePath;
+}
+
+class _FResourceBundleShellWeb extends _FResourceBundleWeb {
+  @override
+  String _getUrlRoot() => "http://resource.fis.plus/themes";
+}
+
+FResourceBundle _createBundle() {
+  switch (FPlatform.current) {
+    case FPlatformEnum.android:
+    case FPlatformEnum.iOS:
+      return _FResourceBundleNative();
+    case FPlatformEnum.web:
+      return _FResourceBundleWeb();
+    case FPlatformEnum.webOnWin:
+    case FPlatformEnum.webOnMac:
+      return _FResourceBundleShellWeb();
+  }
+}
+
+final FResourceBundle fRootBundle = _createBundle();

+ 53 - 0
lib/resource_image_provider.dart

@@ -0,0 +1,53 @@
+import 'dart:typed_data';
+import 'dart:ui' as ui show Codec;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+
+import 'resource.dart';
+import 'theme.dart';
+
+/// 资源图片Provider
+class FResourceImage extends ImageProvider<FResourceImage> {
+  FResourceImage(this.key, {this.scale = 1.0});
+  final FResourceKey key;
+  final double scale;
+  String _themeName = FTheme.ins.data.name;
+
+  @override
+  Future<FResourceImage> obtainKey(ImageConfiguration configuration) {
+    return SynchronousFuture<FResourceImage>(this);
+  }
+
+  @override
+  ImageStreamCompleter load(FResourceImage key, decode) {
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key, decode),
+      scale: key.scale,
+      debugLabel: 'FResourceImage(${describeIdentity(key.key)})',
+    );
+  }
+
+  Future<ui.Codec> _loadAsync(
+      FResourceImage key, DecoderCallback decode) async {
+    assert(key == this);
+    Uint8List bytes = await fRootBundle.loadAsBytes(this.key);
+    return decode(bytes);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) return false;
+    return other is FResourceImage &&
+        other.key == key &&
+        other.scale == scale &&
+        other._themeName == _themeName;
+  }
+
+  @override
+  int get hashCode => hashValues(key.hashCode, scale);
+
+  @override
+  String toString() =>
+      '${objectRuntimeType(this, 'FResourceImage')}(${describeIdentity(key)}, scale: $scale)';
+}

+ 79 - 0
lib/text_size_scheme.dart

@@ -0,0 +1,79 @@
+/// 主题文字尺寸组合
+class FThemeTextSizeScheme {
+  /// 大标题文字尺寸
+  final double headlineLargeSize;
+
+  /// 标题文字尺寸
+  final double headlineSize;
+
+  /// 小标题文字尺寸
+  final double headlineSmallSize;
+
+  /// 正文文字尺寸
+  final double bodySize;
+
+  /// 按钮文字尺寸
+  final double buttonSize;
+
+  /// 提示文字尺寸
+  final double tipsSize;
+
+  /// 提示(小)文字尺寸
+  final double tipsSmallSize;
+
+  factory FThemeTextSizeScheme({
+    double? headlineLargeSize,
+    double? headlineSize,
+    double? headlineSmallSize,
+    double? bodySize,
+    double? buttonSize,
+    double? tipsSize,
+    double? tipsSmallSize,
+  }) {
+    return FThemeTextSizeScheme.raw(
+      headlineLargeSize: headlineLargeSize ?? _FallbackValues.HeadlineLargeSize,
+      headlineSize: headlineSize ?? _FallbackValues.HeadlineSize,
+      headlineSmallSize: headlineSmallSize ?? _FallbackValues.HeadlineSmallSize,
+      bodySize: bodySize ?? _FallbackValues.BodySize,
+      buttonSize: buttonSize ?? _FallbackValues.ButtonSize,
+      tipsSize: tipsSize ?? _FallbackValues.TipsSize,
+      tipsSmallSize: tipsSmallSize ?? _FallbackValues.TipsSmallSize,
+    );
+  }
+
+  FThemeTextSizeScheme.raw({
+    required this.headlineLargeSize,
+    required this.headlineSize,
+    required this.headlineSmallSize,
+    required this.bodySize,
+    required this.buttonSize,
+    required this.tipsSize,
+    required this.tipsSmallSize,
+  });
+
+  factory FThemeTextSizeScheme.fromJson(Map<String, dynamic>? map) {
+    if (map == null) return FThemeTextSizeScheme();
+
+    return FThemeTextSizeScheme(
+      headlineLargeSize: map['headlineLargeSize'] as double?,
+      headlineSize: map['headlineSize'] as double?,
+      headlineSmallSize: map['headlineSmallSize'] as double?,
+      bodySize: map['bodySize'] as double?,
+      buttonSize: map['buttonSize'] as double?,
+      tipsSize: map['tipsSize'] as double?,
+      tipsSmallSize: map['tipsSmallSize'] as double?,
+    );
+  }
+}
+
+abstract class _FallbackValues {
+  _FallbackValues._();
+
+  static const double HeadlineLargeSize = 36;
+  static const double HeadlineSize = 30;
+  static const double HeadlineSmallSize = 26;
+  static const double BodySize = 20;
+  static const double ButtonSize = 16;
+  static const double TipsSize = 18;
+  static const double TipsSmallSize = 16;
+}

+ 214 - 0
lib/theme.dart

@@ -0,0 +1,214 @@
+import 'package:fiscommon/helpers/color.dart';
+import 'package:fiscommon/logger/logger.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+
+import 'manifest.dart';
+import 'text_size_scheme.dart';
+import 'theme_data.dart';
+import 'theme_loader.dart';
+import 'resource.dart';
+import 'theme_color_scheme.dart';
+
+export 'resource.dart';
+export 'resource_image_provider.dart';
+export 'manifest.dart';
+export 'font_loader.dart';
+export 'locale_setting.dart';
+export 'text_size_scheme.dart';
+export 'theme_data.dart';
+export 'theme_loader.dart';
+export 'font_loader.dart';
+
+abstract class FTheme {
+  FTheme({required String defaultName, required ThemeMode themeMode}) {
+    _defaultName = defaultName;
+    _themeMode = themeMode;
+  }
+
+  /* 静态单例 */
+  static bool _hasInit = false;
+  static FTheme? _instance;
+
+  /// 获取单例
+  static FTheme get ins {
+    if (!_hasInit) {
+      throw FlutterError("FTheme has not been init.");
+    }
+    return _instance!;
+  }
+
+  /// 单例初始化
+  static Future<void> init(FTheme instance, [String name = "lightness"]) async {
+    if (!_hasInit) {
+      _instance = instance;
+      _hasInit = true;
+      await instance.switchTheme(name);
+    }
+  }
+  /* 静态单例 end */
+
+  /// 当前语言是否中文
+  bool get isCurrentChinese;
+
+  /// 当前主题模式
+  Brightness get currentBrightness;
+
+  FThemeManifestInfo? _manifestInfo;
+
+  /// 主题清单
+  FThemeManifestInfo get manifestInfo => _manifestInfo!;
+
+  late String _defaultName;
+
+  late ThemeMode _themeMode;
+
+  /// 主题明暗模式
+  ThemeMode get themeMode => _themeMode;
+
+  /// 默认主题名
+  // @protected
+  String get defaultName => _defaultName;
+
+  late FThemeData _data;
+
+  /// 主题数据
+  FThemeData get data => _data;
+
+  /// 是否深色模式
+  bool get isDarkMode {
+    if (themeMode == ThemeMode.system) {
+      return currentBrightness == Brightness.dark;
+    } else {
+      return themeMode == ThemeMode.dark;
+    }
+  }
+
+  /// 颜色组合
+  FThemeColorScheme get colorScheme {
+    return isDarkMode ? _data.darkColorScheme : _data.colorScheme;
+  }
+
+  /// 文字尺寸组合
+  FThemeTextSizeScheme get textSizeScheme => _data.textSizeScheme;
+
+  late ThemeData _innerThemeData;
+  late ThemeData _innerDarkThemeData;
+
+  /// 内置主题数据
+  ThemeData get innerThemeData => getInnerThemeData(currentBrightness);
+
+  /// 应用主题数据
+  @protected
+  void applyThemeData();
+
+  /// 切换主题模式
+  Future<void> switchThemeMode(ThemeMode mode) async {
+    _themeMode = mode;
+  }
+
+  /// 根据明暗模式获取内置主题数据
+  // @protected
+  ThemeData getInnerThemeData(Brightness brightness) =>
+      brightness == Brightness.dark ? _innerDarkThemeData : _innerThemeData;
+
+  /// 重新加载
+  Future<void> reload() async {
+    await buildThemeData(this.data.name);
+    applyThemeData();
+  }
+
+  /// 切换主题
+  Future<bool> switchTheme([String? name]) async {
+    bool loaded = true;
+    if (name != null) {
+      loaded = await loadManifestInfo(name);
+    } else {
+      name = this.data.name;
+    }
+    if (loaded) {
+      await buildThemeData(name);
+      applyThemeData();
+      return true;
+    }
+    return false;
+  }
+
+  /// 加载主题清单
+  @protected
+  Future<bool> loadManifestInfo([String? name]) async {
+    try {
+      FThemeManifestInfo? manifestInfo =
+          await getThemeLoader(name ?? defaultName).load();
+      if (manifestInfo != null) {
+        _manifestInfo = manifestInfo;
+        fRootBundle.init(manifestInfo);
+        return true;
+      }
+    } catch (e) {
+      logger.e("加载主题清单失败", e);
+    }
+    return false;
+  }
+
+  @protected
+  Future<void> buildThemeData(String name) async {
+    _data = FThemeData(name, this.manifestInfo);
+    await buildInnerThemeData();
+  }
+
+  @protected
+  Future<void> buildInnerThemeData() async {
+    final localeSetting = this.isCurrentChinese
+        ? this.data.localeSettings.chinese
+        : this.data.localeSettings.english;
+    final textSizeScheme = this.data.textSizeScheme;
+
+    _innerThemeData = _createThemeData(
+      Brightness.light,
+      this.data.colorScheme,
+      textSizeScheme,
+      localeSetting.fontFamily,
+    );
+    _innerDarkThemeData = this.data.hasDarkColorScheme
+        ? _createThemeData(
+            Brightness.dark,
+            this.data.darkColorScheme,
+            textSizeScheme,
+            localeSetting.fontFamily,
+          )
+        : _innerThemeData;
+  }
+
+  ThemeData _createThemeData(
+      Brightness brightness,
+      FThemeColorScheme colorScheme,
+      FThemeTextSizeScheme textSizeScheme,
+      String fontFamily) {
+    var textTheme = TextTheme(
+      headline3: TextStyle(fontSize: textSizeScheme.headlineLargeSize), // 大标题
+      headline4: TextStyle(fontSize: textSizeScheme.headlineSize), // 标题
+      headline5: TextStyle(fontSize: textSizeScheme.headlineSmallSize), // 小标题
+      bodyText2: TextStyle(fontSize: textSizeScheme.bodySize), // 正文
+      button: TextStyle(fontSize: textSizeScheme.buttonSize), // 按钮文字
+    );
+    textTheme.apply(fontFamily: fontFamily);
+
+    return ThemeData(
+      brightness: brightness,
+      fontFamily: fontFamily,
+      primarySwatch: FColorHelper.createMaterialColor(colorScheme.primary),
+      accentColor: colorScheme.foreground, //前景色
+      backgroundColor: colorScheme.background, //背景色
+      buttonColor: colorScheme.primary, //按钮色
+      canvasColor: colorScheme.background, //基础色
+      dialogBackgroundColor: colorScheme.background, //Dialog背景色
+      disabledColor: colorScheme.line, // 禁用色
+      errorColor: colorScheme.additional, //错误色
+      hintColor: colorScheme.textTips, //提示或占位文字色
+      selectedRowColor: colorScheme.secondary, //选中色
+      splashColor: colorScheme.secondary, //InkWall色
+      textTheme: textTheme,
+    );
+  }
+}

+ 183 - 0
lib/theme_color_scheme.dart

@@ -0,0 +1,183 @@
+import 'dart:ui';
+
+import 'package:fiscommon/helpers/color.dart';
+
+/// 主题配色
+class FThemeColorScheme {
+  /// 主色
+  final Color primary;
+
+  /// 头部
+  final Color header;
+
+  /// 配色
+  final Color secondary;
+
+  /// 背景色
+  final Color background;
+
+  /// 导航色
+  final Color nav;
+
+  /// 线条(边框)色
+  final Color line;
+
+  /// 点缀色
+  final Color additional;
+
+  /// Dialog边框色
+  final Color dialogBorder;
+
+  /// Dialog边框色
+  final Color remedicalBackground;
+
+  /// 按钮主色
+  final Color buttonPrimary;
+
+  /// 按钮次色(取消色)
+  final Color buttonSecondary;
+
+  /// 文字色
+  final Color text;
+
+  /// 文字(选中)色
+  final Color textSelected;
+
+  /// 文字(提示文字)色
+  final Color textTips;
+
+  /// 滚动条色
+  final Color scrollBar;
+
+  /// 前景色
+  final Color foreground;
+
+  factory FThemeColorScheme({
+    Color? primary,
+    Color? header,
+    Color? secondary,
+    Color? background,
+    Color? nav,
+    Color? line,
+    Color? additional,
+    Color? dialogBorder,
+    Color? remedicalBackground,
+    Color? buttonPrimary,
+    Color? buttonSecondary,
+    Color? text,
+    Color? textSelected,
+    Color? textTips,
+    Color? scrollBar,
+    Color? foreground,
+  }) {
+    Color _primary = primary ??= _FallbackValues.Primary;
+    Color _header = header ??= _FallbackValues.Header;
+    Color _secondary = secondary ??= _FallbackValues.Secondary;
+    Color _background = background ??= _FallbackValues.Background;
+    Color _additional = additional ??= _FallbackValues.Additional;
+    Color _nav = nav ??= _FallbackValues.Nav;
+    Color _line = line ??= _FallbackValues.Line;
+
+    Color _dialogBorder = dialogBorder ??= _FallbackValues.DialogBorder;
+    Color _remedicalBackground =
+        remedicalBackground ??= _FallbackValues.RemedicalBackground;
+    Color _buttonPrimary = buttonPrimary ??= _FallbackValues.ButtonPrimary;
+    Color _buttonSecondary =
+        buttonSecondary ??= _FallbackValues.ButtonSecondary;
+    Color _text = text ??= _FallbackValues.Text;
+    Color _textSelected = textSelected ??= _FallbackValues.TextSelected;
+    Color _textTips = textTips ??= _FallbackValues.TextTips;
+    Color _scrollBar = scrollBar ??= _FallbackValues.scrollBar;
+    Color _foreground = foreground ??= _FallbackValues.Foreground;
+
+    return FThemeColorScheme.raw(
+      primary: _primary,
+      header: _header,
+      secondary: _secondary,
+      background: _background,
+      additional: _additional,
+      nav: _nav,
+      line: _line,
+      dialogBorder: _dialogBorder,
+      remedicalBackground: _remedicalBackground,
+      buttonPrimary: _buttonPrimary,
+      buttonSecondary: _buttonSecondary,
+      text: _text,
+      textSelected: _textSelected,
+      textTips: _textTips,
+      scrollBar: _scrollBar,
+      foreground: _foreground,
+    );
+  }
+
+  FThemeColorScheme.raw({
+    required this.primary,
+    required this.header,
+    required this.secondary,
+    required this.background,
+    required this.nav,
+    required this.line,
+    required this.additional,
+    required this.dialogBorder,
+    required this.remedicalBackground,
+    required this.buttonPrimary,
+    required this.buttonSecondary,
+    required this.text,
+    required this.textSelected,
+    required this.textTips,
+    required this.scrollBar,
+    required this.foreground,
+  });
+
+  factory FThemeColorScheme.fromJson(Map<String, dynamic>? map) {
+    if (map == null) return FThemeColorScheme();
+
+    return FThemeColorScheme(
+      primary: _convertHex2Color(map['primary']),
+      header: _convertHex2Color(map['header']),
+      secondary: _convertHex2Color(map['secondary']),
+      background: _convertHex2Color(map['background']),
+      nav: _convertHex2Color(map['nav']),
+      line: _convertHex2Color(map['line']),
+      additional: _convertHex2Color(map['additional']),
+      dialogBorder: _convertHex2Color(map['dialogBorder']),
+      remedicalBackground: _convertHex2Color(map['remedicalBackground']),
+      buttonPrimary: _convertHex2Color(map['buttonPrimary']),
+      buttonSecondary: _convertHex2Color(map['buttonSecondary']),
+      text: _convertHex2Color(map['text']),
+      textSelected: _convertHex2Color(map['textSelected']),
+      textTips: _convertHex2Color(map['textTips']),
+      scrollBar: _convertHex2Color(map['scrollBar']),
+      foreground: _convertHex2Color(map['foreground']),
+    );
+  }
+}
+
+const _transparency = const Color(0x00000000);
+
+Color? _convertHex2Color(String? hexString) {
+  if (hexString == null) return null;
+  if (hexString.isEmpty) return _transparency;
+  return FColorHelper.hex2Color(hexString, hasTag: false);
+}
+
+abstract class _FallbackValues {
+  _FallbackValues._();
+  static const Primary = const Color(0xff2c77e5);
+  static const Header = const Color(0xff2c77e5);
+  static const Secondary = const Color(0xff7bc3ff);
+  static const Nav = const Color(0xff0a2030);
+  static const Additional = const Color(0xfff1413d);
+  static const Line = const Color(0xffd3d3d3);
+  static const Background = const Color(0xfff2f2f2);
+
+  static const DialogBorder = const Color(0x00000000);
+  static const RemedicalBackground = const Color(0xff16161a);
+  static const ButtonPrimary = const Color(0xff2c77e5);
+  static const ButtonSecondary = const Color(0xffd4e3f9);
+  static const Text = const Color(0xff4c4948);
+  static const TextSelected = const Color(0xff16161a);
+  static const TextTips = const Color(0xffa5a3a3);
+  static const scrollBar = const Color(0xff99a2a8);
+  static const Foreground = const Color(0xffFFFFFF);
+}

+ 40 - 0
lib/theme_data.dart

@@ -0,0 +1,40 @@
+import 'package:flutter/foundation.dart';
+
+import 'locale_setting.dart';
+import 'manifest.dart';
+import 'theme_color_scheme.dart';
+import 'text_size_scheme.dart';
+
+class FThemeData with Diagnosticable {
+  FThemeData(this.name, FThemeManifestInfo manifestInfo) {
+    _localeSettings = manifestInfo.localeSettings;
+    _colorScheme = manifestInfo.colorScheme;
+    hasDarkColorScheme = manifestInfo.darkColorScheme != null;
+    _darkColorScheme = manifestInfo.darkColorScheme ?? manifestInfo.colorScheme;
+    _textSizeScheme = manifestInfo.textSizeScheme;
+  }
+
+  final String name;
+
+  late FThemeLocaleSettings _localeSettings;
+
+  /// 主题语言配置
+  FThemeLocaleSettings get localeSettings => _localeSettings;
+
+  late bool hasDarkColorScheme;
+
+  late FThemeColorScheme _colorScheme;
+
+  /// 颜色组合
+  FThemeColorScheme get colorScheme => _colorScheme;
+
+  late FThemeColorScheme _darkColorScheme;
+
+  /// 颜色组合(深色模式)
+  FThemeColorScheme get darkColorScheme => _darkColorScheme;
+
+  late FThemeTextSizeScheme _textSizeScheme;
+
+  /// 文字尺寸组合
+  FThemeTextSizeScheme get textSizeScheme => _textSizeScheme;
+}

+ 218 - 0
lib/theme_loader.dart

@@ -0,0 +1,218 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:archive/archive.dart';
+import 'package:dio/adapter.dart';
+import 'package:dio/dio.dart';
+import 'package:fiscommon/env/env.dart';
+import 'package:fiscommon/helpers/http.dart';
+import 'package:fiscommon/json_rpc/rpc_exception.dart';
+import 'package:fiscommon/json_rpc/rpc_request.dart';
+import 'package:fiscommon/logger/logger.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:path_provider/path_provider.dart';
+
+import 'theme.dart';
+import 'consts.dart';
+import 'resource.dart';
+import 'manifest.dart';
+
+abstract class FThemeLoader {
+  /// 主题名称
+  final String name;
+  FThemeLoader(this.name);
+
+  /// 加载主题清单
+  Future<FThemeManifestInfo?> load() async {
+    bool isDefault = FTheme.ins.defaultName == this.name;
+    if (!isDefault) {
+      bool pkgLoaded = await _loadPackage();
+      if (!pkgLoaded) {
+        logger.w("加载主题包失败");
+        return null;
+      }
+    }
+    String? manifestJson =
+        isDefault ? await _getDefaultManifestJson() : await _getManifestJson();
+    if (manifestJson == null) {
+      logger.w("主题清单json为空");
+      return null;
+    }
+
+    try {
+      Map<String, dynamic> json = jsonDecode(manifestJson);
+      FThemeManifestInfo manifestInfo = FThemeManifestInfo.fromJson(json);
+      return manifestInfo;
+    } catch (e) {
+      logger.e("解析主题清单Json失败", e);
+    }
+    return null;
+  }
+
+  /// 加载主题包
+  Future<bool> _loadPackage();
+
+  /// 获取清单Json字符串
+  Future<String?> _getManifestJson();
+
+  /// 获取默认主题清单Json字符串
+  Future<String?> _getDefaultManifestJson() async {
+    try {
+      String assetPath =
+          "packages/${FisUIConsts.themePackageName}/assets/${FisUIConsts.manifestFileName}";
+      String jsonString = await rootBundle.loadString(assetPath, cache: false);
+      return jsonString;
+    } catch (e) {
+      logger.e("读取默认主题清单Json失败", e);
+    }
+    return null;
+  }
+}
+
+/// 原生平台(iOS/Android)主题加载器
+class FThemeLoaderNative extends FThemeLoader {
+  FThemeLoaderNative(String name) : super(name);
+
+  Directory? _storageDirectory;
+
+  @override
+  Future<bool> _loadPackage() async {
+    try {
+      final String storagePath = await _getStoragePath();
+      final String pkgFileName = "$storagePath/themes/${this.name}.zip";
+      if (!await File(pkgFileName).exists()) {
+        final String url = _getPackageUrl();
+        final Dio dio = Dio(BaseOptions(connectTimeout: 10 * 1000));
+        (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
+            (client) {
+          // handle for cors
+          client.badCertificateCallback =
+              (X509Certificate cert, String host, int port) => true;
+        };
+        var response = await dio.download(url, pkgFileName);
+        if (response.statusCode == null ||
+            response.statusCode != HttpStatus.ok) {
+          logger.w("下载主题包失败, Http code - ${response.statusCode}");
+          return false;
+        }
+      }
+      final zipBytes = await File(pkgFileName).readAsBytes();
+      final archive = ZipDecoder().decodeBytes(zipBytes);
+      for (final file in archive) {
+        final filePath = "$storagePath/themes/${this.name}/${file.name}";
+        if (file.isFile) {
+          final data = file.content as List<int>;
+          File fileInfo = File(filePath);
+          await fileInfo.create(recursive: true);
+          await fileInfo.writeAsBytes(data);
+        } else {
+          await Directory(filePath).create(recursive: true);
+        }
+      }
+      return true;
+    } catch (e) {
+      print(e);
+      logger.e("加载主题包异常", e);
+    }
+    return false;
+  }
+
+  @override
+  Future<String?> _getManifestJson() async {
+    try {
+      final String storagePath = await _getStoragePath();
+      String filePath =
+          "$storagePath/themes/${this.name}/${FisUIConsts.manifestFileName}";
+      String json = await File(filePath).readAsString();
+      return json;
+    } catch (e) {
+      logger.e("读取主题清单Json异常", e);
+    }
+    return null;
+  }
+
+  /// 获取主题文件存储路径
+  Future<String> _getStoragePath() async {
+    if (_storageDirectory == null) {
+      _storageDirectory = await getApplicationDocumentsDirectory();
+    }
+    return _storageDirectory!.path;
+  }
+
+  /// 获取主题包链接地址
+  String _getPackageUrl() => "${fRootBundle.resourcePath}/${this.name}.zip";
+}
+
+/// Web主题加载器
+class FThemeLoaderWeb extends FThemeLoader {
+  FThemeLoaderWeb(String name) : super(name);
+
+  @override
+  Future<String?> _getManifestJson() async {
+    try {
+      String url =
+          "${getUrlRoot()}/${this.name}/${FisUIConsts.manifestFileName}";
+      String? json = await FHttpHelper.downloadString(url);
+      return json;
+    } catch (e) {
+      logger.e("读取主题清单Json异常", e);
+    }
+    return null;
+  }
+
+  @override
+  Future<bool> _loadPackage() async => true;
+
+  /// 获取链接头
+  @protected
+  String getUrlRoot() => fRootBundle.resourcePath;
+}
+
+/// 套壳Web(Win/Mac)主题加载器
+class FThemeLoaderShellWeb extends FThemeLoaderWeb {
+  FThemeLoaderShellWeb(String name) : super(name);
+
+  @override
+  Future<bool> _loadPackage() async {
+    try {
+      final url = "http://platform.fis.plus/IPlatformService";
+      final rpcRequest = JsonRpcRequest("LoadTheme", name);
+      final String package = jsonEncode(rpcRequest.toJson());
+      final dio = Dio();
+      dio.options.sendTimeout = 30 * 1000;
+      dio.options.contentType = Headers.jsonContentType;
+      dio.options.responseType = ResponseType.json;
+      final response = await dio.post(url, data: package);
+      if (response.statusCode == HttpStatus.ok && response.data != null) {
+        var resp = response.data;
+        if (resp.containsKey('error')) {
+          throw JsonRpcServerError.fromJson(resp['error']);
+        }
+        return resp['result'];
+      }
+    } catch (e) {
+      logger.e("加载主题包异常", e);
+    }
+    return false;
+  }
+
+  @override
+  String getUrlRoot() => "http://resource.fis.plus/themes";
+}
+
+/// 获取主题加载器
+///
+/// [name]主题名称
+FThemeLoader getThemeLoader(String name) {
+  switch (FPlatform.current) {
+    case FPlatformEnum.android:
+    case FPlatformEnum.iOS:
+      return FThemeLoaderNative(name);
+    case FPlatformEnum.web:
+      return FThemeLoaderWeb(name);
+    case FPlatformEnum.webOnWin:
+    case FPlatformEnum.webOnMac:
+      return FThemeLoaderShellWeb(name);
+  }
+}

+ 303 - 0
pubspec.lock

@@ -0,0 +1,303 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  archive:
+    dependency: "direct main"
+    description:
+      name: archive
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.2"
+  async:
+    dependency: transitive
+    description:
+      name: async
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.6.1"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  characters:
+    dependency: transitive
+    description:
+      name: characters
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  charcode:
+    dependency: transitive
+    description:
+      name: charcode
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.15.0"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.1"
+  dio:
+    dependency: "direct main"
+    description:
+      name: dio
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.0"
+  fake_async:
+    dependency: transitive
+    description:
+      name: fake_async
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  ffi:
+    dependency: transitive
+    description:
+      name: ffi
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.2"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "6.1.2"
+  fiscommon:
+    dependency: "direct main"
+    description:
+      path: "."
+      ref: HEAD
+      resolved-ref: be80b5b194dce81ae4f194a63d60055e3d9fd19e
+      url: "http://git.ius.plus:88/Flyinsono-Packages/fis-common.git"
+    source: git
+    version: "0.0.1"
+  flutter:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_test:
+    dependency: "direct dev"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  http:
+    dependency: transitive
+    description:
+      name: http
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.13.3"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.0"
+  intl:
+    dependency: transitive
+    description:
+      name: intl
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.17.0"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.3"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.12.10"
+  meta:
+    dependency: transitive
+    description:
+      name: meta
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.3.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.8.0"
+  path_provider:
+    dependency: "direct main"
+    description:
+      name: path_provider
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.4"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  path_provider_macos:
+    dependency: transitive
+    description:
+      name: path_provider_macos
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.3"
+  pedantic:
+    dependency: transitive
+    description:
+      name: pedantic
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.11.1"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.2"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  process:
+    dependency: transitive
+    description:
+      name: process
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.2.3"
+  sky_engine:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.99"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.8.1"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.10.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  synchronized:
+    dependency: transitive
+    description:
+      name: synchronized
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.0"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.3.0"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.3.0"
+  vector_math:
+    dependency: transitive
+    description:
+      name: vector_math
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.9"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.0"
+sdks:
+  dart: ">=2.13.0 <3.0.0"
+  flutter: ">=2.0.0"

+ 64 - 0
pubspec.yaml

@@ -0,0 +1,64 @@
+name: fistheme
+description: A new Flutter package project.
+version: 0.0.1
+homepage:
+
+# add this line for pathing dependency
+publish_to: none
+
+environment:
+  sdk: ">=2.12.0 <3.0.0"
+  flutter: ">=1.17.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+  fiscommon:
+      git:
+          url: http://git.ius.plus:88/Flyinsono-Packages/fis-common.git
+      # path: ../common/
+  path_provider: ^2.0.2
+  dio: ^4.0.0
+  archive: ^3.1.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
+
+  # To add assets to your package, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+  #
+  # For details regarding assets in packages, see
+  # https://flutter.dev/assets-and-images/#from-packages
+  #
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware.
+
+  # To add custom fonts to your package, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts in packages, see
+  # https://flutter.dev/custom-fonts/#from-packages

+ 5 - 0
test/fistheme_test.dart

@@ -0,0 +1,5 @@
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('adds one to input values', () {});
+}