feat: add new structure

This commit is contained in:
mike doornenbal 2024-10-23 15:25:34 +02:00
parent 1d7d9dd541
commit 99d0a49cc2
40 changed files with 772 additions and 681 deletions

View file

@ -1,6 +1,10 @@
## 4.2.1
## 5.0.0
- Updated README
- Added new userstory structure
- refactored package
* Updated flutter_introduction to 5.0.0
## 4.2.1
- Updated flutter_introduction to 5.0.0
## 4.1.0
- Updated README

136
README.md
View file

@ -4,147 +4,45 @@ Flutter_start is a package that allows you to jumpstart your application with a
## Setup
To use this package, add flutter_start as a dependency in your pubspec.yaml file:
To use flutter_start in your flutter app, import it in your pubspec.yaml:
```yaml
flutter_start:
``` yaml
flutter_chat:
git:
url: https://github.com/Iconica-Development/flutter_start
ref: 4.1.0
path: packages/flutter_start
ref: 5.0.0
```
## go_router
After importing flutter_start you can add the `FlutterStartNavigatorUserstory` widget like so:
Add `go_router` as dependency to your project.
Add the following configuration to your flutter_application:
```
List<GoRoute> getStartStoryRoutes() => getStartStoryRoutes();
```
Or with options:
Place the following code somewhere in your project where it can be accessed globally:
```
var startUserStoryConfiguration = const StartUserStoryConfiguration();
```
```
List<GoRoute> getStartStoryRoutes() => getStartStoryRoutes(
configuration: startUserStoryConfiguration,
);
```
Finally add the `getStartRoutes` to your `go_router` routes like so:
```
final GoRouter _router = GoRouter(
routes: <RouteBase>[
...getStartStoryRoutes()
],
);
```
The routes that can be used to navigate are:
For routing to the `splashScreen`:
```
static const String splashScreen = '/splashScreen';
```
For routing to the `introduction`:
```
static const String introduction = '/introduction';
```
For routing to the `home`:
```
static const String home = '/home';
```
If you don't want a SplashScreen in your application set your initialRoute to `Introduction`:
```
final GoRouter _router = GoRouter(
routes: <RouteBase>[
...getStartRoutes()
],
initialLocation: '/introduction',
);
```
## Navigator
Add the following code to the build-method of a chosen widget like so:
```
class Example extends StatelessWidget {
const Example({super.key});
``` dart
class FlutterStart extends StatelessWidget {
const FlutterStart({super.key});
@override
Widget build(BuildContext context) {
return NavigatorStartUserStory(
onComplete: (context) {},
return FlutterStartNavigatorUserstory(
startService: StartService(),
options: const FlutterStartOptions(),
afterIntroduction: (context) async {},
);
}
}
```
or with options:
startService: optional, Used for adding a killswitch implementation to the app. This is a function that will be called before exiting the splash screen. If the function returns false, it will continue to the introductions. otherwise it will go no further.
Place the following code somewhere in your project where it can be accessed globally:
```
var startUserStoryConfiguration = const StartUserStoryConfiguration();
```
```
class Example extends StatelessWidget {
const Example({super.key});
@override
Widget build(BuildContext context) {
return NavigatorStartUserStory(
configuration: startUserStoryConfiguration,
onComplete: (context) {},
);
}
}
```
The `StartUserStoryConfiguration` has its own parameters, as specified below:
| Parameter | Explanation |
|-----------|-------------|
| `splashScreenBuilder` | The builder to override the default splashScreen |
| `introductionBuilder` | A builder to wrap the introductions in your own page |
| `introductionOptionsBuilder` | The builder to override the default `introductionOptions` |
|`introductionService` | The service to override the default `introductionService` |
| `homeScreenRoute` | The route to navigate to after the introduction or splashScreen is completed |
| `introductionFallbackScreen` | The screen that is shown when something goes wrong fetching data for the introduction |
| `introductionScrollPhysics` | The scroll physics for the introduction |
| `showIntroduction` | A boolean to show the introduction or not. Defaults to true |
| `useKillswitch` | A boolean to use the killswitch or not. Defaults to false |
| `minimumSplashScreenDuration` | The minimum duration the splashScreen should be shown. Defaults to 3 seconds |
| `splashScreenFuture` | The future to be completed before the splashScreen is completed |
| `splashScreenCenterWidget` | The widget to be shown in the center of the splashScreen |
| `splashScreenBackgroundColor` | The color of the splashScreen background. Defaults to Color(0xff212121) |
| `canPopFromIntroduction` | Allow popping from introduction, defaults to true. Defaults to true |
| `killswitchService` | The service to override the default killswitch service |
| `showSplashScreen` | A boolean to show the splashScreen or not. Defaults to true |
options: optional, here you can tweek some settings of flutter_start to your liking.
afterIntroduction: required, after showing the introduction, this function will be called. Here you can navigate to the home screen or any other screen you want to show after the introduction. If you do not want introductions you can use the `afterSplashScreen` function in the `FlutterStartOptions` to navigate to the home screen.
## Issues
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_start) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
## Want to contribute
[text](about:blank#blocked)
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_start/pulls).
## Author
This flutter_start for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>
flutter_start for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>

