├── example ├── packages │ ├── base │ │ ├── lib │ │ │ ├── config.dart │ │ │ ├── assets │ │ │ │ └── fonts │ │ │ │ │ ├── Ubuntu-Bold.ttf │ │ │ │ │ ├── Ubuntu-Light.ttf │ │ │ │ │ ├── OpenSans-Bold.ttf │ │ │ │ │ ├── OpenSans-Italic.ttf │ │ │ │ │ ├── OpenSans-Light.ttf │ │ │ │ │ ├── Ubuntu-Regular.ttf │ │ │ │ │ ├── OpenSans-Regular.ttf │ │ │ │ │ └── OpenSans-SemiBold.ttf │ │ │ └── themes.dart │ │ ├── README.md │ │ ├── pubspec.yaml │ │ └── pubspec.lock │ ├── mobile │ │ ├── android │ │ │ ├── gradle.properties │ │ │ ├── gradle │ │ │ │ └── wrapper │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── app │ │ │ │ ├── src │ │ │ │ │ └── main │ │ │ │ │ │ ├── res │ │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ │ ├── values │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── drawable │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ ├── java │ │ │ │ │ │ └── com │ │ │ │ │ │ │ └── example │ │ │ │ │ │ │ └── sharedthemeexamplemobile │ │ │ │ │ │ │ └── MainActivity.java │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ └── build.gradle │ │ │ ├── .gitignore │ │ │ ├── settings.gradle │ │ │ ├── build.gradle │ │ │ ├── gradlew.bat │ │ │ └── gradlew │ │ ├── ios │ │ │ ├── Flutter │ │ │ │ ├── Debug.xcconfig │ │ │ │ ├── Release.xcconfig │ │ │ │ └── AppFrameworkInfo.plist │ │ │ ├── Runner │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── Assets.xcassets │ │ │ │ │ ├── LaunchImage.imageset │ │ │ │ │ │ ├── LaunchImage.png │ │ │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── AppIcon.appiconset │ │ │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── main.m │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Base.lproj │ │ │ │ │ ├── Main.storyboard │ │ │ │ │ └── LaunchScreen.storyboard │ │ │ │ └── Info.plist │ │ │ ├── Runner.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ │ ├── Runner.xcodeproj │ │ │ │ ├── project.xcworkspace │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ │ └── xcschemes │ │ │ │ │ └── Runner.xcscheme │ │ │ └── .gitignore │ │ ├── .gitignore │ │ ├── README.md │ │ ├── .idea │ │ │ ├── runConfigurations │ │ │ │ └── main_dart.xml │ │ │ ├── libraries │ │ │ │ ├── Flutter_for_Android.xml │ │ │ │ └── Dart_SDK.xml │ │ │ ├── modules.xml │ │ │ └── workspace.xml │ │ ├── .metadata │ │ ├── sharedtheme_example_mobile.iml │ │ ├── test │ │ │ └── widget_test.dart │ │ ├── sharedtheme_example_mobile_android.iml │ │ ├── pubspec.yaml │ │ ├── lib │ │ │ └── main.dart │ │ └── pubspec.lock │ ├── web │ │ ├── CHANGELOG.md │ │ ├── web │ │ │ ├── favicon.png │ │ │ ├── main.dart │ │ │ ├── index.html │ │ │ └── styles.scss │ │ ├── .idea │ │ │ ├── watcherTasks.xml │ │ │ ├── misc.xml │ │ │ ├── vcs.xml │ │ │ ├── modules.xml │ │ │ ├── web.iml │ │ │ └── libraries │ │ │ │ └── Dart_SDK.xml │ │ ├── bin │ │ │ └── build_themes.dart │ │ ├── lib │ │ │ ├── app_component.scss │ │ │ ├── app_component.html │ │ │ ├── src │ │ │ │ └── example_list │ │ │ │ │ ├── example_list.dart │ │ │ │ │ ├── example_list.scss │ │ │ │ │ └── example_list.html │ │ │ └── app_component.dart │ │ ├── .gitignore │ │ ├── pubspec.yaml │ │ ├── analysis_options.yaml │ │ ├── test │ │ │ └── app_test.dart │ │ └── README.md │ └── web-mdc │ │ ├── CHANGELOG.md │ │ ├── web │ │ ├── favicon.png │ │ ├── package.json │ │ ├── main.dart │ │ ├── index.html │ │ └── styles.scss │ │ ├── .idea │ │ ├── watcherTasks.xml │ │ ├── misc.xml │ │ ├── vcs.xml │ │ ├── modules.xml │ │ ├── web.iml │ │ └── libraries │ │ │ └── Dart_SDK.xml │ │ ├── bin │ │ └── build_themes.dart │ │ ├── .gitignore │ │ ├── pubspec.yaml │ │ └── README.md ├── images │ ├── web-dark.png │ ├── web-light.png │ ├── mobile-dark.png │ └── mobile-light.png ├── README.md └── example.dart ├── .gitignore ├── CHANGELOG.md ├── pubspec.yaml ├── lib ├── _themify.scss ├── src │ ├── css.dart │ ├── colors.dart │ ├── elements.dart │ └── fonts.dart └── shared_theme.dart ├── LICENSE ├── README.md ├── pubspec.lock └── doc └── how-to.md /example/packages/base/lib/config.dart: -------------------------------------------------------------------------------- 1 | const appName = 'Shared Theme Demo'; 2 | -------------------------------------------------------------------------------- /example/packages/mobile/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/packages/web/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version, created by Stagehand 4 | -------------------------------------------------------------------------------- /example/packages/web-mdc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version, created by Stagehand 4 | -------------------------------------------------------------------------------- /example/images/web-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/images/web-dark.png -------------------------------------------------------------------------------- /example/images/web-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/images/web-light.png -------------------------------------------------------------------------------- /example/images/mobile-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/images/mobile-dark.png -------------------------------------------------------------------------------- /example/images/mobile-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/images/mobile-light.png -------------------------------------------------------------------------------- /example/packages/web/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/web/web/favicon.png -------------------------------------------------------------------------------- /example/packages/web-mdc/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/web-mdc/web/favicon.png -------------------------------------------------------------------------------- /example/packages/mobile/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/Ubuntu-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/Ubuntu-Bold.ttf -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/Ubuntu-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/Ubuntu-Light.ttf -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/Ubuntu-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/Ubuntu-Regular.ttf -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /example/packages/base/lib/assets/fonts/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/base/lib/assets/fonts/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/packages/mobile/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/packages/web/.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /example/packages/web-mdc/.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/packages/web/web/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'package:sharedtheme_example_web/app_component.template.dart' as ng; 3 | 4 | void main() { 5 | runApp(ng.AppComponentNgFactory); 6 | } 7 | -------------------------------------------------------------------------------- /example/packages/mobile/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/packages/web-mdc/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /example/packages/web/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/packages/base/README.md: -------------------------------------------------------------------------------- 1 | # sharedtheme_example 2 | 3 | This is the base package for a project that has both mobile and web versions. 4 | 5 | Included here is anything common to both apps, such as the themes, config, and 6 | assets. -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/packages/web/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jifalops/shared_theme/HEAD/example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/packages/web-mdc/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/packages/web-mdc/bin/build_themes.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:sharedtheme_example/themes.dart'; 3 | 4 | void main() async { 5 | await File('web/_themes.g.scss').writeAsString(themeset.toString(), flush: true); 6 | } 7 | -------------------------------------------------------------------------------- /example/packages/web/bin/build_themes.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:sharedtheme_example/themes.dart'; 3 | 4 | void main() async { 5 | await File('lib/src/_themes.g.scss').writeAsString(themeset.toString(), flush: true); 6 | } 7 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/packages/mobile/README.md: -------------------------------------------------------------------------------- 1 | # sharedtheme_example_mobile 2 | 3 | This is the Flutter version of the app. It is pretty much just [main.dart](lib/main.dart), which shows how to use the project's base package and the `shared_theme_flutter` package to style your app. -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/packages/web/lib/app_component.scss: -------------------------------------------------------------------------------- 1 | @import 'package:sharedtheme_example_web/src/themes.g'; 2 | 3 | :host { 4 | .material-header { 5 | @include font-title; 6 | @include primary-color; 7 | } 8 | #content { 9 | padding: 8px; 10 | } 11 | } -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/packages/web/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | doc/api/ 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.2] - September 27, 2018 2 | 3 | * Add persistence to examples, doc improvements. 4 | 5 | ## [0.1.1] - September 26, 2018 6 | 7 | * Analyzer fixes. 8 | 9 | ## [0.1.0] - September 26, 2018 10 | 11 | * Doc improvements. 12 | 13 | ## [0.0.2] - September 26, 2018 14 | 15 | * First release. 16 | -------------------------------------------------------------------------------- /example/packages/mobile/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /example/packages/web-mdc/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/packages/web/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/packages/web-mdc/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | doc/api/ 12 | 13 | node_modules/ -------------------------------------------------------------------------------- /example/packages/mobile/.idea/runConfigurations/main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /example/packages/mobile/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5ab9e70727d858def3a586db7fb98ee580352957 8 | channel: beta 9 | -------------------------------------------------------------------------------- /example/packages/mobile/.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shared_theme 2 | description: Easily share a theme or set of themes between Flutter and the web. 3 | version: 0.1.2 4 | author: Jacob Phillips 5 | homepage: https://github.com/jifalops/shared_theme 6 | 7 | dependencies: 8 | meta: ^1.1.6 9 | 10 | dev_dependencies: 11 | test: ^1.3.2 12 | 13 | environment: 14 | sdk: ">=2.0.0 <3.0.0" -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/packages/web-mdc/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdc_web_example_demo", 3 | "version": "1.0.0", 4 | "description": "This package is for installing 'material-components-web' ", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "license": "MIT", 10 | "devDependencies": { 11 | "material-components-web": "^0.40.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/packages/web/lib/app_component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{name}} 4 |
5 | 7 |
8 |
9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /example/packages/base/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sharedtheme_example 2 | description: Example base package that uses shared_theme 3 | version: 1.0.0 4 | author: Jacob Phillips 5 | homepage: https://github.com/jifalops/shared_theme/tree/master/example/packages/base 6 | 7 | dependencies: 8 | shared_theme: any 9 | meta: ^1.1.6 10 | 11 | dev_dependencies: 12 | test: ^1.3.2 13 | 14 | environment: 15 | sdk: ">=2.0.0 <3.0.0" -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/packages/web-mdc/web/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | import 'package:mdc_web/mdc_web.dart'; 3 | 4 | void main() { 5 | /// Automatically creates MDC-Web components from html elements that have a 6 | /// `data-mdc-auto-init=""` attribute. 7 | autoInit(); 8 | 9 | /// Programmatically add a ripple to all elements with a class that includes 10 | /// "mdc-button". 11 | querySelectorAll('.mdc-button').forEach(MDCRipple.attachTo); 12 | } 13 | -------------------------------------------------------------------------------- /example/packages/mobile/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/java/com/example/sharedthemeexamplemobile/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.sharedthemeexamplemobile; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/packages/mobile/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/packages/web/lib/src/example_list/example_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'package:angular_components/angular_components.dart'; 3 | 4 | @Component( 5 | selector: 'example-list', 6 | styleUrls: ['example_list.css'], 7 | templateUrl: 'example_list.html', 8 | directives: [ 9 | MaterialButtonComponent, 10 | MaterialListComponent, 11 | MaterialListItemComponent, 12 | materialInputDirectives, 13 | ], 14 | ) 15 | class ExampleList { 16 | void showSnackbar() { 17 | print('Snackbar is not an AngularDart component.'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/packages/mobile/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.1.2' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/packages/web/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sharedtheme_example_web 2 | description: Example use of shared_theme for web 3 | version: 1.0.0 4 | author: Jacob Phillips 5 | homepage: https://github.com/jifalops/shared_theme/tree/master/example/packages/web 6 | 7 | environment: 8 | sdk: '>=2.0.0 <3.0.0' 9 | 10 | dependencies: 11 | sharedtheme_example: 12 | path: ../base 13 | angular: ^5.0.0 14 | angular_components: ^0.9.0 15 | 16 | dev_dependencies: 17 | angular_test: ^2.0.0 18 | build_runner: ^0.10.0 19 | build_test: ^0.10.2 20 | build_web_compilers: ^0.4.0 21 | test: ^1.0.0 22 | sass_builder: ^2.0.0 23 | -------------------------------------------------------------------------------- /example/packages/web/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: [build/**] 3 | errors: 4 | uri_has_not_been_generated: ignore 5 | # Angular plugin support is in beta. You're welcome to try it and report 6 | # issues: https://github.com/dart-lang/angular_analyzer_plugin/issues 7 | # plugins: 8 | # - angular 9 | 10 | # Lint rules and documentation, see http://dart-lang.github.io/linter/lints 11 | linter: 12 | rules: 13 | - cancel_subscriptions 14 | - hash_and_equals 15 | - iterable_contains_unrelated_type 16 | - list_remove_unrelated_type 17 | - test_types_in_equals 18 | - unrelated_type_equality_checks 19 | - valid_regexps 20 | -------------------------------------------------------------------------------- /example/packages/web/lib/src/example_list/example_list.scss: -------------------------------------------------------------------------------- 1 | @import '../themes.g'; 2 | 3 | :host { 4 | material-list-item { 5 | padding: 0; 6 | 7 | /// Give large text some extra room. 8 | &[class*="-display"] { 9 | padding: 12px 0; 10 | } 11 | 12 | [class*="-color"] { 13 | height: 56px; 14 | width: 256px; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | 19 | &.undefined { 20 | @include background-color; 21 | } 22 | } 23 | 24 | .background-color-inverted { 25 | @include invert-colors('background'); 26 | } 27 | 28 | material-button.primary { 29 | margin: 12px 0; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /example/packages/web/test/app_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:angular_test/angular_test.dart'; 3 | import 'package:test/test.dart'; 4 | import 'package:sharedtheme_example_web/app_component.dart'; 5 | import 'package:sharedtheme_example_web/app_component.template.dart' as ng; 6 | 7 | void main() { 8 | final testBed = 9 | NgTestBed.forComponent(ng.AppComponentNgFactory); 10 | NgTestFixture fixture; 11 | 12 | setUp(() async { 13 | fixture = await testBed.create(); 14 | }); 15 | 16 | tearDown(disposeAnyRunningTest); 17 | 18 | test('heading', () { 19 | expect(fixture.text, contains('My First AngularDart App')); 20 | }); 21 | 22 | // Testing info: https://webdev.dartlang.org/angular/guide/testing 23 | } 24 | -------------------------------------------------------------------------------- /example/packages/web/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sharedtheme_example_web 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/packages/web/.idea/web.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/packages/web-mdc/.idea/web.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/packages/web-mdc/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sharedtheme_example_web 2 | description: Example use of shared_theme for web 3 | version: 1.0.0 4 | author: Jacob Phillips 5 | homepage: https://github.com/jifalops/shared_theme/tree/master/example/packages/web 6 | 7 | environment: 8 | sdk: '>=2.0.0 <3.0.0' 9 | 10 | dependencies: 11 | sharedtheme_example: 12 | path: ../base 13 | mdc_web: any 14 | 15 | dev_dependencies: 16 | angular_test: ^2.0.0 17 | build_runner: ^0.10.0 18 | build_test: ^0.10.2 19 | build_web_compilers: ^0.4.0 20 | test: ^1.0.0 21 | sass_builder: 22 | # This is a temporary work around to have sass_builder include node_modules 23 | # when it looks for .scss files. The workaround will no longer be needed 24 | # when https://github.com/dart-league/sass_builder/pull/47 is released. 25 | git: https://github.com/jifalops/sass_builder 26 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Shared Theme Example 2 | 3 | This fully functional example is comprised of three packages. Use the links for 4 | more information about each package. 5 | 6 | Package | Description | Platform dependencies 7 | -|-|- 8 | [base](packages/base) | Contains the shared theme, configuration, and assets. | none 9 | [mobile](packages/mobile) | A Flutter app that uses the shared theme. | Flutter 10 | [web](packages/web) | A web app that uses the shared theme. | AngularDart 11 | 12 | The [example.dart](example.dart) here is simply a copy of [packages/base/lib/themes.dart](packages/base/lib/themes.dart). It serves as the example on Dart pub. 13 | 14 | ## Screenshots 15 | 16 | Mobile light | Mobile dark 17 | -|- 18 | ![mobile-light](images/mobile-light.png) | ![mobile-dark](images/mobile-dark.png) 19 | 20 | Web light | Web dark 21 | -|- 22 | ![web-light](images/web-light.png) | ![web-dark](images/web-dark.png) -------------------------------------------------------------------------------- /example/packages/web/lib/app_component.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | import 'package:angular/angular.dart'; 3 | import 'package:angular_components/angular_components.dart'; 4 | import 'package:sharedtheme_example/config.dart'; 5 | import 'src/example_list/example_list.dart'; 6 | 7 | 8 | @Component( 9 | selector: 'my-app', 10 | styleUrls: [ 11 | 'package:angular_components/app_layout/layout.scss.css', 12 | 'app_component.css' 13 | ], 14 | templateUrl: 'app_component.html', 15 | directives: [ 16 | MaterialToggleComponent, 17 | ExampleList, 18 | ], 19 | ) 20 | class AppComponent { 21 | static const name = appName; 22 | bool dark = window.localStorage['theme'] == 'Dark'; 23 | 24 | void toggle() { 25 | dark = !dark; 26 | final name = dark ? 'Dark' : 'Light'; 27 | document.documentElement.className = 'theme-$name'; 28 | window.localStorage['theme'] = name; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/packages/mobile/sharedtheme_example_mobile.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/_themify.scss: -------------------------------------------------------------------------------- 1 | // See https://medium.com/@dmitriy.borodiy/easy-color-theming-with-scss-bc38fd5734d1 2 | 3 | /// Wrap themed variables in this mixin. 4 | /// 5 | /// ```sass 6 | /// .primary-color { 7 | /// @include themify { 8 | /// background-color: themed('primaryColor'); 9 | /// color: themed('onPrimaryColor'); 10 | /// } 11 | /// } 12 | /// ``` 13 | @mixin themify($themes: $themes) { 14 | @each $theme, $map in $themes { 15 | 16 | .theme-#{$theme} & { 17 | $theme-map: () !global; 18 | @each $key, $submap in $map { 19 | $value: map-get(map-get($themes, $theme), '#{$key}'); 20 | $theme-map: map-merge($theme-map, ($key: $value)) !global; 21 | } 22 | 23 | @content; 24 | $theme-map: null !global; 25 | } 26 | 27 | } 28 | } 29 | 30 | /// Get a theme variable arbitrarily deep in the $theme-map. 31 | /// , [key2, key3, ...] 32 | @function themed($keys...) { 33 | $map: $theme-map; 34 | @each $key in $keys { 35 | $map: map-get($map, $key); 36 | } 37 | @return $map; 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Jacob Phillips 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /example/packages/mobile/.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/packages/mobile/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:sharedtheme_example_mobile/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new App()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /example/packages/web-mdc/.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/packages/web/.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shared Theme 2 | 3 | Easily share a theme between Flutter and the web. 4 | 5 | ## Synopsis 6 | 7 | A `Theme` is a `ColorSet`, `FontSet`, and an `ElementSet`, which 8 | are abstract containers for defining properties such as border, padding, etc. 9 | 10 | Themes are typically combined into a single `ThemeSet` per application. ThemeSet adds support for bundled `FontFace`s, and `ThemeSet.toString()` returns an SCSS string that fully represents it. 11 | 12 | In Flutter, a Theme is converted into Flutter's ThemeData by using the [`shared_theme_flutter`](https://pub.dartlang.org/packages/shared_theme_flutter) package's [`themeData()`](https://pub.dartlang.org/documentation/shared_theme_flutter/latest/shared_theme_flutter/themeData.html) function. 13 | 14 | ## Example 15 | 16 | There is a complete [example](https://github.com/jifalops/shared_theme/tree/master/example) 17 | included, and in particular, see its 18 | [ThemeSet definition](https://github.com/jifalops/shared_theme/blob/master/example/packages/base/lib/themes.dart). 19 | 20 | 21 | 22 | ## Screenshots 23 | 24 | Mobile light | Mobile dark 25 | -|- 26 | ![mobile-light](example/images/mobile-light.png) | ![mobile-dark](example/images/mobile-dark.png) 27 | 28 | Web light | Web dark 29 | -|- 30 | ![web-light](example/images/web-light.png) | ![web-dark](example/images/web-dark.png) 31 | 32 | -------------------------------------------------------------------------------- /example/packages/web-mdc/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | mdc_web Example 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 |

