├── .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 | 7 | -------------------------------------------------------------------------------- /.run/remove.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | --------------------------------------------------------------------------------