View file

@ -1,101 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_start/flutter_start.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => const MaterialApp(
home: Home(),
);
}
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) => NavigatorStartUserStory(
// configuration: config,
onComplete: (context) async {
await Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const HomeEntry()),
);
},
);
}
StartUserStoryConfiguration config = StartUserStoryConfiguration(
// showIntroduction: false,
introductionService: MyIntroductionService(),
splashScreenBuilder: (context, onFinish) => SplashScreen(
onFinish: onFinish,
),
);
class MyIntroductionService implements IntroductionService {
@override
Future<void> onComplete() {
// TODO: implement onComplete
throw UnimplementedError();
}
@override
Future<void> onSkip() {
// TODO: implement onSkip
throw UnimplementedError();
}
@override
Future<bool> shouldShow() {
// TODO: implement shouldShow
throw UnimplementedError();
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({
required this.onFinish,
super.key,
});
final Function() onFinish;
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
Future.delayed(const Duration(seconds: 3), () {
widget.onFinish();
});
super.initState();
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('SplashScreen'),
),
body: const Center(child: Text('SplashScreen')),
);
}
class HomeEntry extends StatelessWidget {
const HomeEntry({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('HomeEntry'),
),
body: const Center(child: Text('HomeEntry')),
);
}

View file

@ -1,53 +0,0 @@
name: example
description: "Flutter_start example app"
publish_to: "none"
environment:
sdk: ">=3.2.5 <4.0.0"
dependencies:
flutter:
sdk: flutter
go_router: any
flutter_start:
path: ../flutter_start/
flutter_introduction_shared_preferences:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_shared_preferences
ref: 5.0.0
# hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub/
# version: ^5.0.0
dependency_overrides:
flutter_data_interface:
git:
url: https://github.com/Iconica-Development/flutter_data_interface.git
ref: 1.0.1
flutter_introduction_interface:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_interface
ref: 5.0.0
flutter_introduction_service:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_service
ref: 5.0.0
flutter_introduction_widget:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_widget
ref: 5.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0
flutter:
uses-material-design: true

View file

@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/

View file

@ -0,0 +1 @@
../../CHANGELOG.md

View file

@ -0,0 +1 @@
../../CONTRIBUTING.md

View file

@ -0,0 +1 @@
../../LICENSE

View file

@ -0,0 +1 @@
../../README.md

View file

@ -1,4 +1,4 @@
include: package:flutter_iconica_analysis/analysis_options.yaml
include: package:flutter_iconica_analysis/components_options.yaml
# Possible to overwrite the rules from the package

View file

@ -0,0 +1,4 @@
///
library firebase_start_repository;
export "src/firebase_killswitch_repository.dart";

View file

