├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── ci.yaml
├── .gitignore
├── .metadata
├── .run
├── create.run.xml
└── remove.run.xml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── build.gradle
├── settings.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── net
│ └── jonhanson
│ └── flutter_native_splash
│ └── FlutterNativeSplashPlugin.java
├── bin
├── create.dart
└── remove.dart
├── example
├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── net
│ │ │ │ │ └── jonhanson
│ │ │ │ │ └── flutter_native_splash_example
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── assets
│ ├── android12splash.png
│ ├── dart.png
│ ├── dart_dark.png
│ ├── logo_lockup_flutter_vertical.png
│ └── logo_lockup_flutter_vertical_wht.png
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ ├── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ │ └── LaunchImage.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ └── README.md
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ └── RunnerTests
│ │ └── RunnerTests.swift
├── lib
│ └── main.dart
├── pubspec.lock
├── pubspec.yaml
├── red.yaml
├── test
│ └── widget_test.dart
└── web
│ ├── favicon.png
│ ├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
│ ├── index.html
│ └── manifest.json
├── ios
├── .gitignore
├── flutter_native_splash.podspec
└── flutter_native_splash
│ ├── Package.swift
│ └── Sources
│ └── flutter_native_splash
│ ├── FlutterNativeSplashPlugin.m
│ ├── PrivacyInfo.xcprivacy
│ └── include
│ └── flutter_native_splash
│ └── FlutterNativeSplashPlugin.h
├── lib
├── android.dart
├── cli_commands.dart
├── constants.dart
├── enums.dart
├── flavor_helper.dart
├── flutter_native_splash.dart
├── flutter_native_splash_web.dart
├── helper_utils.dart
├── ios.dart
├── remove_splash_from_web.dart
├── templates.dart
└── web.dart
├── pubspec.yaml
├── splash_demo.gif
├── splash_demo.webp
├── splash_demo_dark.gif
├── splash_demo_dark.webp
└── test
├── flutter_native_splash_test.dart
└── helper_utils_test.dart
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Attention: If you open a bug report without sufficient details, it will be closed. Is your question
11 | related to Android 12? Please check the notes on Android 12 first (https://pub.dev/packages/flutter_native_splash#android-12-support).
12 |
13 | **Describe the bug**
14 |
15 | A clear and concise description of what the bug is.
16 |
17 | **Configuration**
18 |
19 | Paste the flutter_native_splash section of your yaml config.
20 |
21 | **Device (please complete the following information):**
22 | - Device: [e.g. iPhone6]
23 | - OS: [e.g. iOS8.1]
24 |
25 | **To Reproduce**
26 | Steps to reproduce the behavior, using the example app:
27 | 1. Set the config on the example app to '...'
28 | 2. Run in an emulator configured with '...'
29 | 3. See error
30 |
31 | **Screenshots**
32 | If applicable, add screenshots to help explain your problem. If in doubt, attach a screenshot.
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_native_splash
2 |
3 | on:
4 | pull_request:
5 | paths-ignore:
6 | - '**.md'
7 | push:
8 | branches:
9 | - master
10 | paths-ignore:
11 | - '**.md'
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: subosito/flutter-action@v2
19 | - name: Install Dependencies
20 | run: flutter pub get
21 | - name: Format
22 | run: dart format . --set-exit-if-changed
23 | - name: Analyze
24 | run: flutter analyze
25 | - name: Test
26 | run: flutter test --test-randomize-ordering-seed random
27 |
--------------------------------------------------------------------------------
/.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 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
25 | /pubspec.lock
26 | **/doc/api/
27 | .dart_tool/
28 | .packages
29 | build/
30 |
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: db747aa1331bd95bc9b3874c842261ca2d302cd5
8 | channel: stable
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/.run/create.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.run/remove.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: dart
2 |
3 | dart:
4 | - dev
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Jon Hanson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | linter:
4 | rules:
5 | avoid_print: false
6 | avoid_classes_with_only_static_members: false
7 | # Additional information about this file can be found at
8 | # https://dart.dev/guides/language/analysis-options
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .cxx
10 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group = "net.jonhanson.flutter_native_splash"
2 | version = "1.0"
3 |
4 | buildscript {
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 |
10 | dependencies {
11 | classpath("com.android.tools.build:gradle:8.7.0")
12 | }
13 | }
14 |
15 | rootProject.allprojects {
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | apply plugin: "com.android.library"
23 |
24 | android {
25 | namespace = "net.jonhanson.flutter_native_splash"
26 |
27 | compileSdk = 35
28 |
29 | compileOptions {
30 | sourceCompatibility = JavaVersion.VERSION_11
31 | targetCompatibility = JavaVersion.VERSION_11
32 | }
33 |
34 | defaultConfig {
35 | minSdkVersion 16
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'flutter_native_splash'
2 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/java/net/jonhanson/flutter_native_splash/FlutterNativeSplashPlugin.java:
--------------------------------------------------------------------------------
1 | package net.jonhanson.flutter_native_splash;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import io.flutter.embedding.engine.plugins.FlutterPlugin;
6 | import io.flutter.plugin.common.MethodCall;
7 | import io.flutter.plugin.common.MethodChannel;
8 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
9 | import io.flutter.plugin.common.MethodChannel.Result;
10 |
11 | /** FlutterNativeSplashPlugin */
12 | public class FlutterNativeSplashPlugin implements FlutterPlugin, MethodCallHandler {
13 | /// The MethodChannel that will the communication between Flutter and native Android
14 | ///
15 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it
16 | /// when the Flutter Engine is detached from the Activity
17 | private MethodChannel channel;
18 |
19 | @Override
20 | public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
21 | channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_native_splash");
22 | channel.setMethodCallHandler(this);
23 | }
24 |
25 | @Override
26 | public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
27 | if (call.method.equals("getPlatformVersion")) {
28 | result.success("Android " + android.os.Build.VERSION.RELEASE);
29 | } else {
30 | result.notImplemented();
31 | }
32 | }
33 |
34 | @Override
35 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
36 | channel.setMethodCallHandler(null);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/bin/create.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:args/args.dart';
4 | import 'package:flutter_native_splash/cli_commands.dart';
5 | import 'package:flutter_native_splash/enums.dart';
6 | import 'package:flutter_native_splash/helper_utils.dart';
7 |
8 | void main(List args) {
9 | final parser = ArgParser();
10 |
11 | parser
12 | ..addFlag(
13 | ArgEnums.help.name,
14 | abbr: ArgEnums.help.abbr,
15 | help: 'Show help',
16 | )
17 | ..addOption(
18 | ArgEnums.path.name,
19 | abbr: ArgEnums.path.abbr,
20 | help:
21 | 'Path to the flutter project, if the project is not in it\'s default location.',
22 | )
23 | ..addOption(
24 | ArgEnums.flavor.name,
25 | abbr: ArgEnums.flavor.abbr,
26 | help:
27 | 'Flavor to create the splash for. The flavor must match the pattern flutter_native_splash-*.yaml (where * is the flavor name).',
28 | )
29 | ..addOption(
30 | ArgEnums.flavors.name,
31 | abbr: ArgEnums.flavors.abbr,
32 | help:
33 | 'Comma separated list of flavors to create the splash screens for. Match the pattern flutter_native_splash-*.yaml (where * is the flavor name).',
34 | )
35 | ..addFlag(
36 | ArgEnums.allFlavors.name,
37 | abbr: ArgEnums.allFlavors.abbr,
38 | help:
39 | 'Create the splash screens for all flavors that match the pattern flutter_native_splash-*.yaml (where * is the flavor name).',
40 | );
41 |
42 | final parsedArgs = parser.parse(args);
43 |
44 | final helpArg = parsedArgs[ArgEnums.help.name] as bool?;
45 |
46 | if (helpArg == true) {
47 | // ignore_for_file: avoid_print
48 | print(parser.usage);
49 | return;
50 | }
51 |
52 | final pathArg = parsedArgs[ArgEnums.path.name]?.toString();
53 | final flavorArg = parsedArgs[ArgEnums.flavor.name]?.toString();
54 | final flavorsArg = parsedArgs[ArgEnums.flavors.name]?.toString();
55 | final allFlavorsArg = parsedArgs[ArgEnums.allFlavors.name] as bool?;
56 |
57 | // Validate the flavor arguments
58 | HelperUtils.validateFlavorArgs(
59 | flavorArg: flavorArg,
60 | flavorsArg: flavorsArg,
61 | allFlavorsArg: allFlavorsArg,
62 | );
63 |
64 | if (flavorArg != null) {
65 | createSplash(
66 | path: pathArg,
67 | flavor: flavorArg,
68 | );
69 | } else if (flavorsArg != null) {
70 | for (final flavor in flavorsArg.split(',')) {
71 | createSplash(
72 | path: pathArg,
73 | flavor: flavor,
74 | );
75 | }
76 | } else if (allFlavorsArg == true) {
77 | // Find all flavor configurations in current project directory
78 | final flavors = Directory.current
79 | .listSync()
80 | .whereType()
81 | .map((entity) => entity.path.split(Platform.pathSeparator).last)
82 | .where(HelperUtils.isValidFlavorConfigFileName)
83 | .map(HelperUtils.getFlavorNameFromFileName)
84 | .toList();
85 |
86 | print('Found ${flavors.length} flavor configurations: $flavors');
87 |
88 | for (final flavor in flavors) {
89 | createSplash(
90 | path: pathArg,
91 | flavor: flavor,
92 | );
93 | }
94 | } else {
95 | createSplash(
96 | path: pathArg,
97 | flavor: null,
98 | );
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/bin/remove.dart:
--------------------------------------------------------------------------------
1 | import 'package:args/args.dart';
2 | import 'package:flutter_native_splash/cli_commands.dart';
3 | import 'package:flutter_native_splash/enums.dart';
4 |
5 | void main(List args) {
6 | final parser = ArgParser();
7 |
8 | parser
9 | ..addFlag(
10 | ArgEnums.help.name,
11 | abbr: ArgEnums.help.abbr,
12 | help: 'Show help',
13 | )
14 | ..addOption(
15 | ArgEnums.path.name,
16 | abbr: ArgEnums.path.abbr,
17 | help:
18 | 'Path to the flutter project, if the project is not in it\'s default location.',
19 | )
20 | ..addOption(
21 | ArgEnums.flavor.name,
22 | abbr: ArgEnums.flavor.abbr,
23 | help: 'Flavor to remove the splash for.',
24 | );
25 |
26 | final parsedArgs = parser.parse(args);
27 |
28 | final helpArg = parsedArgs[ArgEnums.help.name];
29 |
30 | if (helpArg != null) {
31 | // ignore_for_file: avoid_print
32 | print(parser.usage);
33 | return;
34 | }
35 |
36 | removeSplash(
37 | path: parsedArgs[ArgEnums.path.name]?.toString(),
38 | flavor: parsedArgs[ArgEnums.flavor.name]?.toString(),
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | **/ios/Flutter/.last_build_id
29 | .dart_tool/
30 | .flutter-plugins
31 | .flutter-plugins-dependencies
32 | .pub-cache/
33 | .pub/
34 | /build/
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Android Studio will place build artifacts here
43 | /android/app/debug
44 | /android/app/profile
45 | /android/app/release
46 |
--------------------------------------------------------------------------------
/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: ee4e09cce01d6f2d7f4baebd247fde02e5008851
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: ee4e09cce01d6f2d7f4baebd247fde02e5008851
17 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
18 | - platform: android
19 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
20 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
21 | - platform: ios
22 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
23 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
24 | - platform: web
25 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
26 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
27 |
28 | # User provided section
29 |
30 | # List of Local paths (relative to this file) that should be
31 | # ignored by the migrate tool.
32 | #
33 | # Files that are not part of the templates will be ignored by default.
34 | unmanaged_files:
35 | - 'lib/main.dart'
36 | - 'ios/Runner.xcodeproj/project.pbxproj'
37 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example for flutter_native_splash
2 |
3 | A new Flutter project for testing a splash screen.
4 |
5 | ## Getting Started
6 |
7 | This is Flutter's example application. Run it now and you will see that it has Flutter's default white splash screen, followed by a secondary Flutter splash screen that is displayed after Flutter loads while the app is loading resources.
8 |
9 | The pubspec.yaml file has been modified to add a color and icon to the splash screen. To apply these modification, run the following command in the terminal:
10 |
11 | ```
12 | flutter pub get
13 | dart run flutter_native_splash:create
14 | ```
15 |
16 | Or, to try specifying a config by setting the path, run the following command in the terminal:
17 |
18 | ```
19 | flutter pub get
20 | dart run flutter_native_splash:create --path=red.yaml
21 | ```
22 |
23 | The updated splash screen will now appear when you run the app, followed by the secondary splash screen.
24 |
25 | Note that with a default configuration, Android has a momentary fade artifact between the native splash and secondary splash screens. In this example, the `android/app/src/main/java/com/example/example/MainActivity.java` has been modified to remove this fade artifact.
26 |
27 | A few resources to get you started if this is your first Flutter project:
28 |
29 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
30 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
31 |
32 | For help getting started with Flutter development, view the
33 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
34 | samples, guidance on mobile development, and a full API reference.
35 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 | .cxx/
9 |
10 | # Remember to never publicly share your keystore.
11 | # See https://flutter.dev/to/reference-keystore
12 | key.properties
13 | **/*.keystore
14 | **/*.jks
15 |
--------------------------------------------------------------------------------
/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 = "net.jonhanson.flutter_native_splash_example"
10 | compileSdk = flutter.compileSdkVersion
11 | ndkVersion = flutter.ndkVersion
12 |
13 | compileOptions {
14 | sourceCompatibility = JavaVersion.VERSION_11
15 | targetCompatibility = JavaVersion.VERSION_11
16 | }
17 |
18 | kotlinOptions {
19 | jvmTarget = JavaVersion.VERSION_11
20 | }
21 |
22 | defaultConfig {
23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24 | applicationId = "net.jonhanson.flutter_native_splash_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 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/net/jonhanson/flutter_native_splash_example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package net.jonhanson.flutter_native_splash_example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/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/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/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/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/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/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/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/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/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.9-all.zip
6 |
--------------------------------------------------------------------------------
/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.7.3' apply false
22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/example/assets/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/assets/android12splash.png
--------------------------------------------------------------------------------
/example/assets/dart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/assets/dart.png
--------------------------------------------------------------------------------
/example/assets/dart_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/assets/dart_dark.png
--------------------------------------------------------------------------------
/example/assets/logo_lockup_flutter_vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/assets/logo_lockup_flutter_vertical.png
--------------------------------------------------------------------------------
/example/assets/logo_lockup_flutter_vertical_wht.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/assets/logo_lockup_flutter_vertical_wht.png
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Flutter Native Splash Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | flutter_native_splash_example
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_native_splash/flutter_native_splash.dart';
3 |
4 | void main() {
5 | WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
6 | FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
7 | runApp(const MyApp());
8 | }
9 |
10 | class MyApp extends StatelessWidget {
11 | const MyApp({super.key});
12 |
13 | // This widget is the root of your application.
14 | @override
15 | Widget build(BuildContext context) {
16 | return MaterialApp(
17 | title: 'Flutter Demo',
18 | theme: ThemeData(
19 | // This is the theme of your application.
20 | //
21 | // TRY THIS: Try running your application with "flutter run". You'll see
22 | // the application has a purple toolbar. Then, without quitting the app,
23 | // try changing the seedColor in the colorScheme below to Colors.green
24 | // and then invoke "hot reload" (save your changes or press the "hot
25 | // reload" button in a Flutter-supported IDE, or press "r" if you used
26 | // the command line to start the app).
27 | //
28 | // Notice that the counter didn't reset back to zero; the application
29 | // state is not lost during the reload. To reset the state, use hot
30 | // restart instead.
31 | //
32 | // This works for code too, not just values: Most code changes can be
33 | // tested with just a hot reload.
34 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
35 | useMaterial3: true,
36 | ),
37 | home: const MyHomePage(title: 'Flutter Demo Home Page'),
38 | );
39 | }
40 | }
41 |
42 | class MyHomePage extends StatefulWidget {
43 | const MyHomePage({super.key, required this.title});
44 |
45 | // This widget is the home page of your application. It is stateful, meaning
46 | // that it has a State object (defined below) that contains fields that affect
47 | // how it looks.
48 |
49 | // This class is the configuration for the state. It holds the values (in this
50 | // case the title) provided by the parent (in this case the App widget) and
51 | // used by the build method of the State. Fields in a Widget subclass are
52 | // always marked "final".
53 |
54 | final String title;
55 |
56 | @override
57 | State createState() => _MyHomePageState();
58 | }
59 |
60 | class _MyHomePageState extends State {
61 | int _counter = 0;
62 |
63 | void _incrementCounter() {
64 | setState(() {
65 | // This call to setState tells the Flutter framework that something has
66 | // changed in this State, which causes it to rerun the build method below
67 | // so that the display can reflect the updated values. If we changed
68 | // _counter without calling setState(), then the build method would not be
69 | // called again, and so nothing would appear to happen.
70 | _counter++;
71 | });
72 | }
73 |
74 | @override
75 | void initState() {
76 | super.initState();
77 | initialization();
78 | }
79 |
80 | void initialization() async {
81 | // This is where you can initialize the resources needed by your app while
82 | // the splash screen is displayed. Remove the following example because
83 | // delaying the user experience is a bad design practice!
84 | // ignore_for_file: avoid_print
85 | print('ready in 3...');
86 | await Future.delayed(const Duration(seconds: 1));
87 | print('ready in 2...');
88 | await Future.delayed(const Duration(seconds: 1));
89 | print('ready in 1...');
90 | await Future.delayed(const Duration(seconds: 1));
91 | print('go!');
92 | FlutterNativeSplash.remove();
93 | }
94 |
95 | @override
96 | Widget build(BuildContext context) {
97 | // This method is rerun every time setState is called, for instance as done
98 | // by the _incrementCounter method above.
99 | //
100 | // The Flutter framework has been optimized to make rerunning build methods
101 | // fast, so that you can just rebuild anything that needs updating rather
102 | // than having to individually change instances of widgets.
103 | return Scaffold(
104 | appBar: AppBar(
105 | // TRY THIS: Try changing the color here to a specific color (to
106 | // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
107 | // change color while the other colors stay the same.
108 | backgroundColor: Theme.of(context).colorScheme.inversePrimary,
109 | // Here we take the value from the MyHomePage object that was created by
110 | // the App.build method, and use it to set our appbar title.
111 | title: Text(widget.title),
112 | ),
113 | body: Center(
114 | // Center is a layout widget. It takes a single child and positions it
115 | // in the middle of the parent.
116 | child: Column(
117 | // Column is also a layout widget. It takes a list of children and
118 | // arranges them vertically. By default, it sizes itself to fit its
119 | // children horizontally, and tries to be as tall as its parent.
120 | //
121 | // Column has various properties to control how it sizes itself and
122 | // how it positions its children. Here we use mainAxisAlignment to
123 | // center the children vertically; the main axis here is the vertical
124 | // axis because Columns are vertical (the cross axis would be
125 | // horizontal).
126 | //
127 | // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
128 | // action in the IDE, or press "p" in the console), to see the
129 | // wireframe for each widget.
130 | mainAxisAlignment: MainAxisAlignment.center,
131 | children: [
132 | const Text(
133 | 'You have pushed the button this many times:',
134 | ),
135 | Text(
136 | '$_counter',
137 | style: Theme.of(context).textTheme.headlineMedium,
138 | ),
139 | ],
140 | ),
141 | ),
142 | floatingActionButton: FloatingActionButton(
143 | onPressed: _incrementCounter,
144 | tooltip: 'Increment',
145 | child: const Icon(Icons.add),
146 | ), // This trailing comma makes auto-formatting nicer for build methods.
147 | );
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | ansicolor:
5 | dependency: transitive
6 | description:
7 | name: ansicolor
8 | sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.0.3"
12 | archive:
13 | dependency: transitive
14 | description:
15 | name: archive
16 | sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "4.0.2"
20 | args:
21 | dependency: transitive
22 | description:
23 | name: args
24 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.7.0"
28 | async:
29 | dependency: transitive
30 | description:
31 | name: async
32 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "2.12.0"
36 | boolean_selector:
37 | dependency: transitive
38 | description:
39 | name: boolean_selector
40 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "2.1.2"
44 | characters:
45 | dependency: transitive
46 | description:
47 | name: characters
48 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.4.0"
52 | clock:
53 | dependency: transitive
54 | description:
55 | name: clock
56 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.1.2"
60 | collection:
61 | dependency: transitive
62 | description:
63 | name: collection
64 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "1.19.1"
68 | crypto:
69 | dependency: transitive
70 | description:
71 | name: crypto
72 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "3.0.6"
76 | csslib:
77 | dependency: transitive
78 | description:
79 | name: csslib
80 | sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
81 | url: "https://pub.dev"
82 | source: hosted
83 | version: "1.0.2"
84 | cupertino_icons:
85 | dependency: "direct main"
86 | description:
87 | name: cupertino_icons
88 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
89 | url: "https://pub.dev"
90 | source: hosted
91 | version: "1.0.8"
92 | fake_async:
93 | dependency: transitive
94 | description:
95 | name: fake_async
96 | sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
97 | url: "https://pub.dev"
98 | source: hosted
99 | version: "1.3.3"
100 | ffi:
101 | dependency: transitive
102 | description:
103 | name: ffi
104 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
105 | url: "https://pub.dev"
106 | source: hosted
107 | version: "2.1.3"
108 | flutter:
109 | dependency: "direct main"
110 | description: flutter
111 | source: sdk
112 | version: "0.0.0"
113 | flutter_lints:
114 | dependency: "direct dev"
115 | description:
116 | name: flutter_lints
117 | sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
118 | url: "https://pub.dev"
119 | source: hosted
120 | version: "6.0.0"
121 | flutter_native_splash:
122 | dependency: "direct main"
123 | description:
124 | path: ".."
125 | relative: true
126 | source: path
127 | version: "2.4.7"
128 | flutter_test:
129 | dependency: "direct dev"
130 | description: flutter
131 | source: sdk
132 | version: "0.0.0"
133 | flutter_web_plugins:
134 | dependency: transitive
135 | description: flutter
136 | source: sdk
137 | version: "0.0.0"
138 | html:
139 | dependency: transitive
140 | description:
141 | name: html
142 | sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
143 | url: "https://pub.dev"
144 | source: hosted
145 | version: "0.15.6"
146 | image:
147 | dependency: transitive
148 | description:
149 | name: image
150 | sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
151 | url: "https://pub.dev"
152 | source: hosted
153 | version: "4.5.4"
154 | leak_tracker:
155 | dependency: transitive
156 | description:
157 | name: leak_tracker
158 | sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
159 | url: "https://pub.dev"
160 | source: hosted
161 | version: "11.0.2"
162 | leak_tracker_flutter_testing:
163 | dependency: transitive
164 | description:
165 | name: leak_tracker_flutter_testing
166 | sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
167 | url: "https://pub.dev"
168 | source: hosted
169 | version: "3.0.10"
170 | leak_tracker_testing:
171 | dependency: transitive
172 | description:
173 | name: leak_tracker_testing
174 | sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
175 | url: "https://pub.dev"
176 | source: hosted
177 | version: "3.0.2"
178 | lints:
179 | dependency: transitive
180 | description:
181 | name: lints
182 | sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
183 | url: "https://pub.dev"
184 | source: hosted
185 | version: "6.0.0"
186 | matcher:
187 | dependency: transitive
188 | description:
189 | name: matcher
190 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
191 | url: "https://pub.dev"
192 | source: hosted
193 | version: "0.12.17"
194 | material_color_utilities:
195 | dependency: transitive
196 | description:
197 | name: material_color_utilities
198 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
199 | url: "https://pub.dev"
200 | source: hosted
201 | version: "0.11.1"
202 | meta:
203 | dependency: transitive
204 | description:
205 | name: meta
206 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
207 | url: "https://pub.dev"
208 | source: hosted
209 | version: "1.16.0"
210 | path:
211 | dependency: transitive
212 | description:
213 | name: path
214 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
215 | url: "https://pub.dev"
216 | source: hosted
217 | version: "1.9.1"
218 | petitparser:
219 | dependency: transitive
220 | description:
221 | name: petitparser
222 | sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
223 | url: "https://pub.dev"
224 | source: hosted
225 | version: "7.0.1"
226 | posix:
227 | dependency: transitive
228 | description:
229 | name: posix
230 | sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
231 | url: "https://pub.dev"
232 | source: hosted
233 | version: "6.0.1"
234 | sky_engine:
235 | dependency: transitive
236 | description: flutter
237 | source: sdk
238 | version: "0.0.0"
239 | source_span:
240 | dependency: transitive
241 | description:
242 | name: source_span
243 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
244 | url: "https://pub.dev"
245 | source: hosted
246 | version: "1.10.1"
247 | stack_trace:
248 | dependency: transitive
249 | description:
250 | name: stack_trace
251 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
252 | url: "https://pub.dev"
253 | source: hosted
254 | version: "1.12.1"
255 | stream_channel:
256 | dependency: transitive
257 | description:
258 | name: stream_channel
259 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
260 | url: "https://pub.dev"
261 | source: hosted
262 | version: "2.1.4"
263 | string_scanner:
264 | dependency: transitive
265 | description:
266 | name: string_scanner
267 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
268 | url: "https://pub.dev"
269 | source: hosted
270 | version: "1.4.1"
271 | term_glyph:
272 | dependency: transitive
273 | description:
274 | name: term_glyph
275 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
276 | url: "https://pub.dev"
277 | source: hosted
278 | version: "1.2.2"
279 | test_api:
280 | dependency: transitive
281 | description:
282 | name: test_api
283 | sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
284 | url: "https://pub.dev"
285 | source: hosted
286 | version: "0.7.6"
287 | typed_data:
288 | dependency: transitive
289 | description:
290 | name: typed_data
291 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
292 | url: "https://pub.dev"
293 | source: hosted
294 | version: "1.4.0"
295 | universal_io:
296 | dependency: transitive
297 | description:
298 | name: universal_io
299 | sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
300 | url: "https://pub.dev"
301 | source: hosted
302 | version: "2.2.2"
303 | vector_math:
304 | dependency: transitive
305 | description:
306 | name: vector_math
307 | sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
308 | url: "https://pub.dev"
309 | source: hosted
310 | version: "2.2.0"
311 | vm_service:
312 | dependency: transitive
313 | description:
314 | name: vm_service
315 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
316 | url: "https://pub.dev"
317 | source: hosted
318 | version: "14.3.1"
319 | xml:
320 | dependency: transitive
321 | description:
322 | name: xml
323 | sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
324 | url: "https://pub.dev"
325 | source: hosted
326 | version: "6.6.1"
327 | yaml:
328 | dependency: transitive
329 | description:
330 | name: yaml
331 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
332 | url: "https://pub.dev"
333 | source: hosted
334 | version: "3.1.3"
335 | sdks:
336 | dart: ">=3.8.0 <4.0.0"
337 | flutter: ">=3.18.0-18.0.pre.54"
338 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_native_splash_example
2 | description: "Example for Flutter Native Splash package."
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6 |
7 | # The following defines the version and build number for your application.
8 | # A version number is three numbers separated by dots, like 1.2.43
9 | # followed by an optional build number separated by a +.
10 | # Both the version and the builder number may be overridden in flutter
11 | # build by specifying --build-name and --build-number, respectively.
12 | # In Android, build-name is used as versionName while build-number used as versionCode.
13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15 | # Read more about iOS versioning at
16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 | # In Windows, build-name is used as the major, minor, and patch parts
18 | # of the product and file versions while build-number is used as the build suffix.
19 | version: 1.0.0+1
20 |
21 | environment:
22 | sdk: ^3.7.2
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 | dependencies:
31 | flutter:
32 | sdk: flutter
33 |
34 | flutter_native_splash:
35 | path: ../
36 |
37 | # The following adds the Cupertino Icons font to your application.
38 | # Use with the CupertinoIcons class for iOS style icons.
39 | cupertino_icons: ^1.0.8
40 |
41 | dev_dependencies:
42 | flutter_test:
43 | sdk: flutter
44 |
45 | # The "flutter_lints" package below contains a set of recommended lints to
46 | # encourage good coding practices. The lint set provided by the package is
47 | # activated in the `analysis_options.yaml` file located at the root of your
48 | # package. See that file for information about deactivating specific lint
49 | # rules and activating additional ones.
50 | flutter_lints: ^6.0.0
51 |
52 | # For information on the generic Dart part of this file, see the
53 | # following page: https://dart.dev/tools/pub/pubspec
54 |
55 | # The following section is specific to Flutter packages.
56 | flutter:
57 |
58 | # The following line ensures that the Material Icons font is
59 | # included with your application, so that you can use the icons in
60 | # the material Icons class.
61 | uses-material-design: true
62 |
63 | # To add assets to your application, add an assets section, like this:
64 | # assets:
65 | # - images/a_dot_burr.jpeg
66 | # - images/a_dot_ham.jpeg
67 |
68 | # An image asset can refer to one or more resolution-specific "variants", see
69 | # https://flutter.dev/to/resolution-aware-images
70 |
71 | # For details regarding adding assets from package dependencies, see
72 | # https://flutter.dev/to/asset-from-package
73 |
74 | # To add custom fonts to your application, add a fonts section here,
75 | # in this "flutter" section. Each entry in this list should have a
76 | # "family" key with the font family name, and a "fonts" key with a
77 | # list giving the asset and other descriptors for the font. For
78 | # example:
79 | # fonts:
80 | # - family: Schyler
81 | # fonts:
82 | # - asset: fonts/Schyler-Regular.ttf
83 | # - asset: fonts/Schyler-Italic.ttf
84 | # style: italic
85 | # - family: Trajan Pro
86 | # fonts:
87 | # - asset: fonts/TrajanPro.ttf
88 | # - asset: fonts/TrajanPro_Bold.ttf
89 | # weight: 700
90 | #
91 | # For details regarding fonts from package dependencies,
92 | # see https://flutter.dev/to/font-from-package
93 |
94 | flutter_native_splash:
95 |
96 | # This package generates native code to customize Flutter's default white native splash screen
97 | # with background color and splash image.
98 | # Customize the parameters below, and run the following command in the terminal:
99 | # dart run flutter_native_splash:create
100 | # To restore Flutter's default white splash screen, run the following command in the terminal:
101 | # dart run flutter_native_splash:remove
102 |
103 | # IMPORTANT NOTE: These parameter do not affect the configuration of Android 12 and later, which
104 | # handle splash screens differently that prior versions of Android. Android 12 and later must be
105 | # configured specifically in the android_12 section below.
106 |
107 | # color or background_image is the only required parameter. Use color to set the background
108 | # of your splash screen to a solid color. Use background_image to set the background of your
109 | # splash screen to a png image. This is useful for gradients. The image will be stretch to the
110 | # size of the app. Only one parameter can be used, color and background_image cannot both be set.
111 | color: "#e1f5fe"
112 | #background_image: "assets/background.png"
113 |
114 | # Optional parameters are listed below. To enable a parameter, uncomment the line by removing
115 | # the leading # character.
116 |
117 | # The image parameter allows you to specify an image used in the splash screen. It must be a
118 | # png file and should be sized for 4x pixel density.
119 | image: assets/logo_lockup_flutter_vertical.png
120 |
121 | # The branding property allows you to specify an image used as branding in the splash screen.
122 | # It must be a png file. It is supported for Android, iOS and the Web. For Android 12,
123 | # see the Android 12 section below.
124 | #branding: assets/dart.png
125 |
126 | # To position the branding image at the bottom of the screen you can use bottom, bottomRight,
127 | # and bottomLeft. The default values is bottom if not specified or specified something else.
128 | #branding_mode: bottom
129 |
130 | # The color_dark, background_image_dark, image_dark, branding_dark are parameters that set the background
131 | # and image when the device is in dark mode. If they are not specified, the app will use the
132 | # parameters from above. If there is no parameter above, the app will use the light mode values.
133 | # If the image_dark parameter is specified, color_dark or background_image_dark must be specified.
134 | # color_dark and background_image_dark cannot both be set.
135 | color_dark: "#042a49"
136 | #background_image_dark: "assets/dark-background.png"
137 | image_dark: assets/logo_lockup_flutter_vertical_wht.png
138 | #branding_dark: assets/dart_dark.png
139 |
140 | # Android 12 handles the splash screen differently than previous versions. Please visit
141 | # https://developer.android.com/guide/topics/ui/splash-screen
142 | # Following are Android 12 specific parameter.
143 | android_12:
144 | # The image parameter sets the splash screen icon image. If this parameter is not specified,
145 | # the app's launcher icon will be used instead.
146 | # Please note that the splash screen will be clipped to a circle on the center of the screen.
147 | # App icon with an icon background: This should be 960×960 pixels, and fit within a circle
148 | # 640 pixels in diameter.
149 | # App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
150 | # 768 pixels in diameter.
151 | image: assets/android12splash.png
152 |
153 | # Splash screen background color.
154 | color: "#42a5f5"
155 |
156 | # App icon background color.
157 | icon_background_color: "#eeeeee"
158 |
159 | # The branding property allows you to specify an image used as branding in the splash screen.
160 | #branding: assets/dart.png
161 |
162 | # The image_dark, color_dark, icon_background_color_dark, and branding_dark set values that
163 | # apply when the device is in dark mode. If they are not specified, the app will use the
164 | # parameters from above. If there is no parameter above, the app will use the light mode values.
165 | #image_dark: assets/android12splash-invert.png
166 | #color_dark: "#042a49"
167 | #icon_background_color_dark: "#111111"
168 |
169 | # The android, ios and web parameters can be used to disable generating a splash screen on a given
170 | # platform.
171 | #android: false
172 | #ios: false
173 | #web: false
174 |
175 | # Platform specific images can be specified with the following parameters, which will override
176 | # the respective parameter. You may specify all, selected, or none of these parameters:
177 | #color_android: "#42a5f5"
178 | #color_dark_android: "#042a49"
179 | #color_ios: "#42a5f5"
180 | #color_dark_ios: "#042a49"
181 | #color_web: "#42a5f5"
182 | #color_dark_web: "#042a49"
183 | #image_android: assets/splash-android.png
184 | #image_dark_android: assets/splash-invert-android.png
185 | #image_ios: assets/splash-ios.png
186 | #image_dark_ios: assets/splash-invert-ios.png
187 | #image_web: assets/splash-web.gif
188 | #image_dark_web: assets/splash-invert-web.gif
189 | #background_image_android: "assets/background-android.png"
190 | #background_image_dark_android: "assets/dark-background-android.png"
191 | #background_image_ios: "assets/background-ios.png"
192 | #background_image_dark_ios: "assets/dark-background-ios.png"
193 | #background_image_web: "assets/background-web.png"
194 | #background_image_dark_web: "assets/dark-background-web.png"
195 | #branding_android: assets/brand-android.png
196 | #branding_dark_android: assets/dart_dark-android.png
197 | #branding_ios: assets/brand-ios.png
198 | #branding_dark_ios: assets/dart_dark-ios.png
199 | #branding_web: assets/brand-web.gif
200 | #branding_dark_web: assets/dart_dark-web.gif
201 |
202 | # The position of the splash image can be set with android_gravity, ios_content_mode, and
203 | # web_image_mode parameters. All default to center.
204 | #
205 | # android_gravity can be one of the following Android Gravity (see
206 | # https://developer.android.com/reference/android/view/Gravity): bottom, center,
207 | # center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal,
208 | # fill_vertical, left, right, start, or top.
209 | #android_gravity: center
210 | #
211 | # ios_content_mode can be one of the following iOS UIView.ContentMode (see
212 | # https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill,
213 | # scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight,
214 | # bottomLeft, or bottomRight.
215 | #ios_content_mode: center
216 | #
217 | # web_image_mode can be one of the following modes: center, contain, stretch, and cover.
218 | #web_image_mode: center
219 |
220 | # The screen orientation can be set in Android with the android_screen_orientation parameter.
221 | # Valid parameters can be found here:
222 | # https://developer.android.com/guide/topics/manifest/activity-element#screen
223 | #android_screen_orientation: sensorLandscape
224 |
225 | # To hide the notification bar, use the fullscreen parameter. Has no effect in web since web
226 | # has no notification bar. Defaults to false.
227 | # NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads.
228 | # To show the notification bar, add the following code to your Flutter app:
229 | # WidgetsFlutterBinding.ensureInitialized();
230 | # SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top], );
231 | #fullscreen: true
232 |
233 | # If you have changed the name(s) of your info.plist file(s), you can specify the filename(s)
234 | # with the info_plist_files parameter. Remove only the # characters in the three lines below,
235 | # do not remove any spaces:
236 | #info_plist_files:
237 | # - 'ios/Runner/Info-Debug.plist'
238 | # - 'ios/Runner/Info-Release.plist'
--------------------------------------------------------------------------------
/example/red.yaml:
--------------------------------------------------------------------------------
1 | flutter_native_splash:
2 | color: "#ff6666"
3 | color_dark: "#660000"
4 | fullscreen: true
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:flutter_native_splash_example/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | flutter_native_splash_example
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flutter_native_splash_example",
3 | "short_name": "flutter_native_splash_example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "Example for Flutter Native Splash package",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/Generated.xcconfig
37 | /Flutter/ephemeral/
38 | /Flutter/flutter_export_environment.sh
39 |
--------------------------------------------------------------------------------
/ios/flutter_native_splash.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
3 | # Run `pod lib lint flutter_native_splash.podspec` to validate before publishing.
4 | #
5 | Pod::Spec.new do |s|
6 | s.name = 'flutter_native_splash'
7 | s.version = '2.4.3'
8 | s.summary = 'Flutter Native Splash'
9 | s.description = <<-DESC
10 | Customize Flutter's default white native splash screen with background color and splash image. Supports dark mode, full screen, and more.
11 | DESC
12 | s.homepage = 'https://github.com/jonbhanson/flutter_native_splash'
13 | s.license = { :file => '../LICENSE' }
14 | s.author = { 'Jon Hanson' => 'jon@jonhanson.net' }
15 | s.source = { :path => '.' }
16 | s.source_files = 'flutter_native_splash/Sources/flutter_native_splash/**/*.{h,m}'
17 | s.public_header_files = 'flutter_native_splash/Sources/flutter_native_splash/include/**/*.h'
18 | s.dependency 'Flutter'
19 | s.platform = :ios, '9.0'
20 | s.resource_bundles = {'flutter_native_splash_privacy' => ['flutter_native_splash/Sources/flutter_native_splash/PrivacyInfo.xcprivacy']}
21 |
22 | # Flutter.framework does not contain a i386 slice.
23 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
24 | end
25 |
--------------------------------------------------------------------------------
/ios/flutter_native_splash/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "flutter_native_splash",
8 | platforms: [
9 | .iOS("12.0")
10 | ],
11 | products: [
12 | .library(name: "flutter-native-splash", targets: ["flutter_native_splash"])
13 | ],
14 | dependencies: [],
15 | targets: [
16 | .target(
17 | name: "flutter_native_splash",
18 | dependencies: [],
19 | resources: [
20 | .process("PrivacyInfo.xcprivacy"),
21 | ],
22 | cSettings: [
23 | .headerSearchPath("include/flutter_native_splash")
24 | ]
25 | )
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/ios/flutter_native_splash/Sources/flutter_native_splash/FlutterNativeSplashPlugin.m:
--------------------------------------------------------------------------------
1 | #import "./include/flutter_native_splash/FlutterNativeSplashPlugin.h"
2 |
3 | @implementation FlutterNativeSplashPlugin
4 | + (void)registerWithRegistrar:(NSObject*)registrar {
5 | FlutterMethodChannel* channel = [FlutterMethodChannel
6 | methodChannelWithName:@"flutter_native_splash"
7 | binaryMessenger:[registrar messenger]];
8 | FlutterNativeSplashPlugin* instance = [[FlutterNativeSplashPlugin alloc] init];
9 | [registrar addMethodCallDelegate:instance channel:channel];
10 | }
11 |
12 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
13 | if ([@"getPlatformVersion" isEqualToString:call.method]) {
14 | result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
15 | } else {
16 | result(FlutterMethodNotImplemented);
17 | }
18 | }
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/ios/flutter_native_splash/Sources/flutter_native_splash/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTrackingDomains
6 |
7 | NSPrivacyAccessedAPITypes
8 |
9 | NSPrivacyCollectedDataTypes
10 |
11 | NSPrivacyTracking
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ios/flutter_native_splash/Sources/flutter_native_splash/include/flutter_native_splash/FlutterNativeSplashPlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface FlutterNativeSplashPlugin : NSObject
4 | @end
5 |
--------------------------------------------------------------------------------
/lib/android.dart:
--------------------------------------------------------------------------------
1 | part of 'cli_commands.dart';
2 |
3 | /// Image template
4 | class _AndroidDrawableTemplate {
5 | final String directoryName;
6 | final double pixelDensity;
7 |
8 | _AndroidDrawableTemplate({
9 | required this.directoryName,
10 | required this.pixelDensity,
11 | });
12 | }
13 |
14 | final _imagesTemplates = _generateImageTemplates();
15 | final _imageDarkTemplates = _generateImageTemplates(dark: true);
16 | final _imagesAndroid12Templates = _generateImageTemplates(android12: true);
17 | final _imagesAndroid12DarkTemplates =
18 | _generateImageTemplates(android12: true, dark: true);
19 |
20 | List<_AndroidDrawableTemplate> _generateImageTemplates({
21 | bool dark = false,
22 | bool android12 = false,
23 | }) {
24 | final prefix = "drawable${dark ? '-night' : ''}";
25 | final suffix = android12 ? '-v31' : '';
26 | return <_AndroidDrawableTemplate>[
27 | _AndroidDrawableTemplate(
28 | directoryName: '$prefix-mdpi$suffix',
29 | pixelDensity: 1,
30 | ),
31 | _AndroidDrawableTemplate(
32 | directoryName: '$prefix-hdpi$suffix',
33 | pixelDensity: 1.5,
34 | ),
35 | _AndroidDrawableTemplate(
36 | directoryName: '$prefix-xhdpi$suffix',
37 | pixelDensity: 2,
38 | ),
39 | _AndroidDrawableTemplate(
40 | directoryName: '$prefix-xxhdpi$suffix',
41 | pixelDensity: 3,
42 | ),
43 | _AndroidDrawableTemplate(
44 | directoryName: '$prefix-xxxhdpi$suffix',
45 | pixelDensity: 4,
46 | ),
47 | ];
48 | }
49 |
50 | /// Create Android splash screen
51 | void _createAndroidSplash({
52 | required String? imagePath,
53 | required String? darkImagePath,
54 | required String? android12ImagePath,
55 | required String? android12DarkImagePath,
56 | required String? android12BackgroundColor,
57 | required String? android12DarkBackgroundColor,
58 | required String? brandingImagePath,
59 | required String? brandingBottomPadding,
60 | required String? brandingDarkImagePath,
61 | required String? color,
62 | required String? darkColor,
63 | required String gravity,
64 | required String brandingGravity,
65 | required bool fullscreen,
66 | required String? backgroundImage,
67 | required String? darkBackgroundImage,
68 | required String? android12IconBackgroundColor,
69 | required String? darkAndroid12IconBackgroundColor,
70 | required String? screenOrientation,
71 | String? android12BrandingImagePath,
72 | String? android12DarkBrandingImagePath,
73 | }) {
74 | _applyImageAndroid(imagePath: imagePath);
75 |
76 | _applyImageAndroid(imagePath: darkImagePath, dark: true);
77 |
78 | //create resources for branding image if provided
79 | _applyImageAndroid(imagePath: brandingImagePath, fileName: 'branding.png');
80 |
81 | _applyImageAndroid(
82 | imagePath: brandingDarkImagePath,
83 | dark: true,
84 | fileName: 'branding.png',
85 | );
86 |
87 | //create android 12 image if provided. (otherwise uses launch icon)
88 | _applyImageAndroid(
89 | imagePath: android12ImagePath,
90 | fileName: 'android12splash.png',
91 | );
92 |
93 | _applyImageAndroid(
94 | imagePath: android12DarkImagePath,
95 | dark: true,
96 | fileName: 'android12splash.png',
97 | );
98 |
99 | _applyImageAndroid(
100 | imagePath: android12BrandingImagePath,
101 | android12: true,
102 | fileName: 'android12branding.png',
103 | );
104 |
105 | _applyImageAndroid(
106 | imagePath: android12DarkBrandingImagePath,
107 | dark: true,
108 | android12: true,
109 | fileName: 'android12branding.png',
110 | );
111 |
112 | _createBackground(
113 | colorString: color,
114 | darkColorString: darkColor,
115 | darkBackgroundImageSource: darkBackgroundImage,
116 | backgroundImageSource: backgroundImage,
117 | darkBackgroundImageDestination:
118 | '${_flavorHelper.androidNightDrawableFolder}background.png',
119 | backgroundImageDestination:
120 | '${_flavorHelper.androidDrawableFolder}background.png',
121 | );
122 |
123 | _createBackground(
124 | colorString: color,
125 | darkColorString: darkColor,
126 | darkBackgroundImageSource: darkBackgroundImage,
127 | backgroundImageSource: backgroundImage,
128 | darkBackgroundImageDestination:
129 | '${_flavorHelper.androidNightV21DrawableFolder}background.png',
130 | backgroundImageDestination:
131 | '${_flavorHelper.androidV21DrawableFolder}background.png',
132 | );
133 |
134 | // ignore_for_file: avoid_print
135 | print('[Android] Updating launch background(s) with splash image path...');
136 |
137 | _applyLaunchBackgroundXml(
138 | gravity: gravity,
139 | launchBackgroundFilePath: _flavorHelper.androidLaunchBackgroundFile,
140 | showImage: imagePath != null,
141 | showBranding: brandingImagePath != null,
142 | brandingGravity: brandingGravity,
143 | brandingBottomPadding: brandingBottomPadding,
144 | );
145 |
146 | if (darkColor != null || darkBackgroundImage != null) {
147 | _applyLaunchBackgroundXml(
148 | gravity: gravity,
149 | launchBackgroundFilePath: _flavorHelper.androidLaunchDarkBackgroundFile,
150 | showImage: imagePath != null,
151 | showBranding: brandingImagePath != null,
152 | brandingGravity: brandingGravity,
153 | brandingBottomPadding: brandingBottomPadding,
154 | );
155 | }
156 |
157 | if (Directory(_flavorHelper.androidV21DrawableFolder).existsSync()) {
158 | _applyLaunchBackgroundXml(
159 | gravity: gravity,
160 | launchBackgroundFilePath: _flavorHelper.androidV21LaunchBackgroundFile,
161 | showImage: imagePath != null,
162 | showBranding: brandingImagePath != null,
163 | brandingGravity: brandingGravity,
164 | brandingBottomPadding: brandingBottomPadding,
165 | );
166 | if (darkColor != null || darkBackgroundImage != null) {
167 | _applyLaunchBackgroundXml(
168 | gravity: gravity,
169 | launchBackgroundFilePath:
170 | _flavorHelper.androidV21LaunchDarkBackgroundFile,
171 | showImage: imagePath != null,
172 | showBranding: brandingImagePath != null,
173 | brandingGravity: brandingGravity,
174 | brandingBottomPadding: brandingBottomPadding,
175 | );
176 | }
177 | }
178 |
179 | print('[Android] Updating styles...');
180 | _applyStylesXml(
181 | fullScreen: fullscreen,
182 | file: _flavorHelper.androidV31StylesFile,
183 | template: _androidV31StylesXml,
184 | android12BackgroundColor: android12BackgroundColor,
185 | android12ImagePath: android12ImagePath,
186 | android12IconBackgroundColor: android12IconBackgroundColor,
187 | android12BrandingImagePath: android12BrandingImagePath,
188 | );
189 |
190 | _applyStylesXml(
191 | fullScreen: fullscreen,
192 | file: _flavorHelper.androidV31StylesNightFile,
193 | template: _androidV31StylesNightXml,
194 | android12BackgroundColor: android12DarkBackgroundColor,
195 | android12ImagePath: android12DarkImagePath,
196 | android12IconBackgroundColor: darkAndroid12IconBackgroundColor,
197 | android12BrandingImagePath: android12DarkBrandingImagePath,
198 | );
199 |
200 | _applyStylesXml(
201 | fullScreen: fullscreen,
202 | file: _flavorHelper.androidStylesFile,
203 | template: _androidStylesXml,
204 | );
205 |
206 | _applyStylesXml(
207 | fullScreen: fullscreen,
208 | file: _flavorHelper.androidNightStylesFile,
209 | template: _androidStylesNightXml,
210 | );
211 |
212 | _applyOrientation(orientation: screenOrientation);
213 | }
214 |
215 | /// Create splash screen as drawables for multiple screens (dpi)
216 | void _applyImageAndroid({
217 | String? imagePath,
218 | bool dark = false,
219 | bool android12 = false,
220 | String fileName = 'splash.png',
221 | }) {
222 | final templates = _getAssociatedTemplates(android12: android12, dark: dark);
223 | if (imagePath == null) {
224 | for (final template in templates) {
225 | _deleteImageAndroid(template: template, fileName: fileName);
226 | }
227 | } else {
228 | print(
229 | '[Android] Creating ${dark ? 'dark mode ' : 'default '}'
230 | '${fileName.split('.')[0]} images',
231 | );
232 |
233 | final image = decodeImage(File(imagePath).readAsBytesSync());
234 | if (image == null) {
235 | print('The file $imagePath could not be read.');
236 | exit(1);
237 | }
238 |
239 | _saveImageAndroid(
240 | templates: templates,
241 | image: image,
242 | fileName: fileName,
243 | androidResFolder: _flavorHelper.androidResFolder,
244 | );
245 | }
246 | }
247 |
248 | List<_AndroidDrawableTemplate> _getAssociatedTemplates({
249 | required bool android12,
250 | required bool dark,
251 | }) {
252 | if (android12) {
253 | return dark ? _imagesAndroid12DarkTemplates : _imagesAndroid12Templates;
254 | } else {
255 | return dark ? _imageDarkTemplates : _imagesTemplates;
256 | }
257 | }
258 |
259 | /// Saves splash screen image to the project
260 | /// Note: Do not change interpolation unless you end up with better results
261 | /// https://github.com/fluttercommunity/flutter_launcher_icons/issues/101#issuecomment-495528733
262 | void _saveImageAndroid({
263 | required List<_AndroidDrawableTemplate> templates,
264 | required Image image,
265 | required fileName,
266 | required String androidResFolder,
267 | }) async {
268 | await Future.wait(
269 | templates.map(
270 | (template) => Isolate.run(() async {
271 | //added file name attribute to make this method generic for splash image and branding image.
272 | final newFile = copyResize(
273 | image,
274 | width: image.width * template.pixelDensity ~/ 4,
275 | height: image.height * template.pixelDensity ~/ 4,
276 | interpolation: Interpolation.average,
277 | );
278 |
279 | // When the flavor value is not specified we will place all the data inside the main directory.
280 | // However if the flavor value is specified, we need to place the data in the correct directory.
281 | // Default: android/app/src/main/res/
282 | // With a flavor: android/app/src/[flavor name]/res/
283 | final file = File(
284 | '$androidResFolder${template.directoryName}/$fileName',
285 | );
286 | // File(_androidResFolder + template.directoryName + '/' + 'splash.png');
287 | await file.create(recursive: true);
288 | await file.writeAsBytes(encodePng(newFile));
289 | }),
290 | ),
291 | );
292 | }
293 |
294 | void _deleteImageAndroid({
295 | required _AndroidDrawableTemplate template,
296 | required fileName,
297 | }) {
298 | final file = File(
299 | '${_flavorHelper.androidResFolder}${template.directoryName}/$fileName',
300 | );
301 | if (file.existsSync()) {
302 | print('[Android] Deleting $fileName');
303 | file.deleteSync(recursive: true);
304 | }
305 | }
306 |
307 | /// Updates launch_background.xml adding splash image path
308 | void _applyLaunchBackgroundXml({
309 | required String launchBackgroundFilePath,
310 | required String gravity,
311 | required bool showImage,
312 | bool showBranding = false,
313 | String? brandingBottomPadding,
314 | String brandingGravity = 'bottom',
315 | }) {
316 | String brandingGravityValue = brandingGravity;
317 | print('[Android] - $launchBackgroundFilePath');
318 | final launchBackgroundFile = File(launchBackgroundFilePath);
319 | launchBackgroundFile.createSync(recursive: true);
320 | final launchBackgroundDocument =
321 | XmlDocument.parse(_androidLaunchBackgroundXml);
322 |
323 | final layerList = launchBackgroundDocument.getElement('layer-list');
324 | final List items = layerList!.children;
325 |
326 | if (showImage) {
327 | final splashItem =
328 | XmlDocument.parse(_androidLaunchItemXml).rootElement.copy();
329 | splashItem.getElement('bitmap')?.setAttribute('android:gravity', gravity);
330 | items.add(splashItem);
331 | }
332 |
333 | if (showBranding && gravity != brandingGravityValue) {
334 | //add branding when splash image and branding image are not at the same position
335 | final androidBrandingItemXml = _androidBrandingItemXml.replaceAll(
336 | "{bottom_padding}", brandingBottomPadding ?? "0");
337 | print('[Android] branding bottom padding: ${brandingBottomPadding ?? "0"}');
338 | final brandingItem =
339 | XmlDocument.parse(androidBrandingItemXml).rootElement.copy();
340 | if (brandingGravityValue == 'bottomRight') {
341 | brandingGravityValue = 'bottom|right';
342 | } else if (brandingGravityValue == 'bottomLeft') {
343 | brandingGravityValue = 'bottom|left';
344 | } else if (brandingGravityValue != 'bottom') {
345 | print(
346 | '$brandingGravityValue illegal property defined for the branding mode. Setting back to default.',
347 | );
348 | brandingGravityValue = 'bottom';
349 | }
350 | brandingItem
351 | .getElement('bitmap')
352 | ?.setAttribute('android:gravity', brandingGravityValue);
353 | items.add(brandingItem);
354 | }
355 |
356 | launchBackgroundFile.writeAsStringSync(
357 | '${launchBackgroundDocument.toXmlString(pretty: true, indent: ' ')}\n',
358 | );
359 | }
360 |
361 | /// Create or update styles.xml full screen mode setting
362 | void _applyStylesXml({
363 | required bool fullScreen,
364 | required String file,
365 | required String template,
366 | String? android12BackgroundColor,
367 | String? android12ImagePath,
368 | String? android12IconBackgroundColor,
369 | String? android12BrandingImagePath,
370 | }) {
371 | final stylesFile = File(file);
372 | print('[Android] - $file');
373 | if (!stylesFile.existsSync()) {
374 | print('[Android] No $file found in your Android project');
375 | print('[Android] Creating $file and adding it to your Android project');
376 | stylesFile.createSync(recursive: true);
377 | stylesFile.writeAsStringSync(template);
378 | }
379 |
380 | _updateStylesFile(
381 | fullScreen: fullScreen,
382 | stylesFile: stylesFile,
383 | android12BackgroundColor: android12BackgroundColor,
384 | android12ImagePath: android12ImagePath,
385 | android12IconBackgroundColor: android12IconBackgroundColor,
386 | android12BrandingImagePath: android12BrandingImagePath,
387 | );
388 | }
389 |
390 | /// Updates styles.xml adding full screen property
391 | Future _updateStylesFile({
392 | required bool fullScreen,
393 | required File stylesFile,
394 | required String? android12BackgroundColor,
395 | required String? android12ImagePath,
396 | required String? android12IconBackgroundColor,
397 | required String? android12BrandingImagePath,
398 | }) async {
399 | final stylesDocument = XmlDocument.parse(stylesFile.readAsStringSync());
400 | final resources = stylesDocument.getElement('resources');
401 | final styles = resources?.findElements('style');
402 | if (styles?.length == 1) {
403 | print(
404 | '[Android] Only 1 style in styles.xml. Flutter V2 embedding has 2 '
405 | 'styles by default. Full screen mode not supported in Flutter V1 '
406 | 'embedding. Skipping update of styles.xml with fullscreen mode',
407 | );
408 | return;
409 | }
410 |
411 | XmlElement launchTheme;
412 | try {
413 | launchTheme = styles!.singleWhere(
414 | (element) => element.attributes.any(
415 | (attribute) =>
416 | attribute.name.toString() == 'name' &&
417 | attribute.value == 'LaunchTheme',
418 | ),
419 | );
420 | } on StateError {
421 | print(
422 | 'LaunchTheme was not found in styles.xml. Skipping fullscreen '
423 | 'mode',
424 | );
425 | return;
426 | }
427 |
428 | _replaceElement(
429 | launchTheme: launchTheme,
430 | name: 'android:forceDarkAllowed',
431 | value: "false",
432 | );
433 |
434 | _replaceElement(
435 | launchTheme: launchTheme,
436 | name: 'android:windowFullscreen',
437 | value: fullScreen.toString(),
438 | );
439 |
440 | _replaceElement(
441 | launchTheme: launchTheme,
442 | name: 'android:windowDrawsSystemBarBackgrounds',
443 | value: fullScreen.toString());
444 |
445 | _replaceElement(
446 | launchTheme: launchTheme,
447 | name: 'android:windowLayoutInDisplayCutoutMode',
448 | value: 'shortEdges',
449 | );
450 |
451 | // In Android 12, the color must be set directly in the styles.xml
452 | if (android12BackgroundColor == null) {
453 | _removeElement(
454 | launchTheme: launchTheme,
455 | name: 'android:windowSplashScreenBackground',
456 | );
457 | } else {
458 | _replaceElement(
459 | launchTheme: launchTheme,
460 | name: 'android:windowSplashScreenBackground',
461 | value: '#$android12BackgroundColor',
462 | );
463 | }
464 |
465 | if (android12BrandingImagePath == null) {
466 | _removeElement(
467 | launchTheme: launchTheme,
468 | name: 'android:windowSplashScreenBrandingImage',
469 | );
470 | } else {
471 | _replaceElement(
472 | launchTheme: launchTheme,
473 | name: 'android:windowSplashScreenBrandingImage',
474 | value: '@drawable/android12branding',
475 | );
476 | }
477 |
478 | if (android12ImagePath == null) {
479 | _removeElement(
480 | launchTheme: launchTheme,
481 | name: 'android:windowSplashScreenAnimatedIcon',
482 | );
483 | } else {
484 | _replaceElement(
485 | launchTheme: launchTheme,
486 | name: 'android:windowSplashScreenAnimatedIcon',
487 | value: '@drawable/android12splash',
488 | );
489 | }
490 |
491 | if (android12IconBackgroundColor == null) {
492 | _removeElement(
493 | launchTheme: launchTheme,
494 | name: 'android:windowSplashScreenIconBackgroundColor',
495 | );
496 | } else {
497 | _replaceElement(
498 | launchTheme: launchTheme,
499 | name: 'android:windowSplashScreenIconBackgroundColor',
500 | value: '#$android12IconBackgroundColor',
501 | );
502 | }
503 |
504 | stylesFile.writeAsStringSync(
505 | '${stylesDocument.toXmlString(pretty: true, indent: ' ')}\n',
506 | );
507 | }
508 |
509 | void _replaceElement({
510 | required XmlElement launchTheme,
511 | required String name,
512 | required String value,
513 | }) {
514 | launchTheme.children.removeWhere(
515 | (element) => element.attributes.any(
516 | (attribute) =>
517 | attribute.name.toString() == 'name' && attribute.value == name,
518 | ),
519 | );
520 |
521 | launchTheme.children.add(
522 | XmlElement(
523 | XmlName('item'),
524 | [XmlAttribute(XmlName('name'), name)],
525 | [XmlText(value)],
526 | ),
527 | );
528 | }
529 |
530 | void _removeElement({required XmlElement launchTheme, required String name}) {
531 | launchTheme.children.removeWhere(
532 | (element) => element.attributes.any(
533 | (attribute) =>
534 | attribute.name.toString() == 'name' && attribute.value == name,
535 | ),
536 | );
537 | }
538 |
539 | void _applyOrientation({required String? orientation}) {
540 | final manifestFile = File(_flavorHelper.androidManifestFile);
541 | final manifestDocument = XmlDocument.parse(manifestFile.readAsStringSync());
542 |
543 | final manifestRoot = manifestDocument.getElement('manifest');
544 | final application = manifestRoot?.getElement('application');
545 | final activity = application?.getElement('activity');
546 | const String attribute = 'android:screenOrientation';
547 | if (orientation == null) {
548 | if (activity?.attributes.any((p0) => p0.name.toString() == attribute) ??
549 | false) {
550 | activity?.removeAttribute(attribute);
551 | } else {
552 | return;
553 | }
554 | } else {
555 | activity?.setAttribute(attribute, orientation);
556 | }
557 | manifestFile.writeAsStringSync(
558 | manifestDocument.toXmlString(
559 | pretty: true,
560 | indent: ' ',
561 | indentAttribute: (XmlAttribute xmlAttribute) {
562 | // Try to preserve AndroidManifest.XML formatting:
563 | if (xmlAttribute.name.toString() == "xmlns:android") return false;
564 | if (xmlAttribute.value == "android.intent.action.MAIN") return false;
565 | if (xmlAttribute.value == "android.intent.category.LAUNCHER") {
566 | return false;
567 | }
568 | return true;
569 | },
570 | ),
571 | );
572 | }
573 |
--------------------------------------------------------------------------------
/lib/cli_commands.dart:
--------------------------------------------------------------------------------
1 | /// ## Flutter Native Splash
2 | ///
3 | /// This is the main entry point for the Flutter Native Splash package.
4 | library;
5 |
6 | import 'dart:isolate';
7 |
8 | import 'package:ansicolor/ansicolor.dart';
9 | import 'package:html/parser.dart' as html_parser;
10 | import 'package:image/image.dart';
11 | import 'package:meta/meta.dart';
12 | import 'package:path/path.dart' as p;
13 | import 'package:universal_io/io.dart';
14 | import 'package:xml/xml.dart';
15 | import 'package:yaml/yaml.dart';
16 |
17 | part 'android.dart';
18 | part 'constants.dart';
19 | part 'flavor_helper.dart';
20 | part 'ios.dart';
21 | part 'templates.dart';
22 | part 'web.dart';
23 |
24 | late _FlavorHelper _flavorHelper;
25 |
26 | /// Create splash screens for Android and iOS
27 | void createSplash({
28 | required String? path,
29 | required String? flavor,
30 | }) {
31 | if (flavor != null) {
32 | // ignore_for_file: avoid_print
33 | print(
34 | '''
35 | ╔════════════════════════════════════════════════════════════════════════════╗
36 | ║ Setting up flavors! ║
37 | ╚════════════════════════════════════════════════════════════════════════════╝
38 | ===> Setting up the $flavor flavor.
39 | ''',
40 | );
41 | }
42 |
43 | final config = getConfig(configFile: path, flavor: flavor);
44 | _createSplashByConfig(config);
45 | }
46 |
47 | /// Create splash screens for Android and iOS based on a config argument
48 | void _createSplashByConfig(Map config) {
49 | // Preparing all the data for later usage
50 | final String? image =
51 | _checkImageExists(config: config, parameter: _Parameter.image);
52 | final String? imageAndroid =
53 | _checkImageExists(config: config, parameter: _Parameter.imageAndroid);
54 | final String? imageIos =
55 | _checkImageExists(config: config, parameter: _Parameter.imageIos);
56 | final String? imageWeb =
57 | _checkImageExists(config: config, parameter: _Parameter.imageWeb);
58 | final String? darkImage =
59 | _checkImageExists(config: config, parameter: _Parameter.darkImage);
60 | final String? darkImageAndroid =
61 | _checkImageExists(config: config, parameter: _Parameter.darkImageAndroid);
62 | final String? darkImageIos =
63 | _checkImageExists(config: config, parameter: _Parameter.darkImageIos);
64 | final String? darkImageWeb =
65 | _checkImageExists(config: config, parameter: _Parameter.darkImageWeb);
66 | final String? brandingImage =
67 | _checkImageExists(config: config, parameter: _Parameter.brandingImage);
68 | final String? brandingBottomPadding =
69 | config[_Parameter.brandingBottomPadding]?.toString();
70 | final String? brandingImageAndroid = _checkImageExists(
71 | config: config, parameter: _Parameter.brandingImageAndroid);
72 | final String? brandingBottomPaddingAndroid =
73 | config[_Parameter.brandingBottomPaddingAndroid]?.toString();
74 | final String? brandingImageIos =
75 | _checkImageExists(config: config, parameter: _Parameter.brandingImageIos);
76 | final String? brandingBottomPaddingIos =
77 | config[_Parameter.brandingBottomPaddingIos]?.toString();
78 | final String? brandingImageWeb =
79 | _checkImageExists(config: config, parameter: _Parameter.brandingImageWeb);
80 | final String? brandingDarkImage = _checkImageExists(
81 | config: config, parameter: _Parameter.brandingDarkImage);
82 | final String? brandingDarkImageAndroid = _checkImageExists(
83 | config: config, parameter: _Parameter.brandingDarkImageAndroid);
84 | final String? brandingDarkImageIos = _checkImageExists(
85 | config: config, parameter: _Parameter.brandingDarkImageIos);
86 | final String? brandingDarkImageWeb = _checkImageExists(
87 | config: config, parameter: _Parameter.brandingDarkImageWeb);
88 | final String? color = parseColor(config[_Parameter.color]);
89 | final String? colorAndroid = parseColor(config[_Parameter.colorAndroid]);
90 | final String? colorIos = parseColor(config[_Parameter.colorIos]);
91 | final String? colorWeb = parseColor(config[_Parameter.colorWeb]);
92 | final String? darkColor = parseColor(config[_Parameter.darkColor]);
93 | final String? darkColorAndroid =
94 | parseColor(config[_Parameter.darkColorAndroid]);
95 | final String? darkColorIos = parseColor(config[_Parameter.darkColorIos]);
96 | final String? darkColorWeb = parseColor(config[_Parameter.darkColorWeb]);
97 | final String? backgroundImage =
98 | _checkImageExists(config: config, parameter: _Parameter.backgroundImage);
99 | final String? backgroundImageAndroid = _checkImageExists(
100 | config: config, parameter: _Parameter.backgroundImageAndroid);
101 | final String? backgroundImageIos = _checkImageExists(
102 | config: config, parameter: _Parameter.backgroundImageIos);
103 | final String? backgroundImageWeb = _checkImageExists(
104 | config: config, parameter: _Parameter.backgroundImageWeb);
105 | final String? darkBackgroundImage = _checkImageExists(
106 | config: config, parameter: _Parameter.darkBackgroundImage);
107 | final String? darkBackgroundImageAndroid = _checkImageExists(
108 | config: config,
109 | parameter: _Parameter.darkBackgroundImageAndroid,
110 | );
111 | final String? darkBackgroundImageIos = _checkImageExists(
112 | config: config, parameter: _Parameter.darkBackgroundImageIos);
113 | final String? darkBackgroundImageWeb = _checkImageExists(
114 | config: config, parameter: _Parameter.darkBackgroundImageWeb);
115 |
116 | final plistFiles = config[_Parameter.plistFiles] as List?;
117 | String gravity = (config['fill'] as bool? ?? false) ? 'fill' : 'center';
118 | if (config[_Parameter.gravity] != null) {
119 | gravity = config[_Parameter.gravity] as String;
120 | }
121 | final String? androidScreenOrientation =
122 | config[_Parameter.androidScreenOrientation] as String?;
123 | final brandingGravity =
124 | config[_Parameter.brandingGravity] as String? ?? 'bottom';
125 | final bool fullscreen = config[_Parameter.fullscreen] as bool? ?? false;
126 | final String iosContentMode =
127 | config[_Parameter.iosContentMode] as String? ?? 'center';
128 | final webImageMode = config[_Parameter.webImageMode] as String? ?? 'center';
129 | String? android12Image;
130 | String? android12DarkImage;
131 | String? android12IconBackgroundColor;
132 | String? darkAndroid12IconBackgroundColor;
133 | String? android12Color;
134 | String? android12DarkColor;
135 | String? android12BrandingImage;
136 | String? android12DarkBrandingImage;
137 |
138 | if (config[_Parameter.android12Section] != null) {
139 | final android12Config =
140 | config[_Parameter.android12Section] as Map;
141 | android12Image =
142 | _checkImageExists(config: android12Config, parameter: _Parameter.image);
143 | android12DarkImage = _checkImageExists(
144 | config: android12Config, parameter: _Parameter.darkImage);
145 | android12IconBackgroundColor =
146 | parseColor(android12Config[_Parameter.iconBackgroundColor]);
147 | darkAndroid12IconBackgroundColor =
148 | parseColor(android12Config[_Parameter.iconBackgroundColorDark]);
149 | android12Color = parseColor(android12Config[_Parameter.color]) ?? color;
150 | android12DarkColor =
151 | parseColor(android12Config[_Parameter.darkColor]) ?? darkColor;
152 | android12BrandingImage = _checkImageExists(
153 | config: android12Config, parameter: _Parameter.brandingImage);
154 | android12DarkBrandingImage = _checkImageExists(
155 | config: android12Config, parameter: _Parameter.brandingDarkImage);
156 | }
157 |
158 | if (!config.containsKey(_Parameter.android) ||
159 | config[_Parameter.android] as bool) {
160 | if (Directory('android').existsSync()) {
161 | _createAndroidSplash(
162 | imagePath: imageAndroid ?? image,
163 | darkImagePath: darkImageAndroid ?? darkImage,
164 | brandingImagePath: brandingImageAndroid ?? brandingImage,
165 | brandingBottomPadding:
166 | brandingBottomPaddingAndroid ?? brandingBottomPadding,
167 | brandingDarkImagePath: brandingDarkImageAndroid ?? brandingDarkImage,
168 | backgroundImage: backgroundImageAndroid ?? backgroundImage,
169 | darkBackgroundImage: darkBackgroundImageAndroid ?? darkBackgroundImage,
170 | color: colorAndroid ?? color,
171 | darkColor: darkColorAndroid ?? darkColor,
172 | gravity: gravity,
173 | brandingGravity: brandingGravity,
174 | fullscreen: fullscreen,
175 | screenOrientation: androidScreenOrientation,
176 | android12ImagePath: android12Image,
177 | android12DarkImagePath: android12DarkImage ?? android12Image,
178 | android12BackgroundColor: android12Color,
179 | android12DarkBackgroundColor: android12DarkColor ?? android12Color,
180 | android12IconBackgroundColor: android12IconBackgroundColor,
181 | darkAndroid12IconBackgroundColor:
182 | darkAndroid12IconBackgroundColor ?? android12IconBackgroundColor,
183 | android12BrandingImagePath: android12BrandingImage,
184 | android12DarkBrandingImagePath:
185 | android12DarkBrandingImage ?? android12BrandingImage,
186 | );
187 | } else {
188 | print('Android folder not found, skipping Android splash update...');
189 | }
190 | }
191 |
192 | if (!config.containsKey(_Parameter.ios) || config[_Parameter.ios] as bool) {
193 | if (Directory('ios').existsSync()) {
194 | _createiOSSplash(
195 | imagePath: imageIos ?? image,
196 | darkImagePath: darkImageIos ?? darkImage,
197 | backgroundImage: backgroundImageIos ?? backgroundImage,
198 | darkBackgroundImage: darkBackgroundImageIos ?? darkBackgroundImage,
199 | brandingImagePath: brandingImageIos ?? brandingImage,
200 | brandingDarkImagePath: brandingDarkImageIos ?? brandingDarkImage,
201 | brandingBottomPadding:
202 | brandingBottomPaddingIos ?? brandingBottomPadding,
203 | color: colorIos ?? color,
204 | darkColor: darkColorIos ?? darkColor,
205 | plistFiles: plistFiles,
206 | iosContentMode: iosContentMode,
207 | iosBrandingContentMode: brandingGravity,
208 | fullscreen: fullscreen,
209 | );
210 | } else {
211 | print('iOS folder not found, skipping iOS splash update...');
212 | }
213 | }
214 |
215 | if (!config.containsKey(_Parameter.web) || config[_Parameter.web] as bool) {
216 | if (Directory('web').existsSync()) {
217 | _createWebSplash(
218 | imagePath: imageWeb ?? image,
219 | darkImagePath: darkImageWeb ?? darkImage,
220 | backgroundImage: backgroundImageWeb ?? backgroundImage,
221 | darkBackgroundImage: darkBackgroundImageWeb ?? darkBackgroundImage,
222 | brandingImagePath: brandingImageWeb ?? brandingImage,
223 | brandingDarkImagePath: brandingDarkImageWeb ?? brandingDarkImage,
224 | color: colorWeb ?? color,
225 | darkColor: darkColorWeb ?? darkColor,
226 | imageMode: webImageMode,
227 | brandingMode: brandingGravity,
228 | );
229 | } else {
230 | print('Web folder not found, skipping web splash update...');
231 | }
232 | }
233 |
234 | const String greet = '''
235 |
236 | ✅ Native splash complete.
237 | Now go finish building something awesome! 💪 You rock! 🤘🤩
238 | Like the package? Please give it a 👍 here: https://pub.dev/packages/flutter_native_splash
239 | ''';
240 |
241 | const String whatsNew = '';
242 | print(whatsNew + greet);
243 | }
244 |
245 | /// Remove any splash screen by setting the default white splash
246 | void removeSplash({
247 | required String? path,
248 | required String? flavor,
249 | }) {
250 | print("Restoring Flutter's default native splash screen...");
251 | final config = getConfig(configFile: path, flavor: flavor);
252 |
253 | final removeConfig = {
254 | _Parameter.color: '#ffffff',
255 | _Parameter.darkColor: '#000000'
256 | };
257 |
258 | if (config.containsKey(_Parameter.android)) {
259 | removeConfig[_Parameter.android] = config[_Parameter.android];
260 | }
261 |
262 | if (config.containsKey(_Parameter.ios)) {
263 | removeConfig[_Parameter.ios] = config[_Parameter.ios];
264 | }
265 |
266 | if (config.containsKey(_Parameter.web)) {
267 | removeConfig[_Parameter.web] = config[_Parameter.web];
268 | }
269 |
270 | /// Checks if the image that was specified in the config file does exist.
271 | /// If not the developer will receive an error message and the process will exit.
272 | if (config.containsKey(_Parameter.plistFiles)) {
273 | removeConfig[_Parameter.plistFiles] = config[_Parameter.plistFiles];
274 | }
275 | _createSplashByConfig(removeConfig);
276 | }
277 |
278 | String? _checkImageExists({
279 | required Map config,
280 | required String parameter,
281 | }) {
282 | final String? image = config[parameter]?.toString();
283 | if (image != null) {
284 | if (image.isNotEmpty && !File(image).existsSync()) {
285 | print(
286 | 'The file "$image" set as the parameter "$parameter" was not found.',
287 | );
288 | exit(1);
289 | }
290 |
291 | // https://github.com/brendan-duncan/image#supported-image-formats
292 | final List supportedFormats = [
293 | "png", "apng", // PNG
294 | "jpg", "jpeg", "jpe", "jfif", // JPEG
295 | "tga", "tpic", // TGA
296 | "gif", // GIF
297 | "ico", // ICO
298 | "bmp", "dib", // BMP
299 | ];
300 |
301 | if (!supportedFormats
302 | .any((format) => p.extension(image).toLowerCase() == ".$format")) {
303 | print(
304 | 'Unsupported file format: $image Your image must be in one of the following formats: $supportedFormats',
305 | );
306 | exit(1);
307 | }
308 | }
309 |
310 | return image == '' ? null : image;
311 | }
312 |
313 | void createBackgroundImage({
314 | required String imageDestination,
315 | required String imageSource,
316 | }) {
317 | // Copy will not work if the directory does not exist, so createSync
318 | // will ensure that the directory exists.
319 | File(imageDestination).createSync(recursive: true);
320 |
321 | // If source image is not already png, convert it, otherwise just copy it.
322 | if (p.extension(imageSource).toLowerCase() != '.png') {
323 | final image = decodeImage(File(imageSource).readAsBytesSync());
324 | if (image == null) {
325 | print('$imageSource could not be read');
326 | exit(1);
327 | }
328 | File(imageDestination)
329 | ..createSync(recursive: true)
330 | ..writeAsBytesSync(encodePng(image));
331 | } else {
332 | File(imageSource).copySync(imageDestination);
333 | }
334 | }
335 |
336 | /// Get config from `pubspec.yaml` or `flutter_native_splash.yaml`
337 | Map getConfig({
338 | required String? configFile,
339 | required String? flavor,
340 | }) {
341 | // It is important that the flavor setup occurs as soon as possible.
342 | // So before we generate anything, we need to setup the flavor (even if it's the default one).
343 | _flavorHelper = _FlavorHelper(flavor);
344 | // if `flutter_native_splash.yaml` exists use it as config file, otherwise use `pubspec.yaml`
345 | String filePath;
346 | if (configFile != null) {
347 | if (File(configFile).existsSync()) {
348 | filePath = configFile;
349 | } else {
350 | print('The config file `$configFile` was not found.');
351 | exit(1);
352 | }
353 | } else if (_flavorHelper.flavor != null) {
354 | filePath = 'flutter_native_splash-${_flavorHelper.flavor}.yaml';
355 | } else if (File('flutter_native_splash.yaml').existsSync()) {
356 | filePath = 'flutter_native_splash.yaml';
357 | } else {
358 | filePath = 'pubspec.yaml';
359 | }
360 |
361 | final Map yamlMap;
362 | try {
363 | yamlMap = loadYaml(File(filePath).readAsStringSync()) as Map;
364 | } catch (e) {
365 | throw Exception('Your `$filePath` appears to be empty or malformed.');
366 | }
367 |
368 | if (yamlMap['flutter_native_splash'] is! Map) {
369 | throw Exception(
370 | 'Your `$filePath` file does not contain a '
371 | '`flutter_native_splash` section.',
372 | );
373 | }
374 |
375 | // yamlMap has the type YamlMap, which has several unwanted side effects
376 | return _yamlToMap(yamlMap['flutter_native_splash'] as YamlMap);
377 | }
378 |
379 | Map _yamlToMap(YamlMap yamlMap) {
380 | final Map map = {};
381 | for (final MapEntry entry in yamlMap.entries) {
382 | if (!_Parameter.all.contains(entry.key)) {
383 | AnsiPen pen = AnsiPen()..red(bold: true);
384 | print(pen("⚠️ The parameter \"${entry.key}\" was found "
385 | "in your flutter_native_splash config, but \"${entry.key}\" "
386 | "is not a valid flutter_native_splash parameter."));
387 | exit(1);
388 | }
389 | if (entry.value is YamlList) {
390 | final list = [];
391 | for (final value in entry.value as YamlList) {
392 | if (value is String) {
393 | list.add(value);
394 | }
395 | }
396 | map[entry.key as String] = list;
397 | } else if (entry.value is YamlMap) {
398 | map[entry.key as String] = _yamlToMap(entry.value as YamlMap);
399 | } else {
400 | map[entry.key as String] = entry.value;
401 | }
402 | }
403 | return map;
404 | }
405 |
406 | @visibleForTesting
407 | String? parseColor(dynamic color) {
408 | dynamic colorValue = color;
409 | if (colorValue is int) colorValue = colorValue.toString().padLeft(6, '0');
410 |
411 | if (colorValue is String) {
412 | colorValue = colorValue.replaceAll('#', '').replaceAll(' ', '');
413 | if (colorValue.length == 6) return colorValue;
414 | }
415 | if (colorValue == null) return null;
416 |
417 | throw Exception('Invalid color value');
418 | }
419 |
420 | class _Parameter {
421 | static const android = 'android';
422 | static const android12Section = 'android_12';
423 | static const androidScreenOrientation = 'android_screen_orientation';
424 | static const backgroundImage = 'background_image';
425 | static const backgroundImageAndroid = 'background_image_android';
426 | static const backgroundImageIos = 'background_image_ios';
427 | static const backgroundImageWeb = 'background_image_web';
428 | static const brandingDarkImage = 'branding_dark';
429 | static const brandingDarkImageAndroid = 'branding_dark_android';
430 | static const brandingDarkImageIos = 'branding_dark_ios';
431 | static const brandingDarkImageWeb = 'branding_dark_web';
432 | static const brandingGravity = 'branding_mode';
433 | static const brandingImage = 'branding';
434 | static const brandingBottomPadding = 'branding_bottom_padding';
435 | static const brandingImageAndroid = 'branding_android';
436 | static const brandingBottomPaddingAndroid = 'branding_bottom_padding_android';
437 | static const brandingImageIos = 'branding_ios';
438 | static const brandingBottomPaddingIos = 'branding_bottom_padding_ios';
439 | static const brandingImageWeb = 'branding_web';
440 | static const color = 'color';
441 | static const colorAndroid = "color_android";
442 | static const colorIos = "color_ios";
443 | static const colorWeb = "color_web";
444 | static const darkBackgroundImage = 'background_image_dark';
445 | static const darkBackgroundImageAndroid = 'background_image_dark_android';
446 | static const darkBackgroundImageIos = 'background_image_dark_ios';
447 | static const darkBackgroundImageWeb = 'background_image_dark_web';
448 | static const darkColor = 'color_dark';
449 | static const darkColorAndroid = "color_dark_android";
450 | static const darkColorIos = "color_dark_ios";
451 | static const darkColorWeb = "color_dark_web";
452 | static const darkImage = 'image_dark';
453 | static const darkImageAndroid = 'image_dark_android';
454 | static const darkImageIos = 'image_dark_ios';
455 | static const darkImageWeb = 'image_dark_web';
456 | static const fullscreen = 'fullscreen';
457 | static const gravity = 'android_gravity';
458 | static const iconBackgroundColor = 'icon_background_color';
459 | static const iconBackgroundColorDark = 'icon_background_color_dark';
460 | static const image = 'image';
461 | static const imageAndroid = 'image_android';
462 | static const imageIos = 'image_ios';
463 | static const imageWeb = 'image_web';
464 | static const ios = 'ios';
465 | static const iosContentMode = 'ios_content_mode';
466 | static const plistFiles = 'info_plist_files';
467 | static const web = 'web';
468 | static const webImageMode = 'web_image_mode';
469 |
470 | static List all = [
471 | android,
472 | android12Section,
473 | androidScreenOrientation,
474 | backgroundImage,
475 | backgroundImageAndroid,
476 | backgroundImageIos,
477 | backgroundImageWeb,
478 | brandingDarkImage,
479 | brandingDarkImageAndroid,
480 | brandingDarkImageIos,
481 | brandingDarkImageWeb,
482 | brandingGravity,
483 | brandingImage,
484 | brandingBottomPadding,
485 | brandingImageAndroid,
486 | brandingImageIos,
487 | brandingBottomPaddingIos,
488 | brandingBottomPaddingAndroid,
489 | brandingImageWeb,
490 | color,
491 | colorAndroid,
492 | colorIos,
493 | colorWeb,
494 | darkBackgroundImage,
495 | darkBackgroundImageAndroid,
496 | darkBackgroundImageIos,
497 | darkBackgroundImageWeb,
498 | darkColor,
499 | darkColorAndroid,
500 | darkColorIos,
501 | darkColorWeb,
502 | darkImage,
503 | darkImageAndroid,
504 | darkImageIos,
505 | darkImageWeb,
506 | fullscreen,
507 | gravity,
508 | iconBackgroundColor,
509 | iconBackgroundColorDark,
510 | image,
511 | imageAndroid,
512 | imageIos,
513 | imageWeb,
514 | ios,
515 | iosContentMode,
516 | plistFiles,
517 | web,
518 | webImageMode,
519 | ];
520 | }
521 |
--------------------------------------------------------------------------------
/lib/constants.dart:
--------------------------------------------------------------------------------
1 | part of 'cli_commands.dart';
2 |
3 | // Web-related constants
4 | const String _webFolder = 'web/';
5 | const String _webSplashFolder = '${_webFolder}splash/';
6 | const String _webSplashImagesFolder = '${_webSplashFolder}img/';
7 | const String _webIndex = '${_webFolder}index.html';
8 |
--------------------------------------------------------------------------------
/lib/enums.dart:
--------------------------------------------------------------------------------
1 | enum ArgEnums {
2 | help(name: 'help', abbr: 'h'),
3 | path(name: 'path', abbr: 'p'),
4 | flavor(name: 'flavor', abbr: 'f'),
5 | flavors(name: 'flavors', abbr: 'F'),
6 | allFlavors(name: 'all-flavors', abbr: 'A');
7 |
8 | final String name;
9 | final String abbr;
10 |
11 | const ArgEnums({required this.name, required this.abbr});
12 | }
13 |
--------------------------------------------------------------------------------
/lib/flavor_helper.dart:
--------------------------------------------------------------------------------
1 | part of 'cli_commands.dart';
2 |
3 | class _FlavorHelper {
4 | _FlavorHelper(this._flavor) {
5 | if (_flavor != null) {
6 | _androidResFolder = 'android/app/src/$_flavor/res/';
7 | _iOSFlavorName = _flavor!.capitalize();
8 | } else {
9 | _androidResFolder = 'android/app/src/main/res/';
10 | _iOSFlavorName = '';
11 | }
12 | }
13 |
14 | // Android related path values
15 | final String? _flavor;
16 | late String _androidResFolder;
17 |
18 | String? get flavor {
19 | return _flavor;
20 | }
21 |
22 | String get androidResFolder {
23 | return _androidResFolder;
24 | }
25 |
26 | String get androidDrawableFolder {
27 | return '${_androidResFolder}drawable/';
28 | }
29 |
30 | String get androidNightDrawableFolder {
31 | return '${_androidResFolder}drawable-night/';
32 | }
33 |
34 | String get androidLaunchBackgroundFile {
35 | return '${androidDrawableFolder}launch_background.xml';
36 | }
37 |
38 | String get androidLaunchDarkBackgroundFile {
39 | return '${androidNightDrawableFolder}launch_background.xml';
40 | }
41 |
42 | String get androidStylesFile {
43 | return '${_androidResFolder}values/styles.xml';
44 | }
45 |
46 | String get androidNightStylesFile {
47 | return '${_androidResFolder}values-night/styles.xml';
48 | }
49 |
50 | String get androidV31StylesFile {
51 | return '${_androidResFolder}values-v31/styles.xml';
52 | }
53 |
54 | String get androidV31StylesNightFile {
55 | return '${_androidResFolder}values-night-v31/styles.xml';
56 | }
57 |
58 | String get androidV21DrawableFolder {
59 | return '${_androidResFolder}drawable-v21/';
60 | }
61 |
62 | String get androidV21LaunchBackgroundFile {
63 | return '${androidV21DrawableFolder}launch_background.xml';
64 | }
65 |
66 | String get androidNightV21DrawableFolder {
67 | return '${_androidResFolder}drawable-night-v21/';
68 | }
69 |
70 | String get androidV21LaunchDarkBackgroundFile {
71 | return '${androidNightV21DrawableFolder}launch_background.xml';
72 | }
73 |
74 | String get androidManifestFile {
75 | return 'android/app/src/main/AndroidManifest.xml';
76 | }
77 |
78 | // iOS related values
79 | late String? _iOSFlavorName;
80 |
81 | String? get iOSFlavorName {
82 | return _iOSFlavorName;
83 | }
84 |
85 | String get iOSAssetsLaunchImageFolder {
86 | return 'ios/Runner/Assets.xcassets/LaunchImage$_iOSFlavorName.imageset/';
87 | }
88 |
89 | String get iOSAssetsBrandingImageFolder {
90 | return 'ios/Runner/Assets.xcassets/BrandingImage$_iOSFlavorName.imageset/';
91 | }
92 |
93 | String get iOSLaunchScreenStoryboardFile {
94 | return 'ios/Runner/Base.lproj/$iOSLaunchScreenStoryboardName.storyboard';
95 | }
96 |
97 | String get iOSLaunchScreenStoryboardName {
98 | return 'LaunchScreen$_iOSFlavorName';
99 | }
100 |
101 | String get iOSInfoPlistFile {
102 | return 'ios/Runner/Info.plist';
103 | }
104 |
105 | String get iOSAssetsLaunchImageBackgroundFolder {
106 | return 'ios/Runner/Assets.xcassets/LaunchBackground$_iOSFlavorName.imageset/';
107 | }
108 |
109 | String get iOSLaunchScreenStoryBoardContent {
110 | return _iOSLaunchScreenStoryboardContent.replaceAll(
111 | '[LAUNCH_IMAGE_PLACEHOLDER]',
112 | iOSLaunchImageName,
113 | );
114 | }
115 |
116 | String get iOSLaunchImageName {
117 | if (_iOSFlavorName == null) {
118 | return 'LaunchImage';
119 | } else {
120 | return 'LaunchImage$_iOSFlavorName';
121 | }
122 | }
123 |
124 | String get iOSBrandingImageName {
125 | if (_iOSFlavorName == null) {
126 | return 'BrandingImage';
127 | } else {
128 | return 'BrandingImage$_iOSFlavorName';
129 | }
130 | }
131 |
132 | String get iOSBrandingSubView {
133 | return _iOSBrandingSubview.replaceAll(
134 | '[BRANDING_IMAGE_PLACEHOLDER]',
135 | iOSBrandingImageName,
136 | );
137 | }
138 |
139 | String get iOSLaunchBackgroundName {
140 | if (_iOSFlavorName == null) {
141 | return 'LaunchBackground';
142 | } else {
143 | return 'LaunchBackground$_iOSFlavorName';
144 | }
145 | }
146 |
147 | String get iOSLaunchBackgroundSubView {
148 | return _iOSLaunchBackgroundSubview.replaceAll(
149 | '[LAUNCH_BACKGROUND_PLACEHOLDER]',
150 | iOSLaunchBackgroundName,
151 | );
152 | }
153 | }
154 |
155 | extension _StringExtension on String {
156 | String capitalize() {
157 | return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/lib/flutter_native_splash.dart:
--------------------------------------------------------------------------------
1 | /// When your app is opened, there is a brief time while the native app loads
2 | /// Flutter. By default, during this time, the native app displays a white
3 | /// splash screen. This package automatically generates iOS, Android, and
4 | /// Web-native code for customizing this native splash screen background color
5 | /// and splash image. Supports dark mode, full screen, and platform-specific
6 | /// options.
7 | library;
8 |
9 | import 'package:flutter/foundation.dart';
10 | import 'package:flutter/scheduler.dart';
11 | import 'package:flutter/services.dart';
12 | import 'package:flutter/widgets.dart';
13 |
14 | class FlutterNativeSplash {
15 | static const MethodChannel _channel = MethodChannel('flutter_native_splash');
16 |
17 | @Deprecated('This class is deprecated use [remove]')
18 | static void removeAfter(
19 | Future Function(BuildContext) initializeFunction,
20 | ) {
21 | final binding = WidgetsFlutterBinding.ensureInitialized();
22 |
23 | // Prevents app from closing splash screen, app layout will be build but not displayed.
24 | binding.deferFirstFrame();
25 | binding.addPostFrameCallback((_) async {
26 | final BuildContext? context = binding.renderViewElement;
27 | if (context != null) {
28 | // Run any sync or awaited async function you want to wait for before showing app layout
29 | await initializeFunction.call(context);
30 | }
31 |
32 | // Closes splash screen, and show the app layout.
33 | binding.allowFirstFrame();
34 | if (kIsWeb) {
35 | remove();
36 | }
37 | });
38 | }
39 |
40 | static WidgetsBinding? _widgetsBinding;
41 |
42 | // Prevents app from closing splash screen, app layout will be build but not displayed.
43 | static void preserve({required WidgetsBinding widgetsBinding}) {
44 | _widgetsBinding = widgetsBinding;
45 | _widgetsBinding?.deferFirstFrame();
46 | }
47 |
48 | static void remove() {
49 | _widgetsBinding?.allowFirstFrame();
50 | _widgetsBinding = null;
51 | if (kIsWeb) {
52 | // Use SchedulerBinding to avoid white flash on splash removal.
53 | SchedulerBinding.instance.addPostFrameCallback((_) {
54 | try {
55 | _channel.invokeMethod('remove');
56 | } catch (e) {
57 | throw Exception(
58 | '$e\nDid you forget to run "dart run flutter_native_splash:create"?',
59 | );
60 | }
61 | });
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/flutter_native_splash_web.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_native_splash/remove_splash_from_web.dart';
5 | import 'package:flutter_web_plugins/flutter_web_plugins.dart';
6 | // In order to *not* need this ignore, consider extracting the "web" version
7 | // of your plugin as a separate package, instead of inlining it in the same
8 | // package as the core of your plugin.
9 | // ignore: avoid_web_libraries_in_flutter
10 |
11 | /// A web implementation of the FlutterNativeSplash plugin.
12 | class FlutterNativeSplashWeb {
13 | static void registerWith(Registrar registrar) {
14 | final MethodChannel channel = MethodChannel(
15 | 'flutter_native_splash',
16 | const StandardMethodCodec(),
17 | registrar,
18 | );
19 |
20 | final pluginInstance = FlutterNativeSplashWeb();
21 | channel.setMethodCallHandler(pluginInstance.handleMethodCall);
22 | }
23 |
24 | Future handleMethodCall(MethodCall call) async {
25 | switch (call.method) {
26 | case 'remove':
27 | try {
28 | removeSplashFromWeb();
29 | } catch (e) {
30 | throw Exception(
31 | 'Did you forget to run "dart run flutter_native_splash:create"? \n Could not run the JS command removeSplashFromWeb()',
32 | );
33 | }
34 | return;
35 | default:
36 | throw PlatformException(
37 | code: 'Unimplemented',
38 | details:
39 | "flutter_native_splash for web doesn't implement '${call.method}'",
40 | );
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/helper_utils.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_native_splash/enums.dart';
2 |
3 | class HelperUtils {
4 | const HelperUtils._();
5 |
6 | /// Checks if a given filename matches the flutter native splash flavor config pattern
7 | /// The pattern is: flutter_native_splash-*.yaml where * is the flavor name
8 | ///
9 | /// Returns true if the filename matches the pattern, false otherwise
10 | static bool isValidFlavorConfigFileName(String fileName) {
11 | return RegExp(r'^flutter_native_splash-[^-]+\.yaml$').hasMatch(fileName);
12 | }
13 |
14 | /// Extracts the flavor name from a valid flavor config filename
15 | ///
16 | /// Throws an exception if the filename is not a valid flavor config filename
17 | static String getFlavorNameFromFileName(String fileName) {
18 | final flavorMatch =
19 | RegExp(r'^flutter_native_splash-(.+)\.yaml$').firstMatch(fileName);
20 |
21 | final flavorName = flavorMatch?.group(1);
22 |
23 | if (flavorName == null) {
24 | throw Exception('Invalid flavor config filename: $fileName');
25 | }
26 |
27 | return flavorName;
28 | }
29 |
30 | /// Validate the flavor arguments
31 | ///
32 | /// Throws an exception if the arguments are invalid.
33 | static void validateFlavorArgs({
34 | required String? flavorArg,
35 | required String? flavorsArg,
36 | required bool? allFlavorsArg,
37 | }) {
38 | if ((flavorArg != null && flavorsArg != null) ||
39 | (flavorArg != null && allFlavorsArg == true) ||
40 | (flavorsArg != null && allFlavorsArg == true)) {
41 | throw Exception(
42 | 'Cannot use multiple flavor options together. Please use only one of: --${ArgEnums.flavor.name}, --${ArgEnums.flavors.name}, or --${ArgEnums.allFlavors.name}.',
43 | );
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/ios.dart:
--------------------------------------------------------------------------------
1 | part of 'cli_commands.dart';
2 |
3 | // Image template
4 | class _IosLaunchImageTemplate {
5 | final String fileName;
6 | final double pixelDensity;
7 |
8 | _IosLaunchImageTemplate({required this.fileName, required this.pixelDensity});
9 | }
10 |
11 | final List<_IosLaunchImageTemplate> _iOSSplashImages =
12 | <_IosLaunchImageTemplate>[
13 | _IosLaunchImageTemplate(fileName: 'LaunchImage.png', pixelDensity: 1),
14 | _IosLaunchImageTemplate(fileName: 'LaunchImage@2x.png', pixelDensity: 2),
15 | _IosLaunchImageTemplate(
16 | fileName: 'LaunchImage@3x.png',
17 | pixelDensity: 3,
18 | ), // original image must be @4x
19 | ];
20 |
21 | final List<_IosLaunchImageTemplate> _iOSSplashImagesDark =
22 | <_IosLaunchImageTemplate>[
23 | _IosLaunchImageTemplate(fileName: 'LaunchImageDark.png', pixelDensity: 1),
24 | _IosLaunchImageTemplate(fileName: 'LaunchImageDark@2x.png', pixelDensity: 2),
25 | _IosLaunchImageTemplate(fileName: 'LaunchImageDark@3x.png', pixelDensity: 3),
26 | // original image must be @3x
27 | ];
28 |
29 | //Resource files for branding assets
30 | final List<_IosLaunchImageTemplate> _iOSBrandingImages =
31 | <_IosLaunchImageTemplate>[
32 | _IosLaunchImageTemplate(fileName: 'BrandingImage.png', pixelDensity: 1),
33 | _IosLaunchImageTemplate(fileName: 'BrandingImage@2x.png', pixelDensity: 2),
34 | _IosLaunchImageTemplate(
35 | fileName: 'BrandingImage@3x.png',
36 | pixelDensity: 3,
37 | ), // original image must be @4x
38 | ];
39 | final List<_IosLaunchImageTemplate> _iOSBrandingImagesDark =
40 | <_IosLaunchImageTemplate>[
41 | _IosLaunchImageTemplate(fileName: 'BrandingImageDark.png', pixelDensity: 1),
42 | _IosLaunchImageTemplate(
43 | fileName: 'BrandingImageDark@2x.png',
44 | pixelDensity: 2,
45 | ),
46 | _IosLaunchImageTemplate(
47 | fileName: 'BrandingImageDark@3x.png',
48 | pixelDensity: 3,
49 | ),
50 | // original image must be @3x
51 | ];
52 |
53 | /// Create iOS splash screen
54 | void _createiOSSplash({
55 | required String? imagePath,
56 | required String? darkImagePath,
57 | String? brandingImagePath,
58 | String? brandingBottomPadding,
59 | String? brandingDarkImagePath,
60 | required String? color,
61 | required String? darkColor,
62 | List? plistFiles,
63 | required String iosContentMode,
64 | String? iosBrandingContentMode,
65 | required bool fullscreen,
66 | required String? backgroundImage,
67 | required String? darkBackgroundImage,
68 | }) {
69 | if (imagePath != null) {
70 | _applyImageiOS(imagePath: imagePath, list: _iOSSplashImages);
71 | } else {
72 | final splashImage = Image(width: 1, height: 1);
73 | for (final template in _iOSSplashImages) {
74 | final file =
75 | File(_flavorHelper.iOSAssetsLaunchImageFolder + template.fileName);
76 | file.createSync(recursive: true);
77 | file.writeAsBytesSync(encodePng(splashImage));
78 | }
79 | }
80 |
81 | if (darkImagePath != null) {
82 | _applyImageiOS(
83 | imagePath: darkImagePath,
84 | dark: true,
85 | list: _iOSSplashImagesDark,
86 | );
87 | } else {
88 | for (final template in _iOSSplashImagesDark) {
89 | final file =
90 | File(_flavorHelper.iOSAssetsLaunchImageFolder + template.fileName);
91 | if (file.existsSync()) file.deleteSync();
92 | }
93 | }
94 |
95 | if (brandingImagePath != null) {
96 | _applyImageiOS(
97 | imagePath: brandingImagePath,
98 | list: _iOSBrandingImages,
99 | targetPath: _flavorHelper.iOSAssetsBrandingImageFolder,
100 | );
101 | } else {
102 | if (Directory(_flavorHelper.iOSAssetsBrandingImageFolder).existsSync()) {
103 | Directory(_flavorHelper.iOSAssetsBrandingImageFolder)
104 | .delete(recursive: true);
105 | }
106 | }
107 | if (brandingDarkImagePath != null) {
108 | _applyImageiOS(
109 | imagePath: brandingDarkImagePath,
110 | dark: true,
111 | list: _iOSBrandingImagesDark,
112 | targetPath: _flavorHelper.iOSAssetsBrandingImageFolder,
113 | );
114 | } else {
115 | for (final template in _iOSBrandingImagesDark) {
116 | final file =
117 | File(_flavorHelper.iOSAssetsBrandingImageFolder + template.fileName);
118 | if (file.existsSync()) file.deleteSync();
119 | }
120 | }
121 |
122 | final launchImageFile =
123 | File('${_flavorHelper.iOSAssetsLaunchImageFolder}Contents.json');
124 | launchImageFile.createSync(recursive: true);
125 | launchImageFile.writeAsStringSync(
126 | darkImagePath != null ? _iOSContentsJsonDark : _iOSContentsJson,
127 | );
128 |
129 | if (brandingImagePath != null) {
130 | final brandingImageFile =
131 | File('${_flavorHelper.iOSAssetsBrandingImageFolder}Contents.json');
132 | brandingImageFile.createSync(recursive: true);
133 | brandingImageFile.writeAsStringSync(
134 | brandingDarkImagePath != null
135 | ? _iOSBrandingContentsJsonDark
136 | : _iOSBrandingContentsJson,
137 | );
138 | }
139 |
140 | _createLaunchScreenStoryboard(
141 | imagePath: imagePath,
142 | brandingImagePath: brandingImagePath,
143 | iosContentMode: iosContentMode,
144 | iosBrandingContentMode: iosBrandingContentMode,
145 | brandingBottomPadding: brandingBottomPadding,
146 | );
147 | _createBackground(
148 | colorString: color,
149 | darkColorString: darkColor,
150 | darkBackgroundImageSource: darkBackgroundImage,
151 | backgroundImageSource: backgroundImage,
152 | darkBackgroundImageDestination:
153 | '${_flavorHelper.iOSAssetsLaunchImageBackgroundFolder}darkbackground.png',
154 | backgroundImageDestination:
155 | '${_flavorHelper.iOSAssetsLaunchImageBackgroundFolder}background.png',
156 | );
157 |
158 | final backgroundImageFile = File(
159 | '${_flavorHelper.iOSAssetsLaunchImageBackgroundFolder}Contents.json',
160 | );
161 | backgroundImageFile.createSync(recursive: true);
162 |
163 | backgroundImageFile.writeAsStringSync(
164 | (darkColor != null || darkBackgroundImage != null)
165 | ? _iOSLaunchBackgroundDarkJson
166 | : _iOSLaunchBackgroundJson,
167 | );
168 |
169 | _applyInfoPList(plistFiles: plistFiles, fullscreen: fullscreen);
170 | }
171 |
172 | /// Create splash screen images for original size, @2x and @3x
173 | void _applyImageiOS({
174 | required String imagePath,
175 | bool dark = false,
176 | required List<_IosLaunchImageTemplate> list,
177 | String? targetPath,
178 | }) async {
179 | // Because the path is no longer static, targetPath can't have a default value.
180 | // That's why this was added, as a setup for a default value.
181 | targetPath ??= _flavorHelper.iOSAssetsLaunchImageFolder;
182 |
183 | // ignore_for_file: avoid_print
184 | print('[iOS] Creating ${dark ? 'dark mode ' : ''} images');
185 |
186 | final image = decodeImage(File(imagePath).readAsBytesSync());
187 | if (image == null) {
188 | print('$imagePath could not be loaded.');
189 | exit(1);
190 | }
191 |
192 | await Future.wait(
193 | list.map(
194 | (template) => Isolate.run(() async {
195 | final newFile = copyResize(
196 | image,
197 | width: image.width * template.pixelDensity ~/ 4,
198 | height: image.height * template.pixelDensity ~/ 4,
199 | interpolation: Interpolation.average,
200 | );
201 |
202 | final file = File(targetPath! + template.fileName);
203 | await file.create(recursive: true);
204 | await file.writeAsBytes(encodePng(newFile));
205 | }),
206 | ),
207 | );
208 | }
209 |
210 | /// Updates LaunchScreen.storyboard adding splash image path
211 | void _updateLaunchScreenStoryboard({
212 | required String? imagePath,
213 | required String iosContentMode,
214 | String? brandingImagePath,
215 | String? brandingBottomPadding,
216 | String? iosBrandingContentMode,
217 | }) {
218 | String? iosBrandingContentModeValue = iosBrandingContentMode;
219 | // Load the data
220 | final file = File(_flavorHelper.iOSLaunchScreenStoryboardFile);
221 | final xmlDocument = XmlDocument.parse(file.readAsStringSync());
222 | final documentData = xmlDocument.getElement('document');
223 |
224 | // Find the view that contains the splash image
225 | final view =
226 | documentData?.descendants.whereType().firstWhere((element) {
227 | return element.name.qualified == 'view' &&
228 | element.getAttribute('id') == 'Ze5-6b-2t3';
229 | });
230 | if (view == null) {
231 | print(
232 | 'Default Flutter view Ze5-6b-2t3 not found. '
233 | 'Did you modify your default ${_flavorHelper.iOSLaunchScreenStoryboardName}.storyboard file?',
234 | );
235 | exit(1);
236 | }
237 |
238 | // Find the splash imageView
239 | final subViews = view.getElement('subviews');
240 | if (subViews == null) {
241 | print(
242 | 'Not able to find "subviews" in ${_flavorHelper.iOSLaunchScreenStoryboardName}.storyboard. Image for '
243 | 'splash screen not updated. Did you modify your default '
244 | '${_flavorHelper.iOSLaunchScreenStoryboardName}.storyboard file?',
245 | );
246 | exit(1);
247 | }
248 | final imageView = subViews.children.whereType().firstWhere(
249 | (element) =>
250 | element.name.qualified == 'imageView' &&
251 | element.getAttribute('image') == _flavorHelper.iOSLaunchImageName,
252 | orElse: () {
253 | print(
254 | 'Not able to find "${_flavorHelper.iOSLaunchImageName}" in ${_flavorHelper.iOSLaunchScreenStoryboardName}.storyboard. Image '
255 | 'for splash screen not updated. Did you modify your default '
256 | '${_flavorHelper.iOSLaunchScreenStoryboardName}.storyboard file? [1]',
257 | );
258 | exit(1);
259 | },
260 | );
261 | subViews.children.whereType().firstWhere(
262 | (element) =>
263 | element.name.qualified == 'imageView' &&
264 | element.getAttribute('image') == _flavorHelper.iOSLaunchBackgroundName,
265 | orElse: () {
266 | subViews.children.insert(
267 | 0,
268 | XmlDocument.parse(_flavorHelper.iOSLaunchBackgroundSubView)
269 | .rootElement
270 | .copy(),
271 | );
272 | return XmlElement(XmlName(''));
273 | },
274 | );
275 | // Update the fill property
276 | imageView.setAttribute('contentMode', iosContentMode);
277 |
278 | if (!['bottom', 'bottomRight', 'bottomLeft']
279 | .contains(iosBrandingContentModeValue)) {
280 | iosBrandingContentModeValue = 'bottom';
281 | }
282 | if (brandingImagePath != null &&
283 | iosBrandingContentModeValue != iosContentMode) {
284 | final brandingImageView =
285 | subViews.children.whereType().firstWhere(
286 | (element) {
287 | return element.name.qualified == 'imageView' &&
288 | element.getAttribute('image') == _flavorHelper.iOSBrandingImageName;
289 | },
290 | orElse: () {
291 | subViews.children.insert(
292 | subViews.children.length - 1,
293 | XmlDocument.parse(_flavorHelper.iOSBrandingSubView)
294 | .rootElement
295 | .copy(),
296 | );
297 | return XmlElement(XmlName(''));
298 | },
299 | );
300 |
301 | brandingImageView.setAttribute('contentMode', iosBrandingContentMode);
302 | }
303 | // Find the resources
304 | final resources = documentData?.getElement('resources');
305 | final launchImageResource =
306 | resources?.children.whereType().firstWhere(
307 | (element) =>
308 | element.name.qualified == 'image' &&
309 | element.getAttribute('name') == _flavorHelper.iOSLaunchImageName,
310 | orElse: () {
311 | print(
312 | 'Not able to find "${_flavorHelper.iOSLaunchImageName}" in ${_flavorHelper.iOSLaunchScreenStoryboardName}.storyboard. Image '
313 | 'for splash screen not updated. Did you modify your default '
314 | '${_flavorHelper.iOSLaunchScreenStoryboardName}.storyboard file? [2]',
315 | );
316 | exit(1);
317 | },
318 | );
319 |
320 | resources?.children.whereType().firstWhere(
321 | (element) =>
322 | element.name.qualified == 'image' &&
323 | element.getAttribute('name') == _flavorHelper.iOSLaunchBackgroundName,
324 | orElse: () {
325 | // If the color has not been set via background image, set it here:
326 |
327 | resources.children.add(
328 | XmlDocument.parse(
329 | '',
330 | ).rootElement.copy(),
331 | );
332 | return XmlElement(XmlName(''));
333 | },
334 | );
335 |
336 | view.children.remove(view.getElement('constraints'));
337 | view.children.add(
338 | XmlDocument.parse(_iOSLaunchBackgroundConstraints).rootElement.copy(),
339 | );
340 |
341 | if (imagePath != null) {
342 | final image = decodeImage(File(imagePath).readAsBytesSync());
343 | if (image == null) {
344 | print('$imagePath could not be loaded.');
345 | exit(1);
346 | }
347 | launchImageResource?.setAttribute('width', image.width.toString());
348 | launchImageResource?.setAttribute('height', image.height.toString());
349 | }
350 |
351 | if (brandingImagePath != null) {
352 | final brandingImageResource =
353 | resources?.children.whereType().firstWhere(
354 | (element) =>
355 | element.name.qualified == 'image' &&
356 | element.getAttribute('name') == _flavorHelper.iOSBrandingImageName,
357 | orElse: () {
358 | resources.children.add(
359 | XmlDocument.parse(
360 | '',
361 | ).rootElement.copy(),
362 | );
363 | return XmlElement(XmlName(''));
364 | },
365 | );
366 |
367 | final branding = decodeImage(File(brandingImagePath).readAsBytesSync());
368 | if (branding == null) {
369 | print('$brandingImagePath could not be loaded.');
370 | exit(1);
371 | }
372 | brandingImageResource?.setAttribute('width', branding.width.toString());
373 | brandingImageResource?.setAttribute('height', branding.height.toString());
374 |
375 | var toParse = _iOSBrandingCenterBottomConstraints;
376 | if (iosBrandingContentModeValue == 'bottomLeft') {
377 | toParse = _iOSBrandingLeftBottomConstraints;
378 | } else if (iosBrandingContentModeValue == 'bottomRight') {
379 | toParse = _iOSBrandingRightBottomConstraints;
380 | }
381 | final element = view.getElement('constraints');
382 |
383 | final toParseBottomPadding =
384 | toParse.replaceAll("{bottom_padding}", brandingBottomPadding ?? "0");
385 | print("[iOS] branding bottom padding: ${brandingBottomPadding ?? "0"}");
386 | final doc = XmlDocument.parse(toParseBottomPadding).rootElement.copy();
387 | if (doc.firstChild != null) {
388 | print('[iOS] updating constraints with splash branding');
389 | for (final v in doc.children) {
390 | element?.children.insert(0, v.copy());
391 | }
392 | }
393 | }
394 |
395 | file.writeAsStringSync(
396 | '${xmlDocument.toXmlString(pretty: true, indent: ' ')}\n',
397 | );
398 | }
399 |
400 | /// Creates LaunchScreen.storyboard with splash image path
401 | void _createLaunchScreenStoryboard({
402 | required String? imagePath,
403 | required String iosContentMode,
404 | required String? iosBrandingContentMode,
405 | required String? brandingImagePath,
406 | required String? brandingBottomPadding,
407 | }) {
408 | final file = File(_flavorHelper.iOSLaunchScreenStoryboardFile);
409 | file.createSync(recursive: true);
410 | file.writeAsStringSync(_flavorHelper.iOSLaunchScreenStoryBoardContent);
411 |
412 | return _updateLaunchScreenStoryboard(
413 | imagePath: imagePath,
414 | brandingImagePath: brandingImagePath,
415 | brandingBottomPadding: brandingBottomPadding,
416 | iosContentMode: iosContentMode,
417 | iosBrandingContentMode: iosBrandingContentMode,
418 | );
419 | }
420 |
421 | void _createBackground({
422 | required String? colorString,
423 | required String? darkColorString,
424 | required String? backgroundImageSource,
425 | required String? darkBackgroundImageSource,
426 | required String backgroundImageDestination,
427 | required String darkBackgroundImageDestination,
428 | }) {
429 | if (colorString != null) {
430 | final background = Image(width: 1, height: 1);
431 | final redChannel = int.parse(colorString.substring(0, 2), radix: 16);
432 | final greenChannel = int.parse(colorString.substring(2, 4), radix: 16);
433 | final blueChannel = int.parse(colorString.substring(4, 6), radix: 16);
434 | background.clear(
435 | ColorRgb8(redChannel, greenChannel, blueChannel),
436 | );
437 | final file = File(backgroundImageDestination);
438 | file.createSync(recursive: true);
439 | file.writeAsBytesSync(encodePng(background));
440 | } else if (backgroundImageSource != null) {
441 | createBackgroundImage(
442 | imageDestination: backgroundImageDestination,
443 | imageSource: backgroundImageSource,
444 | );
445 | } else {
446 | throw Exception('No color string or background image!');
447 | }
448 |
449 | if (darkColorString != null) {
450 | final background = Image(height: 1, width: 1);
451 | final redChannel = int.parse(darkColorString.substring(0, 2), radix: 16);
452 | final greenChannel = int.parse(darkColorString.substring(2, 4), radix: 16);
453 | final blueChannel = int.parse(darkColorString.substring(4, 6), radix: 16);
454 | background.clear(ColorRgb8(redChannel, greenChannel, blueChannel));
455 | final file = File(darkBackgroundImageDestination);
456 | file.createSync(recursive: true);
457 | file.writeAsBytesSync(encodePng(background));
458 | } else if (darkBackgroundImageSource != null) {
459 | createBackgroundImage(
460 | imageDestination: darkBackgroundImageDestination,
461 | imageSource: darkBackgroundImageSource,
462 | );
463 | } else {
464 | final file = File(darkBackgroundImageDestination);
465 | if (file.existsSync()) file.deleteSync();
466 | }
467 | }
468 |
469 | /// Update Info.plist for status bar behaviour (hidden/visible)
470 | void _applyInfoPList({List? plistFiles, required bool fullscreen}) {
471 | List? plistFilesValue = plistFiles;
472 | if (plistFilesValue == null) {
473 | plistFilesValue = [];
474 | plistFilesValue.add(_flavorHelper.iOSInfoPlistFile);
475 | }
476 |
477 | for (final plistFile in plistFilesValue) {
478 | if (!File(plistFile).existsSync()) {
479 | print(
480 | 'File $plistFile not found. If you renamed the file, make sure to'
481 | ' specify it in the info_plist_files section of your '
482 | 'flutter_native_splash configuration.',
483 | );
484 | exit(1);
485 | }
486 |
487 | print('[iOS] Updating $plistFile for status bar hidden/visible');
488 | _updateInfoPlistFile(plistFile: plistFile, fullscreen: fullscreen);
489 | }
490 | }
491 |
492 | /// Update Infop.list with status bar hidden directive
493 | void _updateInfoPlistFile({
494 | required String plistFile,
495 | required bool fullscreen,
496 | }) {
497 | // Load the data
498 | final file = File(plistFile);
499 | final xmlDocument = XmlDocument.parse(file.readAsStringSync());
500 | final dict = xmlDocument.getElement('plist')?.getElement('dict');
501 | if (dict == null) {
502 | throw Exception('$plistFile plist dict element not found');
503 | }
504 |
505 | var elementFound = true;
506 | final uIStatusBarHidden = dict.children.whereType().firstWhere(
507 | (element) {
508 | return element.innerText == 'UIStatusBarHidden';
509 | },
510 | orElse: () {
511 | final builder = XmlBuilder();
512 | builder.element(
513 | 'key',
514 | nest: () {
515 | builder.text('UIStatusBarHidden');
516 | },
517 | );
518 | dict.children.add(builder.buildFragment());
519 | dict.children.add(XmlElement(XmlName(fullscreen.toString())));
520 | elementFound = false;
521 | return XmlElement(XmlName(''));
522 | },
523 | );
524 |
525 | if (elementFound) {
526 | final index = dict.children.indexOf(uIStatusBarHidden);
527 | final uIStatusBarHiddenValue = dict.children[index + 1].following
528 | .firstWhere((element) => element.nodeType == XmlNodeType.ELEMENT);
529 | uIStatusBarHiddenValue.replace(XmlElement(XmlName(fullscreen.toString())));
530 | }
531 |
532 | elementFound = true;
533 | if (fullscreen) {
534 | final uIViewControllerBasedStatusBarAppearance =
535 | dict.children.whereType().firstWhere(
536 | (element) {
537 | return element.innerText == 'UIViewControllerBasedStatusBarAppearance';
538 | },
539 | orElse: () {
540 | final builder = XmlBuilder();
541 | builder.element(
542 | 'key',
543 | nest: () {
544 | builder.text('UIViewControllerBasedStatusBarAppearance');
545 | },
546 | );
547 | dict.children.add(builder.buildFragment());
548 | dict.children.add(XmlElement(XmlName((!fullscreen).toString())));
549 | elementFound = false;
550 | return XmlElement(XmlName(''));
551 | },
552 | );
553 |
554 | if (elementFound) {
555 | final index =
556 | dict.children.indexOf(uIViewControllerBasedStatusBarAppearance);
557 |
558 | final uIViewControllerBasedStatusBarAppearanceValue = dict
559 | .children[index + 1].following
560 | .firstWhere((element) => element.nodeType == XmlNodeType.ELEMENT);
561 | uIViewControllerBasedStatusBarAppearanceValue
562 | .replace(XmlElement(XmlName('false')));
563 | }
564 | }
565 |
566 | file.writeAsStringSync(
567 | '${xmlDocument.toXmlString(pretty: true, indent: ' ')}\n',
568 | );
569 | }
570 |
--------------------------------------------------------------------------------
/lib/remove_splash_from_web.dart:
--------------------------------------------------------------------------------
1 | @JS()
2 | library;
3 |
4 | import 'dart:js_interop';
5 |
6 | @JS("removeSplashFromWeb")
7 | external void removeSplashFromWeb();
8 |
--------------------------------------------------------------------------------
/lib/templates.dart:
--------------------------------------------------------------------------------
1 | part of 'cli_commands.dart';
2 |
3 | // Android-related templates
4 |
5 | const String _androidLaunchItemXml = '''
6 | -
7 |
8 |
9 | ''';
10 |
11 | const String _androidBrandingItemXml = '''
12 | -
13 |
14 |
15 | ''';
16 |
17 | const String _androidLaunchBackgroundXml = '''
18 |
19 |
20 | -
21 |
22 |
23 |
24 | ''';
25 |
26 | const String _androidStylesXml = '''
27 |
28 |
29 |
30 |
35 |
41 |
44 |
45 | ''';
46 |
47 | const String _androidStylesNightXml = '''
48 |
49 |
50 |
51 |
57 |
63 |
66 |
67 | ''';
68 |
69 | const String _androidV31StylesXml = '''
70 |
71 |
72 |
73 |
76 |
82 |
85 |
86 | ''';
87 |
88 | const String _androidV31StylesNightXml = '''
89 |
90 |
91 |
92 |
95 |
101 |
104 |
105 | ''';
106 |
107 | // iOS-related templates
108 | const String _iOSLaunchScreenStoryboardContent = '''
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | ''';
147 |
148 | const String _iOSContentsJson = '''
149 | {
150 | "images" : [
151 | {
152 | "filename" : "LaunchImage.png",
153 | "idiom" : "universal",
154 | "scale" : "1x"
155 | },
156 | {
157 | "filename" : "LaunchImage@2x.png",
158 | "idiom" : "universal",
159 | "scale" : "2x"
160 | },
161 | {
162 | "filename" : "LaunchImage@3x.png",
163 | "idiom" : "universal",
164 | "scale" : "3x"
165 | }
166 | ],
167 | "info" : {
168 | "author" : "xcode",
169 | "version" : 1
170 | }
171 | }
172 | ''';
173 |
174 | const String _iOSContentsJsonDark = '''
175 | {
176 | "images" : [
177 | {
178 | "filename" : "LaunchImage.png",
179 | "idiom" : "universal",
180 | "scale" : "1x"
181 | },
182 | {
183 | "appearances" : [
184 | {
185 | "appearance" : "luminosity",
186 | "value" : "dark"
187 | }
188 | ],
189 | "filename" : "LaunchImageDark.png",
190 | "idiom" : "universal",
191 | "scale" : "1x"
192 | },
193 | {
194 | "filename" : "LaunchImage@2x.png",
195 | "idiom" : "universal",
196 | "scale" : "2x"
197 | },
198 | {
199 | "appearances" : [
200 | {
201 | "appearance" : "luminosity",
202 | "value" : "dark"
203 | }
204 | ],
205 | "filename" : "LaunchImageDark@2x.png",
206 | "idiom" : "universal",
207 | "scale" : "2x"
208 | },
209 | {
210 | "filename" : "LaunchImage@3x.png",
211 | "idiom" : "universal",
212 | "scale" : "3x"
213 | },
214 | {
215 | "appearances" : [
216 | {
217 | "appearance" : "luminosity",
218 | "value" : "dark"
219 | }
220 | ],
221 | "filename" : "LaunchImageDark@3x.png",
222 | "idiom" : "universal",
223 | "scale" : "3x"
224 | }
225 | ],
226 | "info" : {
227 | "author" : "xcode",
228 | "version" : 1
229 | }
230 | }
231 | ''';
232 |
233 | const String _iOSBrandingContentsJson = '''
234 | {
235 | "images" : [
236 | {
237 | "filename" : "BrandingImage.png",
238 | "idiom" : "universal",
239 | "scale" : "1x"
240 | },
241 | {
242 | "filename" : "BrandingImage@2x.png",
243 | "idiom" : "universal",
244 | "scale" : "2x"
245 | },
246 | {
247 | "filename" : "BrandingImage@3x.png",
248 | "idiom" : "universal",
249 | "scale" : "3x"
250 | }
251 | ],
252 | "info" : {
253 | "author" : "xcode",
254 | "version" : 1
255 | }
256 | }
257 | ''';
258 |
259 | const String _iOSBrandingContentsJsonDark = '''
260 | {
261 | "images" : [
262 | {
263 | "filename" : "BrandingImage.png",
264 | "idiom" : "universal",
265 | "scale" : "1x"
266 | },
267 | {
268 | "appearances" : [
269 | {
270 | "appearance" : "luminosity",
271 | "value" : "dark"
272 | }
273 | ],
274 | "filename" : "BrandingImageDark.png",
275 | "idiom" : "universal",
276 | "scale" : "1x"
277 | },
278 | {
279 | "filename" : "BrandingImage@2x.png",
280 | "idiom" : "universal",
281 | "scale" : "2x"
282 | },
283 | {
284 | "appearances" : [
285 | {
286 | "appearance" : "luminosity",
287 | "value" : "dark"
288 | }
289 | ],
290 | "filename" : "BrandingImageDark@2x.png",
291 | "idiom" : "universal",
292 | "scale" : "2x"
293 | },
294 | {
295 | "filename" : "BrandingImage@3x.png",
296 | "idiom" : "universal",
297 | "scale" : "3x"
298 | },
299 | {
300 | "appearances" : [
301 | {
302 | "appearance" : "luminosity",
303 | "value" : "dark"
304 | }
305 | ],
306 | "filename" : "BrandingImageDark@3x.png",
307 | "idiom" : "universal",
308 | "scale" : "3x"
309 | }
310 | ],
311 | "info" : {
312 | "author" : "xcode",
313 | "version" : 1
314 | }
315 | }
316 | ''';
317 |
318 | const String _iOSLaunchBackgroundJson = '''
319 | {
320 | "images" : [
321 | {
322 | "filename" : "background.png",
323 | "idiom" : "universal",
324 | "scale" : "1x"
325 | },
326 | {
327 | "idiom" : "universal",
328 | "scale" : "2x"
329 | },
330 | {
331 | "idiom" : "universal",
332 | "scale" : "3x"
333 | }
334 | ],
335 | "info" : {
336 | "author" : "xcode",
337 | "version" : 1
338 | }
339 | }
340 | ''';
341 |
342 | const String _iOSLaunchBackgroundDarkJson = '''
343 | {
344 | "images" : [
345 | {
346 | "filename" : "background.png",
347 | "idiom" : "universal"
348 | },
349 | {
350 | "appearances" : [
351 | {
352 | "appearance" : "luminosity",
353 | "value" : "dark"
354 | }
355 | ],
356 | "filename" : "darkbackground.png",
357 | "idiom" : "universal"
358 | }
359 | ],
360 | "info" : {
361 | "author" : "xcode",
362 | "version" : 1
363 | }
364 | }
365 | ''';
366 |
367 | const String _iOSLaunchBackgroundSubview =
368 | '';
369 |
370 | const String _iOSBrandingSubview =
371 | '';
372 |
373 | const String _iOSLaunchBackgroundConstraints = '''
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 | ''';
385 |
386 | const String _iOSBrandingCenterBottomConstraints = '''
387 |
388 |
389 |
390 |
391 | ''';
392 |
393 | const String _iOSBrandingLeftBottomConstraints = '''
394 |
395 |
396 |
397 |
398 | ''';
399 |
400 | const String _iOSBrandingRightBottomConstraints = '''
401 |
402 |
403 |
404 |
405 | ''';
406 |
407 | /// Web related templates
408 | const String _webCss = '''
409 | \n';
300 |
301 | // Add css as an inline style in head tag
302 | final webIndex = File(_webIndex);
303 | final document = html_parser.parse(webIndex.readAsStringSync());
304 |
305 | // Update splash css style tag
306 | document.head
307 | ?..querySelector('style#splash-screen-style')?.remove()
308 | ..append(
309 | html_parser.parseFragment(cssContent, container: ''),
310 | );
311 |
312 | // Write the updated index.html
313 | webIndex.writeAsStringSync(document.outerHtml);
314 | }
315 |
316 | void _createSplashJs() {
317 | // Add js as an inline script in head tag
318 | final webIndex = File(_webIndex);
319 | final document = html_parser.parse(webIndex.readAsStringSync());
320 |
321 | // Update splash js script tag
322 | document.head
323 | ?..querySelector('script#splash-screen-script')?.remove()
324 | ..append(
325 | html_parser.parseFragment(_webJS, container: ''),
326 | );
327 |
328 | // Write the updated index.html
329 | webIndex.writeAsStringSync(document.outerHtml);
330 | }
331 |
332 | void _updateHtml({
333 | required String imageMode,
334 | required String? imagePath,
335 | required String brandingMode,
336 | required String? brandingImagePath,
337 | }) {
338 | print('[Web] Updating index.html');
339 | final webIndex = File(_webIndex);
340 | final document = html_parser.parse(webIndex.readAsStringSync());
341 |
342 | // Remove previously used style sheet (migrating to inline style)
343 | document
344 | .querySelector(
345 | 'link[rel="stylesheet"][type="text/css"][href="splash/style.css"]',
346 | )
347 | ?.remove();
348 |
349 | // Add meta viewport if it doesn't exist
350 | document.querySelector(
351 | 'meta[content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"][name="viewport"]',
352 | ) ??
353 | document.head?.append(
354 | html_parser.parseFragment(
355 | ' \n',
356 | container: '',
357 | ),
358 | );
359 |
360 | // Remove previously used src script tag (migrating to inline script)
361 | document
362 | .querySelector(
363 | 'script[src="splash/splash.js"]',
364 | )
365 | ?.remove();
366 |
367 | // Update splash image
368 | document.querySelector('picture#splash')?.remove();
369 | document.querySelector('div#splash')?.remove();
370 | if (imagePath != null) {
371 | document.body?.insertBefore(
372 | html_parser.parseFragment(
373 | '\n${_indexHtmlPicture.replaceAll(
374 | '[IMAGEMODE]',
375 | imageMode,
376 | ).replaceAll(
377 | '[IMAGEEXTENSION]',
378 | imagePath.endsWith('.gif') ? 'gif' : 'png',
379 | )}',
380 | container: '',
381 | ),
382 | document.body?.firstChild,
383 | );
384 | }
385 |
386 | // Update branding image
387 | document.querySelector('picture#splash-branding')?.remove();
388 | if (brandingImagePath != null) {
389 | document.body?.insertBefore(
390 | html_parser.parseFragment(
391 | '\n${_indexHtmlBrandingPicture.replaceAll(
392 | '[BRANDINGMODE]',
393 | brandingMode,
394 | ).replaceAll(
395 | '[BRANDINGEXTENSION]',
396 | brandingImagePath.endsWith('.gif') ? 'gif' : 'png',
397 | )}',
398 | container: '',
399 | ),
400 | document.body?.firstChild,
401 | );
402 | }
403 |
404 | // Write the updated index.html
405 | webIndex.writeAsStringSync(document.outerHtml);
406 | }
407 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_native_splash
2 | description: Customize Flutter's default white native splash screen with
3 | background color and splash image. Supports dark mode, full screen, and more.
4 | version: 2.4.7
5 | repository: https://github.com/jonbhanson/flutter_native_splash
6 | issue_tracker: https://github.com/jonbhanson/flutter_native_splash/issues
7 |
8 | environment:
9 | sdk: '>=3.0.0 <4.0.0'
10 | flutter: ">=2.5.0"
11 |
12 | dependencies:
13 | args: ^2.7.0
14 | flutter:
15 | sdk: flutter
16 | flutter_web_plugins:
17 | sdk: flutter
18 | html: ^0.15.6
19 | image: ^4.5.4
20 | meta: ^1.16.0
21 | path: ^1.9.1
22 | universal_io: ^2.2.2
23 | xml: ^6.6.1
24 | yaml: ^3.1.3
25 | ansicolor: ^2.0.3
26 |
27 | dev_dependencies:
28 | flutter_test:
29 | sdk: flutter
30 | flutter_lints: ^6.0.0
31 |
32 | screenshots:
33 | - description: 'Examples of the splash screen on iOS.'
34 | path: splash_demo.webp
35 | - description: 'Examples of the splash screen on iOS in dark mode.'
36 | path: splash_demo_dark.webp
37 |
38 | flutter:
39 | # This section identifies this Flutter project as a plugin project.
40 | # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
41 | # which should be registered in the plugin registry. This is required for
42 | # using method channels.
43 | # The Android 'package' specifies package in which the registered class is.
44 | # This is required for using method channels on Android.
45 | # The 'ffiPlugin' specifies that native code should be built and bundled.
46 | # This is required for using `dart:ffi`.
47 | # All these are used by the tooling to maintain consistency when
48 | # adding or updating assets for this project.
49 | plugin:
50 | platforms:
51 | android:
52 | package: net.jonhanson.flutter_native_splash
53 | pluginClass: FlutterNativeSplashPlugin
54 | ios:
55 | pluginClass: FlutterNativeSplashPlugin
56 | web:
57 | pluginClass: FlutterNativeSplashWeb
58 | fileName: flutter_native_splash_web.dart
59 |
--------------------------------------------------------------------------------
/splash_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/splash_demo.gif
--------------------------------------------------------------------------------
/splash_demo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/splash_demo.webp
--------------------------------------------------------------------------------
/splash_demo_dark.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/splash_demo_dark.gif
--------------------------------------------------------------------------------
/splash_demo_dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonbhanson/flutter_native_splash/b4a310ce8de6162ebf8c4b27b4917893c89c2f73/splash_demo_dark.webp
--------------------------------------------------------------------------------
/test/flutter_native_splash_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter_native_splash/cli_commands.dart';
4 | import 'package:flutter_test/flutter_test.dart';
5 | import 'package:path/path.dart' as p;
6 |
7 | void main() {
8 | test('parseColor parses values correctly', () {
9 | expect(parseColor('#ffffff'), 'ffffff');
10 | expect(parseColor(' FAFAFA '), 'FAFAFA');
11 | expect(parseColor('121212'), '121212');
12 | expect(parseColor(null), null);
13 | expect(() => parseColor('badcolor'), throwsException);
14 | });
15 |
16 | group('config file from args', () {
17 | final testDir =
18 | p.join('.dart_tool', 'flutter_native_splash', 'test', 'config_file');
19 |
20 | void setCurrentDirectory(String path) {
21 | final pathValue = p.join(testDir, path);
22 | Directory(pathValue).createSync(recursive: true);
23 | Directory.current = pathValue;
24 | }
25 |
26 | test('default', () {
27 | setCurrentDirectory('default');
28 | File('flutter_native_splash.yaml').writeAsStringSync(
29 | '''
30 | flutter_native_splash:
31 | color: "#00ff00"
32 | ''',
33 | );
34 | final Map config = getConfig(
35 | configFile: 'flutter_native_splash.yaml',
36 | flavor: null,
37 | );
38 | File('flutter_native_splash.yaml').deleteSync();
39 | expect(config, isNotNull);
40 | expect(config['color'], '#00ff00');
41 | });
42 | test('default_use_pubspec', () {
43 | setCurrentDirectory('pubspec_only');
44 | File('pubspec.yaml').writeAsStringSync(
45 | '''
46 | flutter_native_splash:
47 | color: "#00ff00"
48 | ''',
49 | );
50 | final Map config = getConfig(
51 | configFile: null,
52 | flavor: null,
53 | );
54 | File('pubspec.yaml').deleteSync();
55 | expect(config, isNotNull);
56 | expect(config['color'], '#00ff00');
57 |
58 | // fails if config file is missing
59 | expect(() => getConfig(configFile: null, flavor: null), throwsException);
60 | });
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/test/helper_utils_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_native_splash/helper_utils.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | void main() {
5 | group('HelperUtils.isValidFlavorConfigFileName', () {
6 | test('should return true for valid flavor config filenames', () {
7 | expect(
8 | HelperUtils.isValidFlavorConfigFileName(
9 | 'flutter_native_splash-dev.yaml'),
10 | isTrue,
11 | );
12 | expect(
13 | HelperUtils.isValidFlavorConfigFileName(
14 | 'flutter_native_splash-prod.yaml'),
15 | isTrue,
16 | );
17 | expect(
18 | HelperUtils.isValidFlavorConfigFileName(
19 | 'flutter_native_splash-staging.yaml'),
20 | isTrue,
21 | );
22 | });
23 |
24 | test('should return false for invalid flavor config filenames', () {
25 | expect(
26 | HelperUtils.isValidFlavorConfigFileName('flutter_native_splash.yaml'),
27 | isFalse,
28 | );
29 | expect(
30 | HelperUtils.isValidFlavorConfigFileName('flutter_native_splash-.yaml'),
31 | isFalse,
32 | );
33 | expect(
34 | HelperUtils.isValidFlavorConfigFileName(
35 | 'flutter_native_splash-dev.yml'),
36 | isFalse,
37 | );
38 | expect(
39 | HelperUtils.isValidFlavorConfigFileName('other-config.yaml'),
40 | isFalse,
41 | );
42 | expect(
43 | HelperUtils.isValidFlavorConfigFileName('random.txt'),
44 | isFalse,
45 | );
46 | });
47 | });
48 |
49 | group('HelperUtils.getFlavorNameFromFileName', () {
50 | test('should return the flavor name from a valid flavor config filename',
51 | () {
52 | expect(
53 | HelperUtils.getFlavorNameFromFileName('flutter_native_splash-dev.yaml'),
54 | 'dev',
55 | );
56 | expect(
57 | HelperUtils.getFlavorNameFromFileName(
58 | 'flutter_native_splash-prod.yaml',
59 | ),
60 | 'prod',
61 | );
62 | expect(
63 | HelperUtils.getFlavorNameFromFileName(
64 | 'flutter_native_splash-staging-flavor.yaml',
65 | ),
66 | 'staging-flavor',
67 | );
68 | });
69 |
70 | test(
71 | 'should throw an exception if the filename is not a valid flavor config filename',
72 | () {
73 | expect(
74 | () => HelperUtils.getFlavorNameFromFileName(
75 | 'flutter_native_splash.yaml',
76 | ),
77 | throwsA(
78 | isA().having(
79 | (e) => e.toString(),
80 | 'message',
81 | contains('Invalid flavor config filename'),
82 | ),
83 | ),
84 | );
85 | });
86 | });
87 |
88 | group('Flavor arguments validation', () {
89 | test('throws when flavor and flavors are used together', () {
90 | expect(
91 | () => HelperUtils.validateFlavorArgs(
92 | flavorArg: 'dev',
93 | flavorsArg: 'dev,prod',
94 | allFlavorsArg: false,
95 | ),
96 | throwsA(
97 | isA().having(
98 | (e) => e.toString(),
99 | 'message',
100 | contains('Cannot use multiple flavor options together'),
101 | ),
102 | ),
103 | );
104 | });
105 |
106 | test('throws when flavor and allFlavors are used together', () {
107 | expect(
108 | () => HelperUtils.validateFlavorArgs(
109 | flavorArg: 'dev',
110 | flavorsArg: null,
111 | allFlavorsArg: true,
112 | ),
113 | throwsA(
114 | isA().having(
115 | (e) => e.toString(),
116 | 'message',
117 | contains('Cannot use multiple flavor options together'),
118 | ),
119 | ),
120 | );
121 | });
122 |
123 | test('throws when flavors and allFlavors are used together', () {
124 | expect(
125 | () => HelperUtils.validateFlavorArgs(
126 | flavorArg: null,
127 | flavorsArg: 'dev,prod',
128 | allFlavorsArg: true,
129 | ),
130 | throwsA(
131 | isA().having(
132 | (e) => e.toString(),
133 | 'message',
134 | contains('Cannot use multiple flavor options together'),
135 | ),
136 | ),
137 | );
138 | });
139 |
140 | test('does not throw with single flavor option', () {
141 | expect(
142 | () => HelperUtils.validateFlavorArgs(
143 | flavorArg: 'dev',
144 | flavorsArg: null,
145 | allFlavorsArg: false,
146 | ),
147 | returnsNormally,
148 | );
149 | });
150 | });
151 | }
152 |
--------------------------------------------------------------------------------