├── example
├── lib
│ ├── images
│ │ └── themed.png
│ └── main.dart
├── SponsoredByMyTextAi.png
├── android
│ ├── 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
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values-night
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── dev
│ │ │ │ │ │ └── glasberg
│ │ │ │ │ │ └── example_xaxa
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── build.gradle
│ └── settings.gradle
├── pubspec.yaml
├── README.md
├── .gitignore
├── .metadata
├── pubspec.lock
└── analysis_options.yaml
├── lib
├── themed.dart
└── src
│ ├── const_theme_exception.dart
│ ├── color_swatches.dart
│ ├── themed_extensions.dart
│ ├── color_util.dart
│ ├── change_colors.dart
│ └── themed.dart
├── .metadata
├── pubspec.yaml
├── LICENSE
├── CHANGELOG.md
├── .gitignore
├── pubspec.lock
├── analysis_options.yaml
└── README.md
/example/lib/images/themed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/themed/HEAD/example/lib/images/themed.png
--------------------------------------------------------------------------------
/example/SponsoredByMyTextAi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/themed/HEAD/example/SponsoredByMyTextAi.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/themed/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/themed/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/themed/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/themed/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/themed/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/dev/glasberg/example_xaxa/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.glasberg.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/lib/themed.dart:
--------------------------------------------------------------------------------
1 | export 'src/change_colors.dart';
2 | export 'src/color_swatches.dart';
3 | export 'src/color_util.dart';
4 | export 'src/const_theme_exception.dart';
5 | export 'src/themed.dart';
6 | export 'src/themed_extensions.dart';
7 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
6 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/to/reference-keystore
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/.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: d79295af24c3ed621c33713ecda14ad196fd9c31
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: Example for Themed
3 | publish_to: 'none'
4 | version: 1.0.0+1
5 |
6 | environment:
7 | sdk: '>=3.5.0 <4.0.0'
8 |
9 | dependencies:
10 | themed:
11 | path: ../
12 | flutter:
13 | sdk: flutter
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 |
19 | flutter:
20 | uses-material-design: true
21 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = "../build"
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(":app")
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/src/const_theme_exception.dart:
--------------------------------------------------------------------------------
1 | class ConstThemeException {
2 | String msg;
3 |
4 | ConstThemeException(this.msg);
5 |
6 | @override
7 | String toString() => msg;
8 |
9 | @override
10 | bool operator ==(Object other) =>
11 | identical(this, other) ||
12 | other is ConstThemeException && runtimeType == other.runtimeType && msg == other.msg;
13 |
14 | @override
15 | int get hashCode => msg.hashCode;
16 | }
17 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: themed
2 | description: The themed package lets you define a theme with const values, and then, by using some dark Dart magic, go and change them dynamically anyway.
3 | version: 8.1.0
4 | # author: Marcelo Glasberg
5 | homepage: https://github.com/marcglasberg/themed
6 | topics:
7 | - theme
8 | - theming
9 | - ui
10 | - color
11 | - font
12 |
13 | environment:
14 | sdk: '>=3.5.0 <4.0.0'
15 | flutter: ">=3.16.0"
16 |
17 | dependencies:
18 | flutter:
19 | sdk: flutter
20 |
21 | dev_dependencies:
22 | flutter_test:
23 | sdk: flutter
24 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | Themed example
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.1.0" apply false
22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Symbolication related
35 | app.*.symbols
36 |
37 | # Obfuscation related
38 | app.*.map.json
39 |
40 | # Android Studio will place build artifacts here
41 | /android/app/debug
42 | /android/app/profile
43 | /android/app/release
44 |
--------------------------------------------------------------------------------
/example/.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: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
17 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
18 | - platform: android
19 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
20 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 Parkside Technologies
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted
4 | provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
7 | and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
10 | and the following disclaimer in the documentation and/or other materials provided with the
11 | distribution.
12 |
13 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
14 | or promote products derived from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
17 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
18 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
23 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5 | id "dev.flutter.flutter-gradle-plugin"
6 | }
7 |
8 | android {
9 | namespace = "dev.glasberg.example"
10 | compileSdk = flutter.compileSdkVersion
11 | ndkVersion = flutter.ndkVersion
12 |
13 | compileOptions {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 | }
17 |
18 | kotlinOptions {
19 | jvmTarget = JavaVersion.VERSION_1_8
20 | }
21 |
22 | defaultConfig {
23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24 | applicationId = "dev.glasberg.example"
25 | // You can update the following values to match your application needs.
26 | // For more information, see: https://flutter.dev/to/review-gradle-config.
27 | minSdk = flutter.minSdkVersion
28 | targetSdk = flutter.targetSdkVersion
29 | versionCode = flutter.versionCode
30 | versionName = flutter.versionName
31 | }
32 |
33 | buildTypes {
34 | release {
35 | // TODO: Add your own signing config for the release build.
36 | // Signing with the debug keys for now, so `flutter run --release` works.
37 | signingConfig = signingConfigs.debug
38 | }
39 | }
40 | }
41 |
42 | flutter {
43 | source = "../.."
44 | }
45 |
--------------------------------------------------------------------------------
/lib/src/color_swatches.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// You can create a [MaterialColor] color swatch from a single [primary]
4 | /// color (which you can later change using the Themed package):
5 | ///
6 | /// ```
7 | /// static const MaterialColor myColorSwatch = MaterialColorRef(
8 | /// AppColors.primary,
9 | /// {
10 | /// 50: AppColors.primary,
11 | /// 100: AppColors.primary,
12 | /// 200: AppColors.primary,
13 | /// 300: AppColors.primary,
14 | /// 400: AppColors.primary,
15 | /// 500: AppColors.primary,
16 | /// 600: AppColors.primary,
17 | /// 700: AppColors.primary,
18 | /// 800: AppColors.primary,
19 | /// 900: AppColors.primary,
20 | /// },
21 | /// );
22 | /// ```
23 | class MaterialColorSwatch extends MaterialColor {
24 | final Color primary;
25 |
26 | const MaterialColorSwatch(this.primary, Map swatch) : super(0, swatch);
27 |
28 | @override
29 | int get value => primary.value;
30 | }
31 |
32 | /// You can create a [MaterialAccentColorSwatch] color swatch from a
33 | /// single [primary] color (which you can later change using the Themed package):
34 | ///
35 | /// ```
36 | /// static const MaterialColor myColorSwatch = MaterialAccentColorSwatch(
37 | /// AppColors.primary,
38 | /// {
39 | /// 50: AppColors.primary,
40 | /// 100: AppColors.primary,
41 | /// 200: AppColors.primary,
42 | /// 400: AppColors.primary,
43 | /// 700: AppColors.primary,
44 | /// },
45 | /// );
46 | /// ```
47 | class MaterialAccentColorSwatch extends MaterialAccentColor {
48 | final Color primary;
49 |
50 | const MaterialAccentColorSwatch(this.primary, Map swatch)
51 | : super(0, swatch);
52 |
53 | @override
54 | int get value => primary.value;
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/themed_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | extension TextStyleExtension on TextStyle {
4 | //
5 | /// You can create a [TextStyle] by adding the [TextStyle] to one these types:
6 | /// [Color], [String] (fontFamily), [FontSize], [FontWeight], [FontStyle],
7 | /// [TextBaseline], [Locale], List<[Shadow]>, List<[FontFeature]>, [Decoration],
8 | /// [TextDecorationStyle], or [TextHeight].
9 | ///
10 | /// For example:
11 | ///
12 | /// ```
13 | /// Text('Hello', style: TextStyle(fontSize: 14.0) + "Roboto" + Colors.red + FontStyle.italic);
14 | /// ```
15 | ///
16 | /// Note: If you add null, that's not an error. It will simply return the same TextStyle.
17 | /// However, if you add an invalid type it will throw an error in RUN TIME.
18 | ///
19 | TextStyle operator +(Object? obj) => //
20 | (obj == null)
21 | ? this
22 | : copyWith(
23 | color: obj is Color ? obj : null,
24 | fontFamily: obj is String ? obj : null,
25 | fontSize: obj is FontSize ? obj.fontSize : null,
26 | fontWeight: obj is FontWeight ? obj : null,
27 | fontStyle: obj is FontStyle ? obj : null,
28 | textBaseline: obj is TextBaseline ? obj : null,
29 | locale: obj is Locale ? obj : null,
30 | shadows: obj is List ? obj : null,
31 | fontFeatures: obj is List ? obj : null,
32 | decoration: obj is TextDecoration ? obj : null,
33 | decorationStyle: obj is TextDecorationStyle ? obj : null,
34 | height: obj is TextHeight ? obj.height : null,
35 | );
36 |
37 | /// Instead of using [operator +] you can use the [add] method.
38 | /// If [apply] is false, the provided [obj] will not be added.
39 | ///
40 | TextStyle add(Object? obj, {bool apply = true}) => (apply) ? this + obj : this;
41 | }
42 |
43 | class TextHeight {
44 | final double height;
45 |
46 | const TextHeight(this.height);
47 | }
48 |
49 | class FontSize {
50 | final double fontSize;
51 |
52 | const FontSize(this.fontSize);
53 | }
54 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Sponsored by [MyText.ai](https://mytext.ai)
2 |
3 | [](https://mytext.ai)
4 |
5 | ## 8.1.0
6 |
7 | * `Themed.reset`
8 |
9 | ## 8.0.2
10 |
11 | * Version 8 and up are compatible with Flutter 3.27.0 and up. Note: Version 7.0.0 is
12 | not compatible with the new Flutter versions, but it will not throw any errors. It will
13 | just not work as expected. This means you MUST upgrade as soon as you upgrade your
14 | Flutter version:
15 |
16 | ```yaml
17 | dependencies:
18 | themed: ^8.0.1
19 | ```
20 |
21 | ## 7.0.0
22 |
23 | * Theme change improvement: Now, when a theme is changed, it will make all color
24 | references different from themselves during exactly one frame. This will assure
25 | that all widgets that depend on the theme will be rebuilt with the new theme.
26 | While technically this is a breaking change, it's unlikely to affect you.
27 |
28 | * `ColorRef.sameColor()` method to compare the current color of two `ColorRef` objects,
29 | or with a `Color` object. Note that the compared color is the effective one, that
30 | depend on the current theme.
31 |
32 | * Fixed bug that affected Hot Reload.
33 |
34 | ## 5.1.1
35 |
36 | * Added `Color.removeOpacity()` extension method.
37 | Note methods `addOpacity()`, `darker()`, `lighter()`, `average()` and `decolorize`
38 | already existed.
39 |
40 | ## 5.0.3
41 |
42 | * Flutter 3.16.0 compatible.
43 |
44 | ## 4.0.0
45 |
46 | * Flutter 3.13.0 compatible.
47 |
48 | ## 3.0.2
49 |
50 | * Flutter 2.8.0 compatible.
51 |
52 | ## 2.4.0
53 |
54 | * `ChangeColors` widget to change the brightness, saturation and hue of any widget,
55 | including images.
56 |
57 | ## 2.3.0
58 |
59 | * Color extension: `darker`, `lighter`, `average`, `decolorize`, `addOpacity`,
60 | `rgbaToArgb` and `abgrToArgb` methods.
61 |
62 | ## 2.2.0
63 |
64 | * Improved `ColorRef.toString()` and `TextStyleRef.toString()` methods.
65 |
66 | ## 2.1.0
67 |
68 | * Saving and setting themes by key: `Themed.save()`, `Themed.setThemeByKey()` etc.
69 |
70 | * Fixed https://github.com/marcglasberg/themed/issues/1
71 |
72 | ## 2.0.5
73 |
74 | * Compatible with Flutter 2.5.
75 |
76 | ## 2.0.1
77 |
78 | * Breaking change: The `id` now must only be provided if it's necessary to differentiate
79 | constants.
80 |
81 | ## 1.0.0
82 |
83 | * Initial Commit.
84 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # Flutter/Dart/Pub related
19 | **/doc/api/
20 | .dart_tool/
21 | .flutter-plugins
22 | .flutter-plugins-dependencies
23 | .packages
24 | .pub-cache/
25 | .pub/
26 | build/
27 |
28 | # Android related
29 | **/android/**/gradle-wrapper.jar
30 | **/android/.gradle
31 | **/android/captures/
32 | **/android/gradlew
33 | **/android/gradlew.bat
34 | **/android/local.properties
35 | **/android/**/GeneratedPluginRegistrant.java
36 |
37 | # iOS/XCode related
38 | **/ios/**/*.mode1v3
39 | **/ios/**/*.mode2v3
40 | **/ios/**/*.moved-aside
41 | **/ios/**/*.pbxuser
42 | **/ios/**/*.perspectivev3
43 | **/ios/**/*sync/
44 | **/ios/**/.sconsign.dblite
45 | **/ios/**/.tags*
46 | **/ios/**/.vagrant/
47 | **/ios/**/DerivedData/
48 | **/ios/**/Icon?
49 | **/ios/**/Pods/
50 | **/ios/**/.symlinks/
51 | **/ios/**/profile
52 | **/ios/**/xcuserdata
53 | **/ios/.generated/
54 | **/ios/Flutter/App.framework
55 | **/ios/Flutter/Flutter.framework
56 | **/ios/Flutter/Flutter.podspec
57 | **/ios/Flutter/Generated.xcconfig
58 | **/ios/Flutter/ephemeral
59 | **/ios/Flutter/app.flx
60 | **/ios/Flutter/app.zip
61 | **/ios/Flutter/flutter_assets/
62 | **/ios/Flutter/flutter_export_environment.sh
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 |
66 | # Exceptions to above rules.
67 | !**/ios/**/default.mode1v3
68 | !**/ios/**/default.mode2v3
69 | !**/ios/**/default.pbxuser
70 | !**/ios/**/default.perspectivev3
71 |
72 | ### Intellij ###
73 |
74 | # For each user.
75 | .idea/**/workspace.xml
76 | .idea/**/tasks.xml
77 | .idea/**/usage.statistics.xml
78 | .idea/**/dictionaries
79 | .idea/**/shelf
80 | **/.idea/workspace.xml
81 |
82 | # Generated files.
83 | .idea/**/contentModel.xml
84 |
85 | # Sensitive information or files that change a lot.
86 | .idea/**/dataSources/
87 | .idea/**/dataSources.ids
88 | .idea/**/dataSources.local.xml
89 | .idea/**/sqlDataSources.xml
90 | .idea/**/dynamic.xml
91 | .idea/**/uiDesigner.xml
92 | .idea/**/dbnavigator.xml
93 |
94 | # Gradle.
95 | .idea/**/gradle.xml
96 | .idea/**/libraries/
97 |
98 | # IntelliJ.
99 | out/
100 |
101 | # Plugin mpeltonen/sbt-idea.
102 | .idea_modules/
103 |
104 | # Web related
105 | lib/generated_plugin_registrant.dart
106 |
107 | # Symbolication related
108 | app.*.symbols
109 |
110 | # Obfuscation related
111 | app.*.map.json
112 |
113 | # Android Studio will place build artifacts here
114 | /android/app/debug
115 | /android/app/profile
116 | /android/app/release
117 |
118 | # Android serialized cache of Android Studio 3.1+.
119 | .idea/caches/build_file_checksums.ser
120 |
121 | # VS Code
122 | .vscode/
123 |
124 | .env
125 |
--------------------------------------------------------------------------------
/lib/src/color_util.dart:
--------------------------------------------------------------------------------
1 | import "package:flutter/material.dart";
2 | import 'package:themed/src/change_colors.dart';
3 |
4 | extension ColorUtil on Color {
5 | //
6 | /// Makes the color lighter (more white), by the given [value], from `0` (no change) to `1` (white).
7 | /// If [value] is not provided, it will be 0.5 (50% change).
8 | /// If [value] is less than 0, it's 0. If more than 1, it's 1.
9 | /// Doesn't change the opacity.
10 | ///
11 | /// See also: [ChangeColors]
12 | ///
13 | Color lighter([double value = 0.5]) =>
14 | Color.lerp(this, Colors.white, _limit(value))!.withAlpha(alpha);
15 |
16 | /// Makes the color darker (more black), by the given [value], from `0` (no change) to `1` (black).
17 | /// If [value] is not provided, it will be 0.5 (50% change).
18 | /// If [value] is less than 0, it's 0. If more than 1, it's 1.
19 | /// Doesn't change the opacity.
20 | ///
21 | /// See also: [ChangeColors]
22 | ///
23 | Color darker([double value = 0.5]) =>
24 | Color.lerp(this, Colors.black, _limit(value))!.withAlpha(alpha);
25 |
26 | /// Makes the current color more similar to the given [color], by the given [value],
27 | /// from `0` (no change) to `1` (equal to [color]).
28 | /// If [value] is not provided, it will be 0.5 (50% change).
29 | /// If [value] is less than 0, it's 0. If more than 1, it's 1.
30 | /// Doesn't change the opacity.
31 | ///
32 | /// See also: [ChangeColors]
33 | ///
34 | Color average(Color color, [double value = 0.5]) =>
35 | Color.lerp(this, color, _limit(value))!;
36 |
37 | /// Makes the current color more grey (aprox. keeping its luminance), by the given [value],
38 | /// from `0` (no change) to `1` (grey).
39 | /// If [value] is not provided, it will be 1 (100% change, no color at all).
40 | /// If [value] is less than 0, it's 0. If more than 1, it's 1.
41 | /// Doesn't change the opacity.
42 | ///
43 | /// See also: [ChangeColors]
44 | ///
45 | Color decolorize([double value = 1]) {
46 | int average = (red + green + blue) ~/ 3;
47 | var color = Color.fromARGB(alpha, average, average, average);
48 | return Color.lerp(this, color, _limit(value))!;
49 | }
50 |
51 | /// Makes the current color more transparent, by the given [value],
52 | /// from `0` (total transparency) to `1` (no change).
53 | /// If [value] is not provided, it will be 0.5 (50% change).
54 | /// If [value] is less than 0, it's 0. If more than 1, it's 1.
55 | /// Makes it more transparent if percent < 1.
56 | Color addOpacity([double value = 0.5]) =>
57 | Color.fromARGB((alpha * _limit(value)).round(), red, green, blue);
58 |
59 | /// Makes the current color more opaque, by the given [value],
60 | /// from `0` (no change) to `1` (fully opaque).
61 | /// If [value] is not provided, it will be 0.5 (50% change).
62 | /// If [value] is less than 0, it's 0. If more than 1, it's 1.
63 | /// Makes it more opaque if percent < 1.
64 | Color removeOpacity([double value = 0.5]) {
65 | return Color.fromARGB(
66 | (alpha + ((255 - alpha) * _limit(value))).round(), red, green, blue);
67 | }
68 |
69 | /// Converts the RGBA color representation to ARGB.
70 | static int rgbaToArgb(int rgbaColor) {
71 | int a = rgbaColor & 0xFF;
72 | int rgb = rgbaColor >> 8;
73 | return rgb + (a << 24);
74 | }
75 |
76 | /// Converts the ABGR color representation to ARGB.
77 | static int abgrToArgb(int argbColor) {
78 | int r = (argbColor >> 16) & 0xFF;
79 | int b = argbColor & 0xFF;
80 | return (argbColor & 0xFF00FF00) | (b << 16) | r;
81 | }
82 |
83 | double _limit(double value) => (value < 0) ? 0 : (value > 1 ? 1 : value);
84 | }
85 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.11.0"
12 | boolean_selector:
13 | dependency: transitive
14 | description:
15 | name: boolean_selector
16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.1.1"
20 | characters:
21 | dependency: transitive
22 | description:
23 | name: characters
24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "1.3.0"
28 | clock:
29 | dependency: transitive
30 | description:
31 | name: clock
32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.1.1"
36 | collection:
37 | dependency: transitive
38 | description:
39 | name: collection
40 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.19.0"
44 | fake_async:
45 | dependency: transitive
46 | description:
47 | name: fake_async
48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.3.1"
52 | flutter:
53 | dependency: "direct main"
54 | description: flutter
55 | source: sdk
56 | version: "0.0.0"
57 | flutter_test:
58 | dependency: "direct dev"
59 | description: flutter
60 | source: sdk
61 | version: "0.0.0"
62 | leak_tracker:
63 | dependency: transitive
64 | description:
65 | name: leak_tracker
66 | sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
67 | url: "https://pub.dev"
68 | source: hosted
69 | version: "10.0.7"
70 | leak_tracker_flutter_testing:
71 | dependency: transitive
72 | description:
73 | name: leak_tracker_flutter_testing
74 | sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
75 | url: "https://pub.dev"
76 | source: hosted
77 | version: "3.0.8"
78 | leak_tracker_testing:
79 | dependency: transitive
80 | description:
81 | name: leak_tracker_testing
82 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
83 | url: "https://pub.dev"
84 | source: hosted
85 | version: "3.0.1"
86 | matcher:
87 | dependency: transitive
88 | description:
89 | name: matcher
90 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
91 | url: "https://pub.dev"
92 | source: hosted
93 | version: "0.12.16+1"
94 | material_color_utilities:
95 | dependency: transitive
96 | description:
97 | name: material_color_utilities
98 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
99 | url: "https://pub.dev"
100 | source: hosted
101 | version: "0.11.1"
102 | meta:
103 | dependency: transitive
104 | description:
105 | name: meta
106 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
107 | url: "https://pub.dev"
108 | source: hosted
109 | version: "1.15.0"
110 | path:
111 | dependency: transitive
112 | description:
113 | name: path
114 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
115 | url: "https://pub.dev"
116 | source: hosted
117 | version: "1.9.0"
118 | sky_engine:
119 | dependency: transitive
120 | description: flutter
121 | source: sdk
122 | version: "0.0.0"
123 | source_span:
124 | dependency: transitive
125 | description:
126 | name: source_span
127 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
128 | url: "https://pub.dev"
129 | source: hosted
130 | version: "1.10.0"
131 | stack_trace:
132 | dependency: transitive
133 | description:
134 | name: stack_trace
135 | sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
136 | url: "https://pub.dev"
137 | source: hosted
138 | version: "1.12.0"
139 | stream_channel:
140 | dependency: transitive
141 | description:
142 | name: stream_channel
143 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
144 | url: "https://pub.dev"
145 | source: hosted
146 | version: "2.1.2"
147 | string_scanner:
148 | dependency: transitive
149 | description:
150 | name: string_scanner
151 | sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
152 | url: "https://pub.dev"
153 | source: hosted
154 | version: "1.3.0"
155 | term_glyph:
156 | dependency: transitive
157 | description:
158 | name: term_glyph
159 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
160 | url: "https://pub.dev"
161 | source: hosted
162 | version: "1.2.1"
163 | test_api:
164 | dependency: transitive
165 | description:
166 | name: test_api
167 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
168 | url: "https://pub.dev"
169 | source: hosted
170 | version: "0.7.3"
171 | vector_math:
172 | dependency: transitive
173 | description:
174 | name: vector_math
175 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
176 | url: "https://pub.dev"
177 | source: hosted
178 | version: "2.1.4"
179 | vm_service:
180 | dependency: transitive
181 | description:
182 | name: vm_service
183 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
184 | url: "https://pub.dev"
185 | source: hosted
186 | version: "14.3.0"
187 | sdks:
188 | dart: ">=3.5.0 <4.0.0"
189 | flutter: ">=3.18.0-18.0.pre.54"
190 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.11.0"
12 | boolean_selector:
13 | dependency: transitive
14 | description:
15 | name: boolean_selector
16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.1.1"
20 | characters:
21 | dependency: transitive
22 | description:
23 | name: characters
24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "1.3.0"
28 | clock:
29 | dependency: transitive
30 | description:
31 | name: clock
32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.1.1"
36 | collection:
37 | dependency: transitive
38 | description:
39 | name: collection
40 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.19.0"
44 | fake_async:
45 | dependency: transitive
46 | description:
47 | name: fake_async
48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.3.1"
52 | flutter:
53 | dependency: "direct main"
54 | description: flutter
55 | source: sdk
56 | version: "0.0.0"
57 | flutter_test:
58 | dependency: "direct dev"
59 | description: flutter
60 | source: sdk
61 | version: "0.0.0"
62 | leak_tracker:
63 | dependency: transitive
64 | description:
65 | name: leak_tracker
66 | sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
67 | url: "https://pub.dev"
68 | source: hosted
69 | version: "10.0.7"
70 | leak_tracker_flutter_testing:
71 | dependency: transitive
72 | description:
73 | name: leak_tracker_flutter_testing
74 | sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
75 | url: "https://pub.dev"
76 | source: hosted
77 | version: "3.0.8"
78 | leak_tracker_testing:
79 | dependency: transitive
80 | description:
81 | name: leak_tracker_testing
82 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
83 | url: "https://pub.dev"
84 | source: hosted
85 | version: "3.0.1"
86 | matcher:
87 | dependency: transitive
88 | description:
89 | name: matcher
90 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
91 | url: "https://pub.dev"
92 | source: hosted
93 | version: "0.12.16+1"
94 | material_color_utilities:
95 | dependency: transitive
96 | description:
97 | name: material_color_utilities
98 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
99 | url: "https://pub.dev"
100 | source: hosted
101 | version: "0.11.1"
102 | meta:
103 | dependency: transitive
104 | description:
105 | name: meta
106 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
107 | url: "https://pub.dev"
108 | source: hosted
109 | version: "1.15.0"
110 | path:
111 | dependency: transitive
112 | description:
113 | name: path
114 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
115 | url: "https://pub.dev"
116 | source: hosted
117 | version: "1.9.0"
118 | sky_engine:
119 | dependency: transitive
120 | description: flutter
121 | source: sdk
122 | version: "0.0.0"
123 | source_span:
124 | dependency: transitive
125 | description:
126 | name: source_span
127 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
128 | url: "https://pub.dev"
129 | source: hosted
130 | version: "1.10.0"
131 | stack_trace:
132 | dependency: transitive
133 | description:
134 | name: stack_trace
135 | sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
136 | url: "https://pub.dev"
137 | source: hosted
138 | version: "1.12.0"
139 | stream_channel:
140 | dependency: transitive
141 | description:
142 | name: stream_channel
143 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
144 | url: "https://pub.dev"
145 | source: hosted
146 | version: "2.1.2"
147 | string_scanner:
148 | dependency: transitive
149 | description:
150 | name: string_scanner
151 | sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
152 | url: "https://pub.dev"
153 | source: hosted
154 | version: "1.3.0"
155 | term_glyph:
156 | dependency: transitive
157 | description:
158 | name: term_glyph
159 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
160 | url: "https://pub.dev"
161 | source: hosted
162 | version: "1.2.1"
163 | test_api:
164 | dependency: transitive
165 | description:
166 | name: test_api
167 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
168 | url: "https://pub.dev"
169 | source: hosted
170 | version: "0.7.3"
171 | themed:
172 | dependency: "direct main"
173 | description:
174 | path: ".."
175 | relative: true
176 | source: path
177 | version: "8.1.0"
178 | vector_math:
179 | dependency: transitive
180 | description:
181 | name: vector_math
182 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
183 | url: "https://pub.dev"
184 | source: hosted
185 | version: "2.1.4"
186 | vm_service:
187 | dependency: transitive
188 | description:
189 | name: vm_service
190 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
191 | url: "https://pub.dev"
192 | source: hosted
193 | version: "14.3.0"
194 | sdks:
195 | dart: ">=3.5.0 <4.0.0"
196 | flutter: ">=3.18.0-18.0.pre.54"
197 |
--------------------------------------------------------------------------------
/lib/src/change_colors.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | // Based upon: https://stackoverflow.com/questions/64639589/how-to-adjust-hue-saturation-and-brightness-of-an-image-in-flutter
6 | // from BananaNeil: https://stackoverflow.com/users/937841/banananeil.
7 | // This is, in turn, based upon: https://stackoverflow.com/a/7917978/937841
8 | // All credit goes to the above authors.
9 |
10 | /// Use the [ChangeColors] widget to change the brightness, saturation
11 | /// and hue of any widget, including images.
12 | ///
13 | /// Example:
14 | ///
15 | /// ```
16 | /// ChangeColors(
17 | /// hue: 0.55,
18 | /// brightness: 0.2,
19 | /// saturation: 0.1,
20 | /// child: Image.asset('myImage.png'),
21 | /// );
22 | /// ```
23 | ///
24 | /// To achieve a greyscale effect, you may also use the
25 | /// [ChangeColors.greyscale] constructor.
26 | ///
27 | class ChangeColors extends StatelessWidget {
28 | //
29 |
30 | /// Negative value will make it darker (-1 is darkest).
31 | /// Positive value will make it lighter (1 is the maximum, but you can go above it).
32 | /// Note: 0.0 is unchanged.
33 | final double brightness;
34 |
35 | /// Negative value will make it less saturated (-1 is greyscale).
36 | /// Positive value will make it more saturated (1 is the maximum, but you can go above it).
37 | /// Note: 0.0 is unchanged.
38 | final double saturation;
39 |
40 | /// From -1.0 to 1.0 (Note: 1.0 wraps into -1.0, such as 1.2 is the same as -0.8).
41 | /// Note: 0.0 is unchanged. Adding or subtracting multiples of 2.0 also keeps it unchanged.
42 | final double hue;
43 |
44 | final Widget child;
45 |
46 | ChangeColors({
47 | Key? key,
48 | this.brightness = 0.0,
49 | double saturation = 0.0,
50 | this.hue = 0.0,
51 | required this.child,
52 | }) : saturation = _clampSaturation(saturation),
53 | super(key: key);
54 |
55 | ChangeColors.greyscale({
56 | Key? key,
57 | this.brightness = 0.0,
58 | required this.child,
59 | }) : saturation = -1.0,
60 | hue = 0.0,
61 | super(key: key);
62 |
63 | static double _clampSaturation(double value) => value.clamp(-1.0, double.nan);
64 |
65 | @override
66 | Widget build(BuildContext context) {
67 | return ColorFiltered(
68 | colorFilter: ColorFilter.matrix(_ColorFilterGenerator.brightnessAdjustMatrix(
69 | value: brightness,
70 | )),
71 | child: ColorFiltered(
72 | colorFilter: ColorFilter.matrix(_ColorFilterGenerator.saturationAdjustMatrix(
73 | value: saturation,
74 | )),
75 | child: ColorFiltered(
76 | colorFilter: ColorFilter.matrix(_ColorFilterGenerator.hueAdjustMatrix(
77 | value: hue,
78 | )),
79 | child: child,
80 | )));
81 | }
82 | }
83 |
84 |
85 |
86 | class _ColorFilterGenerator {
87 | //
88 | static List hueAdjustMatrix({required double value}) {
89 | value = value * pi;
90 |
91 | if (value == 0)
92 | return [
93 | 1,
94 | 0,
95 | 0,
96 | 0,
97 | 0,
98 | 0,
99 | 1,
100 | 0,
101 | 0,
102 | 0,
103 | 0,
104 | 0,
105 | 1,
106 | 0,
107 | 0,
108 | 0,
109 | 0,
110 | 0,
111 | 1,
112 | 0,
113 | ];
114 |
115 | double cosVal = cos(value);
116 | double sinVal = sin(value);
117 | double lumR = 0.213;
118 | double lumG = 0.715;
119 | double lumB = 0.072;
120 |
121 | return List.from([
122 | (lumR + (cosVal * (1 - lumR))) + (sinVal * (-lumR)),
123 | (lumG + (cosVal * (-lumG))) + (sinVal * (-lumG)),
124 | (lumB + (cosVal * (-lumB))) + (sinVal * (1 - lumB)),
125 | 0,
126 | 0,
127 | (lumR + (cosVal * (-lumR))) + (sinVal * 0.143),
128 | (lumG + (cosVal * (1 - lumG))) + (sinVal * 0.14),
129 | (lumB + (cosVal * (-lumB))) + (sinVal * (-0.283)),
130 | 0,
131 | 0,
132 | (lumR + (cosVal * (-lumR))) + (sinVal * (-(1 - lumR))),
133 | (lumG + (cosVal * (-lumG))) + (sinVal * lumG),
134 | (lumB + (cosVal * (1 - lumB))) + (sinVal * lumB),
135 | 0,
136 | 0,
137 | 0,
138 | 0,
139 | 0,
140 | 1,
141 | 0,
142 | ]).map((i) => i.toDouble()).toList();
143 | }
144 |
145 | static List brightnessAdjustMatrix({required double value}) {
146 | if (value <= 0)
147 | value = value * 255;
148 | else
149 | value = value * 100;
150 |
151 | if (value == 0)
152 | return [
153 | 1,
154 | 0,
155 | 0,
156 | 0,
157 | 0,
158 | 0,
159 | 1,
160 | 0,
161 | 0,
162 | 0,
163 | 0,
164 | 0,
165 | 1,
166 | 0,
167 | 0,
168 | 0,
169 | 0,
170 | 0,
171 | 1,
172 | 0,
173 | ];
174 |
175 | return List.from(
176 | [1, 0, 0, 0, value, 0, 1, 0, 0, value, 0, 0, 1, 0, value, 0, 0, 0, 1, 0])
177 | .map((i) => i.toDouble())
178 | .toList();
179 | }
180 |
181 | static List saturationAdjustMatrix({required double value}) {
182 | value = value * 100;
183 |
184 | if (value == 0)
185 | return [
186 | 1,
187 | 0,
188 | 0,
189 | 0,
190 | 0,
191 | 0,
192 | 1,
193 | 0,
194 | 0,
195 | 0,
196 | 0,
197 | 0,
198 | 1,
199 | 0,
200 | 0,
201 | 0,
202 | 0,
203 | 0,
204 | 1,
205 | 0,
206 | ];
207 |
208 | double x = ((1 + ((value > 0) ? ((3 * value) / 100) : (value / 100)))).toDouble();
209 | double lumR = 0.3086;
210 | double lumG = 0.6094;
211 | double lumB = 0.082;
212 |
213 | return List.from([
214 | (lumR * (1 - x)) + x,
215 | lumG * (1 - x),
216 | lumB * (1 - x),
217 | 0,
218 | 0,
219 | lumR * (1 - x),
220 | (lumG * (1 - x)) + x,
221 | lumB * (1 - x),
222 | 0,
223 | 0,
224 | lumR * (1 - x),
225 | lumG * (1 - x),
226 | (lumB * (1 - x)) + x,
227 | 0,
228 | 0,
229 | 0,
230 | 0,
231 | 0,
232 | 1,
233 | 0,
234 | ]).map((i) => i.toDouble()).toList();
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # Specify analysis options.
2 | #
3 | # Until there are meta linter rules, each desired lint must be explicitly enabled.
4 | # See: https://github.com/dart-lang/linter/issues/288
5 | #
6 | # For a list of lints, see: http://dart-lang.github.io/linter/lints/
7 | # See the configuration guide for more
8 | # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer
9 | #
10 | # There are four similar analysis options files in the flutter repos:
11 | # - analysis_options.yaml (this file)
12 | # - packages/flutter/lib/analysis_options_user.yaml
13 | # - https://github.com/flutter/plugins/blob/master/analysis_options.yaml
14 | # - https://github.com/flutter/engine/blob/master/analysis_options.yaml
15 | #
16 | # This file contains the analysis options used by Flutter tools, such as IntelliJ,
17 | # Android Studio, and the `flutter analyze` command.
18 | #
19 | # The flutter/plugins repo contains a copy of this file, which should be kept
20 | # in sync with this file.
21 |
22 | analyzer:
23 | # strong-mode:
24 | # implicit-casts: false
25 | # implicit-dynamic: false
26 | errors:
27 | # treat missing required parameters as a warning (not a hint)
28 | missing_required_param: warning
29 | # treat missing returns as a warning (not a hint)
30 | missing_return: error
31 | # allow having TODOs in the code
32 | todo: ignore
33 | exclude:
34 | - 'bin/cache/**'
35 | # the following two are relative to the stocks example and the flutter package respectively
36 | # see https://github.com/dart-lang/sdk/issues/28463
37 | - '**/i18n/string_messages_**.dart'
38 | - '/**/i18n/string_messages_**.dart'
39 | - '**/i18n/string_messages_***.dart'
40 | - '/**/i18n/string_messages_***.dart'
41 | - 'lib/src/http/**'
42 |
43 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml
44 | linter:
45 | rules:
46 | - always_declare_return_types
47 | - always_require_non_null_named_parameters
48 | - annotate_overrides
49 | - avoid_empty_else
50 | - avoid_field_initializers_in_const_classes
51 | # - avoid_function_literals_in_foreach_calls
52 | - avoid_init_to_null
53 | - avoid_null_checks_in_equality_operators
54 | - avoid_relative_lib_imports
55 | - avoid_renaming_method_parameters
56 | - avoid_return_types_on_setters
57 | - avoid_slow_async_io
58 | - await_only_futures
59 | # - camel_case_types
60 | - cancel_subscriptions
61 | - control_flow_in_finally
62 | - directives_ordering
63 | - empty_catches
64 | - empty_constructor_bodies
65 | - empty_statements
66 | - hash_and_equals
67 | - implementation_imports
68 | - collection_methods_unrelated_type
69 | - library_names
70 | - library_prefixes
71 | - no_duplicate_case_values
72 | - overridden_fields
73 | - package_api_docs
74 | - package_names
75 | - package_prefixed_library_names
76 | - prefer_adjacent_string_concatenation
77 | - prefer_asserts_in_initializer_lists
78 | - prefer_collection_literals
79 | - prefer_conditional_assignment
80 | - prefer_const_constructors
81 | # - prefer_const_constructors_in_immutables
82 | - prefer_const_declarations
83 | - prefer_contains
84 | - prefer_final_fields
85 | # - prefer_foreach
86 | - prefer_generic_function_type_aliases
87 | - prefer_initializing_formals
88 | - prefer_is_empty
89 | - prefer_is_not_empty
90 | - prefer_typing_uninitialized_variables
91 | - recursive_getters
92 | - slash_for_doc_comments
93 | - sort_unnamed_constructors_first
94 | - test_types_in_equals
95 | - throw_in_finally
96 | - type_init_formals
97 | - unnecessary_brace_in_string_interps
98 | - unnecessary_getters_setters
99 | - unnecessary_null_aware_assignments
100 | - unnecessary_null_in_if_null_operators
101 | - unnecessary_overrides
102 | - unnecessary_this
103 | - unrelated_type_equality_checks
104 | - use_rethrow_when_possible
105 | - valid_regexps
106 | # - prefer_double_quotes
107 | # - unnecessary_new ➜ Isso aqui eu queria deixar, mas o linter ainda não reconhece. Tentar novamente no futuro.
108 | # - unnecessary_const ➜ Isso aqui eu queria deixar, mas o linter ainda não reconhece. Tentar novamente no futuro.
109 | # - non_constant_identifier_names
110 | # - always_put_control_body_on_new_line
111 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
112 | # - always_specify_types
113 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types
114 | # - avoid_as
115 | # - avoid_bool_literals_in_conditional_expressions # not yet tested
116 | # - avoid_catches_without_on_clauses # we do this commonly
117 | # - avoid_catching_errors # we do this commonly
118 | # - avoid_classes_with_only_static_members
119 | # - avoid_double_and_int_checks # only useful when targeting JS runtime
120 | # - avoid_js_rounded_ints # only useful when targeting JS runtime
121 | # - avoid_positional_boolean_parameters # not yet tested
122 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
123 | # - avoid_returning_null # we do this commonly
124 | # - avoid_returning_this # https://github.com/dart-lang/linter/issues/842
125 | # - avoid_setters_without_getters # not yet tested
126 | # - avoid_single_cascade_in_expression_statements # not yet tested
127 | # - avoid_types_as_parameter_names # https://github.com/dart-lang/linter/pull/954/files
128 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types
129 | # - avoid_unused_constructor_parameters # https://github.com/dart-lang/linter/pull/847
130 | # - cascade_invocations # not yet tested
131 | # - close_sinks # https://github.com/flutter/flutter/issues/5789
132 | # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153
133 | # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
134 | # - invariant_booleans # https://github.com/flutter/flutter/issues/5790
135 | # - join_return_with_assignment # not yet tested
136 | # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791
137 | # - no_adjacent_strings_in_list
138 | # - omit_local_variable_types # opposite of always_specify_types
139 | # - one_member_abstracts # too many false positives
140 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792
141 | # - parameter_assignments # we do this commonly
142 | # - prefer_const_literals_to_create_immutables
143 | # - prefer_constructors_over_static_methods # not yet tested
144 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods
145 | # - prefer_final_locals
146 | # - prefer_function_declarations_over_variables # not yet tested
147 | # - prefer_interpolation_to_compose_strings # not yet tested
148 | # - prefer_iterable_whereType # https://github.com/dart-lang/sdk/issues/32463
149 | # - prefer_single_quotes
150 | # - sort_constructors_first
151 | # - type_annotate_public_apis # subset of always_specify_types
152 | # - unawaited_futures # https://github.com/flutter/flutter/issues/5793
153 | # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498
154 | # - unnecessary_parenthesis
155 | # - unnecessary_statements # not yet tested
156 | # - use_setters_to_change_properties # not yet tested
157 | # - use_string_buffers # https://github.com/dart-lang/linter/pull/664
158 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
159 | # - void_checks # not yet tested
160 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # Specify analysis options.
2 | #
3 | # Until there are meta linter rules, each desired lint must be explicitly enabled.
4 | # See: https://github.com/dart-lang/linter/issues/288
5 | #
6 | # For a list of lints, see: http://dart-lang.github.io/linter/lints/
7 | # See the configuration guide for more
8 | # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer
9 | #
10 | # There are four similar analysis options files in the flutter repos:
11 | # - analysis_options.yaml (this file)
12 | # - packages/flutter/lib/analysis_options_user.yaml
13 | # - https://github.com/flutter/plugins/blob/master/analysis_options.yaml
14 | # - https://github.com/flutter/engine/blob/master/analysis_options.yaml
15 | #
16 | # This file contains the analysis options used by Flutter tools, such as IntelliJ,
17 | # Android Studio, and the `flutter analyze` command.
18 | #
19 | # The flutter/plugins repo contains a copy of this file, which should be kept
20 | # in sync with this file.
21 |
22 | analyzer:
23 | # strong-mode:
24 | # implicit-casts: false
25 | # implicit-dynamic: false
26 | errors:
27 | # treat missing required parameters as a warning (not a hint)
28 | missing_required_param: warning
29 | # treat missing returns as a warning (not a hint)
30 | missing_return: error
31 | # allow having TODOs in the code
32 | todo: ignore
33 | exclude:
34 | - 'bin/cache/**'
35 | # the following two are relative to the stocks example and the flutter package respectively
36 | # see https://github.com/dart-lang/sdk/issues/28463
37 | - '**/i18n/string_messages_**.dart'
38 | - '/**/i18n/string_messages_**.dart'
39 | - '**/i18n/string_messages_***.dart'
40 | - '/**/i18n/string_messages_***.dart'
41 | - 'lib/src/http/**'
42 |
43 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml
44 | linter:
45 | rules:
46 | - always_declare_return_types
47 | - always_require_non_null_named_parameters
48 | - annotate_overrides
49 | - avoid_empty_else
50 | - avoid_field_initializers_in_const_classes
51 | # - avoid_function_literals_in_foreach_calls
52 | - avoid_init_to_null
53 | - avoid_null_checks_in_equality_operators
54 | - avoid_relative_lib_imports
55 | - avoid_renaming_method_parameters
56 | - avoid_return_types_on_setters
57 | - avoid_slow_async_io
58 | - await_only_futures
59 | # - camel_case_types
60 | - cancel_subscriptions
61 | - control_flow_in_finally
62 | - directives_ordering
63 | - empty_catches
64 | - empty_constructor_bodies
65 | - empty_statements
66 | - hash_and_equals
67 | - implementation_imports
68 | - collection_methods_unrelated_type
69 | - library_names
70 | - library_prefixes
71 | - no_duplicate_case_values
72 | - overridden_fields
73 | - package_api_docs
74 | - package_names
75 | - package_prefixed_library_names
76 | - prefer_adjacent_string_concatenation
77 | - prefer_asserts_in_initializer_lists
78 | - prefer_collection_literals
79 | - prefer_conditional_assignment
80 | - prefer_const_constructors
81 | # - prefer_const_constructors_in_immutables
82 | - prefer_const_declarations
83 | - prefer_contains
84 | - prefer_equal_for_default_values
85 | - prefer_final_fields
86 | # - prefer_foreach
87 | - prefer_generic_function_type_aliases
88 | - prefer_initializing_formals
89 | - prefer_is_empty
90 | - prefer_is_not_empty
91 | - prefer_typing_uninitialized_variables
92 | - recursive_getters
93 | - slash_for_doc_comments
94 | - sort_unnamed_constructors_first
95 | - test_types_in_equals
96 | - throw_in_finally
97 | - type_init_formals
98 | - unnecessary_brace_in_string_interps
99 | - unnecessary_getters_setters
100 | - unnecessary_null_aware_assignments
101 | - unnecessary_null_in_if_null_operators
102 | - unnecessary_overrides
103 | - unnecessary_this
104 | - unrelated_type_equality_checks
105 | - use_rethrow_when_possible
106 | - valid_regexps
107 | # - prefer_double_quotes
108 | # - unnecessary_new ➜ Isso aqui eu queria deixar, mas o linter ainda não reconhece. Tentar novamente no futuro.
109 | # - unnecessary_const ➜ Isso aqui eu queria deixar, mas o linter ainda não reconhece. Tentar novamente no futuro.
110 | # - non_constant_identifier_names
111 | # - always_put_control_body_on_new_line
112 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
113 | # - always_specify_types
114 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types
115 | # - avoid_as
116 | # - avoid_bool_literals_in_conditional_expressions # not yet tested
117 | # - avoid_catches_without_on_clauses # we do this commonly
118 | # - avoid_catching_errors # we do this commonly
119 | # - avoid_classes_with_only_static_members
120 | # - avoid_double_and_int_checks # only useful when targeting JS runtime
121 | # - avoid_js_rounded_ints # only useful when targeting JS runtime
122 | # - avoid_positional_boolean_parameters # not yet tested
123 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
124 | # - avoid_returning_null # we do this commonly
125 | # - avoid_returning_this # https://github.com/dart-lang/linter/issues/842
126 | # - avoid_setters_without_getters # not yet tested
127 | # - avoid_single_cascade_in_expression_statements # not yet tested
128 | # - avoid_types_as_parameter_names # https://github.com/dart-lang/linter/pull/954/files
129 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types
130 | # - avoid_unused_constructor_parameters # https://github.com/dart-lang/linter/pull/847
131 | # - cascade_invocations # not yet tested
132 | # - close_sinks # https://github.com/flutter/flutter/issues/5789
133 | # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153
134 | # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
135 | # - invariant_booleans # https://github.com/flutter/flutter/issues/5790
136 | # - join_return_with_assignment # not yet tested
137 | # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791
138 | # - no_adjacent_strings_in_list
139 | # - omit_local_variable_types # opposite of always_specify_types
140 | # - one_member_abstracts # too many false positives
141 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792
142 | # - parameter_assignments # we do this commonly
143 | # - prefer_const_literals_to_create_immutables
144 | # - prefer_constructors_over_static_methods # not yet tested
145 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods
146 | # - prefer_final_locals
147 | # - prefer_function_declarations_over_variables # not yet tested
148 | # - prefer_interpolation_to_compose_strings # not yet tested
149 | # - prefer_iterable_whereType # https://github.com/dart-lang/sdk/issues/32463
150 | # - prefer_single_quotes
151 | # - sort_constructors_first
152 | # - type_annotate_public_apis # subset of always_specify_types
153 | # - unawaited_futures # https://github.com/flutter/flutter/issues/5793
154 | # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498
155 | # - unnecessary_parenthesis
156 | # - unnecessary_statements # not yet tested
157 | # - use_setters_to_change_properties # not yet tested
158 | # - use_string_buffers # https://github.com/dart-lang/linter/pull/664
159 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
160 | # - void_checks # not yet tested
161 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:themed/themed.dart';
3 |
4 | /// This example demonstrates:
5 | /// 1) The [Themed] package is compatible with [Theme] and [ThemeData].
6 | /// 2) We can use the `const` keyword.
7 | /// 3) An extension allows us to add a Color to a TextStyle.
8 |
9 | class MyTheme {
10 | static const color1 = ColorRef(Colors.white);
11 | static const color2 = ColorRef(Colors.blue);
12 | static const color3 = ColorRef(Colors.green);
13 |
14 | static const mainStyle = TextStyleRef(
15 | TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: MyTheme.color1),
16 | );
17 | }
18 |
19 | Map anotherTheme = {
20 | MyTheme.color1: Colors.yellow,
21 | MyTheme.color2: Colors.pink,
22 | MyTheme.color3: Colors.purple,
23 | MyTheme.mainStyle: const TextStyle(
24 | fontSize: 22,
25 | fontWeight: FontWeight.w900,
26 | color: MyTheme.color1,
27 | ),
28 | };
29 |
30 | Map yellowTheme = {
31 | MyTheme.color1: Colors.yellow[200]!,
32 | MyTheme.color2: Colors.yellow[600]!,
33 | MyTheme.color3: Colors.yellow[900]!,
34 | MyTheme.mainStyle: const TextStyle(
35 | fontSize: 22,
36 | fontWeight: FontWeight.w900,
37 | color: MyTheme.color1,
38 | ),
39 | };
40 |
41 | const Widget space16 = SizedBox(width: 16, height: 16);
42 | final Widget divider = Container(width: double.infinity, height: 2, color: Colors.grey);
43 |
44 | void main() {
45 | runApp(MyApp());
46 | }
47 |
48 | class MyApp extends StatelessWidget {
49 | @override
50 | Widget build(BuildContext context) {
51 | return Themed(
52 | child: MaterialApp(
53 | title: 'Themed example',
54 | debugShowCheckedModeBanner: false,
55 | //
56 | // 1) The [Themed] package is compatible with [Theme] and [ThemeData]:
57 | theme: ThemeData(
58 | elevatedButtonTheme: ElevatedButtonThemeData(
59 | style: ElevatedButton.styleFrom(
60 | foregroundColor: MyTheme.color1,
61 | backgroundColor: Colors.blue,
62 | ),
63 | ),
64 | ),
65 | //
66 | home: const HomePage(),
67 | ),
68 | );
69 | }
70 | }
71 |
72 | class HomePage extends StatelessWidget {
73 | const HomePage({super.key});
74 |
75 | @override
76 | Widget build(BuildContext context) {
77 | return Scaffold(
78 | appBar: AppBar(
79 | backgroundColor: MyTheme.color2,
80 | //
81 | // 2) We can use the `const` keyword:
82 | title: const Text('Themed example', style: MyTheme.mainStyle),
83 | ),
84 | body: const HomePageContent(),
85 | );
86 | }
87 | }
88 |
89 | class HomePageContent extends StatelessWidget {
90 | const HomePageContent({super.key});
91 |
92 | @override
93 | Widget build(BuildContext context) {
94 | return Center(
95 | child: Column(
96 | mainAxisSize: MainAxisSize.min,
97 | children: [
98 | const Text(
99 | 'Click to navigate:',
100 | style: TextStyle(
101 | fontWeight: FontWeight.bold,
102 | color: MyTheme.color3,
103 | ),
104 | ),
105 | //
106 | ElevatedButton(
107 | onPressed: () {
108 | Navigator.push(
109 | context,
110 | MaterialPageRoute(
111 | builder: (context) => ColorSettingsPage(),
112 | ),
113 | );
114 | },
115 | child: const Text('Open Color Settings Page'),
116 | ),
117 | //
118 | ],
119 | ),
120 | );
121 | }
122 | }
123 |
124 | class ColorSettingsPage extends StatefulWidget {
125 | @override
126 | _ColorSettingsPageState createState() => _ColorSettingsPageState();
127 | }
128 |
129 | class _ColorSettingsPageState extends State {
130 | bool _usingStaticMethod = false;
131 |
132 | @override
133 | Widget build(BuildContext ctx) {
134 | return Scaffold(
135 | appBar: AppBar(
136 | backgroundColor: MyTheme.color2,
137 | //
138 | // 2) We can use the `const` keyword:
139 | title: const Text('Color Settings Page', style: MyTheme.mainStyle),
140 | ),
141 | body: Column(
142 | children: [
143 | const ExampleText(),
144 | //
145 | SwitchListTile(
146 | title: _usingStaticMethod
147 | ? const Text('Themed.[static method]')
148 | : const Text('Themed.of(context)'),
149 | value: _usingStaticMethod,
150 | onChanged: (bool value) {
151 | setState(() {
152 | _usingStaticMethod = value;
153 | });
154 | },
155 | ),
156 | //
157 | Expanded(
158 | child: SingleChildScrollView(
159 | child: Padding(
160 | padding: const EdgeInsets.symmetric(horizontal: 16),
161 | child:
162 | _usingStaticMethod ? _usingThemedStaticMethod() : _usingThemedOf(ctx),
163 | ),
164 | ),
165 | ),
166 | ],
167 | ),
168 | );
169 | }
170 |
171 | Column _usingThemedOf(BuildContext ctx) {
172 | return Column(
173 | mainAxisAlignment: MainAxisAlignment.center,
174 | children: [
175 | space16,
176 | //
177 | ElevatedButton(
178 | onPressed: () => Themed.of(ctx).currentTheme = anotherTheme,
179 | child: const Text('Themed.of(ctx).currentTheme = anotherTheme'),
180 | ),
181 | //
182 | ElevatedButton(
183 | onPressed: () => Themed.of(ctx).currentTheme = null,
184 | child: const Text('Themed.of(ctx).currentTheme = null'),
185 | ),
186 | //
187 | ElevatedButton(
188 | onPressed: () => Themed.of(ctx).clearCurrentTheme(),
189 | child: const Text('Themed.of(ctx).clearCurrentTheme()'),
190 | ),
191 | //
192 | ElevatedButton(
193 | onPressed: () => Themed.of(ctx).defaultTheme = yellowTheme,
194 | child: const Text('Themed.of(ctx).defaultTheme = yellowTheme'),
195 | ),
196 | //
197 | ElevatedButton(
198 | onPressed: () => Themed.of(ctx).defaultTheme = null,
199 | child: const Text('Themed.of(ctx).defaultTheme = null'),
200 | ),
201 | //
202 | ElevatedButton(
203 | onPressed: () => Themed.of(ctx).transformColor = ColorRef.shadesOfGreyTransform,
204 | child: const Text(
205 | 'Themed.of(ctx).transformColor = ColorRef.shadesOfGreyTransform',
206 | textAlign: TextAlign.center),
207 | ),
208 | //
209 | ElevatedButton(
210 | onPressed: () => Themed.of(ctx).transformColor = null,
211 | child: const Text('Themed.of(ctx).transformColor = null'),
212 | ),
213 | //
214 | ElevatedButton(
215 | onPressed: () => Themed.of(ctx).clearTransformColor(),
216 | child: const Text('Themed.of(ctx).clearTransformColor()'),
217 | ),
218 | //
219 | ElevatedButton(
220 | onPressed: () => Themed.of(ctx).transformTextStyle = largerText,
221 | child: const Text('Themed.of(ctx).transformTextStyle = largerText',
222 | textAlign: TextAlign.center),
223 | ),
224 | //
225 | ElevatedButton(
226 | onPressed: () => Themed.of(ctx).transformTextStyle = null,
227 | child: const Text('Themed.of(ctx).transformTextStyle = null'),
228 | ),
229 | //
230 | ElevatedButton(
231 | onPressed: () => Themed.of(ctx).clearTransformTextStyle(),
232 | child: const Text('Themed.of(ctx).clearTransformTextStyle()'),
233 | ),
234 | //
235 | space16,
236 | Text(
237 | 'Themed.of(ctx).ifCurrentThemeIs({}) == ${Themed.of(ctx).ifCurrentThemeIs({})}'),
238 | space16,
239 | Text(
240 | 'Themed.of(ctx).ifCurrentThemeIs(anotherTheme) == ${Themed.of(ctx).ifCurrentThemeIs(anotherTheme)}'),
241 | space16,
242 | Text(
243 | 'Themed.of(ctx).ifCurrentThemeIs(yellowTheme) == ${Themed.of(ctx).ifCurrentThemeIs(yellowTheme)}'),
244 | space16,
245 | Text(
246 | 'Themed.of(ctx).ifCurrentTransformColorIs(null) == ${Themed.of(ctx).ifCurrentTransformColorIs(null)}'),
247 | space16,
248 | Text(
249 | 'Themed.of(ctx).ifCurrentTransformColorIs(ColorRef.shadesOfGreyTransform) == ${Themed.of(ctx).ifCurrentTransformColorIs(ColorRef.shadesOfGreyTransform)}'),
250 | space16,
251 | Text(
252 | 'Themed.of(ctx).ifCurrentTransformTextStyleIs(null) == ${Themed.of(ctx).ifCurrentTransformTextStyleIs(null)}'),
253 | space16,
254 | Text(
255 | 'Themed.of(ctx).ifCurrentTransformTextStyleIs(largerText) == ${Themed.of(ctx).ifCurrentTransformTextStyleIs(largerText)}'),
256 | space16,
257 | ],
258 | );
259 | }
260 |
261 | Column _usingThemedStaticMethod() {
262 | return Column(
263 | mainAxisAlignment: MainAxisAlignment.center,
264 | children: [
265 | space16,
266 | //
267 | ElevatedButton(
268 | onPressed: () => Themed.currentTheme = anotherTheme,
269 | child: const Text('Themed.currentTheme = anotherTheme'),
270 | ),
271 | //
272 | ElevatedButton(
273 | onPressed: () => Themed.currentTheme = null,
274 | child: const Text('Themed.currentTheme = null'),
275 | ),
276 | //
277 | ElevatedButton(
278 | onPressed: () => Themed.clearCurrentTheme(),
279 | child: const Text('Themed.clearCurrentTheme()'),
280 | ),
281 | //
282 | ElevatedButton(
283 | onPressed: () => Themed.defaultTheme = yellowTheme,
284 | child: const Text('Themed.defaultTheme = yellowTheme'),
285 | ),
286 | //
287 | ElevatedButton(
288 | onPressed: () => Themed.defaultTheme = null,
289 | child: const Text('Themed.defaultTheme = null'),
290 | ),
291 | //
292 | ElevatedButton(
293 | onPressed: () => Themed.transformColor = ColorRef.shadesOfGreyTransform,
294 | child: const Text('Themed.transformColor = ColorRef.shadesOfGreyTransform',
295 | textAlign: TextAlign.center),
296 | ),
297 | //
298 | ElevatedButton(
299 | onPressed: () => Themed.transformColor = null,
300 | child: const Text('Themed.transformColor = null'),
301 | ),
302 | //
303 | ElevatedButton(
304 | onPressed: () => Themed.clearTransformColor(),
305 | child: const Text('Themed.clearTransformColor()'),
306 | ),
307 | //
308 | ElevatedButton(
309 | onPressed: () => Themed.transformTextStyle = largerText,
310 | child: const Text('Themed.transformTextStyle = largerText',
311 | textAlign: TextAlign.center),
312 | ),
313 | //
314 | ElevatedButton(
315 | onPressed: () => Themed.transformTextStyle = null,
316 | child: const Text('Themed.transformTextStyle = null'),
317 | ),
318 | //
319 | ElevatedButton(
320 | onPressed: () => Themed.clearTransformTextStyle(),
321 | child: const Text('Themed.clearTransformTextStyle()'),
322 | ),
323 | //
324 | space16,
325 | Text('Themed.ifCurrentThemeIs({}) == ${Themed.ifCurrentThemeIs({})}'),
326 | space16,
327 | Text(
328 | 'Themed.ifCurrentThemeIs(anotherTheme) == ${Themed.ifCurrentThemeIs(anotherTheme)}'),
329 | space16,
330 | Text(
331 | 'Themed.ifCurrentThemeIs(yellowTheme) == ${Themed.ifCurrentThemeIs(yellowTheme)}'),
332 | space16,
333 | Text(
334 | 'Themed.ifCurrentTransformColorIs(null) == ${Themed.ifCurrentTransformColorIs(null)}'),
335 | space16,
336 | Text(
337 | 'Themed.ifCurrentTransformColorIs(ColorRef.shadesOfGreyTransform) == ${Themed.ifCurrentTransformColorIs(ColorRef.shadesOfGreyTransform)}'),
338 | space16,
339 | Text(
340 | 'Themed.ifCurrentTransformTextStyleIs(null) == ${Themed.ifCurrentTransformTextStyleIs(null)}'),
341 | space16,
342 | Text(
343 | 'Themed.ifCurrentTransformTextStyleIs(largerText) == ${Themed.ifCurrentTransformTextStyleIs(largerText)}'),
344 | space16,
345 | ],
346 | );
347 | }
348 |
349 | static TextStyle largerText(TextStyle textStyle) =>
350 | textStyle.copyWith(fontSize: textStyle.fontSize! * 1.5);
351 | }
352 |
353 | class ExampleText extends StatelessWidget {
354 | const ExampleText({super.key});
355 |
356 | @override
357 | Widget build(BuildContext context) {
358 | return Container(
359 | color: Colors.grey[300],
360 | child: ConstrainedBox(
361 | constraints: const BoxConstraints(maxHeight: 135),
362 | child: Column(
363 | mainAxisSize: MainAxisSize.min,
364 | mainAxisAlignment: MainAxisAlignment.center,
365 | children: [
366 | const Spacer(),
367 | Column(
368 | children: [
369 | Container(
370 | color: MyTheme.color3,
371 | child: const Text(
372 | 'This is some text!',
373 | style: MyTheme.mainStyle,
374 | ),
375 | ),
376 | space16,
377 | Container(
378 | color: MyTheme.color3,
379 | child: Text(
380 | 'This is another text!',
381 | // 3) An extension allows us to add a Color to a TextStyle:
382 | style: MyTheme.mainStyle + Colors.black,
383 | ),
384 | ),
385 | ],
386 | ),
387 | const Spacer(),
388 | divider,
389 | ],
390 | ),
391 | ),
392 | );
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://pub.dev/packages/themed)
2 | [](https://pub.dev/packages/themed)
3 | [](https://github.com/marcglasberg/themed)
4 | 
5 | 
6 | 
7 | 
8 | 
9 | 
10 | [](https://glasberg.dev/)
11 | [](https://pub.dev/publishers/glasberg.dev/packages)
12 | [](https://pub.dev/packages/themed)
13 |
14 | #### Sponsor
15 |
16 | [](https://mytext.ai)
17 |
18 | # themed
19 |
20 | The **Themed** package:
21 |
22 | * Lets you define a theme with **const** values, but change them dynamically anyway.
23 | * `ChangeColors` widget to change the brightness, saturation and hue of widgets or images.
24 | * Color extension methods like `decolorize`, `addOpacity`, `removeOpacity`,
25 | `darker`, `lighter`, `average` and more.
26 | * Static methods `ColorUtil.rgbaToArgb` and `ColorUtil.abgrToArgb`.
27 | * TextStyle extension methods: `var myStyle = TextStyle(fontSize: 15) + Colors.blue` and
28 | more.
29 |
30 | ## Const values that you can change
31 |
32 | As we all know, using const variables is the easiest way to create and use themes:
33 |
34 | ```
35 | static const myColor = Colors.white;
36 | static const myStyle = TextStyle(fontSize: 16);
37 |
38 | Container(
39 | color: myColor,
40 | child: const Text('Hi', style: myStyle)))
41 | ```
42 |
43 | However, if you do it like that you can't later change the theme dynamically.
44 | By using the **Themed** package you can:
45 |
46 | ```
47 | static const myColor = ColorRef(Colors.white);
48 | static const myStyle = TextStyleRef(TextStyle(fontSize: 16));
49 |
50 | Container(
51 | color: myColor,
52 | child: const Text('Hello', style: myStyle)))
53 |
54 | // Later, change the theme dynamically.
55 | Themed.currentTheme = {
56 | myColor: Colors.blue,
57 | myStyle: TextStyle(fontSize: 20);
58 | }
59 | ```
60 |
61 | 
62 |
63 | There is no need to use `Theme.of(context)` anymore:
64 |
65 | ```
66 | // So old-fashioned.
67 | Container(
68 | color: Theme.of(context).primary,
69 | child: Text('Hello', style: TextStyle(color: Theme.of(context).secondary)))
70 | ```
71 |
72 | Also, since `Theme.of` needs the `context` and is not constant, you can't use it in
73 | constructors. However, the *Themed* package has no such limitations:
74 |
75 | ```
76 | // The const color is the default value of an optional parameter.
77 | MyWidget({
78 | this.color = myColor,
79 | });
80 | ```
81 |
82 | ---
83 |
84 | # Setup
85 |
86 | Wrap your widget tree with the `Themed` widget, above the `MaterialApp`:
87 |
88 | ```
89 | @override
90 | Widget build(BuildContext context) {
91 | return Themed(
92 | child: MaterialApp(
93 | ...
94 | ```
95 |
96 | # Compatibility
97 |
98 | The *Themed* package is a competitor to writing `Theme.of(context).xxx` in your build
99 | methods, but it’s *NOT* a competitor to Flutter’s native theme system and the `Theme`
100 | widget. It’s there to solve a different problem, and it’s usually used together with
101 | the `Theme` widget. For example, if you want to set a global default color for all
102 | buttons, you’ll use the `Theme` widget. You may use it together with the `Themed`
103 | package however, meaning that `Themed` colors and styles may be used inside
104 | a `ThemeData` widget:
105 |
106 | ```
107 | static const myColor1 = ColorRef(Colors.red);
108 | static const myColor2 = ColorRef(Colors.blue);
109 | ...
110 |
111 | child: MaterialApp(
112 | theme: ThemeData(
113 | primaryColor: MyTheme.color2,
114 | elevatedButtonTheme:
115 | ElevatedButtonThemeData(
116 | style: ElevatedButton.styleFrom(primary: MyTheme.color2),
117 | ),
118 | ),
119 | ```
120 |
121 | ## How to define a theme map
122 |
123 | Each theme should be a `Map`, where the **keys** are your `ColorRef`
124 | and `TextStyleRef` const values, and the **values** are the colors and styles you want to
125 | use on that theme. For example:
126 |
127 | ```
128 | Map theme1 = {
129 | MyTheme.color1: Colors.yellow,
130 | MyTheme.color2: Colors.pink,
131 | MyTheme.color3: Colors.purple,
132 | MyTheme.mainStyle: const TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: MyTheme.color1),
133 | };
134 | ```
135 |
136 | At any point in your app you can just change the current theme by doing:
137 |
138 | ```
139 | // Setting a theme:
140 | Themed.currentTheme = theme1;
141 |
142 | // Setting another theme:
143 | Themed.currentTheme = theme2;
144 |
145 | // Removing the current theme (and falling back to the default theme):
146 | Themed.clearCurrentTheme();
147 |
148 | // This would also remove the current theme:
149 | Themed.currentTheme = null;
150 | ```
151 |
152 | ### Resetting
153 |
154 | Calling the static method `Themed.reset()` will remove the entire widget tree inside
155 | the `Themed` widget for one frame, and then restore it, rebuilding everything. This can
156 | be helpful when some widgets are not responding to theme changes. Usage of this method
157 | is not usually necessary. A side effect is that the all stateful widgets below `Themed`
158 | will be recreated, and you'll need to have mechanisms to recover their state, like for
159 | example having the state come from above `Themed` widget, or using a proper state
160 | management solution.
161 |
162 | # Organization
163 |
164 | You can also organize your theme in a class:
165 |
166 | ```
167 | class MyTheme {
168 | static const myColor = ColorRef(Colors.white);
169 | static const myStyle = TextStyleRef(TextStyle(fontSize: 16, color: Colors.red));
170 | }
171 |
172 | Container(
173 | color: MyTheme.myColor,
174 | child: const Text('Hello', style: MyTheme.myStyle)))
175 | ```
176 |
177 | # Color transform
178 |
179 | Instead of changing the current theme you can create a **color transformation**.
180 | For example, this will turn your theme into shades of grey:
181 |
182 | ```
183 | static Color shadesOfGreyTransform(Color color) {
184 | int average = (color.red + color.green + color.blue) ~/ 3;
185 | return Color.fromARGB(color.alpha, average, average, average);
186 | }
187 | ```
188 |
189 | Note you can create your own function to process colors, but `shadesOfGreyTransform` is
190 | already provided:
191 |
192 | ```
193 | // Turn it on:
194 | Themed.transformColor = ColorRef.shadesOfGreyTransform;
195 |
196 | // Then, later, turn it off:
197 | Themed.clearTransformColor();
198 | ```
199 |
200 | # Changing brightness, saturation and hue of widgets or images.
201 |
202 | Use the provided `ChangeColors` widget to change the brightness, saturation and hue of any
203 | widget, including images. Example:
204 |
205 | ```
206 | ChangeColors(
207 | hue: 0.55,
208 | brightness: 0.2,
209 | saturation: 0.1,
210 | child: Image.asset('myImage.png'),
211 | );
212 | ```
213 |
214 | To achieve a greyscale effect, you may also use the `ChangeColors.greyscale` constructor.
215 |
216 | _Note: This widget is based upon
217 |
218 | this code (from
219 | BananaNeil's), which is in turn based
220 | upon
221 | this code (by
222 | Richard Lalancette)._
223 |
224 | # Color extension
225 |
226 | The `lighter` method makes the color lighter (more white). Example:
227 |
228 | ```
229 | // 20% more white.
230 | Colors.blue.lighter(0.2);
231 | ```
232 |
233 | The `darker` method makes the color darker (more black). Example:
234 |
235 | ```
236 | // 20% more black.
237 | Colors.blue.darker(0.2);
238 | ```
239 |
240 | The `average` method makes the current color more similar to the given `color`. Example:
241 |
242 | ```
243 | // 50% blue and 50% red.
244 | Colors.blue.average(Colors.red);
245 |
246 | // 20% blue and 80% red.
247 | Colors.blue.average(Colors.red, 0.8);
248 | ```
249 |
250 | The `decolorize` method makes the current color more grey. Example:
251 |
252 | ```
253 | // Grey, with luminance similar to the original blue.
254 | Colors.blue.decolorize();
255 |
256 | // Blue with 20% less color.
257 | Colors.blue.decolorize(0.2);
258 | ```
259 |
260 | The `addOpacity` method makes the current color more transparent than it already is, by
261 | the given amount. The `removeOpacity` method makes the current color less transparent than
262 | it already is, by the given amount. This is different from the `withOpacity` method,
263 | as you can see below.
264 |
265 | ```
266 | // 50% transparent blue.
267 | Colors.blue.addOpacity(0.5);
268 |
269 | // 80% transparent black.
270 | Colors.transparent.removeOpacity(0.2);
271 |
272 | // Also 50% transparent blue.
273 | Colors.withOpacity(0.5);
274 |
275 | // 75% transparent blue, because we add 50% and then more 50%.
276 | Colors.blue.addOpacity(0.5).addOpacity(0.5);
277 |
278 | // This is 50% transparent blue, because the opacity is replaced, not added.
279 | Colors.withOpacity(0.5).withOpacity(0.5);
280 | ```
281 |
282 | There are also two static methods for advanced color representation conversion:
283 | The `ColorUtil.rgbaToArgb` method converts the RGBA color representation to ARGB.
284 | The `ColorUtil.abgrToArgb` method converts the ABGR color representation to ARGB.
285 |
286 | # TextStyle transform
287 |
288 | You can also create a **style transformation**. For example, this will make your fonts
289 | larger:
290 |
291 | ```
292 | static TextStyle largerText(TextStyle textStyle) =>
293 | textStyle.copyWith(fontSize: textStyle.fontSize! * 1.5);
294 |
295 | // Turn it on:
296 | Themed.transformTextStyle = largerText;
297 |
298 | // Then, later, turn it off:
299 | Themed.clearTransformTextStyle();
300 | ```
301 |
302 | # TextStyle extension
303 |
304 | With the provided extension, you can make your code more clean-code by creating new text
305 | styles by adding colors and other values to a `TextStyle`. For example:
306 |
307 | ```
308 | const myStyle = TextStyle(...);
309 |
310 | // Using some style:
311 | Text('Hello', style: myStyle);
312 |
313 | // Making text black:
314 | Text('Hello', style: myStyle + Colors.black);
315 |
316 | // Changing some other stuff:
317 | Text('Hello', style: myStyle + FontWeight.w900 + FontSize(20.0) + TextHeight(1.2));
318 | ```
319 |
320 | # Beware not to define the same constant
321 |
322 | Please remember Dart constants point to the same memory space.
323 | In this example, `colorA`, `colorB` and `colorC` represent the same variable:
324 |
325 | ```
326 | class MyTheme {
327 | static const colorA = ColorRef(Colors.white);
328 | static const colorB = ColorRef(Colors.white);
329 | static const colorC = colorA;
330 | }
331 | ```
332 |
333 | If you later change the color of `colorA`, you are also automatically changing the color
334 | of `colorB` and `colorB`.
335 |
336 | If you want to create 3 independent colors, and be able to change them independently, you
337 | have to create different constants. You can provide an `id` string, just to
338 | differentiate them. For example:
339 |
340 | ```
341 | class MyTheme {
342 | static const colorA = ColorRef(Colors.white, id:'A');
343 | static const colorB = ColorRef(Colors.white, id:'B');
344 | static const colorB = ColorRef(colorA, id:'C');
345 | }
346 | ```
347 |
348 | # Avoid circular dependencies
349 |
350 | The following will lead to a `StackOverflowError` error:
351 |
352 | ```
353 | Map anotherTheme = {
354 | MyTheme.color1: MyTheme.color2,
355 | MyTheme.color2: MyTheme.color1,
356 | };
357 | ```
358 |
359 | You can have references which depend on other references, no problem. But both direct and
360 | indirect circular references must be avoided.
361 |
362 | # Other ways to use it
363 |
364 | If you want, you may also define a **default** theme, and a **current** theme for your
365 | app:
366 |
367 | ```
368 | @override
369 | Widget build(BuildContext context) {
370 | return Themed(
371 | defaultTheme: { ... },
372 | currentTheme: { ... },
373 | child: MaterialApp(
374 | ...
375 | ```
376 |
377 | The `defaultTheme` and `currentTheme` are both optional. They are simply theme maps, as
378 | explained below.
379 |
380 | When a color/style is used, it will first search it inside the `currentTheme`.
381 |
382 | If it's not found there, it searches inside of `defaultTheme`.
383 |
384 | If it's still not found there, it uses the default color/style which was defined in the
385 | constructor. For example, here the default color is white: `ColorRef(Colors.white)`.
386 |
387 | Please note: If you define all your colors in the `defaultTheme`, then you don't need to
388 | provide default values in the constructor. You can then use the `fromId` constructor:
389 |
390 | ```
391 | class MyTheme {
392 | static const color1 = ColorRef.fromId('c1');
393 | static const color2 = ColorRef.fromId('c2');
394 | static const color3 = ColorRef.fromId('c3');
395 | static const mainStyle = TextStyleRef.fromId('mainStyle');
396 | }
397 | ```
398 |
399 | ## Saving and setting Themes by key
400 |
401 | You can save themes with keys, and then later use the keys to set the theme. The keys can
402 | be anything (Strings, enums etc.):
403 |
404 | ```
405 | // Save some themes using keys.
406 | enum Keys {light, dark};
407 | Themed.save(key: Keys.light, theme: { ... })
408 | Themed.save(key: Keys.dark, theme: { ... })
409 |
410 | // Then set the theme.
411 | Themed.setThemeByKey(Keys.light);
412 | ```
413 |
414 | It also works the other way around:
415 | If you use the key first, and only save a theme with that key later:
416 |
417 | ```
418 | // Set the theme with a key, even before saving the theme.
419 | Themed.setThemeByKey(Keys.light);
420 |
421 | // The theme will change as soon as you save a theme with that key.
422 | Themed.save(key: Keys.light, theme: { ... })
423 | ```
424 |
425 | Note: You can also use the methods `saveAll` to save many themes by key at the same time,
426 | and `clearSavedThemeByKey` to remove saved themes.
427 |
428 | Important: When I say "save" above, I mean it's saved in memory, not in the device disk.
429 |
430 | ---
431 |
432 | # Copyright
433 |
434 | **This package is copyrighted and brought to you
435 | by
436 | Parkside Technologies, a company which is simplifying global access to US stocks.**
437 |
438 | This package is published here with permission.
439 |
440 | Please, see the license page for more information.
441 |
442 | ***
443 |
444 | ## By Marcelo Glasberg
445 |
446 | _glasberg.dev_
447 |
448 | _github.com/marcglasberg_
449 |
450 | _linkedin.com/in/marcglasberg/_
451 |
452 | _twitter.com/glasbergmarcelo_
453 |
454 |
455 | _stackoverflow.com/users/3411681/marcg_
456 |
457 | _medium.com/@marcglasberg_
458 |
459 |
460 | *My article in the official Flutter documentation*:
461 |
462 | * Understanding
463 | constraints
464 |
465 | *The Flutter packages I've authored:*
466 |
467 | * async_redux
468 | * provider_for_redux
469 | * i18n_extension
470 | * align_positioned
471 | * network_to_file_image
472 | * image_pixels
473 | * matrix4_transform
474 | * back_button_interceptor
475 | * indexed_list_view
476 | * animated_size_and_fade
477 | * assorted_layout_widgets
478 | * weak_map
479 | * themed
480 | * bdd_framework
481 | *
482 | tiktoken_tokenizer_gpt4o_o1
483 |
484 | *My Medium Articles:*
485 |
486 | *
487 | Async Redux: Flutter’s non-boilerplate version of Redux
488 | (versions:
489 | Português)
490 | *
491 | i18n_extension
492 | (versions:
493 | Português)
494 | *
495 | Flutter: The Advanced Layout Rule Even Beginners Must Know
496 | (versions: русский)
497 | *
498 | The New Way to create Themes in your Flutter App
499 |
500 | [](https://mytext.ai)
501 |
--------------------------------------------------------------------------------
/lib/src/themed.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: deprecated_member_use
2 |
3 | import 'dart:ui' as ui
4 | show
5 | ParagraphStyle,
6 | TextStyle,
7 | Shadow,
8 | FontFeature,
9 | TextHeightBehavior,
10 | TextLeadingDistribution,
11 | FontVariation;
12 |
13 | import 'package:flutter/cupertino.dart';
14 | import 'package:flutter/foundation.dart';
15 | import 'package:flutter/material.dart';
16 | import 'package:themed/src/const_theme_exception.dart';
17 |
18 | abstract class ThemeRef {}
19 |
20 | /// You must have a single [Themed] widget in your widget tree, above
21 | /// your [MaterialApp] or [CupertinoApp] widgets:
22 | ///
23 | /// ```dart
24 | /// import 'package:themed/themed.dart';
25 | ///
26 | /// Widget build(BuildContext context) {
27 | /// return Themed(
28 | /// child: MaterialApp(...)
29 | /// );
30 | /// }
31 | /// ```
32 | ///
33 | /// You may, or may not, also provide a [defaultTheme] and a [currentTheme]:
34 | ///
35 | /// ```dart
36 | /// Widget build(BuildContext context) {
37 | /// return Themed(
38 | /// defaultTheme: { ... },
39 | /// currentTheme: { ... },
40 | /// child:
41 | /// );
42 | /// }
43 | /// ```
44 | ///
45 | /// # Usage
46 | ///
47 | /// Instead of:
48 | ///
49 | /// Container(
50 | /// color: Theme.of(context).warningColor,
51 | /// child: Text("hello!", style: Theme.of(context).titleTextStyle,
52 | /// );
53 | ///
54 | /// You can write:
55 | ///
56 | /// Container(
57 | /// color: const AppColor.warning,
58 | /// child: Text("hello!", style: const AppStyle.title,
59 | /// );
60 | ///
61 | class Themed extends StatefulWidget {
62 | //
63 | static final _themedKey = GlobalKey<_ThemedState>();
64 |
65 | final Widget child;
66 |
67 | /// Saved themes.
68 | static final Map