@ -0,0 +1,34 @@
import "dart:convert";
import "package:http/http.dart" as http;
import "package:package_info_plus/package_info_plus.dart";
import "package:start_repository_interface/start_repository_interface.dart";
/// The Firebase killswitch repository.
class FirebaseKillswitchRepository implements KillswitchRepositoryInterface {
@override
Future<bool> isKillswitchActive() async {
var packageInfo = await PackageInfo.fromPlatform();
var appName = packageInfo.appName;
http.Response response;
response = await http
.get(
Uri.parse("https://active-obelugnnza-uc.a.run.app/?appName=$appName"),
)
.timeout(
const Duration(seconds: 5),
onTimeout: () => http.Response("false", 500),
)
.onError(
(error, stackTrace) => http.Response("false", 500),
);
var decoded = jsonDecode(response.body);
if (decoded == true) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,23 @@
name: firebase_start_repository
description: "A new Flutter package project."
version: 0.0.1
homepage: none
publish_to: none
environment:
sdk: ^3.5.3
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
http: ^1.2.2
package_info_plus: ^8.1.0
start_repository_interface:
path: ../start_repository_interface
dev_dependencies:
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0

View file

@ -0,0 +1 @@
../../CONTRIBUTING.md

View file

@ -1,4 +1,9 @@
include: package:flutter_lints/flutter.yaml
include: package:flutter_iconica_analysis/components_options.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
# Possible to overwrite the rules from the package
analyzer:
exclude:
linter:
rules:

View file

@ -0,0 +1,16 @@
# example
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,54 @@
import 'package:example/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_start/flutter_start.dart';
void main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: theme,
home: const FlutterStart(),
);
}
}
class FlutterStart extends StatelessWidget {
const FlutterStart({super.key});
@override
Widget build(BuildContext context) {
return FlutterStartNavigatorUserstory(
startService: StartService(),
options: const FlutterStartOptions(),
afterIntroduction: (context) async {
await Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const AfterIntroduction(),
),
);
},
);
}
}
class AfterIntroduction extends StatelessWidget {
const AfterIntroduction({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("After Introduction"),
),
body: const Center(
child: Text("After Introduction"),
),
);
}
}

View file

@ -0,0 +1,154 @@
import 'package:flutter/material.dart';
const Color primaryColor = Color(0xFF71C6D1);
ThemeData theme = ThemeData(
actionIconTheme: ActionIconThemeData(
drawerButtonIconBuilder: (context) {
return const Icon(
Icons.menu,
color: Colors.white,
size: 32,
);
},
backButtonIconBuilder: (context) {
return const Icon(Icons.arrow_back_ios_new_rounded);
},
),
scaffoldBackgroundColor: const Color(0xFFFAF9F6),
primaryColor: primaryColor,
checkboxTheme: CheckboxThemeData(
side: const BorderSide(
color: Color(0xFF8D8D8D),
width: 1,
),
fillColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return primaryColor;
}
return const Color(0xFFEEEEEE);
},
),
),
switchTheme: SwitchThemeData(
trackColor:
WidgetStateProperty.resolveWith<Color>((Set<WidgetState> states) {
if (!states.contains(WidgetState.selected)) {
return const Color(0xFF8D8D8D);
}
return primaryColor;
}),
thumbColor: const WidgetStatePropertyAll(
Colors.white,
),
),
appBarTheme: const AppBarTheme(
centerTitle: true,
iconTheme: IconThemeData(
color: Colors.white,
size: 16,
),
elevation: 0,
backgroundColor: Color(0xFF212121),
titleTextStyle: TextStyle(
fontWeight: FontWeight.w900,
fontSize: 24,
color: primaryColor,
fontFamily: "Merriweather",
),
actionsIconTheme: IconThemeData()),
fontFamily: "Merriweather",
useMaterial3: false,
textTheme: const TextTheme(
headlineSmall: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 16,
color: Colors.white,
),
headlineLarge: TextStyle(
fontWeight: FontWeight.w900,
fontSize: 24,
color: primaryColor,
),
displayLarge: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w800,
fontSize: 20,
color: Colors.white,
),
displayMedium: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w800,
fontSize: 18,
color: Color(0xFF71C6D1),
),
displaySmall: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w800,
fontSize: 14,
color: Colors.black,
),
// TITLE
titleSmall: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w800,
fontSize: 14,
color: Colors.white,
),
titleMedium: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w800,
fontSize: 16,
color: Colors.black,
),
titleLarge: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w800,
fontSize: 20,
color: Colors.black,
),
// LABEL
labelSmall: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: 12,
color: Color(0xFF8D8D8D),
),
// BODY
bodySmall: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: 14,
color: Colors.black,
),
bodyMedium: TextStyle(
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: 16,
color: Colors.black,
),
),
radioTheme: RadioThemeData(
visualDensity: const VisualDensity(
horizontal: 0,
vertical: -2,
),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
fillColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return Colors.black;
}
return Colors.black;
},
),
),
colorScheme: const ColorScheme.light(
primary: primaryColor,
),
);