mdc_web Example

22 | 23 | 25 | 26 | 27 | 29 | 30 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/packages/mobile/sharedtheme_example_mobile_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/packages/web/README.md: -------------------------------------------------------------------------------- 1 | # sharedtheme_example_web 2 | 3 | This is the Web version of the app. It has several changes from the default `stagehand web-angular` setup: 4 | 5 | File | Changes 6 | -|- 7 | [`pubspec.yaml`](pubspec.yaml) | Add `sass_builder` as a dev dependency, which compiles *.scss files to *.css for `webdev serve` and `webdev build`. 8 | [`web/index.html`](web/index.html) | A simple script was added to set the theme without waiting for Angular to load. 9 | [`web/styles.css`](web/styles.scss) | Renamed to `styles.scss` and rewritten. All styles here are global and pierce component boundaries. 10 | [`bin/build_themes.dart`](bin/build_themes.dart) | Created this file to generate the web app's themes. Use `pub run theme_builder.dart` from the root of the web package to run it. It takes the themes in `../base/lib/themes.dart` and compiles them into `./lib/src/_themes.g.scss`. 11 | [`lib/`](lib/) | Modified the default example components to create the example you see here. 12 | 13 | ## Workflow 14 | 15 | While building the example I found it easiest to have two terminals open. The first terminal is for `webdev serve` to run indefinitely on, and the second terminal is for running `pub run theme_builder.dart` to update the themes file if you make changes in the base package. The webdev server will then pickup those changes. 16 | 17 | Also Webstorm currently has better support (than VS Code) for auto-completing all the mixins and class names from `_themes.g.scss` and whatever other Sass files you might import, but it does tend to think SCSS imports are wrong if they use the `@import 'package:...';` scheme. -------------------------------------------------------------------------------- /example/packages/web-mdc/README.md: -------------------------------------------------------------------------------- 1 | # sharedtheme_example_web 2 | 3 | This is the Web version of the app. It has several changes from the default `stagehand web-angular` setup: 4 | 5 | File | Changes 6 | -|- 7 | [`pubspec.yaml`](pubspec.yaml) | Add `sass_builder` as a dev dependency, which compiles *.scss files to *.css for `webdev serve` and `webdev build`. 8 | [`web/index.html`](web/index.html) | A simple script was added to set the theme without waiting for Angular to load. 9 | [`web/styles.css`](web/styles.scss) | Renamed to `styles.scss` and rewritten. All styles here are global and pierce component boundaries. 10 | [`bin/build_themes.dart`](bin/build_themes.dart) | Created this file to generate the web app's themes. Use `pub run theme_builder.dart` from the root of the web package to run it. It takes the themes in `../base/lib/themes.dart` and compiles them into `./lib/src/_themes.g.scss`. 11 | [`lib/`](lib/) | Modified the default example components to create the example you see here. 12 | 13 | ## Workflow 14 | 15 | While building the example I found it easiest to have two terminals open. The first terminal is for `webdev serve` to run indefinitely on, and the second terminal is for running `pub run theme_builder.dart` to update the themes file if you make changes in the base package. The webdev server will then pickup those changes. 16 | 17 | Also Webstorm currently has better support (than VS Code) for auto-completing all the mixins and class names from `_themes.g.scss` and whatever other Sass files you might import, but it does tend to think SCSS imports are wrong if they use the `@import 'package:...';` scheme. -------------------------------------------------------------------------------- /example/packages/mobile/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/packages/mobile/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sharedtheme_example_mobile 2 | description: Example use of shared_theme_flutter for mobile 3 | version: 1.0.0 4 | author: Jacob Phillips 5 | homepage: https://github.com/jifalops/shared_theme/tree/master/example/packages/mobile 6 | 7 | environment: 8 | sdk: ">=2.0.0 <3.0.0" 9 | 10 | dependencies: 11 | sharedtheme_example: 12 | path: ../base 13 | shared_theme_flutter: any 14 | flutter: 15 | sdk: flutter 16 | cupertino_icons: ^0.1.2 17 | shared_preferences: ^0.4.2 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | flutter: 24 | uses-material-design: true 25 | # Even though these are defined in `package:sharedtheme_example/themes.dart`, 26 | # They currently have to be duplicated here manually. 27 | fonts: 28 | - family: Ubuntu 29 | fonts: 30 | - asset: packages/sharedtheme_example/assets/fonts/Ubuntu-Regular.ttf 31 | - asset: packages/sharedtheme_example/assets/fonts/Ubuntu-Bold.ttf 32 | weight: 700 33 | - asset: packages/sharedtheme_example/assets/fonts/Ubuntu-Light.ttf 34 | weight: 300 35 | - family: Open Sans 36 | fonts: 37 | - asset: packages/sharedtheme_example/assets/fonts/OpenSans-Regular.ttf 38 | - asset: packages/sharedtheme_example/assets/fonts/OpenSans-Bold.ttf 39 | weight: 700 40 | - asset: packages/sharedtheme_example/assets/fonts/OpenSans-Italic.ttf 41 | style: italic 42 | - asset: packages/sharedtheme_example/assets/fonts/OpenSans-Light.ttf 43 | weight: 300 44 | - asset: packages/sharedtheme_example/assets/fonts/OpenSans-SemiBold.ttf 45 | weight: 500 -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | sharedtheme_example_mobile 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/src/css.dart: -------------------------------------------------------------------------------- 1 | /// Classes that can describe themselves using a map of CSS properties should 2 | /// extend or mixin this class. 3 | abstract class CssEntity implements ScssMap { 4 | const CssEntity(); 5 | 6 | Map get cssValues; 7 | 8 | String asScssMap() { 9 | final sb = StringBuffer(); 10 | cssValues.forEach((key, value) => sb.writeln('$key: $value,')); 11 | return '(${sb.toString()})'; 12 | } 13 | 14 | /// Return [cssValues] as a mixin that uses the `themify` SCSS utility. 15 | String asThemifiedMixin(String name, List parentKeys) { 16 | var sb = StringBuffer(); 17 | parentKeys.forEach((key) => sb.write("'$key', ")); 18 | final prefix = sb.toString(); 19 | sb = StringBuffer(); 20 | cssValues.keys.forEach((key) => sb.writeln( 21 | "@if index(\$include, '$key') and not index(\$exclude, '$key') { $key: themed($prefix '$key'); }")); 22 | return ''' 23 | @mixin $name(\$include: ('${cssValues.keys.join("','")}'), \$exclude: ()) { 24 | @include themify { 25 | ${sb.toString()} 26 | } 27 | } 28 | .$name { 29 | @include $name; 30 | } 31 | '''; 32 | } 33 | 34 | @override 35 | String toString() => cssValues.entries 36 | .map((entry) => '${entry.key}: ${entry.value};') 37 | .join('\n'); 38 | } 39 | 40 | // Collects mixins 41 | abstract class MixinAggregator implements ScssMap { 42 | List getMixins(); 43 | } 44 | 45 | /// Wraps a [CssEntity], adding to its [parentKeys] at mixin definition time. 46 | abstract class CssEntityContainer implements ScssMap { 47 | List getMixins(List parentKeys); 48 | } 49 | 50 | // Can describe its properties in an SCSS map. 51 | abstract class ScssMap { 52 | String asScssMap(); 53 | } 54 | -------------------------------------------------------------------------------- /example/packages/web-mdc/web/styles.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto); 2 | @import url(https://fonts.googleapis.com/css?family=Material+Icons); 3 | 4 | 5 | @import "themes.g"; 6 | 7 | :root { 8 | @include themify { 9 | $mdc-theme-primary: themed('colors', 'primary', 'background-color'); 10 | $mdc-theme-secondary: themed('colors', 'secondary', 'background-color'); 11 | $mdc-theme-on-primary: themed('colors', 'primary', 'color'); 12 | $mdc-theme-on-secondary: themed('colors', 'secondary', 'color'); 13 | $mdc-theme-background: themed('colors', 'background', 'background-color'); 14 | } 15 | } 16 | 17 | @import "material-components-web/material-components-web"; 18 | 19 | 20 | 21 | .mdc-button.primary { 22 | @include mdc-elevation(2.0); 23 | @include mdc-button-container-fill-color(secondary); 24 | @include mdc-button-ink-color(on-secondary); 25 | @include mdc-states; 26 | // Add ripple via scss mixins. 27 | // @include mdc-ripple-surface; 28 | // @include mdc-ripple-radius-bounded; 29 | // @include mdc-states-base-color(black); 30 | // @include mdc-states-hover-opacity(.1); 31 | // @include mdc-states-focus-opacity(.3); 32 | // @include mdc-states-press-opacity(.4); 33 | 34 | } 35 | 36 | .mdc-button.secondary { 37 | @include mdc-button-container-fill-color(primary); 38 | @include mdc-button-ink-color(on-primary); 39 | @include mdc-states; 40 | } 41 | 42 | .mdc-button.tertiary { 43 | @include mdc-button-outline-color(gray); 44 | @include mdc-button-ink-color(mdc-theme-accessible-ink-color('background')); 45 | @include mdc-states; 46 | } 47 | 48 | html, 49 | body { 50 | width: 100%; 51 | height: 100%; 52 | margin: 0; 53 | padding: 0; 54 | font-family: 'Roboto', sans-serif; 55 | font-size: 16px; 56 | @include background-color; 57 | } -------------------------------------------------------------------------------- /example/packages/mobile/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 27 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.sharedthemeexamplemobile" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /example/packages/mobile/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/packages/mobile/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/packages/web/web/styles.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto); 2 | @import url(https://fonts.googleapis.com/css?family=Material+Icons); 3 | 4 | //@import 'package:angular_components/css/material/material'; 5 | @import 'package:sharedtheme_example_web/src/themes.g'; 6 | 7 | /// Force all elements to be transparent by default. This makes theming elements 8 | /// easier because many of the angular components set background colors to white. 9 | /// 10 | /// It doesn't effect some elements, for example material-list needed to have 11 | /// its background changed manually. 12 | * { 13 | background-color: transparent; 14 | } 15 | 16 | body { 17 | @include background-color; 18 | @include font-body1; 19 | padding: 0; 20 | margin: 0; 21 | } 22 | 23 | material-button.primary { 24 | @include primary-button; 25 | } 26 | 27 | material-button.secondary { 28 | @include secondary-button; 29 | } 30 | 31 | material-button.tertiary { 32 | @include tertiary-button; 33 | } 34 | 35 | material-button .content { 36 | text-transform: none; 37 | // Inherit the colors set by the above mixins. 38 | color: inherit !important; 39 | background-color: inherit !important; 40 | border-radius: inherit !important; 41 | } 42 | 43 | material-list { 44 | @include background-color; 45 | } 46 | 47 | material-list-item { 48 | 49 | // Taken from `package:angular_components/material_list/mixins`. 50 | &:not([separator="present"]) { 51 | 52 | &:hover, 53 | &:focus, 54 | &.active { 55 | @include selected-row-color(background-color); 56 | } 57 | } 58 | } 59 | 60 | 61 | /// Material input hack. 62 | /// This element was much harder than others to share a theme definition with 63 | /// Flutter. However, it is pretty well duplicated here for both outline and 64 | /// underline input types. 65 | material-input[type="text"]:not(.no-theme) { 66 | padding: 16px 0 0 !important; 67 | @include background-color; 68 | 69 | .label-text { 70 | @include background-color; 71 | } 72 | 73 | .underline { 74 | display: none; 75 | } 76 | 77 | .top-section { 78 | @include input-base; 79 | margin-bottom: 2px !important; 80 | 81 | .input-container { 82 | margin: 0 !important; 83 | } 84 | 85 | .label-text.animated { 86 | transform: translateY(-50%) translateY(-10px) !important; 87 | padding: 0 2px; 88 | } 89 | 90 | /// Copy Flutter input behavior. 91 | /// hint color: the border and text color. 92 | /// if no hint color, use primary color for light themes and secondary color 93 | /// for dark themes. 94 | &:focus-within { 95 | @include themify { 96 | $hint: theme-color('hint'); 97 | 98 | @if $hint and $hint !='inherit' { 99 | border-color: $hint; 100 | 101 | .label-text { 102 | color: $hint !important; 103 | } 104 | } 105 | 106 | @else { 107 | @if themed('brightness')=='light' { 108 | $color: theme-color('primary'); 109 | border-color: $color; 110 | 111 | .label-text { 112 | color: $color !important; 113 | } 114 | } 115 | 116 | @else { 117 | $color: theme-color('secondary'); 118 | border-color: $color; 119 | 120 | .label-text { 121 | color: $color !important; 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /example/packages/web/lib/src/example_list/example_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 4 | 5 | Display4 6 | Display3 7 | Display2 8 | Display1 9 | Headline 10 | Title 11 | Subhead 12 | Body2 13 | Body1 14 | Button 15 | Caption 16 | 17 |
Primary
18 |
Primary Light
19 |
Primary Dark
20 |
Secondary
21 |
Secondary Light
22 |
Secondary Dark
23 |
Background
24 |
Background (inverted)
25 |
Card
26 |
Divider
27 |
Divider
28 |
Notice
29 |
Indicator
30 |
Hint
31 |
Selected Row
32 | 33 | 34 | Primary Button 35 | 36 | 37 | Secondary Button 38 | 39 | 40 | Tertiary Button 41 | 42 | 43 | 44 | 45 | 46 |
-------------------------------------------------------------------------------- /example/packages/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/shared_theme.dart: -------------------------------------------------------------------------------- 1 | /// *Write your themes once and easily use them in Flutter and on the web.* 2 | /// 3 | /// ## Synopsis 4 | /// 5 | /// A [Theme] is a [ColorSet], [FontSet], and an [ElementSet], which 6 | /// are abstract containers for defining properties such as border, padding, etc. 7 | /// 8 | /// Themes are typically combined into a single [ThemeSet] per application. 9 | /// ThemeSet adds support for bundled [FontFace]s, and [ThemeSet.toString()] 10 | /// returns an SCSS string that fully represents it. 11 | /// 12 | /// In Flutter, a Theme is converted into Flutter's ThemeData by using the 13 | /// [`shared_theme_flutter`](https://pub.dartlang.org/packages/shared_theme_flutter) 14 | /// package's [`themeData()`](https://pub.dartlang.org/documentation/shared_theme_flutter/latest/shared_theme_flutter/themeData.html) 15 | /// function. 16 | /// 17 | /// ## Example 18 | /// 19 | /// There is a complete [example](https://github.com/jifalops/shared_theme/tree/master/example) 20 | /// included, and in particular, see its 21 | /// [ThemeSet definition](https://github.com/jifalops/shared_theme/blob/master/example/packages/base/lib/themes.dart). 22 | library shared_theme; 23 | 24 | import 'package:meta/meta.dart'; 25 | import 'package:shared_theme/src/css.dart'; 26 | import 'package:shared_theme/src/colors.dart'; 27 | import 'package:shared_theme/src/fonts.dart'; 28 | import 'package:shared_theme/src/elements.dart'; 29 | 30 | export 'package:shared_theme/src/colors.dart'; 31 | export 'package:shared_theme/src/fonts.dart'; 32 | export 'package:shared_theme/src/elements.dart'; 33 | 34 | /// The colors, fonts, and elements in a theme. 35 | class Theme implements MixinAggregator { 36 | const Theme({ 37 | @required this.name, 38 | @required this.brightness, 39 | @required this.colors, 40 | @required this.fonts, 41 | @required this.elements, 42 | }) : assert(name != null), 43 | assert(colors != null), 44 | assert(fonts != null), 45 | assert(elements != null); 46 | 47 | final String name; 48 | final Brightness brightness; 49 | final ColorSet colors; 50 | final FontSet fonts; 51 | final ElementSet elements; 52 | 53 | @override 54 | String asScssMap() => '''( 55 | name: $name, 56 | brightness: $brightness, 57 | colors: ${colors.asScssMap()}, 58 | fonts: ${fonts.asScssMap()}, 59 | elements: ${elements.asScssMap()}, 60 | )'''; 61 | 62 | @override 63 | List getMixins() => colors.getMixins(['colors']) 64 | ..addAll(fonts.getMixins(['fonts'])) 65 | ..addAll(elements.getMixins(['elements'])); 66 | } 67 | 68 | /// A collection of themes and font-faces. 69 | class ThemeSet implements MixinAggregator { 70 | const ThemeSet({@required this.themes, this.fontFaces: const []}) 71 | : assert(themes != null), 72 | assert(fontFaces != null); 73 | 74 | final List themes; 75 | final List fontFaces; 76 | 77 | /// Use [getThemeByName()]. 78 | @deprecated 79 | Theme getTheme(String name) => getThemeByName(name); 80 | 81 | /// Return the first theme with a name that matches [name]. 82 | Theme getThemeByName(String name) => 83 | themes.firstWhere((theme) => theme.name == name, orElse: () => null); 84 | 85 | /// Return the first theme with a brightness that matches [brightness]. 86 | Theme getThemeByBrightness(Brightness brightness) => 87 | themes.firstWhere((theme) => theme.brightness == brightness, 88 | orElse: () => null); 89 | 90 | @override 91 | String asScssMap() => 92 | '(' + 93 | themes.map((theme) => '${theme.name}: ${theme.asScssMap()}').join(', ') + 94 | ')'; 95 | 96 | @override 97 | List getMixins() => themes.first.getMixins(); 98 | 99 | List getFontFaces() { 100 | final list = []; 101 | fontFaces.forEach((fontFace) => list.add(fontFace.toString())); 102 | return list; 103 | } 104 | 105 | /// SCSS output that fully represents this set of themes. 106 | @override 107 | String toString() => ''' 108 | // 109 | // Global map of themes. 110 | // 111 | \$themes: ${asScssMap()} !global; 112 | 113 | // 114 | // Font faces 115 | // 116 | ${getFontFaces().join('')} 117 | 118 | // 119 | // Themify utility 120 | // 121 | @import 'package:shared_theme/themify'; 122 | 123 | // 124 | // Functions 125 | // 126 | 127 | /// Get a main color of the current theme. 128 | /// Can only be used within a `@themify` block. 129 | @function theme-color(\$themeColorName) { 130 | @return themed('colors', \$themeColorName, 'background-color'); 131 | } 132 | 133 | /// Get a main color of the current theme. 134 | /// Can only be used within a `@themify` block. 135 | @function theme-contrast(\$themeColorName) { 136 | @return themed('colors', \$themeColorName, 'color'); 137 | } 138 | 139 | // 140 | // Mixins 141 | // 142 | 143 | /// Use a theme color as the foreground and contrast as the background. 144 | @mixin invert-colors(\$themeColorName) { 145 | @include themify { 146 | color: theme-color(\$themeColorName); 147 | background-color: theme-contrast(\$themeColorName); 148 | } 149 | } 150 | 151 | ${getMixins().join('')} 152 | '''; 153 | } 154 | 155 | /// An enum-like class. 156 | class Brightness { 157 | const Brightness._(this._name); 158 | final String _name; 159 | @override 160 | toString() => _name; 161 | static const light = Brightness._('light'); 162 | static const dark = Brightness._('dark'); 163 | } 164 | -------------------------------------------------------------------------------- /example/packages/mobile/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /example/packages/base/lib/themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_theme/shared_theme.dart'; 2 | 3 | /// See the theme colors at 4 | /// https://material.io/tools/color/#!/?view.left=1&view.right=0&secondary.color=FFAB00&primary.color=2E7D32 5 | final themeset = ThemeSet(themes: [ 6 | Theme( 7 | name: 'Light', 8 | brightness: Brightness.light, 9 | colors: _lightColors, 10 | fonts: _darkFonts, 11 | elements: _lightElements), 12 | Theme( 13 | name: 'Dark', 14 | brightness: Brightness.dark, 15 | colors: _darkColors, 16 | fonts: _lightFonts, 17 | elements: _darkElements), 18 | ], fontFaces: _fontFaces); 19 | 20 | // Declaring shared theme colors here allows you to refer to them as a default 21 | // value. 22 | const _primary = ContrastingColors(Color(0xff2e7d32), Colors.white); 23 | const _primaryLight = ContrastingColors(Color(0xff60ad5e), Colors.black); 24 | const _primaryDark = ContrastingColors(Color(0xff005005), Colors.white); 25 | const _secondary = ContrastingColors(Color(0xffffab00), Colors.black); 26 | const _secondaryLight = ContrastingColors(Color(0xffffdd4b), Colors.black); 27 | const _secondaryDark = ContrastingColors(Color(0xffc67c00), Colors.black); 28 | const _error = ContrastingColors(Colors.error, Colors.onError); 29 | const _notice = ContrastingColors(Color(0xffb39ddb), Colors.black); 30 | 31 | /// This helps define [ColorSet]s that share the same colors (e.g. brand colors). 32 | class _ThemeColors extends ColorSet { 33 | const _ThemeColors({ 34 | ContrastingColors background, 35 | ContrastingColors surface, 36 | ContrastingColors divider, 37 | }) : super( 38 | primary: _primary, 39 | primaryLight: _primaryLight, 40 | primaryDark: _primaryDark, 41 | secondary: _secondary, 42 | secondaryLight: _secondaryLight, 43 | secondaryDark: _secondaryDark, 44 | error: _error, 45 | notice: _notice, 46 | background: background, 47 | scaffold: surface, 48 | dialog: surface, 49 | card: surface, 50 | divider: divider, 51 | selectedRow: divider, 52 | indicator: _secondary, 53 | textSelection: _primaryLight, 54 | textSelectionHandle: _primaryDark, 55 | ); 56 | } 57 | 58 | const _lightColors = _ThemeColors( 59 | background: ContrastingColors(Color(0xfffefefe), Colors.black), 60 | surface: ContrastingColors(Color(0xfffefefe), Colors.black), 61 | divider: ContrastingColors(Color(0xffeeeeee), Colors.black), 62 | ); 63 | 64 | const _darkColors = _ThemeColors( 65 | background: ContrastingColors(Color(0xff333333), Colors.white), 66 | surface: ContrastingColors(Color(0xff333333), Colors.white), 67 | divider: ContrastingColors(Color(0xff484848), Colors.white), 68 | ); 69 | 70 | final _baseFonts = FontSet.dark.apply(family: 'Open Sans'); 71 | 72 | // The default font set. 73 | final _darkFonts = _baseFonts.copyWith( 74 | headline: FontSet.dark.headline 75 | .copyWith(weight: 700, height: 2.5, family: 'Ubuntu'), 76 | title: FontSet.dark.title.copyWith(weight: 700, family: 'Ubuntu'), 77 | subhead: FontSet.dark.subhead 78 | .copyWith(size: 18.0, height: 1.75, weight: 300, family: 'Ubuntu'), 79 | body2: FontSet.dark.body2.copyWith(size: 18.0, weight: 600), 80 | body1: FontSet.dark.body1.copyWith(size: 16.0, height: 1.15), 81 | caption: FontSet.dark.caption.copyWith(size: 16.0)); 82 | 83 | // Copy and apply the corresponding light color to each font. 84 | final _lightFonts = _darkFonts.lighten(); 85 | 86 | class _ButtonBase extends Element { 87 | _ButtonBase( 88 | {Color color: Colors.transparent, 89 | Border border, 90 | Font font, 91 | ShadowElevation shadow: ShadowElevation.none}) 92 | : super( 93 | align: TextAlign.center, 94 | padding: BoxSpacing.symmetric(vertical: 4.0, horizontal: 8.0), 95 | border: border ?? Border(radii: BorderRadius(999.0)), 96 | font: font ?? _darkFonts.button, 97 | shadow: shadow, 98 | color: color); 99 | } 100 | 101 | /// Default elements. 102 | final _lightElements = ElementSet( 103 | primaryButton: _ButtonBase( 104 | color: _lightColors.secondary.color, 105 | font: _darkFonts.button 106 | .copyWith(color: _lightColors.secondary.contrast, size: 16.0), 107 | border: Border(radii: BorderRadius.asymmetric(99.0, 29.0)), 108 | shadow: ShadowElevation.dp8), 109 | secondaryButton: _ButtonBase( 110 | color: _lightColors.primary.color, 111 | font: _darkFonts.button.copyWith(color: _lightColors.primary.contrast)), 112 | tertiaryButton: _ButtonBase(), 113 | inputBase: Element.outlineInput, 114 | ); 115 | 116 | /// Same as [_lightElements] but with a different text color on the tertiary button. 117 | final _darkElements = _lightElements.copyWith( 118 | tertiaryButton: _lightElements.tertiaryButton.copyWith( 119 | font: _lightElements.tertiaryButton.font 120 | .copyWith(color: _lightFonts.button.color))); 121 | 122 | /// These will be included in the CSS, and someday into clients' Flutter app, 123 | /// but for now they must be copied into your Flutter app's pubspec.yaml. The 124 | /// URL is exactly the same though. 125 | final _fontFaces = [ 126 | FontFace( 127 | family: 'Open Sans', 128 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Regular.ttf'), 129 | FontFace( 130 | family: 'Open Sans', 131 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-SemiBold.ttf', 132 | weight: 500), 133 | FontFace( 134 | family: 'Open Sans', 135 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Light.ttf', 136 | weight: 300), 137 | FontFace( 138 | family: 'Open Sans', 139 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Bold.ttf', 140 | weight: 700), 141 | FontFace( 142 | family: 'Open Sans', 143 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Italic.ttf', 144 | style: FontStyle.italic), 145 | FontFace( 146 | family: 'Ubuntu', 147 | url: 'packages/sharedtheme_example/assets/fonts/Ubuntu-Regular.ttf'), 148 | FontFace( 149 | family: 'Ubuntu', 150 | url: 'packages/sharedtheme_example/assets/fonts/Ubuntu-Light.ttf', 151 | weight: 300), 152 | FontFace( 153 | family: 'Ubuntu', 154 | url: 'packages/sharedtheme_example/assets/fonts/Ubuntu-Bold.ttf', 155 | weight: 700), 156 | ]; 157 | -------------------------------------------------------------------------------- /example/packages/mobile/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sharedtheme_example/config.dart'; 3 | import 'package:sharedtheme_example/themes.dart'; 4 | import 'package:shared_theme_flutter/shared_theme_flutter.dart' as themer; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | void main() => runApp(App()); 8 | 9 | class App extends StatefulWidget { 10 | _AppState createState() => _AppState(); 11 | } 12 | 13 | class _AppState extends State { 14 | themer.Theme theme; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | // Set a default theme synchronously. If you want to wait until the user's 20 | // preferred theme is read from SharedPreferences, you'll need to show some 21 | // UI to the user in the mean time, such as a spinner or a splash screen. 22 | _setTheme(themeset.themes.first); 23 | 24 | // Lookup the preferred theme. 25 | _readTheme(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) => MaterialApp( 30 | title: appName, 31 | theme: themer.themeData(theme), 32 | home: Scaffold( 33 | appBar: AppBar( 34 | title: Text(appName, 35 | style: themer.textStyle(theme.fonts.title).copyWith( 36 | color: themer.contrastOf(theme.colors.primary))), 37 | actions: [_buildThemeSwitch()]), 38 | body: Padding( 39 | padding: EdgeInsets.all(8.0), 40 | child: SingleChildScrollView(child: DemoItems()), 41 | ), 42 | ), 43 | ); 44 | 45 | void _setTheme(themer.Theme t) { 46 | theme = t; 47 | themer.setTheme(t); 48 | } 49 | 50 | Widget _buildThemeSwitch() => 51 | Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ 52 | Text( 53 | 'Dark', 54 | style: themer.textStyleColored( 55 | theme.fonts.title, themer.contrastOf(theme.colors.primary)), 56 | ), 57 | Switch( 58 | value: theme.brightness == themer.Brightness.dark, 59 | onChanged: (enabled) => _toggleTheme(), 60 | ) 61 | ]); 62 | 63 | void _readTheme() async { 64 | final prefs = await SharedPreferences.getInstance(); 65 | try { 66 | final name = prefs.getString('theme'); 67 | setState(() => _setTheme(themeset.getTheme(name))); 68 | } catch (noSavedTheme) {} 69 | } 70 | 71 | void _toggleTheme() async { 72 | setState(() => _setTheme(theme == themeset.themes.first 73 | ? themeset.themes.last 74 | : themeset.themes.first)); 75 | final prefs = await SharedPreferences.getInstance(); 76 | prefs.setString('theme', theme.name); 77 | } 78 | } 79 | 80 | class DemoItems extends StatelessWidget { 81 | @override 82 | Widget build(BuildContext context) { 83 | final theme = themer.currentTheme; 84 | void _showSnackBar() => Scaffold.of(context) 85 | .showSnackBar(SnackBar(content: Text("I'm a SnackBar."))); 86 | Widget _colorWidget(themer.ContrastingColors colors, String text) => 87 | _buildColorWidget(context, colors, text); 88 | return Column( 89 | crossAxisAlignment: CrossAxisAlignment.start, 90 | children: [ 91 | Text( 92 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'), 93 | Text('Display4', style: Theme.of(context).textTheme.display4), 94 | Text('Display3', style: Theme.of(context).textTheme.display3), 95 | Text('Display2', style: Theme.of(context).textTheme.display2), 96 | Text('Display1', style: Theme.of(context).textTheme.display1), 97 | Text('Headline', style: Theme.of(context).textTheme.headline), 98 | Text('Title', style: Theme.of(context).textTheme.title), 99 | Text('Subhead', style: Theme.of(context).textTheme.subhead), 100 | Text('Body2', style: Theme.of(context).textTheme.body2), 101 | Text('Body1', style: Theme.of(context).textTheme.body1), 102 | Text('Button', style: Theme.of(context).textTheme.button), 103 | Text('Caption', style: Theme.of(context).textTheme.caption), 104 | _colorWidget(theme.colors.primary, 'Primary'), 105 | _colorWidget(theme.colors.primaryLight, 'Primary Light'), 106 | _colorWidget(theme.colors.primaryDark, 'Primary Dark'), 107 | _colorWidget(theme.colors.secondary, 'Secondary ("Accent")'), 108 | _colorWidget(theme.colors.secondaryLight, 'Secondary Light'), 109 | _colorWidget(theme.colors.secondaryDark, 'Secondary Dark'), 110 | _colorWidget(theme.colors.background, 'Background'), 111 | _colorWidget(theme.colors.background.invert(), 'Background (inverted)'), 112 | _colorWidget(theme.colors.card, 'Card'), 113 | _colorWidget(theme.colors.divider, 'Divider'), 114 | _colorWidget(theme.colors.error, 'Error'), 115 | _colorWidget(theme.colors.notice, 'Notice'), 116 | _colorWidget(theme.colors.indicator, 'Indicator'), 117 | _colorWidget(theme.colors.hint, 'Hint'), 118 | _colorWidget(theme.colors.selectedRow, 'SelectedRow'), 119 | SizedBox(height: 12.0), 120 | themer.primaryButton(_showSnackBar, text: 'Primary Button'), 121 | SizedBox(height: 12.0), 122 | themer.secondaryButton(_showSnackBar, text: 'Secondary Button'), 123 | themer.tertiaryButton(_showSnackBar, text: 'Tertiary Button'), 124 | themer.wrapInput( 125 | TextField(decoration: InputDecoration(labelText: 'Input'))), 126 | ], 127 | ); 128 | } 129 | 130 | Widget _buildColorWidget( 131 | BuildContext context, themer.ContrastingColors colors, String text) => 132 | Container( 133 | alignment: Alignment.center, 134 | height: 56.0, 135 | width: 256.0, 136 | color: themer.colorOf(colors, Theme.of(context).backgroundColor), 137 | child: Text(text, 138 | style: Theme.of(context).textTheme.body2.copyWith( 139 | color: themer.contrastOf( 140 | colors, Theme.of(context).textTheme.body2.color)))); 141 | } 142 | -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | // For a complete working example, see https://github.com/jifalops/shared_theme/example 2 | import 'package:shared_theme/shared_theme.dart'; 3 | 4 | /// See the theme colors at 5 | /// https://material.io/tools/color/#!/?view.left=1&view.right=0&secondary.color=FFAB00&primary.color=2E7D32 6 | final themeset = ThemeSet(themes: [ 7 | Theme( 8 | name: 'Light', 9 | brightness: Brightness.light, 10 | colors: _lightColors, 11 | fonts: _darkFonts, 12 | elements: _lightElements), 13 | Theme( 14 | name: 'Dark', 15 | brightness: Brightness.dark, 16 | colors: _darkColors, 17 | fonts: _lightFonts, 18 | elements: _darkElements), 19 | ], fontFaces: _fontFaces); 20 | 21 | // Declaring common theme colors here allows you to refer to them as a default 22 | // value in contructors. 23 | const _primary = ContrastingColors(Color(0xff2e7d32), Colors.white); 24 | const _primaryLight = ContrastingColors(Color(0xff60ad5e), Colors.black); 25 | const _primaryDark = ContrastingColors(Color(0xff005005), Colors.white); 26 | const _secondary = ContrastingColors(Color(0xffffab00), Colors.black); 27 | const _secondaryLight = ContrastingColors(Color(0xffffdd4b), Colors.black); 28 | const _secondaryDark = ContrastingColors(Color(0xffc67c00), Colors.black); 29 | const _error = ContrastingColors(Colors.error, Colors.onError); 30 | const _notice = ContrastingColors(Color(0xffb39ddb), Colors.black); 31 | 32 | /// This helps define [ColorSet]s that share the same colors (e.g. brand colors). 33 | class _ThemeColors extends ColorSet { 34 | const _ThemeColors({ 35 | ContrastingColors background, 36 | ContrastingColors surface, 37 | ContrastingColors divider, 38 | }) : super( 39 | primary: _primary, 40 | primaryLight: _primaryLight, 41 | primaryDark: _primaryDark, 42 | secondary: _secondary, 43 | secondaryLight: _secondaryLight, 44 | secondaryDark: _secondaryDark, 45 | error: _error, 46 | notice: _notice, 47 | background: background, 48 | scaffold: surface, 49 | dialog: surface, 50 | card: surface, 51 | divider: divider, 52 | selectedRow: divider, 53 | indicator: _secondary, 54 | textSelection: _primaryLight, 55 | textSelectionHandle: _primaryDark, 56 | ); 57 | } 58 | 59 | const _lightColors = _ThemeColors( 60 | background: ContrastingColors(Color(0xfffefefe), Colors.black), 61 | surface: ContrastingColors(Color(0xfffefefe), Colors.black), 62 | divider: ContrastingColors(Color(0xffeeeeee), Colors.black), 63 | ); 64 | 65 | const _darkColors = _ThemeColors( 66 | background: ContrastingColors(Color(0xff333333), Colors.white), 67 | surface: ContrastingColors(Color(0xff333333), Colors.white), 68 | divider: ContrastingColors(Color(0xff484848), Colors.white), 69 | ); 70 | 71 | final _baseFonts = FontSet.dark.apply(family: 'Open Sans'); 72 | 73 | // The default font set. 74 | final _darkFonts = _baseFonts.copyWith( 75 | headline: FontSet.dark.headline 76 | .copyWith(weight: 700, height: 2.5, family: 'Ubuntu'), 77 | title: FontSet.dark.title.copyWith(weight: 700, family: 'Ubuntu'), 78 | subhead: FontSet.dark.subhead 79 | .copyWith(size: 18.0, height: 1.75, weight: 300, family: 'Ubuntu'), 80 | body2: FontSet.dark.body2.copyWith(size: 18.0, weight: 600), 81 | body1: FontSet.dark.body1.copyWith(size: 16.0, height: 1.15), 82 | caption: FontSet.dark.caption.copyWith(size: 16.0)); 83 | 84 | // Copy and apply the corresponding light color to each font. 85 | final _lightFonts = _darkFonts.lighten(); 86 | 87 | class _ButtonBase extends Element { 88 | _ButtonBase( 89 | {Color color: Colors.transparent, 90 | Font font, 91 | ShadowElevation shadow: ShadowElevation.none}) 92 | : super( 93 | align: TextAlign.center, 94 | padding: BoxSpacing.symmetric(vertical: 4.0, horizontal: 8.0), 95 | border: Border(radii: BorderRadius(4.0)), 96 | font: font ?? _darkFonts.button, 97 | shadow: shadow, 98 | color: color); 99 | } 100 | 101 | /// Default elements. 102 | final _lightElements = ElementSet( 103 | primaryButton: _ButtonBase( 104 | color: _lightColors.secondary.color, 105 | font: _darkFonts.button 106 | .copyWith(color: _lightColors.secondary.contrast, size: 16.0), 107 | shadow: ShadowElevation.dp8), 108 | secondaryButton: _ButtonBase( 109 | color: _lightColors.primary.color, 110 | font: _darkFonts.button.copyWith(color: _lightColors.primary.contrast)), 111 | tertiaryButton: _ButtonBase(), 112 | inputBase: Element.outlineInput, 113 | ); 114 | 115 | /// Same as [_lightElements] but with a different text color on the tertiary button. 116 | final _darkElements = _lightElements.copyWith( 117 | tertiaryButton: _lightElements.tertiaryButton.copyWith( 118 | font: _lightElements.tertiaryButton.font 119 | .copyWith(color: _lightFonts.button.color))); 120 | 121 | /// These will be included in the CSS, and someday into clients' Flutter app, 122 | /// but for now they must be copied into your Flutter app's pubspec.yaml. The 123 | /// URL is exactly the same though. 124 | final _fontFaces = [ 125 | FontFace( 126 | family: 'Open Sans', 127 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Regular.ttf'), 128 | FontFace( 129 | family: 'Open Sans', 130 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-SemiBold.ttf', 131 | weight: 500), 132 | FontFace( 133 | family: 'Open Sans', 134 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Light.ttf', 135 | weight: 300), 136 | FontFace( 137 | family: 'Open Sans', 138 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Bold.ttf', 139 | weight: 700), 140 | FontFace( 141 | family: 'Open Sans', 142 | url: 'packages/sharedtheme_example/assets/fonts/OpenSans-Italic.ttf', 143 | style: FontStyle.italic), 144 | FontFace( 145 | family: 'Ubuntu', 146 | url: 'packages/sharedtheme_example/assets/fonts/Ubuntu-Regular.ttf'), 147 | FontFace( 148 | family: 'Ubuntu', 149 | url: 'packages/sharedtheme_example/assets/fonts/Ubuntu-Light.ttf', 150 | weight: 300), 151 | FontFace( 152 | family: 'Ubuntu', 153 | url: 'packages/sharedtheme_example/assets/fonts/Ubuntu-Bold.ttf', 154 | weight: 700), 155 | ]; 156 | 157 | 158 | void main() { 159 | /// A very long string. 160 | print(themeset.toString()); 161 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.32.5" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.8" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.4" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.0.2" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.6" 60 | csslib: 61 | dependency: transitive 62 | description: 63 | name: csslib 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.14.5" 67 | front_end: 68 | dependency: transitive 69 | description: 70 | name: front_end 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.1.4+1" 74 | glob: 75 | dependency: transitive 76 | description: 77 | name: glob 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.1.7" 81 | html: 82 | dependency: transitive 83 | description: 84 | name: html 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.13.3+3" 88 | http: 89 | dependency: transitive 90 | description: 91 | name: http 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "0.12.0" 95 | http_multi_server: 96 | dependency: transitive 97 | description: 98 | name: http_multi_server 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.0.5" 102 | http_parser: 103 | dependency: transitive 104 | description: 105 | name: http_parser 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "3.1.3" 109 | io: 110 | dependency: transitive 111 | description: 112 | name: io 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "0.3.3" 116 | js: 117 | dependency: transitive 118 | description: 119 | name: js 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.6.1+1" 123 | json_rpc_2: 124 | dependency: transitive 125 | description: 126 | name: json_rpc_2 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "2.0.9" 130 | kernel: 131 | dependency: transitive 132 | description: 133 | name: kernel 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.3.4+1" 137 | logging: 138 | dependency: transitive 139 | description: 140 | name: logging 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "0.11.3+2" 144 | matcher: 145 | dependency: transitive 146 | description: 147 | name: matcher 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "0.12.3+1" 151 | meta: 152 | dependency: "direct main" 153 | description: 154 | name: meta 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.1.6" 158 | mime: 159 | dependency: transitive 160 | description: 161 | name: mime 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "0.9.6+2" 165 | multi_server_socket: 166 | dependency: transitive 167 | description: 168 | name: multi_server_socket 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.0.2" 172 | node_preamble: 173 | dependency: transitive 174 | description: 175 | name: node_preamble 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.4.4" 179 | package_config: 180 | dependency: transitive 181 | description: 182 | name: package_config 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "1.0.5" 186 | package_resolver: 187 | dependency: transitive 188 | description: 189 | name: package_resolver 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "1.0.6" 193 | path: 194 | dependency: transitive 195 | description: 196 | name: path 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.6.2" 200 | plugin: 201 | dependency: transitive 202 | description: 203 | name: plugin 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.2.0+3" 207 | pool: 208 | dependency: transitive 209 | description: 210 | name: pool 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.3.6" 214 | pub_semver: 215 | dependency: transitive 216 | description: 217 | name: pub_semver 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "1.4.2" 221 | shelf: 222 | dependency: transitive 223 | description: 224 | name: shelf 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "0.7.3+3" 228 | shelf_packages_handler: 229 | dependency: transitive 230 | description: 231 | name: shelf_packages_handler 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "1.0.4" 235 | shelf_static: 236 | dependency: transitive 237 | description: 238 | name: shelf_static 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "0.2.8" 242 | shelf_web_socket: 243 | dependency: transitive 244 | description: 245 | name: shelf_web_socket 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "0.2.2+4" 249 | source_map_stack_trace: 250 | dependency: transitive 251 | description: 252 | name: source_map_stack_trace 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "1.1.5" 256 | source_maps: 257 | dependency: transitive 258 | description: 259 | name: source_maps 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "0.10.7" 263 | source_span: 264 | dependency: transitive 265 | description: 266 | name: source_span 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.4.1" 270 | stack_trace: 271 | dependency: transitive 272 | description: 273 | name: stack_trace 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "1.9.3" 277 | stream_channel: 278 | dependency: transitive 279 | description: 280 | name: stream_channel 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.6.8" 284 | string_scanner: 285 | dependency: transitive 286 | description: 287 | name: string_scanner 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "1.0.4" 291 | term_glyph: 292 | dependency: transitive 293 | description: 294 | name: term_glyph 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.0.1" 298 | test: 299 | dependency: "direct dev" 300 | description: 301 | name: test 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "1.3.3" 305 | typed_data: 306 | dependency: transitive 307 | description: 308 | name: typed_data 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "1.1.6" 312 | utf: 313 | dependency: transitive 314 | description: 315 | name: utf 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "0.9.0+5" 319 | vm_service_client: 320 | dependency: transitive 321 | description: 322 | name: vm_service_client 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "0.2.6" 326 | watcher: 327 | dependency: transitive 328 | description: 329 | name: watcher 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "0.9.7+10" 333 | web_socket_channel: 334 | dependency: transitive 335 | description: 336 | name: web_socket_channel 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.0.9" 340 | yaml: 341 | dependency: transitive 342 | description: 343 | name: yaml 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "2.1.15" 347 | sdks: 348 | dart: ">=2.0.0 <3.0.0" 349 | -------------------------------------------------------------------------------- /example/packages/base/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.32.5" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.8" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.4" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.0.2" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.6" 60 | csslib: 61 | dependency: transitive 62 | description: 63 | name: csslib 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.14.5" 67 | front_end: 68 | dependency: transitive 69 | description: 70 | name: front_end 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.1.4+1" 74 | glob: 75 | dependency: transitive 76 | description: 77 | name: glob 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.1.7" 81 | html: 82 | dependency: transitive 83 | description: 84 | name: html 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.13.3+3" 88 | http: 89 | dependency: transitive 90 | description: 91 | name: http 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "0.12.0" 95 | http_multi_server: 96 | dependency: transitive 97 | description: 98 | name: http_multi_server 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.0.5" 102 | http_parser: 103 | dependency: transitive 104 | description: 105 | name: http_parser 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "3.1.3" 109 | io: 110 | dependency: transitive 111 | description: 112 | name: io 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "0.3.3" 116 | js: 117 | dependency: transitive 118 | description: 119 | name: js 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.6.1+1" 123 | json_rpc_2: 124 | dependency: transitive 125 | description: 126 | name: json_rpc_2 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "2.0.9" 130 | kernel: 131 | dependency: transitive 132 | description: 133 | name: kernel 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.3.4+1" 137 | logging: 138 | dependency: transitive 139 | description: 140 | name: logging 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "0.11.3+2" 144 | matcher: 145 | dependency: transitive 146 | description: 147 | name: matcher 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "0.12.3+1" 151 | meta: 152 | dependency: "direct main" 153 | description: 154 | name: meta 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.1.6" 158 | mime: 159 | dependency: transitive 160 | description: 161 | name: mime 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "0.9.6+2" 165 | multi_server_socket: 166 | dependency: transitive 167 | description: 168 | name: multi_server_socket 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.0.2" 172 | node_preamble: 173 | dependency: transitive 174 | description: 175 | name: node_preamble 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.4.4" 179 | package_config: 180 | dependency: transitive 181 | description: 182 | name: package_config 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "1.0.5" 186 | package_resolver: 187 | dependency: transitive 188 | description: 189 | name: package_resolver 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "1.0.6" 193 | path: 194 | dependency: transitive 195 | description: 196 | name: path 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.6.2" 200 | plugin: 201 | dependency: transitive 202 | description: 203 | name: plugin 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.2.0+3" 207 | pool: 208 | dependency: transitive 209 | description: 210 | name: pool 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.3.6" 214 | pub_semver: 215 | dependency: transitive 216 | description: 217 | name: pub_semver 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "1.4.2" 221 | shared_theme: 222 | dependency: "direct main" 223 | description: 224 | name: shared_theme 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "0.1.1" 228 | shelf: 229 | dependency: transitive 230 | description: 231 | name: shelf 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "0.7.3+3" 235 | shelf_packages_handler: 236 | dependency: transitive 237 | description: 238 | name: shelf_packages_handler 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "1.0.4" 242 | shelf_static: 243 | dependency: transitive 244 | description: 245 | name: shelf_static 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "0.2.8" 249 | shelf_web_socket: 250 | dependency: transitive 251 | description: 252 | name: shelf_web_socket 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "0.2.2+4" 256 | source_map_stack_trace: 257 | dependency: transitive 258 | description: 259 | name: source_map_stack_trace 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.1.5" 263 | source_maps: 264 | dependency: transitive 265 | description: 266 | name: source_maps 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "0.10.7" 270 | source_span: 271 | dependency: transitive 272 | description: 273 | name: source_span 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "1.4.1" 277 | stack_trace: 278 | dependency: transitive 279 | description: 280 | name: stack_trace 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.9.3" 284 | stream_channel: 285 | dependency: transitive 286 | description: 287 | name: stream_channel 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "1.6.8" 291 | string_scanner: 292 | dependency: transitive 293 | description: 294 | name: string_scanner 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.0.4" 298 | term_glyph: 299 | dependency: transitive 300 | description: 301 | name: term_glyph 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "1.0.1" 305 | test: 306 | dependency: "direct dev" 307 | description: 308 | name: test 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "1.3.3" 312 | typed_data: 313 | dependency: transitive 314 | description: 315 | name: typed_data 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.1.6" 319 | utf: 320 | dependency: transitive 321 | description: 322 | name: utf 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "0.9.0+5" 326 | vm_service_client: 327 | dependency: transitive 328 | description: 329 | name: vm_service_client 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "0.2.6" 333 | watcher: 334 | dependency: transitive 335 | description: 336 | name: watcher 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "0.9.7+10" 340 | web_socket_channel: 341 | dependency: transitive 342 | description: 343 | name: web_socket_channel 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "1.0.9" 347 | yaml: 348 | dependency: transitive 349 | description: 350 | name: yaml 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "2.1.15" 354 | sdks: 355 | dart: ">=2.0.0 <3.0.0" 356 | -------------------------------------------------------------------------------- /lib/src/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:shared_theme/src/css.dart'; 3 | 4 | /// A color in the red-green-blue scheme with optional alpha. 5 | class Color { 6 | /// Alpha Red Green Blue. 7 | final int argb; 8 | 9 | /// ARGB format. 10 | const Color(this.argb); 11 | 12 | /// Construct a [Color] from parts. 13 | const Color.from({int red: 0, int green: 0, int blue: 0, double alpha = 1.0}) 14 | : assert(red >= 0 && red <= 0xFF), 15 | assert(green >= 0 && green <= 0xFF), 16 | assert(blue >= 0 && blue <= 0xFF), 17 | assert(alpha >= 0 && alpha <= 1), 18 | argb = 19 | (((alpha * 0xFF) ~/ 1) << 24) | (red << 16) | (green << 8) | blue; 20 | 21 | double get alpha => alphaByte / 0xFF; 22 | int get alphaByte => (argb >> 24) & 0xFF; 23 | int get red => (argb >> 16) & 0xFF; 24 | int get green => (argb >> 8) & 0xFF; 25 | int get blue => argb & 0xFF; 26 | 27 | int get rgb => argb & 0xFFFFFF; 28 | int get rgba => (rgb << 8) & alphaByte; 29 | 30 | /// CSS value 31 | @override 32 | String toString() => 'rgba($red, $green, $blue, $alpha)'; 33 | 34 | /// Calculates the luminence of this color and considers it dark if below a 35 | /// certain level. Does *not* take alpha into consideration. 36 | /// 37 | /// See https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color 38 | bool get isDark { 39 | final luminence = (0.2126 * red + 0.7152 * green + 0.0722 * blue); 40 | return luminence < 150; 41 | } 42 | 43 | /// White or black, depending on [isDark]. 44 | Color get contrast => isDark ? Colors.white : Colors.black; 45 | 46 | @override 47 | bool operator ==(o) => o is Color && o.argb == argb; 48 | @override 49 | int get hashCode => argb.hashCode; 50 | } 51 | 52 | /// A color and its contrast. 53 | /// 54 | /// The colors can both be `null` to use platform defaults such as a base theme 55 | /// color in Flutter or "inherit" for CSS. 56 | class ContrastingColors extends CssEntity { 57 | const ContrastingColors(this.color, this.contrast); 58 | 59 | /// The main color, usually used as a background. 60 | final Color color; 61 | 62 | /// A color that contrasts [color]. 63 | final Color contrast; 64 | 65 | /// Swap [color] and [contrast]. 66 | ContrastingColors invert() => ContrastingColors(contrast, color); 67 | 68 | /// Uses [color] for the CSS `background-color`, and [contrast] for the CSS 69 | /// `color`. 70 | @override 71 | Map get cssValues => { 72 | 'background-color': color?.toString() ?? 'inherit', 73 | 'color': contrast?.toString() ?? 'inherit', 74 | }; 75 | 76 | /// Both colors are `null`. 77 | static const none = ContrastingColors(null, null); 78 | } 79 | 80 | /// The named colors in a theme. 81 | class ColorSet implements CssEntityContainer { 82 | const ColorSet({ 83 | @required this.primary, 84 | @required this.primaryLight, 85 | @required this.primaryDark, 86 | @required this.secondary, 87 | this.secondaryLight: ContrastingColors.none, 88 | this.secondaryDark: ContrastingColors.none, 89 | this.background: ContrastingColors.none, 90 | this.scaffold: ContrastingColors.none, 91 | this.dialog: ContrastingColors.none, 92 | this.card: ContrastingColors.none, 93 | this.divider: ContrastingColors.none, 94 | this.error: const ContrastingColors(Colors.error, Colors.onError), 95 | this.notice: ContrastingColors.none, 96 | this.indicator: ContrastingColors.none, 97 | this.hint: ContrastingColors.none, 98 | this.splash: ContrastingColors.none, 99 | this.selectedRow: ContrastingColors.none, 100 | this.highlight: ContrastingColors.none, 101 | this.textSelectionHandle: ContrastingColors.none, 102 | this.textSelection: ContrastingColors.none, 103 | }); 104 | 105 | final ContrastingColors primary; 106 | final ContrastingColors primaryLight; 107 | final ContrastingColors primaryDark; 108 | final ContrastingColors secondary; 109 | final ContrastingColors secondaryLight; 110 | final ContrastingColors secondaryDark; 111 | 112 | final ContrastingColors background; 113 | final ContrastingColors divider; 114 | final ContrastingColors error; 115 | final ContrastingColors notice; 116 | 117 | final ContrastingColors indicator; 118 | final ContrastingColors hint; 119 | final ContrastingColors splash; 120 | final ContrastingColors selectedRow; 121 | final ContrastingColors highlight; 122 | final ContrastingColors textSelection; 123 | final ContrastingColors textSelectionHandle; 124 | final ContrastingColors scaffold; 125 | final ContrastingColors dialog; 126 | final ContrastingColors card; 127 | 128 | ColorSet copyWith({ 129 | ContrastingColors primary, 130 | ContrastingColors primaryLight, 131 | ContrastingColors primaryDark, 132 | ContrastingColors secondary, 133 | ContrastingColors secondaryLight, 134 | ContrastingColors secondaryDark, 135 | ContrastingColors background, 136 | ContrastingColors divider, 137 | ContrastingColors error, 138 | ContrastingColors notice, 139 | ContrastingColors textSelection, 140 | ContrastingColors textSelectionHandle, 141 | ContrastingColors highlight, 142 | ContrastingColors indicator, 143 | ContrastingColors hint, 144 | ContrastingColors splash, 145 | ContrastingColors selectedRow, 146 | ContrastingColors scaffold, 147 | ContrastingColors dialog, 148 | ContrastingColors card, 149 | }) => 150 | ColorSet( 151 | primary: primary ?? this.primary, 152 | primaryLight: primaryLight ?? this.primaryLight, 153 | primaryDark: primaryDark ?? this.primaryDark, 154 | secondary: secondary ?? this.secondary, 155 | secondaryLight: secondaryLight ?? this.secondaryLight, 156 | secondaryDark: secondaryDark ?? this.secondaryDark, 157 | background: background ?? this.background, 158 | divider: divider ?? this.divider, 159 | error: error ?? this.error, 160 | notice: notice ?? this.notice, 161 | indicator: indicator ?? this.indicator, 162 | hint: hint ?? this.hint, 163 | splash: splash ?? this.splash, 164 | selectedRow: selectedRow ?? this.selectedRow, 165 | highlight: highlight ?? this.highlight, 166 | textSelectionHandle: textSelectionHandle ?? this.textSelectionHandle, 167 | textSelection: textSelection ?? this.textSelection, 168 | scaffold: scaffold ?? this.scaffold, 169 | dialog: dialog ?? this.dialog, 170 | card: card ?? this.card, 171 | ); 172 | 173 | @override 174 | String asScssMap() => '''( 175 | primary: ${primary.asScssMap()}, 176 | primaryLight: ${primaryLight.asScssMap()}, 177 | primaryDark: ${primaryDark.asScssMap()}, 178 | secondary: ${secondary.asScssMap()}, 179 | secondaryLight: ${secondaryLight.asScssMap()}, 180 | secondaryDark: ${secondaryDark.asScssMap()}, 181 | background: ${background.asScssMap()}, 182 | scaffold: ${scaffold.asScssMap()}, 183 | card: ${card.asScssMap()}, 184 | dialog: ${dialog.asScssMap()}, 185 | divider: ${divider.asScssMap()}, 186 | error: ${error.asScssMap()}, 187 | notice: ${notice.asScssMap()}, 188 | indicator: ${indicator.asScssMap()}, 189 | hint: ${hint.asScssMap()}, 190 | splash: ${splash.asScssMap()}, 191 | selectedRow: ${selectedRow.asScssMap()}, 192 | highlight: ${highlight.asScssMap()}, 193 | textSelection: ${textSelection.asScssMap()}, 194 | textSelectionHandle: ${textSelectionHandle.asScssMap()}, 195 | )'''; 196 | 197 | @override 198 | List getMixins(List parentKeys) => [ 199 | primary.asThemifiedMixin( 200 | 'primary-color', List.from(parentKeys)..add('primary')), 201 | primaryLight.asThemifiedMixin( 202 | 'primary-color-light', List.from(parentKeys)..add('primaryLight')), 203 | primaryDark.asThemifiedMixin( 204 | 'primary-color-dark', List.from(parentKeys)..add('primaryDark')), 205 | secondary.asThemifiedMixin( 206 | 'secondary-color', List.from(parentKeys)..add('secondary')), 207 | secondaryLight.asThemifiedMixin('secondary-color-light', 208 | List.from(parentKeys)..add('secondaryLight')), 209 | secondaryDark.asThemifiedMixin('secondary-color-dark', 210 | List.from(parentKeys)..add('secondaryDark')), 211 | background.asThemifiedMixin( 212 | 'background-color', List.from(parentKeys)..add('background')), 213 | scaffold.asThemifiedMixin( 214 | 'scaffold-color', List.from(parentKeys)..add('scaffold')), 215 | dialog.asThemifiedMixin( 216 | 'dialog-color', List.from(parentKeys)..add('dialog')), 217 | card.asThemifiedMixin('card-color', List.from(parentKeys)..add('card')), 218 | divider.asThemifiedMixin( 219 | 'divider-color', List.from(parentKeys)..add('divider')), 220 | error.asThemifiedMixin( 221 | 'error-color', List.from(parentKeys)..add('error')), 222 | notice.asThemifiedMixin( 223 | 'notice-color', List.from(parentKeys)..add('notice')), 224 | indicator.asThemifiedMixin( 225 | 'indicator-color', List.from(parentKeys)..add('indicator')), 226 | hint.asThemifiedMixin('hint-color', List.from(parentKeys)..add('hint')), 227 | splash.asThemifiedMixin( 228 | 'splash-color', List.from(parentKeys)..add('splash')), 229 | selectedRow.asThemifiedMixin( 230 | 'selected-row-color', List.from(parentKeys)..add('selectedRow')), 231 | highlight.asThemifiedMixin( 232 | 'highlight-color', List.from(parentKeys)..add('highlight')), 233 | textSelection.asThemifiedMixin('text-selection-color', 234 | List.from(parentKeys)..add('textSelection')), 235 | textSelectionHandle.asThemifiedMixin('text-selection-handle-color', 236 | List.from(parentKeys)..add('textSelectionHandle')), 237 | ]; 238 | } 239 | 240 | /// Material design Font colors with opacity. 241 | class Colors { 242 | Colors._(); 243 | static const transparent = const Color(0x00000000); 244 | static const black = const Color(0xFF000000); 245 | static const black87 = const Color(0xDD000000); 246 | static const black54 = const Color(0x8A000000); 247 | static const black45 = const Color(0x73000000); 248 | static const black38 = const Color(0x61000000); 249 | static const black26 = const Color(0x42000000); 250 | static const black12 = const Color(0x1F000000); 251 | static const white = const Color(0xFFFFFFFF); 252 | static const white70 = const Color(0xB3FFFFFF); 253 | static const white30 = const Color(0x4DFFFFFF); 254 | static const white24 = const Color(0x3DFFFFFF); 255 | static const white12 = const Color(0x1FFFFFFF); 256 | static const white10 = const Color(0x1AFFFFFF); 257 | static const error = Color(0xFFB00020); 258 | 259 | /// Text on top of [error]. 260 | static const onError = Colors.white; 261 | } 262 | -------------------------------------------------------------------------------- /example/packages/mobile/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.32.4" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.8" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.4" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.0.2" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.6" 60 | csslib: 61 | dependency: transitive 62 | description: 63 | name: csslib 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.14.5" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.1.2" 74 | flutter: 75 | dependency: "direct main" 76 | description: flutter 77 | source: sdk 78 | version: "0.0.0" 79 | flutter_test: 80 | dependency: "direct dev" 81 | description: flutter 82 | source: sdk 83 | version: "0.0.0" 84 | front_end: 85 | dependency: transitive 86 | description: 87 | name: front_end 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "0.1.4" 91 | glob: 92 | dependency: transitive 93 | description: 94 | name: glob 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "1.1.7" 98 | html: 99 | dependency: transitive 100 | description: 101 | name: html 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "0.13.3+3" 105 | http: 106 | dependency: transitive 107 | description: 108 | name: http 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.11.3+17" 112 | http_multi_server: 113 | dependency: transitive 114 | description: 115 | name: http_multi_server 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "2.0.5" 119 | http_parser: 120 | dependency: transitive 121 | description: 122 | name: http_parser 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "3.1.3" 126 | io: 127 | dependency: transitive 128 | description: 129 | name: io 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "0.3.3" 133 | js: 134 | dependency: transitive 135 | description: 136 | name: js 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "0.6.1+1" 140 | json_rpc_2: 141 | dependency: transitive 142 | description: 143 | name: json_rpc_2 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "2.0.9" 147 | kernel: 148 | dependency: transitive 149 | description: 150 | name: kernel 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.3.4" 154 | logging: 155 | dependency: transitive 156 | description: 157 | name: logging 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.11.3+2" 161 | matcher: 162 | dependency: transitive 163 | description: 164 | name: matcher 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "0.12.3+1" 168 | meta: 169 | dependency: transitive 170 | description: 171 | name: meta 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "1.1.6" 175 | mime: 176 | dependency: transitive 177 | description: 178 | name: mime 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "0.9.6+2" 182 | multi_server_socket: 183 | dependency: transitive 184 | description: 185 | name: multi_server_socket 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "1.0.2" 189 | node_preamble: 190 | dependency: transitive 191 | description: 192 | name: node_preamble 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "1.4.4" 196 | package_config: 197 | dependency: transitive 198 | description: 199 | name: package_config 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "1.0.5" 203 | package_resolver: 204 | dependency: transitive 205 | description: 206 | name: package_resolver 207 | url: "https://pub.dartlang.org" 208 | source: hosted 209 | version: "1.0.4" 210 | path: 211 | dependency: transitive 212 | description: 213 | name: path 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "1.6.2" 217 | plugin: 218 | dependency: transitive 219 | description: 220 | name: plugin 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "0.2.0+3" 224 | pool: 225 | dependency: transitive 226 | description: 227 | name: pool 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "1.3.6" 231 | pub_semver: 232 | dependency: transitive 233 | description: 234 | name: pub_semver 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "1.4.2" 238 | quiver: 239 | dependency: transitive 240 | description: 241 | name: quiver 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "2.0.0+1" 245 | shared_preferences: 246 | dependency: "direct main" 247 | description: 248 | name: shared_preferences 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "0.4.2" 252 | shared_theme: 253 | dependency: transitive 254 | description: 255 | name: shared_theme 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "0.1.1" 259 | shared_theme_flutter: 260 | dependency: "direct main" 261 | description: 262 | name: shared_theme_flutter 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "0.1.1" 266 | sharedtheme_example: 267 | dependency: "direct main" 268 | description: 269 | path: "../base" 270 | relative: true 271 | source: path 272 | version: "1.0.0" 273 | shelf: 274 | dependency: transitive 275 | description: 276 | name: shelf 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "0.7.3+3" 280 | shelf_packages_handler: 281 | dependency: transitive 282 | description: 283 | name: shelf_packages_handler 284 | url: "https://pub.dartlang.org" 285 | source: hosted 286 | version: "1.0.4" 287 | shelf_static: 288 | dependency: transitive 289 | description: 290 | name: shelf_static 291 | url: "https://pub.dartlang.org" 292 | source: hosted 293 | version: "0.2.8" 294 | shelf_web_socket: 295 | dependency: transitive 296 | description: 297 | name: shelf_web_socket 298 | url: "https://pub.dartlang.org" 299 | source: hosted 300 | version: "0.2.2+4" 301 | sky_engine: 302 | dependency: transitive 303 | description: flutter 304 | source: sdk 305 | version: "0.0.99" 306 | source_map_stack_trace: 307 | dependency: transitive 308 | description: 309 | name: source_map_stack_trace 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "1.1.5" 313 | source_maps: 314 | dependency: transitive 315 | description: 316 | name: source_maps 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "0.10.7" 320 | source_span: 321 | dependency: transitive 322 | description: 323 | name: source_span 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "1.4.1" 327 | stack_trace: 328 | dependency: transitive 329 | description: 330 | name: stack_trace 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.9.3" 334 | stream_channel: 335 | dependency: transitive 336 | description: 337 | name: stream_channel 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "1.6.8" 341 | string_scanner: 342 | dependency: transitive 343 | description: 344 | name: string_scanner 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "1.0.4" 348 | term_glyph: 349 | dependency: transitive 350 | description: 351 | name: term_glyph 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "1.0.1" 355 | test: 356 | dependency: transitive 357 | description: 358 | name: test 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "1.3.0" 362 | typed_data: 363 | dependency: transitive 364 | description: 365 | name: typed_data 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "1.1.6" 369 | utf: 370 | dependency: transitive 371 | description: 372 | name: utf 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "0.9.0+5" 376 | vector_math: 377 | dependency: transitive 378 | description: 379 | name: vector_math 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "2.0.8" 383 | vm_service_client: 384 | dependency: transitive 385 | description: 386 | name: vm_service_client 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "0.2.6" 390 | watcher: 391 | dependency: transitive 392 | description: 393 | name: watcher 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "0.9.7+10" 397 | web_socket_channel: 398 | dependency: transitive 399 | description: 400 | name: web_socket_channel 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "1.0.9" 404 | yaml: 405 | dependency: transitive 406 | description: 407 | name: yaml 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "2.1.15" 411 | sdks: 412 | dart: ">=2.0.0 <3.0.0" 413 | flutter: ">=0.1.4 <2.0.0" 414 | -------------------------------------------------------------------------------- /doc/how-to.md: -------------------------------------------------------------------------------- 1 | # How to Share a Theme between Flutter and the Web 2 | 3 | This how-to will get you set up with a project that has a light and dark theme that work both in Flutter and on the Web. 4 | 5 | You should have [Flutter][1] and the the [Dart SDK][2] installed and accessible on your system's path, with `webdev` and `stagehand` activated by pub ([instructions][3]). 6 | 7 | > Although AngularDart is used in this guide, the CSS generated here can be used without it. 8 | 9 | **TL;DR**: See a complete example [here][5]. 10 | 11 | Two new packages are introduced, [shared_theme][18] and [shared_theme_flutter][19]. 12 | 13 | ## Contents 14 | 15 | 1. [Getting Started](#Getting_Started) 16 | 1. [Generate the Packages](#Generate_the_Packages) 17 | 2. [Add Dependencies](#Add_Dependencies) 18 | 3. [Name the App](#Name_the_App) 19 | 2. [Create Your Themes](#Create_Your_Themes) 20 | 1. [Choose Brand Colors](#Choose_Brand_Colors) 21 | 3. [Try it](#Try_it) 22 | 1. [Run the Flutter App](#Run_the_Flutter_App) 23 | 2. [Run the Web App](#Run_the_Web_App) 24 | 1. [First Run](#First_Run) 25 | 1. [Subsequent Runs](#Subsequent_Runs) 26 | 4. [Adding Elements](#Adding_Elements) 27 | 5. [Adding Fonts and Assets](#Adding_Fonts_and_Assets) 28 | 6. [Screenshots](#Screenshots) 29 | 30 | 31 | ## Getting Started 32 | 33 | Your project will need three packages, one each for Flutter and the web, and a base package they will share. 34 | 35 | Create this minimal directory structure, and add READMEs etc. as you see fit. 36 | 37 | ``` 38 | 39 | packages/ 40 | / 41 | _mobile/ 42 | _web/ 43 | ``` 44 | 45 | 46 | ### Generate the Packages 47 | 48 | The name of each package directory will be the name of the generated package. Afterwards we'll rename the directories to make things simpler. 49 | 50 | > Before doing this you may want to ensure your version of `stagehand` and `flutter` are up to date. 51 | > ``` 52 | > > pub global activate stagehand 53 | > > flutter upgrade 54 | > ``` 55 | 56 | 1. Generate the base package by entering the following from the `packages/` directory. 57 | 58 | ```sh 59 | > stagehand package-simple 60 | > pub get 61 | ``` 62 | 63 | *Delete everything inside the generated `lib/` directory, we're going to create our own files.* 64 | 65 | 2. Generate the web package by entering the following from the `packages/_web` directory. 66 | 67 | ```sh 68 | > stagehand web-angular 69 | > pub get 70 | ``` 71 | 72 | 3. Generate the mobile package by entering the following from the `packages/_mobile` directory. 73 | 74 | ```sh 75 | > flutter create . 76 | ``` 77 | 78 | Now that the packages have been created with their desired names, we can rename the directories to make things easier. 79 | 80 | ``` 81 | 82 | packages/ 83 | base/ 84 | mobile/ 85 | web/ 86 | ``` 87 | 88 | 89 | ### Add Dependencies 90 | 91 | Add the following dependencies to each package. Do not delete the existing ones. 92 | 93 | > Be sure to use the latest version of each dependency. A quick way to check is to add the dependency without a version constraint, and then check the version that is added to the `.packages` file. 94 | 95 | In the base project's `pubspec.yaml`, add the following dependency. 96 | 97 | ``` 98 | dependencies: 99 | shared_theme: ^0.1.2 100 | ``` 101 | 102 | For the mobile package, add these dependencies. 103 | 104 | ``` 105 | dependencies: 106 | : 107 | path: ../base 108 | shared_theme_flutter: ^0.1.2 109 | shared_preferences: ^0.4.2 # For persisting theme preference. 110 | ``` 111 | 112 | The web packages requires a dev dependency as well. 113 | 114 | ``` 115 | dependencies: 116 | : 117 | path: ../base 118 | 119 | dev_dependencies: 120 | sass_builder: ^2.1.1 121 | ``` 122 | 123 | 124 | ### Name the App 125 | 126 | Since both apps are going to use the same user-facing name let's define it in `base/lib/config.dart`. 127 | 128 | ```dart 129 | const appName = 'My Awesome App'; 130 | ``` 131 | 132 | 133 | 134 | ## Create Your Themes 135 | 136 | Create the file `packages/base/lib/themes.dart` and add the following to it. 137 | 138 | ```dart 139 | import 'package:shared_theme/shared_theme.dart'; 140 | 141 | final themeset = ThemeSet(themes: [ 142 | Theme( 143 | name: 'Light', 144 | brightness: Brightness.light, 145 | colors: ColorSet(), 146 | fonts: FontSet.dark, 147 | elements: ElementSet() 148 | ), 149 | Theme( 150 | name: 'Dark', 151 | brightness: Brightness.dark, 152 | colors: ColorSet(), 153 | fonts: FontSet.light, 154 | elements: ElementSet() 155 | ), 156 | ]); 157 | ``` 158 | 159 | 160 | ### Choose Brand Colors 161 | 162 | You'll notice a `ColorSet` requires at least a few colors, so for this example we picked some using the material color tool. You can see them [here][4]. 163 | 164 | Add the following light and dark theme colors to `themes.dart`. 165 | 166 | ```dart 167 | // See the theme at https://material.io/tools/color/#!/?view.left=0&view.right=0&primary.color=009688&secondary.color=FF6D00 168 | const _primary = ContrastingColors(Color(0xFF009688), Colors.black); 169 | const _primaryLight = ContrastingColors(Color(0xFF52c7b8), Colors.black); 170 | const _primaryDark = ContrastingColors(Color(0xFF00675b), Colors.white); 171 | const _secondary = ContrastingColors(Color(0xFFff6d00), Colors.black); 172 | const _secondaryLight = ContrastingColors(Color(0xFFff9e40), Colors.black); 173 | const _secondaryDark = ContrastingColors(Color(0xFFc43c00), Colors.white); 174 | const _error = ContrastingColors(Colors.error, Colors.onError); 175 | 176 | const _lightColors = _ThemeColors( 177 | background: ContrastingColors(Color(0xFFfefefe), Colors.black), 178 | surface: ContrastingColors(Color(0xFFfefefe), Colors.black), 179 | divider: ContrastingColors(Color(0xFFeeeeee), Colors.black), 180 | ); 181 | 182 | const _darkColors = _ThemeColors( 183 | background: ContrastingColors(Color(0xFF333333), Colors.white), 184 | surface: ContrastingColors(Color(0xFF333333), Colors.white), 185 | divider: ContrastingColors(Color(0xFF484848), Colors.white), 186 | ); 187 | 188 | /// Makes using shared colors easier. 189 | class _ThemeColors extends ColorSet { 190 | const _ThemeColors({ 191 | ContrastingColors background, 192 | ContrastingColors surface, 193 | ContrastingColors divider, 194 | }) : super( 195 | primary: _primary, 196 | primaryLight: _primaryLight, 197 | primaryDark: _primaryDark, 198 | secondary: _secondary, 199 | secondaryLight: _secondaryLight, 200 | secondaryDark: _secondaryDark, 201 | error: _error, 202 | background: background, 203 | scaffold: surface, 204 | dialog: surface, 205 | card: surface, 206 | divider: divider, 207 | selectedRow: divider, 208 | indicator: _secondary, 209 | textSelection: _primaryLight, 210 | textSelectionHandle: _primaryDark, 211 | ); 212 | } 213 | ``` 214 | 215 | > The brand colors are declared as private constants so they can be used in various places as constructor arguments. 216 | 217 | Now change your `themeset` declaration to use `_lightColors` and `_darkColors`. 218 | 219 | 220 | ## Try it 221 | 222 | 223 | ### Run the Flutter App 224 | 225 | Replace the contents of `mobile/lib/main.dart` with the example [here][6]. Be sure to replace "``" in the imports with the name of your base package. 226 | 227 | Run the example by using `flutter run` or the equivalent in your IDE. 228 | 229 | 230 | ### Run the Web App 231 | 232 | 233 | #### First Run 234 | 235 | Before running the web project for the first time, several files have to be modified or created. They are described by the following table, which links to each file in the [gist][7]. 236 | 237 | File | Description 238 | -|- 239 | [`bin/build_themes.dart`][8] | Script to generate `lib/src/_themes.g.scss`. 240 | [`web/index.html`][9] | Add small script to load saved theme. 241 | [`web/styles.scss`][10] | Global styles for the web app. 242 | [`lib/app_component.dart`][11] | The main component of the app. 243 | [`lib/app_component.html`][12] | The main component of the app. 244 | [`lib/app_component.scss`][13] | The main component of the app. 245 | [`lib/src/example_list/example_list.dart`][14] | Demo of current theme. 246 | [`lib/src/example_list/example_list.html`][15] | Demo of current theme. 247 | [`lib/src/example_list/example_list.scss`][16] | Demo of current theme. 248 | 249 | Be sure to replace any occurrences of "``" with the name of your base package, or for the case of `index.html`, your app's name. 250 | 251 | Generate your initial themes file and start the local server. 252 | 253 | ```sh 254 | > pub run build_themes.dart 255 | > webdev serve 256 | ``` 257 | 258 | 259 | #### Subsequent Runs 260 | 261 | Since `webdev serve` runs indefinitely, it helps to have a second terminal open and run `pub run theme_builder.dart` any time you make theme changes in the base package. The webdev server will then pickup those changes automatically. 262 | 263 | 264 | ## Adding Elements 265 | 266 | The theme looks pretty good so far, but those buttons and input field in the demos are bare. Let's add some elements to the theme. 267 | 268 | Add the following to the bottom of `base/lib/themes.dart`. 269 | 270 | ```dart 271 | class _ButtonBase extends Element { 272 | _ButtonBase({ 273 | Color color, 274 | Font font, 275 | ShadowElevation shadow: ShadowElevation.none, 276 | }) : super( 277 | align: TextAlign.center, 278 | padding: BoxSpacing.symmetric(vertical: 4.0, horizontal: 8.0), 279 | border: Border(radii: BorderRadius(4.0)), 280 | font: font, 281 | shadow: shadow, 282 | color: color, 283 | ); 284 | } 285 | final _elements = ElementSet( 286 | primaryButton: _ButtonBase( 287 | color: _lightColors.secondary.color, 288 | font: FontSet.dark.button 289 | .copyWith(color: _lightColors.secondary.contrast, size: 16.0), 290 | shadow: ShadowElevation.dp8), 291 | secondaryButton: _ButtonBase( 292 | color: _lightColors.primary.color, 293 | font: FontSet.dark.button.copyWith(color: _lightColors.primary.contrast)), 294 | inputBase: Element.outlineInput, 295 | ); 296 | ``` 297 | 298 | Now your themeset declaration should look like this. 299 | 300 | ```dart 301 | final themeset = ThemeSet(themes: [ 302 | Theme( 303 | name: 'Light', 304 | brightness: Brightness.light, 305 | colors: _lightColors, 306 | fonts: FontSet.dark, 307 | elements: _elements), 308 | Theme( 309 | name: 'Dark', 310 | brightness: Brightness.dark, 311 | colors: _darkColors, 312 | fonts: FontSet.light, 313 | elements: _elements), 314 | ]); 315 | ``` 316 | 317 | Hot-restart your Flutter app and `pub run build_themes.dart` in your web app to see the changes! 318 | 319 | 320 | ## Adding Fonts and Assets 321 | 322 | To see how to use custom fonts and shared assets, see [themes.dart][17] from the `shared_theme` package's example. 323 | 324 | 325 | 326 | ## Screenshots 327 | 328 | Mobile light | Mobile dark 329 | -|- 330 | ![mobile-light][20] | ![mobile-dark][21] 331 | 332 | Web light | Web dark 333 | -|- 334 | ![web-light][22] | ![web-dark][23] 335 | 336 | [1]: https://flutter.io/get-started/install 337 | [2]: https://webdev.dartlang.org/guides/get-started#2-install-dart 338 | [3]: https://webdev.dartlang.org/guides/get-started#3-get-cli-tools-or-webstorm-or-both 339 | [4]: https://material.io/tools/color/#!/?view.left=0&view.right=0&primary.color=009688&secondary.color=FF6D00 340 | [5]: https://github.com/jifalops/shared_theme/tree/master/example 341 | [6]: https://gist.github.com/jifalops/4db10fb8b8b22b2896e8adac24938c33#file-main-dart 342 | [7]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2 343 | [8]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-bin-build_themes-dart 344 | [9]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-web-index-html 345 | [10]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-web-styles-scss 346 | [11]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-lib-app_component-dart 347 | [12]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-lib-app_component-html 348 | [13]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-lib-app_component-scss 349 | [14]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-lib-src-example_list-example_list-dart 350 | [15]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-lib-src-example_list-example_list-html 351 | [16]: https://gist.github.com/jifalops/ca4b209936637df8fb6e28118ce11ce2#file-lib-src-example_list-example_list-scss 352 | [17]: https://github.com/jifalops/shared_theme/blob/master/example/packages/base/lib/themes.dart 353 | [18]: https://pub.dartlang.org/packages/shared_theme 354 | [19]: https://pub.dartlang.org/packages/shared_theme_flutter 355 | [20]: https://github.com/jifalops/shared_theme/blob/master/example/images/mobile-light.png 356 | [21]: https://github.com/jifalops/shared_theme/blob/master/example/images/mobile-dark.png 357 | [22]: https://github.com/jifalops/shared_theme/blob/master/example/images/web-light.png 358 | [23]: https://github.com/jifalops/shared_theme/blob/master/example/images/web-dark.png -------------------------------------------------------------------------------- /lib/src/elements.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_theme/src/css.dart'; 2 | import 'package:shared_theme/src/fonts.dart'; 3 | import 'package:shared_theme/src/colors.dart'; 4 | 5 | /// Used to scale certain sizes between CSS pixels and Logical pixels. 6 | /// Currently only used for [BoxSpacing] (margins and padding). 7 | const _dpToPx = 96 / 160; 8 | 9 | /// An abstract container with commonly used properties such as color, font, 10 | /// margin, padding, border, shadow, text align, and size constraints. 11 | /// 12 | /// Leave a property `null` to inherit from the underlying platform. 13 | class Element extends CssEntity { 14 | const Element({ 15 | this.color, 16 | this.font, 17 | this.border, 18 | this.margin, 19 | this.padding, 20 | this.shadow, 21 | this.align, 22 | this.size, 23 | }); 24 | 25 | final Color color; 26 | final Font font; 27 | final Border border; 28 | final BoxSpacing margin; 29 | final BoxSpacing padding; 30 | final ShadowElevation shadow; 31 | final TextAlign align; 32 | final SizeLimits size; 33 | 34 | Element copyWith({ 35 | Color color, 36 | Font font, 37 | Border border, 38 | BoxSpacing margin, 39 | BoxSpacing padding, 40 | ShadowElevation shadow, 41 | TextAlign align, 42 | SizeLimits sizeLimits, 43 | }) => 44 | Element( 45 | color: color ?? this.color, 46 | font: font ?? this.font, 47 | border: border ?? this.border, 48 | margin: margin ?? this.margin, 49 | padding: padding ?? this.padding, 50 | shadow: shadow ?? this.shadow, 51 | align: align ?? this.align, 52 | size: sizeLimits ?? this.size, 53 | ); 54 | 55 | @override 56 | Map get cssValues { 57 | final map = {}; 58 | if (color != null) map['background-color'] = color.toString(); 59 | if (margin != null) map['margin'] = margin.toString(); 60 | if (padding != null) map['padding'] = padding.toString(); 61 | if (align != null) map['text-align'] = align.toString(); 62 | if (shadow != null) map['box-shadow'] = '($shadow)'; 63 | if (font != null) map.addAll(font.cssValues); 64 | if (border != null) map.addAll(border.cssValues); 65 | if (size != null) map.addAll(size.cssValues); 66 | return map; 67 | } 68 | 69 | /// An element with the border and padding of Flutter's underline input type. 70 | static const underlineInput = 71 | Element(border: Border.underlineInput, padding: BoxSpacing.inputPadding); 72 | 73 | /// An element with the border and padding of Flutter's outline input type. 74 | static const outlineInput = 75 | Element(border: Border.outlineInput, padding: BoxSpacing.inputPadding); 76 | } 77 | 78 | /// The named elements in a theme. 79 | class ElementSet implements CssEntityContainer { 80 | const ElementSet({ 81 | this.primaryButton: const Element(), 82 | this.secondaryButton: const Element(), 83 | this.tertiaryButton: const Element(), 84 | this.inputBase: const Element(), 85 | }); 86 | final Element primaryButton; 87 | final Element secondaryButton; 88 | final Element tertiaryButton; 89 | final Element inputBase; 90 | 91 | ElementSet copyWith({ 92 | Element primaryButton, 93 | Element secondaryButton, 94 | Element tertiaryButton, 95 | Element inputBase, 96 | }) => 97 | ElementSet( 98 | primaryButton: primaryButton ?? this.primaryButton, 99 | secondaryButton: secondaryButton ?? this.secondaryButton, 100 | tertiaryButton: tertiaryButton ?? this.tertiaryButton, 101 | inputBase: inputBase ?? this.inputBase, 102 | ); 103 | 104 | @override 105 | String asScssMap() => '''( 106 | primaryButton: ${primaryButton.asScssMap()}, 107 | secondaryButton: ${secondaryButton.asScssMap()}, 108 | tertiaryButton: ${tertiaryButton.asScssMap()}, 109 | inputBase: ${inputBase.asScssMap()}, 110 | )'''; 111 | 112 | @override 113 | List getMixins(List parentKeys) => [ 114 | primaryButton.asThemifiedMixin( 115 | 'primary-button', List.from(parentKeys)..add('primaryButton')), 116 | secondaryButton.asThemifiedMixin( 117 | 'secondary-button', List.from(parentKeys)..add('secondaryButton')), 118 | tertiaryButton.asThemifiedMixin( 119 | 'tertiary-button', List.from(parentKeys)..add('tertiaryButton')), 120 | inputBase.asThemifiedMixin( 121 | 'input-base', List.from(parentKeys)..add('inputBase')), 122 | ]; 123 | } 124 | 125 | /// Height and width min/max limits. Defaults to unlimited. 126 | class SizeLimits extends CssEntity { 127 | const SizeLimits( 128 | {this.minHeight: 0.0, 129 | this.maxHeight: double.maxFinite, 130 | this.minWidth: 0.0, 131 | this.maxWidth: double.maxFinite}); 132 | final double minHeight; 133 | final double maxHeight; 134 | final double minWidth; 135 | final double maxWidth; 136 | 137 | @override 138 | Map get cssValues => { 139 | 'min-height': '${minHeight}px', 140 | 'max-height': '${maxHeight}px', 141 | 'min-width': '${minWidth}px', 142 | 'max-width': '${maxWidth}px', 143 | }; 144 | } 145 | 146 | /// Such as margins or padding. 147 | class BoxSpacing { 148 | const BoxSpacing([double value = 0.0]) 149 | : top = value, 150 | right = value, 151 | bottom = value, 152 | left = value; 153 | const BoxSpacing.symmetric({double horizontal: 0.0, double vertical: 0.0}) 154 | : top = vertical, 155 | right = horizontal, 156 | bottom = vertical, 157 | left = horizontal; 158 | const BoxSpacing.only( 159 | {this.top: 0.0, this.right: 0.0, this.bottom: 0.0, this.left: 0.0}); 160 | final double top; 161 | final double right; 162 | final double bottom; 163 | final double left; 164 | 165 | /// CSS value. 166 | @override 167 | String toString() => 168 | '${top * _dpToPx}px ${right * _dpToPx}px ${bottom * _dpToPx}px ${left * _dpToPx}px'; 169 | 170 | @override 171 | bool operator ==(o) => 172 | o is BoxSpacing && 173 | top == o.top && 174 | right == o.right && 175 | bottom == o.bottom && 176 | left == o.left; 177 | @override 178 | int get hashCode => toString().hashCode; 179 | 180 | static const zero = BoxSpacing(0.0); 181 | 182 | /// The padding to mimic Flutter's TextField input. 183 | static const inputPadding = 184 | BoxSpacing.symmetric(horizontal: 8.0, vertical: 16.0); 185 | } 186 | 187 | /// How text should be aligned in a container, e.g. an [Element]. 188 | class TextAlign { 189 | const TextAlign._(this._name); 190 | final String _name; 191 | 192 | static const left = TextAlign._('left'); 193 | static const right = TextAlign._('right'); 194 | static const center = TextAlign._('center'); 195 | static const justify = TextAlign._('justify'); 196 | static const start = TextAlign._('start'); 197 | static const end = TextAlign._('end'); 198 | 199 | /// CSS value. 200 | @override 201 | String toString() => _name; 202 | } 203 | 204 | /// Valid material elevations in dp. 205 | class ShadowElevation { 206 | const ShadowElevation._(this._boxShadow, this.elevation); 207 | final String _boxShadow; 208 | final double elevation; 209 | 210 | static const none = ShadowElevation._('none', 0.0); 211 | 212 | static const dp2 = ShadowElevation._( 213 | '0 2px 2px 0 rgba(0, 0, 0, $_keyPenumbraOpacity), ' 214 | '0 3px 1px -2px rgba(0, 0, 0, $_ambientShadowOpacity), ' 215 | '0 1px 5px 0 rgba(0, 0, 0, $_keyUmbraOpacity)', 216 | 2.0); 217 | 218 | static const dp3 = ShadowElevation._( 219 | '0 3px 4px 0 rgba(0, 0, 0, $_keyPenumbraOpacity), ' 220 | '0 3px 3px -2px rgba(0, 0, 0, $_ambientShadowOpacity), ' 221 | '0 1px 8px 0 rgba(0, 0, 0, $_keyUmbraOpacity)', 222 | 3.0); 223 | 224 | static const dp4 = ShadowElevation._( 225 | '0 4px 5px 0 rgba(0, 0, 0, $_keyPenumbraOpacity), ' 226 | '0 1px 10px 0 rgba(0, 0, 0, $_ambientShadowOpacity), ' 227 | '0 2px 4px -1px rgba(0, 0, 0, $_keyUmbraOpacity)', 228 | 4.0); 229 | 230 | static const dp6 = ShadowElevation._( 231 | '0 6px 10px 0 rgba(0, 0, 0, $_keyPenumbraOpacity), ' 232 | '0 1px 18px 0 rgba(0, 0, 0, $_ambientShadowOpacity), ' 233 | '0 3px 5px -1px rgba(0, 0, 0, $_keyUmbraOpacity)', 234 | 6.0); 235 | 236 | static const dp8 = ShadowElevation._( 237 | '0 8px 10px 1px rgba(0, 0, 0, $_keyPenumbraOpacity), ' 238 | '0 3px 14px 2px rgba(0, 0, 0, $_ambientShadowOpacity), ' 239 | '0 5px 5px -3px rgba(0, 0, 0, $_keyUmbraOpacity)', 240 | 8.0); 241 | 242 | static const dp12 = ShadowElevation._( 243 | '0 12px 17px 2px rgba(0, 0, 0, $_keyPenumbraOpacity), ' 244 | '0 5px 22px 4px rgba(0, 0, 0, $_ambientShadowOpacity), ' 245 | '0 7px 8px -4px rgba(0, 0, 0, $_keyUmbraOpacity)', 246 | 12.0); 247 | 248 | static const dp16 = ShadowElevation._( 249 | '0 16px 24px 2px rgba(0, 0, 0, $_keyPenumbraOpacity), ' 250 | '0 6px 30px 5px rgba(0, 0, 0, $_ambientShadowOpacity), ' 251 | '0 8px 10px -5px rgba(0, 0, 0, $_keyUmbraOpacity)', 252 | 16.0); 253 | 254 | static const dp24 = ShadowElevation._( 255 | '0 24px 38px 3px rgba(0, 0, 0, $_keyPenumbraOpacity), ' 256 | '0 9px 46px 8px rgba(0, 0, 0, $_ambientShadowOpacity), ' 257 | '0 11px 15px -7px rgba(0, 0, 0, $_keyUmbraOpacity)', 258 | 24.0); 259 | 260 | /// CSS value 261 | @override 262 | String toString() => _boxShadow; 263 | } 264 | 265 | const _keyUmbraOpacity = 0.2; 266 | const _keyPenumbraOpacity = 0.14; 267 | const _ambientShadowOpacity = 0.12; 268 | 269 | /// A border has four [BorderSide]s and four corners, each a [BorderRadius]. 270 | class Border extends CssEntity { 271 | const Border({BorderSide sides, BorderRadius radii}) 272 | : top = sides, 273 | right = sides, 274 | bottom = sides, 275 | left = sides, 276 | topLeft = radii, 277 | topRight = radii, 278 | bottomRight = radii, 279 | bottomLeft = radii; 280 | const Border.complex({ 281 | this.top, 282 | this.right, 283 | this.bottom, 284 | this.left, 285 | this.topLeft, 286 | this.topRight, 287 | this.bottomRight, 288 | this.bottomLeft, 289 | }); 290 | final BorderSide top, right, bottom, left; 291 | final BorderRadius topLeft, topRight, bottomRight, bottomLeft; 292 | 293 | @override 294 | Map get cssValues { 295 | final map = {}; 296 | if (top != null) map['border-top'] = top.toString(); 297 | if (bottom != null) map['border-bottom'] = bottom.toString(); 298 | if (left != null) map['border-left'] = left.toString(); 299 | if (right != null) map['border-right'] = right.toString(); 300 | if (topLeft != null) map['border-top-left-radius'] = topLeft.toString(); 301 | if (topRight != null) map['border-top-right-radius'] = topRight.toString(); 302 | if (bottomRight != null) 303 | map['border-bottom-right-radius'] = bottomRight.toString(); 304 | if (bottomLeft != null) 305 | map['border-bottom-left-radius'] = bottomLeft.toString(); 306 | return map; 307 | } 308 | 309 | /// True if any corners have a non-zero radius. 310 | bool get hasRadius => 311 | (topLeft != null && topLeft != BorderRadius.zero) || 312 | (topRight != null && topRight != BorderRadius.zero) || 313 | (bottomRight != null && bottomRight != BorderRadius.zero) || 314 | (bottomLeft != null && bottomLeft != BorderRadius.zero); 315 | 316 | /// True if any size is non-zero. 317 | bool get hasSides => 318 | (top != null && top != BorderSide.none) || 319 | (right != null && right != BorderSide.none) || 320 | (bottom != null && bottom != BorderSide.none) || 321 | (left != null && left != BorderSide.none); 322 | 323 | // True if all four sides are equal. 324 | bool get hasUniformSides => top == bottom && top == left && top == right; 325 | 326 | @override 327 | bool operator ==(o) => 328 | o is Border && 329 | top == o.top && 330 | right == o.right && 331 | bottom == o.bottom && 332 | left == o.left && 333 | topLeft == o.topLeft && 334 | topRight == o.topRight && 335 | bottomRight == o.bottomRight && 336 | bottomLeft == o.bottomLeft; 337 | @override 338 | int get hashCode => toString().hashCode; 339 | 340 | static const none = Border(); 341 | 342 | /// Mimics the border in Flutter's underline input. 343 | static const underlineInput = Border.complex( 344 | bottom: BorderSide(style: BorderStyle.solid, width: 1.0), 345 | topLeft: BorderRadius(4.0), 346 | topRight: BorderRadius(4.0)); 347 | 348 | /// Mimics the border in Flutter's outline input. 349 | static const outlineInput = Border( 350 | sides: BorderSide(style: BorderStyle.solid, width: 1.0), 351 | radii: BorderRadius(4.0)); 352 | } 353 | 354 | /// One side of a [Border]. 355 | class BorderSide { 356 | const BorderSide({this.width: 1.0, this.style: BorderStyle.none, this.color}); 357 | final double width; 358 | final BorderStyle style; 359 | final Color color; 360 | 361 | /// CSS value. 362 | @override 363 | String toString() => '${width}px $style ${color?.toString() ?? ''}'; 364 | 365 | @override 366 | bool operator ==(o) => 367 | o is BorderSide && 368 | width == o.width && 369 | style == o.style && 370 | color == o.color; 371 | @override 372 | int get hashCode => toString().hashCode; 373 | 374 | static const none = BorderSide(); 375 | } 376 | 377 | /// A corner of a [Border]. 378 | class BorderRadius { 379 | const BorderRadius([double radius = 0.0]) 380 | : x = radius, 381 | y = radius; 382 | const BorderRadius.asymmetric(this.x, this.y); 383 | final double x; 384 | final double y; 385 | 386 | @override 387 | String toString() => '${x}px ${y}px'; 388 | 389 | @override 390 | bool operator ==(o) => o is BorderRadius && x == o.x && y == o.y; 391 | @override 392 | int get hashCode => toString().hashCode; 393 | 394 | static const zero = BorderRadius(0.0); 395 | } 396 | 397 | /// An enum-like class. 398 | class BorderStyle { 399 | const BorderStyle._(this._name); 400 | final String _name; 401 | 402 | static const BorderStyle none = BorderStyle._('none'); 403 | static const BorderStyle solid = BorderStyle._('solid'); 404 | 405 | /// CSS value. 406 | @override 407 | String toString() => _name; 408 | } 409 | -------------------------------------------------------------------------------- /lib/src/fonts.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:shared_theme/src/colors.dart'; 3 | import 'package:shared_theme/src/css.dart'; 4 | 5 | /// Font properties that can be mapped to Flutter and CSS. 6 | class Font extends CssEntity { 7 | const Font({ 8 | this.size: 16.0, 9 | this.family: 'Roboto', 10 | this.color: Colors.black87, 11 | this.height: 1.0, 12 | this.weight: 400, 13 | this.style: FontStyle.normal, 14 | this.decoration: const TextDecoration(), 15 | this.letterSpacing: 0.0, 16 | this.wordSpacing: 0.0, 17 | }); 18 | 19 | final double size; 20 | final String family; 21 | final Color color; 22 | final int weight; 23 | final FontStyle style; 24 | final TextDecoration decoration; 25 | final double height; 26 | final double letterSpacing; 27 | final double wordSpacing; 28 | 29 | Font copyWith({ 30 | double size, 31 | String family, 32 | Color color, 33 | double height, 34 | int weight, 35 | FontStyle style, 36 | TextDecoration decoration, 37 | double letterSpacing, 38 | double wordSpacing, 39 | }) => 40 | Font( 41 | size: size ?? this.size, 42 | family: family ?? this.family, 43 | color: color ?? this.color, 44 | height: height ?? this.height, 45 | weight: weight ?? this.weight, 46 | style: style ?? this.style, 47 | decoration: decoration ?? this.decoration, 48 | letterSpacing: letterSpacing ?? this.letterSpacing, 49 | wordSpacing: wordSpacing ?? this.wordSpacing, 50 | ); 51 | 52 | @override 53 | Map get cssValues => { 54 | 'font': '$style $weight ${size}px $family', 55 | 'line-height': '$height', 56 | 'text-decoration': '$decoration', 57 | 'color': color.toString(), 58 | 'letter-spacing': '${letterSpacing}px', 59 | 'word-spacing': '${wordSpacing}px', 60 | }; 61 | } 62 | 63 | /// The types of formats a [FontFace] can have. 64 | class FontFormat { 65 | const FontFormat._(this._name); 66 | final String _name; 67 | 68 | static const ttf = FontFormat._('truetype'); 69 | static const woff = FontFormat._('woff'); 70 | static const woff2 = FontFormat._('woff2'); 71 | static const eot = FontFormat._('embedded-opentype'); 72 | static const svg = FontFormat._('svg'); 73 | 74 | static FontFormat parse(String format) { 75 | if (format.endsWith('.ttf')) return ttf; 76 | if (format.endsWith('.woff')) return woff; 77 | if (format.endsWith('.woff2')) return woff2; 78 | if (format.endsWith('.eot')) return eot; 79 | if (format.endsWith('.svg')) return svg; 80 | return FontFormat._(''); 81 | } 82 | 83 | /// CSS value 84 | @override 85 | String toString() => _name; 86 | } 87 | 88 | /// The named fonts in a theme. 89 | class FontSet implements CssEntityContainer { 90 | const FontSet({ 91 | this.display4: _Fonts.display4Black, 92 | this.display3: _Fonts.display3Black, 93 | this.display2: _Fonts.display2Black, 94 | this.display1: _Fonts.display1Black, 95 | this.headline: _Fonts.headlineBlack, 96 | this.title: _Fonts.titleBlack, 97 | this.subhead: _Fonts.subheadBlack, 98 | this.body2: _Fonts.body2Black, 99 | this.body1: _Fonts.body1Black, 100 | this.caption: _Fonts.captionBlack, 101 | this.button: _Fonts.buttonBlack, 102 | }); 103 | 104 | final Font display4; 105 | final Font display3; 106 | final Font display2; 107 | final Font display1; 108 | final Font headline; 109 | final Font title; 110 | final Font subhead; 111 | final Font body2; 112 | final Font body1; 113 | final Font caption; 114 | final Font button; 115 | 116 | /// Copy this FontSet and replace each font's color with a corresponding 117 | /// color from [FontSet.dark]. 118 | FontSet darken() => FontSet( 119 | display4: display4.copyWith(color: FontSet.dark.display4.color), 120 | display3: display3.copyWith(color: FontSet.dark.display3.color), 121 | display2: display2.copyWith(color: FontSet.dark.display2.color), 122 | display1: display1.copyWith(color: FontSet.dark.display1.color), 123 | headline: headline.copyWith(color: FontSet.dark.headline.color), 124 | title: title.copyWith(color: FontSet.dark.title.color), 125 | subhead: subhead.copyWith(color: FontSet.dark.subhead.color), 126 | body2: body2.copyWith(color: FontSet.dark.body2.color), 127 | body1: body1.copyWith(color: FontSet.dark.body1.color), 128 | caption: caption.copyWith(color: FontSet.dark.caption.color), 129 | button: button.copyWith(color: FontSet.dark.button.color)); 130 | 131 | /// Copy this FontSet and replace each font's color with a corresponding 132 | /// color from [FontSet.light]. 133 | FontSet lighten() => FontSet( 134 | display4: display4.copyWith(color: FontSet.light.display4.color), 135 | display3: display3.copyWith(color: FontSet.light.display3.color), 136 | display2: display2.copyWith(color: FontSet.light.display2.color), 137 | display1: display1.copyWith(color: FontSet.light.display1.color), 138 | headline: headline.copyWith(color: FontSet.light.headline.color), 139 | title: title.copyWith(color: FontSet.light.title.color), 140 | subhead: subhead.copyWith(color: FontSet.light.subhead.color), 141 | body2: body2.copyWith(color: FontSet.light.body2.color), 142 | body1: body1.copyWith(color: FontSet.light.body1.color), 143 | caption: caption.copyWith(color: FontSet.light.caption.color), 144 | button: button.copyWith(color: FontSet.light.button.color)); 145 | 146 | FontSet copyWith({ 147 | Font display4, 148 | Font display3, 149 | Font display2, 150 | Font display1, 151 | Font headline, 152 | Font title, 153 | Font subhead, 154 | Font body2, 155 | Font body1, 156 | Font caption, 157 | Font button, 158 | }) => 159 | FontSet( 160 | display4: display4 ?? this.display4, 161 | display3: display3 ?? this.display3, 162 | display2: display2 ?? this.display2, 163 | display1: display1 ?? this.display1, 164 | headline: headline ?? this.headline, 165 | title: title ?? this.title, 166 | subhead: subhead ?? this.subhead, 167 | body2: body2 ?? this.body2, 168 | body1: body1 ?? this.body1, 169 | caption: caption ?? this.caption, 170 | button: button ?? this.button, 171 | ); 172 | 173 | /// Modify one or more values in each [Font] in this [FontSet]. 174 | FontSet apply({ 175 | double size, 176 | String family, 177 | Color color, 178 | double height, 179 | int weight, 180 | FontStyle style, 181 | TextDecoration decoration, 182 | double letterSpacing, 183 | double wordSpacing, 184 | }) => 185 | FontSet( 186 | display4: display4.copyWith( 187 | size: size, 188 | family: family, 189 | color: color, 190 | height: height, 191 | weight: weight, 192 | style: style, 193 | decoration: decoration, 194 | letterSpacing: letterSpacing, 195 | wordSpacing: wordSpacing), 196 | display3: display3.copyWith( 197 | size: size, 198 | family: family, 199 | color: color, 200 | height: height, 201 | weight: weight, 202 | style: style, 203 | decoration: decoration, 204 | letterSpacing: letterSpacing, 205 | wordSpacing: wordSpacing), 206 | display2: display2.copyWith( 207 | size: size, 208 | family: family, 209 | color: color, 210 | height: height, 211 | weight: weight, 212 | style: style, 213 | decoration: decoration, 214 | letterSpacing: letterSpacing, 215 | wordSpacing: wordSpacing), 216 | display1: display1.copyWith( 217 | size: size, 218 | family: family, 219 | color: color, 220 | height: height, 221 | weight: weight, 222 | style: style, 223 | decoration: decoration, 224 | letterSpacing: letterSpacing, 225 | wordSpacing: wordSpacing), 226 | headline: headline.copyWith( 227 | size: size, 228 | family: family, 229 | color: color, 230 | height: height, 231 | weight: weight, 232 | style: style, 233 | decoration: decoration, 234 | letterSpacing: letterSpacing, 235 | wordSpacing: wordSpacing), 236 | title: title.copyWith( 237 | size: size, 238 | family: family, 239 | color: color, 240 | height: height, 241 | weight: weight, 242 | style: style, 243 | decoration: decoration, 244 | letterSpacing: letterSpacing, 245 | wordSpacing: wordSpacing), 246 | subhead: subhead.copyWith( 247 | size: size, 248 | family: family, 249 | color: color, 250 | height: height, 251 | weight: weight, 252 | style: style, 253 | decoration: decoration, 254 | letterSpacing: letterSpacing, 255 | wordSpacing: wordSpacing), 256 | body2: body2.copyWith( 257 | size: size, 258 | family: family, 259 | color: color, 260 | height: height, 261 | weight: weight, 262 | style: style, 263 | decoration: decoration, 264 | letterSpacing: letterSpacing, 265 | wordSpacing: wordSpacing), 266 | body1: body1.copyWith( 267 | size: size, 268 | family: family, 269 | color: color, 270 | height: height, 271 | weight: weight, 272 | style: style, 273 | decoration: decoration, 274 | letterSpacing: letterSpacing, 275 | wordSpacing: wordSpacing), 276 | caption: caption.copyWith( 277 | size: size, 278 | family: family, 279 | color: color, 280 | height: height, 281 | weight: weight, 282 | style: style, 283 | decoration: decoration, 284 | letterSpacing: letterSpacing, 285 | wordSpacing: wordSpacing), 286 | button: button.copyWith( 287 | size: size, 288 | family: family, 289 | color: color, 290 | height: height, 291 | weight: weight, 292 | style: style, 293 | decoration: decoration, 294 | letterSpacing: letterSpacing, 295 | wordSpacing: wordSpacing), 296 | ); 297 | 298 | @override 299 | String asScssMap() => '''( 300 | display4: ${display4.asScssMap()}, 301 | display3: ${display3.asScssMap()}, 302 | display2: ${display2.asScssMap()}, 303 | display1: ${display1.asScssMap()}, 304 | headline: ${headline.asScssMap()}, 305 | title: ${title.asScssMap()}, 306 | subhead: ${subhead.asScssMap()}, 307 | body2: ${body2.asScssMap()}, 308 | body1: ${body1.asScssMap()}, 309 | caption: ${caption.asScssMap()}, 310 | button: ${button.asScssMap()}, 311 | )'''; 312 | 313 | @override 314 | List getMixins(List parentKeys) => [ 315 | display4.asThemifiedMixin( 316 | 'font-display4', List.from(parentKeys)..add('display4')), 317 | display3.asThemifiedMixin( 318 | 'font-display3', List.from(parentKeys)..add('display3')), 319 | display2.asThemifiedMixin( 320 | 'font-display2', List.from(parentKeys)..add('display2')), 321 | display1.asThemifiedMixin( 322 | 'font-display1', List.from(parentKeys)..add('display1')), 323 | headline.asThemifiedMixin( 324 | 'font-headline', List.from(parentKeys)..add('headline')), 325 | title.asThemifiedMixin( 326 | 'font-title', List.from(parentKeys)..add('title')), 327 | subhead.asThemifiedMixin( 328 | 'font-subhead', List.from(parentKeys)..add('subhead')), 329 | body2.asThemifiedMixin( 330 | 'font-body2', List.from(parentKeys)..add('body2')), 331 | body1.asThemifiedMixin( 332 | 'font-body1', List.from(parentKeys)..add('body1')), 333 | caption.asThemifiedMixin( 334 | 'font-caption', List.from(parentKeys)..add('caption')), 335 | button.asThemifiedMixin( 336 | 'font-button', List.from(parentKeys)..add('button')), 337 | ]; 338 | 339 | static const dark = FontSet(); 340 | 341 | static const light = FontSet( 342 | display4: _Fonts.display4White, 343 | display3: _Fonts.display3White, 344 | display2: _Fonts.display2White, 345 | display1: _Fonts.display1White, 346 | headline: _Fonts.headlineWhite, 347 | title: _Fonts.titleWhite, 348 | subhead: _Fonts.subheadWhite, 349 | body2: _Fonts.body2White, 350 | body1: _Fonts.body1White, 351 | caption: _Fonts.captionWhite, 352 | button: _Fonts.buttonWhite, 353 | ); 354 | } 355 | 356 | /// Italic or not. 357 | class FontStyle { 358 | const FontStyle._(this._name); 359 | final String _name; 360 | 361 | static const normal = FontStyle._('normal'); 362 | static const italic = FontStyle._('italic'); 363 | 364 | /// CSS value. 365 | @override 366 | toString() => _name; 367 | } 368 | 369 | /// Underline, line-through, solid, dashed, color, etc. 370 | class TextDecoration { 371 | const TextDecoration({ 372 | this.lines: const [], 373 | this.style: TextDecorationStyle.solid, 374 | this.color, 375 | }); 376 | final List lines; 377 | final TextDecorationStyle style; 378 | final Color color; 379 | 380 | /// CSS value 381 | @override 382 | String toString() => lines.isEmpty 383 | ? 'none' 384 | : '${lines.join(' ')} $style ${color?.toString() ?? ''}'; 385 | } 386 | 387 | /// A the types of lines that can be in a [TextDecoration]. 388 | class TextDecorationLine { 389 | const TextDecorationLine._(this._name); 390 | final String _name; 391 | 392 | static const underline = TextDecorationLine._('underline'); 393 | static const overline = TextDecorationLine._('overline'); 394 | static const lineThrough = TextDecorationLine._('line-through'); 395 | 396 | /// CSS value 397 | @override 398 | toString() => _name; 399 | } 400 | 401 | /// An enum-like class. 402 | class TextDecorationStyle { 403 | const TextDecorationStyle._(this._name); 404 | final String _name; 405 | 406 | static const solid = TextDecorationStyle._('solid'); 407 | static const double = TextDecorationStyle._('double'); 408 | static const dotted = TextDecorationStyle._('dotted'); 409 | static const dashed = TextDecorationStyle._('dashed'); 410 | static const wavy = TextDecorationStyle._('wavy'); 411 | 412 | /// CSS value 413 | @override 414 | toString() => _name; 415 | } 416 | 417 | /// A CSS `@font-face` definition. 418 | class FontFace { 419 | const FontFace({ 420 | @required this.family, 421 | @required this.url, 422 | this.weight: 400, 423 | this.style: FontStyle.normal, 424 | }); 425 | final String family; 426 | final String url; 427 | final int weight; 428 | final FontStyle style; 429 | 430 | /// CSS `@font-face` value. 431 | @override 432 | String toString() => ''' 433 | @font-face { 434 | font-family: '$family'; 435 | font-weight: $weight; 436 | font-style: $style; 437 | src: url('$url') format('${FontFormat.parse(url)}'); 438 | } 439 | '''; 440 | 441 | String asCssImport() => '@import url($url);'; 442 | String asHtmlLink() => ''; 443 | } 444 | 445 | /// Default fonts from material design. 446 | class _Fonts { 447 | // Black fonts. 448 | 449 | /// size: 112.0, weight: 100, color: Colors.black54 450 | static const display4Black = 451 | Font(size: 112.0, weight: 100, color: Colors.black54); 452 | 453 | /// size: 56.0, color: Colors.black54 454 | static const display3Black = Font(size: 56.0, color: Colors.black54); 455 | 456 | /// size: 45.0, color: Colors.black54 457 | static const display2Black = Font(size: 45.0, color: Colors.black54); 458 | 459 | /// size: 34.0, color: Colors.black54 460 | static const display1Black = Font(size: 34.0, color: Colors.black54); 461 | 462 | /// size: 24.0 463 | static const headlineBlack = Font(size: 24.0); 464 | 465 | /// size: 20.0, weight: 500 466 | static const titleBlack = Font(size: 20.0, weight: 500); 467 | 468 | /// size: 16.0 469 | static const subheadBlack = Font(size: 16.0); 470 | 471 | /// size: 14.0, weight: 500 472 | static const body2Black = Font(size: 14.0, weight: 500); 473 | 474 | /// size: 14.0 475 | static const body1Black = Font(size: 14.0); 476 | 477 | /// size: 12.0, color: Colors.black54 478 | static const captionBlack = Font(size: 12.0, color: Colors.black54); 479 | 480 | /// size: 14.0, weight: 500 481 | static const buttonBlack = Font(size: 14.0, weight: 500); 482 | 483 | // White fonts. 484 | 485 | /// size: 112.0, weight: 100, color: Colors.white70 486 | static const display4White = 487 | Font(size: 112.0, weight: 100, color: Colors.white70); 488 | 489 | /// size: 56.0, color: Colors.white70 490 | static const display3White = Font(size: 56.0, color: Colors.white70); 491 | 492 | /// size: 45.0, color: Colors.white70 493 | static const display2White = Font(size: 45.0, color: Colors.white70); 494 | 495 | /// size: 34.0, color: Colors.white70 496 | static const display1White = Font(size: 34.0, color: Colors.white70); 497 | 498 | /// size: 24.0, color: Colors.white 499 | static const headlineWhite = Font(size: 24.0, color: Colors.white); 500 | 501 | /// size: 20.0, weight: 500, color: Colors.white 502 | static const titleWhite = Font(size: 20.0, weight: 500, color: Colors.white); 503 | 504 | /// size: 16.0, color: Colors.white 505 | static const subheadWhite = Font(size: 16.0, color: Colors.white); 506 | 507 | /// size: 14.0, weight: 500, color: Colors.white 508 | static const body2White = Font(size: 14.0, weight: 500, color: Colors.white); 509 | 510 | /// size: 14.0, color: Colors.white 511 | static const body1White = Font(size: 14.0, color: Colors.white); 512 | 513 | /// size: 12.0, color: Colors.white70 514 | static const captionWhite = Font(size: 12.0, color: Colors.white70); 515 | 516 | /// size: 14.0, weight: 500, color: Colors.white 517 | static const buttonWhite = Font(size: 14.0, weight: 500, color: Colors.white); 518 | } 519 | --------------------------------------------------------------------------------