View file

@ -0,0 +1,24 @@
name: example
description: "A new Flutter project."
publish_to: "none"
version: 1.0.0+1
environment:
sdk: ^3.5.3
dependencies:
flutter:
sdk: flutter
flutter_start:
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
flutter:
uses-material-design: true

View file

@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

View file

@ -1,4 +1,16 @@
export 'package:flutter_introduction/flutter_introduction.dart';
export 'package:flutter_introduction_shared_preferences/flutter_introduction_shared_preferences.dart';
export 'src/models/start_configuration.dart';
export 'src/user_stories/flutter_start_userstory_navigator.dart';
// ignore_for_file: directives_ordering
/// Userstory
library;
export "package:start_repository_interface/start_repository_interface.dart";
export "package:flutter_introduction/flutter_introduction.dart";
export "src/flutter_start_navigator_userstory.dart";
/// models
export "src/models/flutter_start_options.dart";
/// Screens
export "src/screens/splash_screen.dart";
export "src/screens/introductions_screen.dart";

View file

@ -0,0 +1,52 @@
import "package:flutter/material.dart";
import "package:flutter_introduction_shared_preferences/flutter_introduction_shared_preferences.dart";
import "package:flutter_start/flutter_start.dart";
/// A userstory that navigates to the start of the Flutter app.
class FlutterStartNavigatorUserstory extends StatelessWidget {
/// FlutterStartNavigatorUserstory constructor.
FlutterStartNavigatorUserstory({
required this.afterIntroduction,
this.options = const FlutterStartOptions(),
StartService? startService,
super.key,
}) : startService = startService ?? StartService();
/// The start service to start the Flutter app.
final StartService startService;
/// The options to configure the start of the Flutter app.
final FlutterStartOptions options;
/// The function that is executed after the introduction screen.
final Function(BuildContext context) afterIntroduction;
@override
Widget build(BuildContext context) =>
options.useSplashScreen ? splashScreen() : introductionScreen(context);
/// The splash screen of the Flutter app.
Widget splashScreen() => SplashScreen(
options: options,
startService: startService,
afterSplashScreen: (ctx) async {
options.afterSplashScreen?.call(ctx) ??
await Navigator.of(ctx).pushReplacement(
MaterialPageRoute(
builder: introductionScreen,
),
);
},
);
/// The introduction screen of the Flutter app.
Widget introductionScreen(BuildContext context) => IntroductionsScreen(
options: options,
introductionOptions: options.introductionOptions,
afterIntroduction: () async => afterIntroduction.call(context),
introductionService: options.introductionService ??
IntroductionService(SharedPreferencesIntroductionDataProvider()),
introductionScrollPhysics: options.introductionScrollPhysics,
introDuctionFallBackScreen: options.introDuctionFallBackScreen,
);
}

View file

@ -0,0 +1,66 @@
import "package:flutter/material.dart";
import "package:flutter_introduction/flutter_introduction.dart";
/// Options to configure the start of the Flutter app.
class FlutterStartOptions {
/// FlutterStartOptions constructor.
const FlutterStartOptions({
this.splashScreenBackgroundColor = const Color(0xff212121),
this.introductionOptions = const IntroductionOptions(),
this.minimumSplashScreenDuration = 3,
this.canPopFromIntroduction = true,
this.introDuctionFallBackScreen,
this.iskillswitchEnabled = true,
this.introductionScrollPhysics,
this.splashScreenCenterWidget,
this.useSplashScreen = true,
this.splashScreenBuilder,
this.introductionService,
this.splashScreenFuture,
this.afterSplashScreen,
this.afterIntroduction,
});
/// The background color of the splash screen.
final Color splashScreenBackgroundColor;
/// The center widget of the splash screen.
final Widget? splashScreenCenterWidget;
/// If the splash screen should be used.
final bool useSplashScreen;
/// The minimum duration of the splash screen.
final double minimumSplashScreenDuration;
/// The future that is executed when the splash screen is shown.
final Future<void> Function(BuildContext context)? splashScreenFuture;
/// The builder of the splash screen.
final Widget Function(BuildContext context)? splashScreenBuilder;
/// The function that is executed after the introduction screen.
final IntroductionOptions introductionOptions;
/// The introduction options to configure the introduction screen.
final IntroductionService? introductionService;
/// The scroll physics of the introduction screen.
final ScrollPhysics? introductionScrollPhysics;
/// The fallback screen of the introduction screen.
final Widget? introDuctionFallBackScreen;
/// The function that is executed after the splash screen.
final Function(BuildContext context)? afterSplashScreen;
/// The function that is executed after the introduction screen.
final Function(BuildContext context)? afterIntroduction;
/// Whether or not the Introduction can be popped.
final bool canPopFromIntroduction;
/// Whether or not the killswitch is enabled.
final bool iskillswitchEnabled;
}

View file

@ -1,74 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_introduction/flutter_introduction.dart';
import '../services/killswitch_service.dart';
/// An immutable class that represents the configuration for
/// starting a user story.
@immutable
class StartUserStoryConfiguration {
/// Creates a new instance of [StartUserStoryConfiguration].
const StartUserStoryConfiguration({
this.splashScreenBuilder,
this.introductionBuilder,
this.introductionOptionsBuilder,
this.introductionService,
this.homeScreenRoute,
this.introductionFallbackScreen,
this.introductionScrollPhysics,
this.showIntroduction = true,
this.useKillswitch = false,
this.minimumSplashScreenDuration = 3,
this.splashScreenFuture,
this.splashScreenCenterWidget,
this.splashScreenBackgroundColor = const Color(0xff212121),
this.canPopFromIntroduction = true,
this.killswitchService,
this.showSplashScreen = true,
});
/// You can use this to build your own splash screen.
final Widget Function(
BuildContext context,
Function() onFinish,
)? splashScreenBuilder;
/// This is used to wrap the introduction widget in your own custom page.
final Widget Function(
BuildContext context,
Widget introductionPage,
)? introductionBuilder;
/// The route that is used to navigate to the home screen.
final String? homeScreenRoute;
final IntroductionOptions Function(BuildContext context)?
introductionOptionsBuilder;
final Widget? introductionFallbackScreen;
final IntroductionService? introductionService;
final KillswitchService? killswitchService;
final ScrollPhysics? introductionScrollPhysics;
/// If the introduction should be shown.
final bool showIntroduction;
/// If the killswitch is enabled this app can be remotely disabled.
final bool useKillswitch;
/// The widget that is shown in the center of the splash screen.
final WidgetBuilder? splashScreenCenterWidget;
/// The background color of the splash screen.
final Color? splashScreenBackgroundColor;
/// The minimum duration the splash screen in seconds.
final int minimumSplashScreenDuration;
/// The future that is awaited before the splash screen is closed.
final Future<String?> Function(BuildContext context)? splashScreenFuture;
/// Allow popping from introduction, defaults to true
final bool canPopFromIntroduction;
/// If the splash screen should be shown or not.
final bool showSplashScreen;
}

View file

@ -0,0 +1,46 @@
import "package:flutter/material.dart";
import "package:flutter_start/flutter_start.dart";
/// The introduction screen of the Flutter app.
class IntroductionsScreen extends StatelessWidget {
/// IntrdoctionScreen constructor
const IntroductionsScreen({
required this.introDuctionFallBackScreen,
required this.introductionScrollPhysics,
required this.introductionOptions,
required this.introductionService,
required this.afterIntroduction,
required this.options,
super.key,
});
/// The function that is executed after the introduction screen.
final Function() afterIntroduction;
/// The options to configure the introduction screen.
final IntroductionOptions introductionOptions;
/// The introduction service to configure the introduction screen.
final IntroductionService introductionService;
/// The scroll physics of the introduction screen.
final ScrollPhysics? introductionScrollPhysics;
/// The fallback screen of the introduction screen.
final Widget? introDuctionFallBackScreen;
/// The options to configure the start of the Flutter app.
final FlutterStartOptions options;
@override
Widget build(BuildContext context) => PopScope(
canPop: options.canPopFromIntroduction,
child: Introduction(
options: introductionOptions,
navigateTo: afterIntroduction,
physics: introductionScrollPhysics,
service: introductionService,
child: introDuctionFallBackScreen,
),
);
}

View file

@ -0,0 +1,96 @@
import "dart:async";
import "package:flutter/material.dart";
import "package:flutter_start/flutter_start.dart";
/// A splash screen that is shown when the app is started.
class SplashScreen extends StatefulWidget {
/// SplashScreen constructor.
const SplashScreen({
required this.afterSplashScreen,
required this.startService,
required this.options,
super.key,
});
/// The options to configure the splash screen.
final FlutterStartOptions options;
/// The function that is executed after the splash screen.
final Function(BuildContext context) afterSplashScreen;
/// The start service to start the Flutter app.
final StartService startService;
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
bool minimumDurationPassed = false;
bool futureCompleted = false;
bool canContinue = false;
@override
void initState() {
super.initState();
Future.delayed(
Duration(seconds: widget.options.minimumSplashScreenDuration.toInt()),
() {
setState(() {
minimumDurationPassed = true;
});
checkTransitionConditions();
});
if (widget.options.splashScreenFuture != null) {
unawaited(
widget.options.splashScreenFuture!(context).then((_) {
setState(() {
futureCompleted = true;
});
checkTransitionConditions();
}),
);
} else {
futureCompleted = true;
}
if (widget.options.iskillswitchEnabled) {
unawaited(
widget.startService.isKillswitchActive().then((value) {
setState(() {
canContinue = !value;
});
checkTransitionConditions();
}),
);
} else {
canContinue = true;
}
}
void checkTransitionConditions() {
if (minimumDurationPassed &&
futureCompleted &&
canContinue &&
context.mounted) {
widget.afterSplashScreen(context);
}
}
@override
Widget build(BuildContext context) =>
widget.options.splashScreenBuilder?.call(context) ??
Scaffold(
backgroundColor: widget.options.splashScreenBackgroundColor,
body: Center(
child: widget.options.splashScreenCenterWidget ??
Text(
"iconinstagram",
style: Theme.of(context).textTheme.headlineLarge,
),
),
);
}

View file

@ -1,46 +0,0 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:package_info_plus/package_info_plus.dart';
/// A service class to check if a killswitch is active for the current app.
abstract interface class KillswitchService {
/// Checks if the killswitch is active for the current app.
///
/// It makes a GET request to a specific URL with the app
/// name as a query parameter.
/// If the request fails or times out after 5 seconds,
/// it defaults to returning 'false'.
///
/// Returns a [Future] that completes with 'true' if the killswitch is active,
/// and 'false' otherwise.
Future<bool> isKillswitchActive() => throw UnimplementedError();
}
class DefaultKillswitchService implements KillswitchService {
@override
Future<bool> isKillswitchActive() async {
var packageInfo = await PackageInfo.fromPlatform();
var appName = packageInfo.appName;
http.Response response;
response = await http
.get(
Uri.parse('https://active-obelugnnza-uc.a.run.app/?appName=$appName'),
)
.timeout(
const Duration(seconds: 5),
onTimeout: () => http.Response('false', 500),
)
.onError(
(error, stackTrace) => http.Response('false', 500),
);
var decoded = jsonDecode(response.body);
if (decoded == true) {
return true;
}
return false;
}
}

View file

@ -1,162 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../flutter_start.dart';
import '../services/killswitch_service.dart';
import '../widgets/default_splash_screen.dart';
/// Initial screen of the user story.
///
/// Use this when defining an initial route.
class NavigatorStartUserStory extends StatelessWidget {
const NavigatorStartUserStory({
required this.onComplete,
this.configuration = const StartUserStoryConfiguration(),
super.key,
});
final StartUserStoryConfiguration configuration;
final void Function(BuildContext context) onComplete;
@override
Widget build(BuildContext context) {
if (!configuration.showSplashScreen) {
return _introduction(configuration, context, onComplete);
}
return _splashScreen(configuration, context, onComplete);
}
}
/// Enter the start user story with the Navigator 1.0 API.
///
/// Requires a Navigator widget to exist in the given [context].
///
/// Customization can be done through the [configuration] parameter.
///
/// [onComplete] triggers as soon as the userstory is finished.
///
/// The context provided here is a context has a guaranteed navigator.
Future<void> startNavigatorUserStory(
BuildContext context,
StartUserStoryConfiguration configuration, {
required void Function(BuildContext context) onComplete,
}) async {
var initialRoute = MaterialPageRoute(
builder: (context) => _splashScreen(
configuration,
context,
onComplete,
),
);
if (!configuration.showSplashScreen && configuration.showIntroduction) {
initialRoute = MaterialPageRoute(
builder: (context) => _introduction(
configuration,
context,
onComplete,
),
);
}
await Navigator.of(context).push(initialRoute);
}
Widget _splashScreen(
StartUserStoryConfiguration configuration,
BuildContext context,
void Function(BuildContext context) onComplete,
) {
var navigator = Navigator.of(context);
var isAllowedToPassThrough = false;
Future<void> splashHandler() async {
await Future.wait<void>(
[
configuration.splashScreenFuture?.call(context) ?? Future.value(),
Future.delayed(
Duration.zero,
() async {
if (configuration.useKillswitch) {
var killswitchService =
configuration.killswitchService ?? DefaultKillswitchService();
isAllowedToPassThrough =
await killswitchService.isKillswitchActive();
}
},
),
Future.delayed(
Duration(
seconds: configuration.minimumSplashScreenDuration,
),
),
],
);
if (configuration.useKillswitch && isAllowedToPassThrough) return;
if ((!configuration.showIntroduction) && context.mounted) {
onComplete(context);
return;
}
if (context.mounted) {
await navigator.pushReplacement(
MaterialPageRoute(
builder: (context) => _introduction(
configuration,
context,
onComplete,
),
),
);
}
}
var builder = configuration.splashScreenBuilder;
if (builder == null) {
unawaited(splashHandler());
return Scaffold(
backgroundColor: configuration.splashScreenBackgroundColor,
body: Center(
child: configuration.splashScreenCenterWidget?.call(context) ??
defaultSplashScreen(context),
),
);
}
return builder.call(
context,
splashHandler,
);
}
Widget _introduction(
StartUserStoryConfiguration configuration,
BuildContext context,
void Function(BuildContext context) onComplete,
) {
var introduction = Introduction(
service: configuration.introductionService ??
IntroductionService(SharedPreferencesIntroductionDataProvider()),
navigateTo: () async => onComplete(context),
options: configuration.introductionOptionsBuilder?.call(context) ??
const IntroductionOptions(),
physics: configuration.introductionScrollPhysics,
child: configuration.introductionFallbackScreen,
);
return PopScope(
canPop: configuration.canPopFromIntroduction,
child: configuration.introductionBuilder?.call(
context,
introduction,
) ??
Scaffold(
body: introduction,
),
);
}

View file

@ -1,6 +0,0 @@
import 'package:flutter/material.dart';
Text defaultSplashScreen(BuildContext context) => Text(
'iconinstagram',
style: Theme.of(context).textTheme.headlineLarge,
);

View file

@ -1,63 +1,27 @@
name: flutter_start
description: "Flutter_start is a package that allows you to jumpstart your application with a splashScreen, introduction and a home."
version: 4.2.1
# publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
publish_to: 'none'
description: "A new Flutter package project."
version: 0.0.1
homepage: none
publish_to: none
environment:
sdk: ">=3.2.5 <4.0.0"
sdk: ^3.5.3
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ">=1.0.2 <2.0.0"
go_router: ">=14.2.0 <15.0.0"
http: ">=1.2.1 <2.0.0"
package_info_plus: ">=8.0.0 <9.0.0"
flutter_introduction:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction
ref: 5.0.0
# hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
# version: "^5.0.0"
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^5.0.0
flutter_introduction_shared_preferences:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_shared_preferences
ref: 5.0.0
# hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub/
# version: ^5.0.0
dependency_overrides:
flutter_data_interface:
git:
url: https://github.com/Iconica-Development/flutter_data_interface.git
ref: 1.0.1
flutter_introduction_interface:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_interface
ref: 5.0.0
flutter_introduction_service:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_service
ref: 5.0.0
flutter_introduction_widget:
git:
url: https://github.com/Iconica-Development/flutter_introduction.git
path: packages/flutter_introduction_widget
ref: 5.0.0
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub/
version: ^5.0.0
start_repository_interface:
path: ../start_repository_interface
dev_dependencies:
flutter_test:
sdk: flutter
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0
flutter:
uses-material-design: true
ref: 7.0.0

View file

@ -0,0 +1 @@
../../CONTRIBUTING.md

View file

@ -1,4 +1,9 @@
include: package:flutter_lints/flutter.yaml
include: package:flutter_iconica_analysis/components_options.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
# Possible to overwrite the rules from the package
analyzer:
exclude:
linter:
rules:

View file

@ -0,0 +1,6 @@
/// Killswitch repository interface
// ignore: one_member_abstracts
abstract class KillswitchRepositoryInterface {
/// Checks if the killswitch is active for the current app.
Future<bool> isKillswitchActive();
}

View file

@ -0,0 +1,7 @@
import "package:start_repository_interface/src/interfaces/killswitch_repository_interface.dart";
/// The local killswitch repository.
class LocalKillswitchRepository implements KillswitchRepositoryInterface {
@override
Future<bool> isKillswitchActive() async => false;
}

View file

@ -0,0 +1,18 @@
import "package:start_repository_interface/src/interfaces/killswitch_repository_interface.dart";
import "package:start_repository_interface/src/local/local_killswitch_repository.dart";
/// Start service.
class StartService {
/// The start service constructor.
StartService({
KillswitchRepositoryInterface? killswitchRepositoryInterface,
}) : killswitchRepositoryInterface =
killswitchRepositoryInterface ?? LocalKillswitchRepository();
/// The killswitch repository interface.
final KillswitchRepositoryInterface? killswitchRepositoryInterface;
/// Check if the killswitch is active.
Future<bool> isKillswitchActive() async =>
killswitchRepositoryInterface!.isKillswitchActive();
}

View file

@ -1,7 +1,6 @@
///
library start_repository_interface;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}
export "src/interfaces/killswitch_repository_interface.dart";
export "src/local/local_killswitch_repository.dart";
export "src/services/start_service.dart";

View file

@ -1,7 +1,7 @@
name: start_repository_interface
description: "A new Flutter package project."
version: 0.0.1
homepage:
homepage: none
environment:
sdk: ^3.5.3
@ -12,43 +12,8 @@ dependencies:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package

View file

@ -1,12 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:start_repository_interface/start_repository_interface.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}