Compare commits

..

No commits in common. "master" and "2.1.0" have entirely different histories.

45 changed files with 229 additions and 638 deletions

View file

@ -1,14 +0,0 @@
name: Iconica Standard Component Documentation Workflow
# Workflow Caller version: 1.0.0
on:
release:
types: [published]
workflow_dispatch:
jobs:
call-iconica-component-documentation-workflow:
uses: Iconica-Development/.github/.github/workflows/component-documentation.yml@master
secrets: inherit
permissions: write-all

5
.gitignore vendored
View file

@ -31,6 +31,7 @@ build/
.metadata .metadata
pubspec.lock pubspec.lock
pubspec_overrides.yaml pubspec_overrides.yaml
@ -40,7 +41,3 @@ example/web
example/android example/android
example/linux example/linux
example/macos example/macos
# FVM Version Cache
.fvm/
.fvmrc

View file

@ -1,24 +1,3 @@
## 5.0.0
* Changed color of indicators
* Changed default button text
## 4.0.0
* Added Buildcontext to the pages parameter.
* Added `dotColor` so the default can be changed.
* Changed the default `pages` to include theme.
## 3.1.0
* Introduction now uses `IntroductionScreenMode` to determine how often the introductions should be shown
* Added `dotSize` and `dotSpacing` to `IntroductionOptions` to allow for customization of the dots for the introduction
## 3.0.0
* Update default styling
* Add default introduction
## 2.1.0 ## 2.1.0
* Upgrade dependencies * Upgrade dependencies

View file

@ -32,7 +32,7 @@ Please file any issues, bugs or feature request as an issue on our [GitHub](http
## Want to contribute ## Want to contribute
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_introduction/pulls). 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_introduction/pulls).
## Author ## Author

View file

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

View file

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

View file

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

View file

@ -38,21 +38,21 @@ class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) => Scaffold( Widget build(BuildContext context) => Scaffold(
body: Introduction( body: Introduction(
options: IntroductionOptions( options: IntroductionOptions(
pages: (context) => [ pages: [
const IntroductionPage( IntroductionPage(
title: Text('First page'), title: const Text('First page'),
text: Text('Wow a page'), text: const Text('Wow a page'),
graphic: FlutterLogo(), graphic: const FlutterLogo(),
), ),
const IntroductionPage( IntroductionPage(
title: Text('Second page'), title: const Text('Second page'),
text: Text('Another page'), text: const Text('Another page'),
graphic: FlutterLogo(), graphic: const FlutterLogo(),
), ),
const IntroductionPage( IntroductionPage(
title: Text('Third page'), title: const Text('Third page'),
text: Text('The final page of this app'), text: const Text('The final page of this app'),
graphic: FlutterLogo(), graphic: const FlutterLogo(),
), ),
], ],
introductionTranslations: const IntroductionTranslations( introductionTranslations: const IntroductionTranslations(

View file

@ -19,19 +19,10 @@ class Introduction extends StatefulWidget {
super.key, super.key,
}); });
/// Callback function to navigate to the next screen.
final VoidCallback navigateTo; final VoidCallback navigateTo;
/// The introduction service to use.
final IntroductionService? service; final IntroductionService? service;
/// Options for configuring the introduction screen.
final IntroductionOptions options; final IntroductionOptions options;
/// The scrolling physics for the introduction screen.
final ScrollPhysics? physics; final ScrollPhysics? physics;
/// Child widget to display.
final Widget? child; final Widget? child;
@override @override
@ -56,9 +47,7 @@ class _IntroductionState extends State<Introduction> {
// ignore: discarded_futures // ignore: discarded_futures
future: _service.shouldShow(), future: _service.shouldShow(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.data == null || if (snapshot.data == null || snapshot.data!) {
snapshot.data! ||
widget.options.mode == IntroductionScreenMode.showAlways) {
return IntroductionScreen( return IntroductionScreen(
options: widget.options, options: widget.options,
onComplete: () async { onComplete: () async {

View file

@ -1,21 +1,25 @@
name: flutter_introduction name: flutter_introduction
description: Combined Package of Flutter Introduction Widget and Flutter Introduction Service description: Combined Package of Flutter Introduction Widget and Flutter Introduction Service
version: 5.0.0 version: 2.1.0
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: none
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=2.18.0 <3.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_introduction_widget: flutter_introduction_widget:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: "^5.0.0" url: https://github.com/Iconica-Development/flutter_introduction
ref: 2.1.0
path: packages/flutter_introduction_widget
flutter_introduction_service: flutter_introduction_service:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: "^5.0.0" url: https://github.com/Iconica-Development/flutter_introduction
ref: 2.1.0
path: packages/flutter_introduction_service
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

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

View file

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

View file

@ -115,7 +115,7 @@ class _IntroductionState extends State<IntroductionFirebase> {
snapshot.data is List<IntroductionPageData>) { snapshot.data is List<IntroductionPageData>) {
return IntroductionScreen( return IntroductionScreen(
options: widget.options.copyWith( options: widget.options.copyWith(
pages: (context) => snapshot.data!.map( pages: snapshot.data?.map(
(e) { (e) {
var title = e.title.isEmpty var title = e.title.isEmpty
? '' ? ''

View file

@ -1,7 +1,7 @@
name: flutter_introduction_firebase name: flutter_introduction_firebase
description: Flutter Introduction Page that uses firebase for the pages and some settings description: Flutter Introduction Page that uses firebase for the pages and some settings
version: 5.0.0 version: 2.1.0
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: none
environment: environment:
sdk: ">=3.1.5 <4.0.0" sdk: ">=3.1.5 <4.0.0"
@ -9,15 +9,19 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cloud_firestore: "^4.12.2" cloud_firestore: ^4.12.2
cached_network_image: "^3.3.0" cached_network_image: ^3.3.0
flutter_introduction_widget: flutter_introduction_widget:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: "^5.0.0" url: https://github.com/Iconica-Development/flutter_introduction
ref: 2.1.0
path: packages/flutter_introduction_widget
flutter_introduction_service: flutter_introduction_service:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: "^5.0.0" url: https://github.com/Iconica-Development/flutter_introduction
ref: 2.1.0
path: packages/flutter_introduction_service
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

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

View file

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

View file

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

View file

@ -6,35 +6,20 @@ import 'package:flutter_data_interface/flutter_data_interface.dart';
import 'package:flutter_introduction_interface/src/local_introduction.dart'; import 'package:flutter_introduction_interface/src/local_introduction.dart';
abstract class IntroductionInterface extends DataInterface { abstract class IntroductionInterface extends DataInterface {
/// Constructs an instance of [IntroductionInterface].
///
/// The [token] is used for verification purposes.
IntroductionInterface() : super(token: _token); IntroductionInterface() : super(token: _token);
static final Object _token = Object(); static final Object _token = Object();
static IntroductionInterface _instance = LocalIntroductionDataProvider(); static IntroductionInterface _instance = LocalIntroductionDataProvider();
/// Retrieves the current instance of [IntroductionInterface].
static IntroductionInterface get instance => _instance; static IntroductionInterface get instance => _instance;
/// Sets the current instance of [IntroductionInterface].
///
/// Throws an error if the provided instance does not match the token.
static set instance(IntroductionInterface instance) { static set instance(IntroductionInterface instance) {
DataInterface.verify(instance, _token); DataInterface.verify(instance, _token);
_instance = instance; _instance = instance;
} }
/// Sets whether the introduction is completed or not.
///
/// The [value] parameter specifies whether the introduction is completed.
/// By default, it is set to `true`.
Future<void> setCompleted({bool value = true}); Future<void> setCompleted({bool value = true});
/// Checks if the introduction should be shown.
///
/// Returns `true` if the introduction should be shown;
/// otherwise, returns `false`.
Future<bool> shouldShow(); Future<bool> shouldShow();
} }

View file

@ -4,17 +4,9 @@
import 'package:flutter_introduction_interface/src/introduction_interface.dart'; import 'package:flutter_introduction_interface/src/introduction_interface.dart';
/// Provides local data storage for managing introduction data.
///
/// This class extends [IntroductionInterface] and implements methods to manage
/// introduction data locally.
class LocalIntroductionDataProvider extends IntroductionInterface { class LocalIntroductionDataProvider extends IntroductionInterface {
/// Constructs an instance of [LocalIntroductionDataProvider].
///
/// Initializes the [hasViewed] flag to `false`.
LocalIntroductionDataProvider(); LocalIntroductionDataProvider();
/// Flag indicating whether the introduction has been viewed or not.
bool hasViewed = false; bool hasViewed = false;
@override @override

View file

@ -1,18 +1,19 @@
name: flutter_introduction_interface name: flutter_introduction_interface
description: A new Flutter package project. description: A new Flutter package project.
version: 5.0.0 version: 2.1.0
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: none
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=2.18.0 <3.0.0'
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_data_interface: flutter_data_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: "^1.0.0" url: https://github.com/Iconica-Development/flutter_data_interface.git
ref: 1.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

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

View file

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

View file

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

View file

@ -4,36 +4,15 @@
import 'package:flutter_introduction_interface/flutter_introduction_interface.dart'; import 'package:flutter_introduction_interface/flutter_introduction_interface.dart';
/// A service for managing introduction-related operations.
///
/// This class provides methods for handling introduction-related actions
/// such as skipping, completing,
/// and determining whether to show the introduction.
class IntroductionService { class IntroductionService {
/// Constructs an instance of [IntroductionService].
///
/// Optionally takes a [dataProvider] parameter,
/// which is an implementation of [IntroductionInterface].
/// If no data provider is provided,
/// it defaults to [LocalIntroductionDataProvider].
IntroductionService([IntroductionInterface? dataProvider]) IntroductionService([IntroductionInterface? dataProvider])
: _dataProvider = dataProvider ?? LocalIntroductionDataProvider(); : _dataProvider = dataProvider ?? LocalIntroductionDataProvider();
late final IntroductionInterface _dataProvider; late final IntroductionInterface _dataProvider;
/// Marks the introduction as skipped.
///
/// Calls [_dataProvider.setCompleted] with the value `true`.
Future<void> onSkip() => _dataProvider.setCompleted(value: true); Future<void> onSkip() => _dataProvider.setCompleted(value: true);
/// Marks the introduction as completed.
///
/// Calls [_dataProvider.setCompleted] with the value `true`.
Future<void> onComplete() => _dataProvider.setCompleted(value: true); Future<void> onComplete() => _dataProvider.setCompleted(value: true);
/// Checks whether the introduction should be shown.
///
/// Returns a `Future<bool>` indicating whether the
/// introduction should be shown.
Future<bool> shouldShow() => _dataProvider.shouldShow(); Future<bool> shouldShow() => _dataProvider.shouldShow();
} }

View file

@ -1,18 +1,20 @@
name: flutter_introduction_service name: flutter_introduction_service
description: A new Flutter package project. description: A new Flutter package project.
version: 5.0.0 version: 2.1.0
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: none
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=2.18.0 <3.0.0'
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_introduction_interface: flutter_introduction_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: "^5.0.0" url: https://github.com/Iconica-Development/flutter_introduction
ref: 2.1.0
path: packages/flutter_introduction_interface
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

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

View file

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

View file

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

View file

@ -5,27 +5,16 @@
import 'package:flutter_introduction_interface/flutter_introduction_interface.dart'; import 'package:flutter_introduction_interface/flutter_introduction_interface.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
/// Provides data storage using SharedPreferences for
/// managing introduction data.
///
/// This class extends [IntroductionInterface] and implements methods to manage
/// introduction data using SharedPreferences.
class SharedPreferencesIntroductionDataProvider extends IntroductionInterface { class SharedPreferencesIntroductionDataProvider extends IntroductionInterface {
/// Constructs an instance of [SharedPreferencesIntroductionDataProvider].
SharedPreferencesIntroductionDataProvider(); SharedPreferencesIntroductionDataProvider();
SharedPreferences? _prefs; SharedPreferences? _prefs;
String key = '_completedIntroduction'; String key = '_completedIntroduction';
/// Writes a key-value pair to SharedPreferences.
///
/// The [key] is the key under which to store the [value].
/// The [value] is the boolean value to be stored.
Future<void> _writeKeyValue(String key, bool value) async { Future<void> _writeKeyValue(String key, bool value) async {
await _prefs!.setBool(key, value); await _prefs!.setBool(key, value);
} }
/// Initializes the SharedPreferences instance.
Future<void> _init() async { Future<void> _init() async {
_prefs ??= await SharedPreferences.getInstance(); _prefs ??= await SharedPreferences.getInstance();
} }

View file

@ -1,19 +1,21 @@
name: flutter_introduction_shared_preferences name: flutter_introduction_shared_preferences
description: A new Flutter package project. description: A new Flutter package project.
version: 5.0.0 version: 2.1.0
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: none
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=2.18.0 <3.0.0'
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_introduction_interface: flutter_introduction_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: "^5.0.0" url: https://github.com/Iconica-Development/flutter_introduction
shared_preferences: "^2.2.0" ref: 2.1.0
path: packages/flutter_introduction_interface
shared_preferences: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

@ -31,30 +31,30 @@ class MyApp extends StatelessWidget {
), ),
home: IntroductionScreen( home: IntroductionScreen(
options: IntroductionOptions( options: IntroductionOptions(
pages: (context) => [ pages: [
const IntroductionPage( IntroductionPage(
title: Text('Basic Page'), title: const Text('Basic Page'),
text: Text( text: const Text(
'A page with some text and a widget in the middle.', 'A page with some text and a widget in the middle.',
), ),
graphic: FlutterLogo(size: 100), graphic: const FlutterLogo(size: 100),
), ),
const IntroductionPage( IntroductionPage(
title: Text('Layout Shift'), title: const Text('Layout Shift'),
text: Text( text: const Text(
'You can change the layout of a page to mix things up.', 'You can change the layout of a page to mix things up.',
), ),
graphic: FlutterLogo(size: 100), graphic: const FlutterLogo(size: 100),
layoutStyle: IntroductionLayoutStyle.imageTop, layoutStyle: IntroductionLayoutStyle.imageTop,
), ),
const IntroductionPage( IntroductionPage(
title: Text( title: const Text(
'Decoration', 'Decoration',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
), ),
), ),
decoration: BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topRight, begin: Alignment.topRight,
end: Alignment.bottomLeft, end: Alignment.bottomLeft,
@ -66,24 +66,24 @@ class MyApp extends StatelessWidget {
], ],
), ),
), ),
text: Text( text: const Text(
'Add a Decoration to make a custom background, like a LinearGradient', 'Add a Decoration to make a custom background, like a LinearGradient',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
), ),
), ),
graphic: FlutterLogo( graphic: const FlutterLogo(
size: 100, size: 100,
), ),
), ),
const IntroductionPage( IntroductionPage(
title: Text( title: const Text(
'Background Image', 'Background Image',
), ),
text: Text( text: const Text(
'Add a Decoration with a DecorationImage, to add an background image', 'Add a Decoration with a DecorationImage, to add an background image',
), ),
decoration: BoxDecoration( decoration: const BoxDecoration(
image: DecorationImage( image: DecorationImage(
fit: BoxFit.cover, fit: BoxFit.cover,
image: AssetImage( image: AssetImage(

View file

@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_introduction_widget/flutter_introduction_widget.dart';
List<IntroductionPage> defaultIntroductionPages(BuildContext context) {
var theme = Theme.of(context);
return [
IntroductionPage(
title: Column(
children: [
const SizedBox(height: 50),
Text(
'welcome to iconinstagram',
style: theme.textTheme.headlineLarge,
),
const SizedBox(height: 6),
],
),
graphic: const Image(
image: AssetImage(
'assets/first.png',
package: 'flutter_introduction_widget',
),
),
text: Text(
'Welcome to the world of Instagram, where creativity'
' knows no bounds and connections are made'
' through captivating visuals.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium,
),
),
IntroductionPage(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 50),
Text(
'discover iconinstagram',
style: theme.textTheme.headlineLarge,
),
const SizedBox(height: 6),
],
),
text: Text(
'Dive into the vibrant world of'
' Instagram and discover endless possibilities.'
' From stunning photography to engaging videos,'
' Instagram offers a diverse range of content to explore and enjoy.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium,
),
graphic: const Image(
image: AssetImage(
'assets/second.png',
package: 'flutter_introduction_widget',
),
),
),
IntroductionPage(
title: Column(
children: [
const SizedBox(height: 50),
Text(
'elevate your experience',
style: theme.textTheme.headlineLarge,
),
const SizedBox(height: 6),
],
),
graphic: const Image(
image: AssetImage(
'assets/third.png',
package: 'flutter_introduction_widget',
),
),
text: Text(
'Whether promoting your business, or connecting'
' with friends and family, Instagram provides the'
' tools and platform to make your voice heard.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium,
),
),
];
}

View file

@ -3,7 +3,6 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_introduction_widget/src/config/default_introduction_pages.dart';
enum IntroductionScreenMode { showNever, showAlways, showOnce } enum IntroductionScreenMode { showNever, showAlways, showOnce }
@ -43,7 +42,7 @@ class IntroductionPage {
/// ///
/// The [background] is fully optional and if not provided will show the /// The [background] is fully optional and if not provided will show the
/// [ThemeData.colorScheme.background] as default. /// [ThemeData.colorScheme.background] as default.
const IntroductionPage({ IntroductionPage({
this.title, this.title,
this.text, this.text,
this.graphic, this.graphic,
@ -61,11 +60,11 @@ class IntroductionOptions {
const IntroductionOptions({ const IntroductionOptions({
this.introductionTranslations = const IntroductionTranslations(), this.introductionTranslations = const IntroductionTranslations(),
this.introductionButtonTextstyles = const IntroductionButtonTextstyles(), this.introductionButtonTextstyles = const IntroductionButtonTextstyles(),
this.indicatorMode = IndicatorMode.dot, this.indicatorMode = IndicatorMode.dash,
this.indicatorBuilder, this.indicatorBuilder,
this.layoutStyle = IntroductionLayoutStyle.imageBottom, this.layoutStyle = IntroductionLayoutStyle.imageCenter,
this.pages = defaultIntroductionPages, this.pages = const [],
this.buttonMode = IntroductionScreenButtonMode.text, this.buttonMode = IntroductionScreenButtonMode.disabled,
this.tapEnabled = false, this.tapEnabled = false,
this.mode = IntroductionScreenMode.showNever, this.mode = IntroductionScreenMode.showNever,
this.textAlign = TextAlign.center, this.textAlign = TextAlign.center,
@ -73,9 +72,6 @@ class IntroductionOptions {
this.skippable = false, this.skippable = false,
this.buttonBuilder, this.buttonBuilder,
this.controlMode = IntroductionControlMode.previousNextButton, this.controlMode = IntroductionControlMode.previousNextButton,
this.dotSize = 12,
this.dotSpacing = 24,
this.dotColor,
}) : assert( }) : assert(
!(identical(indicatorMode, IndicatorMode.custom) && !(identical(indicatorMode, IndicatorMode.custom) &&
indicatorBuilder == null), indicatorBuilder == null),
@ -96,7 +92,7 @@ class IntroductionOptions {
/// List of introduction pages to set the text, icons or images for the /// List of introduction pages to set the text, icons or images for the
/// introduction screens. /// introduction screens.
final List<IntroductionPage> Function(BuildContext context) pages; final List<IntroductionPage> pages;
/// Determines whether the user can tap the screen to go to the next /// Determines whether the user can tap the screen to go to the next
/// introduction screen. /// introduction screen.
@ -207,19 +203,9 @@ class IntroductionOptions {
/// - Finish /// - Finish
final IntroductionButtonTextstyles introductionButtonTextstyles; final IntroductionButtonTextstyles introductionButtonTextstyles;
/// The size of the dots in the indicator. Default is 12
final double dotSize;
/// The distance between the center of each dot. Default is 24
final double dotSpacing;
/// The color of the dots in the indicator. Default is the primary color of
/// the theme
final Color? dotColor;
IntroductionOptions copyWith({ IntroductionOptions copyWith({
IntroductionScreenMode? mode, IntroductionScreenMode? mode,
List<IntroductionPage> Function(BuildContext context)? pages, List<IntroductionPage>? pages,
bool? tapEnabled, bool? tapEnabled,
IntroductionScreenButtonMode? buttonMode, IntroductionScreenButtonMode? buttonMode,
IntroductionLayoutStyle? layoutStyle, IntroductionLayoutStyle? layoutStyle,
@ -261,10 +247,10 @@ class IntroductionOptions {
class IntroductionTranslations { class IntroductionTranslations {
const IntroductionTranslations({ const IntroductionTranslations({
this.skipButton = 'Skip', this.skipButton = 'skip',
this.nextButton = 'Next', this.nextButton = 'next',
this.previousButton = 'Previous', this.previousButton = 'previous',
this.finishButton = 'Get started', this.finishButton = 'finish',
}); });
final String skipButton; final String skipButton;
final String nextButton; final String nextButton;

View file

@ -11,12 +11,7 @@ import 'package:flutter_introduction_widget/src/widgets/background.dart';
import 'package:flutter_introduction_widget/src/widgets/indicator.dart'; import 'package:flutter_introduction_widget/src/widgets/indicator.dart';
import 'package:flutter_introduction_widget/src/widgets/page_content.dart'; import 'package:flutter_introduction_widget/src/widgets/page_content.dart';
/// A screen widget for displaying a multi-page introduction.
///
/// This widget provides a multi-page introduction experience with options
/// for handling navigation and completion callbacks.
class MultiPageIntroductionScreen extends StatefulWidget { class MultiPageIntroductionScreen extends StatefulWidget {
/// Creates a new instance of [MultiPageIntroductionScreen].
const MultiPageIntroductionScreen({ const MultiPageIntroductionScreen({
required this.options, required this.options,
required this.onComplete, required this.onComplete,
@ -27,22 +22,12 @@ class MultiPageIntroductionScreen extends StatefulWidget {
super.key, super.key,
}); });
/// Callback function triggered when the introduction is completed.
final VoidCallback onComplete; final VoidCallback onComplete;
/// Callback function triggered when the "Next" button is pressed.
final void Function(IntroductionPage)? onNext;
/// Callback function triggered when the "Previous" button is pressed.
final void Function(IntroductionPage)? onPrevious;
/// Callback function triggered when the "Skip" button is pressed.
final VoidCallback? onSkip; final VoidCallback? onSkip;
final void Function(IntroductionPage)? onNext;
/// Physics for the scrolling behavior. final void Function(IntroductionPage)? onPrevious;
final ScrollPhysics? physics; final ScrollPhysics? physics;
/// Introduction options specifying the configuration of the introduction.
final IntroductionOptions options; final IntroductionOptions options;
@override @override
@ -50,9 +35,6 @@ class MultiPageIntroductionScreen extends StatefulWidget {
_MultiPageIntroductionScreenState(); _MultiPageIntroductionScreenState();
} }
/// State class for [MultiPageIntroductionScreen].
///
/// Manages the state and behavior of the [MultiPageIntroductionScreen] widget.
class _MultiPageIntroductionScreenState class _MultiPageIntroductionScreenState
extends State<MultiPageIntroductionScreen> { extends State<MultiPageIntroductionScreen> {
final PageController _controller = PageController(); final PageController _controller = PageController();
@ -77,7 +59,7 @@ class _MultiPageIntroductionScreenState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var pages = widget.options.pages.call(context); var pages = widget.options.pages;
var translations = widget.options.introductionTranslations; var translations = widget.options.introductionTranslations;
return Stack( return Stack(
children: [ children: [
@ -150,21 +132,15 @@ class _MultiPageIntroductionScreenState
AnimatedBuilder( AnimatedBuilder(
animation: _currentPage, animation: _currentPage,
builder: (context, _) => Indicator( builder: (context, _) => Indicator(
options: widget.options,
indicatorBuilder: widget.options.indicatorBuilder, indicatorBuilder: widget.options.indicatorBuilder,
mode: widget.options.indicatorMode, mode: widget.options.indicatorMode,
controller: _controller, controller: _controller,
count: pages.length, count: pages.length,
index: _currentPage.value, index: _currentPage.value,
dotSize: widget.options.dotSize,
dotSpacing: widget.options.dotSpacing,
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.all(32),
vertical: 40,
horizontal: 20,
),
child: AnimatedBuilder( child: AnimatedBuilder(
animation: _controller, animation: _controller,
builder: (context, _) { builder: (context, _) {
@ -276,14 +252,14 @@ class ExplainerPage extends StatelessWidget {
title: Padding( title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 32),
child: DefaultTextStyle( child: DefaultTextStyle(
style: theme.textTheme.titleMedium!, style: theme.textTheme.displayMedium!,
child: page.title ?? Text('introduction.$index.title'), child: page.title ?? Text('introduction.$index.title'),
), ),
), ),
text: Padding( text: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 32),
child: DefaultTextStyle( child: DefaultTextStyle(
style: theme.textTheme.bodyMedium!, style: theme.textTheme.bodyLarge!,
child: page.text ?? Text('introduction.$index.description'), child: page.text ?? Text('introduction.$index.description'),
), ),
), ),
@ -342,157 +318,69 @@ class IntroductionTwoButtons extends StatelessWidget {
var translations = options.introductionTranslations; var translations = options.introductionTranslations;
var showFinishButton = var showFinishButton =
options.buttonMode == IntroductionScreenButtonMode.singleFinish; options.buttonMode == IntroductionScreenButtonMode.singleFinish;
var theme = Theme.of(context);
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
if (options.buttonMode == IntroductionScreenButtonMode.text) ...[ if (options.buttonMode == IntroductionScreenButtonMode.text) ...[
Flexible( if (previous) ...[
child: Padding( options.buttonBuilder?.call(
padding: const EdgeInsets.only(right: 6),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 180,
),
child: Opacity(
opacity: previous ? 1 : 0,
child: IgnorePointer(
ignoring: !previous,
child: options.buttonBuilder?.call(
context, context,
_previous, _previous,
Text( Text(
translations.previousButton, translations.previousButton,
style: options.introductionButtonTextstyles style: options
.previousButtonStyle ?? .introductionButtonTextstyles.previousButtonStyle,
theme.textTheme.bodyMedium,
), ),
IntroductionButtonType.previous, IntroductionButtonType.previous,
) ?? ) ??
InkWell( TextButton(
onTap: _previous, onPressed: _previous,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
),
),
),
child: Center(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 4),
child: Text( child: Text(
translations.previousButton, translations.previousButton,
style: options.introductionButtonTextstyles style: options
.previousButtonStyle ?? .introductionButtonTextstyles.previousButtonStyle,
theme.textTheme.bodyMedium,
),
),
),
),
),
),
),
),
), ),
), ),
] else
const SizedBox.shrink(),
if (next) ...[ if (next) ...[
Flexible( options.buttonBuilder?.call(
child: Padding(
padding: const EdgeInsets.only(left: 6),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 180,
),
child: options.buttonBuilder?.call(
context, context,
_next, _next,
Text( Text(
translations.nextButton, translations.nextButton,
style: options.introductionButtonTextstyles style: options.introductionButtonTextstyles.nextButtonStyle,
.nextButtonStyle ??
theme.textTheme.bodyMedium,
), ),
IntroductionButtonType.next, IntroductionButtonType.next,
) ?? ) ??
InkWell( TextButton(
onTap: _next, onPressed: _next,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
),
),
),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text( child: Text(
translations.nextButton, translations.nextButton,
style: options.introductionButtonTextstyles style: options.introductionButtonTextstyles.nextButtonStyle,
.nextButtonStyle ??
theme.textTheme.bodyMedium,
),
),
),
),
),
),
), ),
), ),
] else if (last) ...[ ] else if (last) ...[
Flexible( options.buttonBuilder?.call(
child: Padding(
padding: const EdgeInsets.only(left: 6),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 180,
),
child: options.buttonBuilder?.call(
context, context,
() { () {
onFinish?.call(); onFinish?.call();
}, },
Text( Text(
translations.finishButton, translations.finishButton,
style: options.introductionButtonTextstyles style:
.finishButtonStyle ?? options.introductionButtonTextstyles.finishButtonStyle,
theme.textTheme.bodyMedium,
), ),
IntroductionButtonType.finish, IntroductionButtonType.finish,
) ?? ) ??
InkWell( TextButton(
onTap: () { onPressed: () {
onFinish?.call(); onFinish?.call();
}, },
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
),
),
),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text( child: Text(
translations.finishButton, translations.finishButton,
style: options.introductionButtonTextstyles style:
.finishButtonStyle ?? options.introductionButtonTextstyles.finishButtonStyle,
theme.textTheme.bodyMedium,
),
),
),
),
),
),
), ),
), ),
] else ...[ ] else ...[
@ -508,11 +396,6 @@ class IntroductionTwoButtons extends StatelessWidget {
maintainState: true, maintainState: true,
maintainInteractivity: false, maintainInteractivity: false,
child: Align( child: Align(
child: Flexible(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 180,
),
child: options.buttonBuilder?.call( child: options.buttonBuilder?.call(
context, context,
() { () {
@ -520,39 +403,19 @@ class IntroductionTwoButtons extends StatelessWidget {
}, },
Text( Text(
translations.finishButton, translations.finishButton,
style: options.introductionButtonTextstyles style: options
.finishButtonStyle ?? .introductionButtonTextstyles.finishButtonStyle,
theme.textTheme.bodyMedium,
), ),
IntroductionButtonType.finish, IntroductionButtonType.finish,
) ?? ) ??
InkWell( ElevatedButton(
onTap: () { onPressed: () {
onFinish?.call(); onFinish?.call();
}, },
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
),
),
),
child: Center(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 4),
child: Text( child: Text(
translations.finishButton, translations.finishButton,
style: options.introductionButtonTextstyles style: options
.finishButtonStyle ?? .introductionButtonTextstyles.finishButtonStyle,
theme.textTheme.bodyMedium,
),
),
),
),
),
), ),
), ),
), ),
@ -586,31 +449,16 @@ class IntroductionOneButton extends StatelessWidget {
super.key, super.key,
}); });
/// Options specifying the configuration of the introduction.
final IntroductionOptions options; final IntroductionOptions options;
/// Controller for managing the pages of the introduction.
final PageController controller; final PageController controller;
/// Callback function triggered when the introduction is completed.
final VoidCallback? onFinish; final VoidCallback? onFinish;
/// Callback function triggered when the "Next" button is pressed.
final VoidCallback? onNext; final VoidCallback? onNext;
/// Callback function triggered when the "Previous" button is pressed.
final VoidCallback? onPrevious; final VoidCallback? onPrevious;
/// Indicates whether there are previous pages.
final bool previous; final bool previous;
/// Indicates whether there are next pages.
final bool next; final bool next;
/// Indicates whether this is the last page.
final bool last; final bool last;
/// Handles the navigation to the previous page.
Future<void> _previous() async { Future<void> _previous() async {
await controller.previousPage( await controller.previousPage(
duration: kAnimationDuration, duration: kAnimationDuration,
@ -737,31 +585,16 @@ class IntroductionIconButtons extends StatelessWidget {
super.key, super.key,
}); });
/// Options specifying the configuration of the introduction.
final IntroductionOptions options; final IntroductionOptions options;
/// Controller for managing the pages of the introduction.
final PageController controller; final PageController controller;
/// Callback function triggered when the introduction is completed.
final VoidCallback? onFinish; final VoidCallback? onFinish;
/// Callback function triggered when the "Next" button is pressed.
final VoidCallback? onNext; final VoidCallback? onNext;
/// Callback function triggered when the "Previous" button is pressed.
final VoidCallback? onPrevious; final VoidCallback? onPrevious;
/// Indicates whether there are previous pages.
final bool previous; final bool previous;
/// Indicates whether there are next pages.
final bool next; final bool next;
/// Indicates whether this is the last page.
final bool last; final bool last;
/// Handles the navigation to the previous page.
Future<void> _previous() async { Future<void> _previous() async {
await controller.previousPage( await controller.previousPage(
duration: kAnimationDuration, duration: kAnimationDuration,

View file

@ -6,15 +6,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter_introduction_widget/src/config/introduction.dart'; import 'package:flutter_introduction_widget/src/config/introduction.dart';
/// Widget representing a single introduction page.
class SingleIntroductionPage extends StatelessWidget { class SingleIntroductionPage extends StatelessWidget {
/// Constructs a [SingleIntroductionPage] widget.
const SingleIntroductionPage({ const SingleIntroductionPage({
required this.options, required this.options,
super.key, super.key,
}); });
/// Options specifying the configuration of the introduction.
final IntroductionOptions options; final IntroductionOptions options;
@override @override

View file

@ -4,19 +4,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// Widget representing a background with optional decoration.
class Background extends StatelessWidget { class Background extends StatelessWidget {
/// Constructs a Background widget.
const Background({ const Background({
required this.child, required this.child,
this.background, this.background,
super.key, super.key,
}); });
/// Optional decoration for the background.
final BoxDecoration? background; final BoxDecoration? background;
/// The widget to be placed on the background.
final Widget child; final Widget child;
@override @override
@ -24,7 +19,7 @@ class Background extends StatelessWidget {
var theme = Theme.of(context); var theme = Theme.of(context);
var background = this.background ?? var background = this.background ??
BoxDecoration( BoxDecoration(
color: theme.colorScheme.surface, color: theme.colorScheme.background,
); );
var size = MediaQuery.of(context).size; var size = MediaQuery.of(context).size;
return Container( return Container(

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_introduction_widget/src/config/introduction.dart'; import 'package:flutter_introduction_widget/src/config/introduction.dart';
@ -15,9 +16,6 @@ class Indicator extends StatelessWidget {
required this.count, required this.count,
required this.index, required this.index,
required this.indicatorBuilder, required this.indicatorBuilder,
required this.dotSize,
required this.dotSpacing,
required this.options,
super.key, super.key,
}) : assert( }) : assert(
!(mode == IndicatorMode.custom && indicatorBuilder == null), !(mode == IndicatorMode.custom && indicatorBuilder == null),
@ -25,29 +23,16 @@ class Indicator extends StatelessWidget {
'must be provided', 'must be provided',
); );
/// The mode of the indicator.
final IndicatorMode mode; final IndicatorMode mode;
/// The PageController for which the indicator is displayed.
final PageController controller; final PageController controller;
final Widget Function(
/// The total number of items managed by the PageController. BuildContext,
final int count; PageController,
int,
/// The index of the current item in the PageController. int,
)? indicatorBuilder;
final int index; final int index;
final int count;
/// Builder function for a custom indicator.
final Widget Function(BuildContext, PageController, int, int)?
indicatorBuilder;
/// The size of the dots.
final double dotSize;
/// The distance between the center of each dot.
final double dotSpacing;
final IntroductionOptions options;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -57,10 +42,9 @@ class Indicator extends StatelessWidget {
return indicatorBuilder!.call(context, controller, index, count); return indicatorBuilder!.call(context, controller, index, count);
case IndicatorMode.dot: case IndicatorMode.dot:
return DotsIndicator( return DotsIndicator(
dotSize: dotSize,
dotSpacing: dotSpacing,
controller: controller, controller: controller,
color: options.dotColor ?? theme.colorScheme.primary, color: theme.colorScheme.primary,
dotcolor: theme.colorScheme.secondary,
itemCount: count, itemCount: count,
onPageSelected: (int page) { onPageSelected: (int page) {
unawaited( unawaited(
@ -74,9 +58,8 @@ class Indicator extends StatelessWidget {
); );
case IndicatorMode.dash: case IndicatorMode.dash:
return DashIndicator( return DashIndicator(
color: theme.colorScheme.primary,
controller: controller, controller: controller,
selectedColor: options.dotColor ?? theme.colorScheme.primary, selectedColor: theme.colorScheme.primary,
itemCount: count, itemCount: count,
onPageSelected: (int page) { onPageSelected: (int page) {
unawaited( unawaited(
@ -101,20 +84,10 @@ class DashIndicator extends AnimatedWidget {
this.color = Colors.white, this.color = Colors.white,
super.key, super.key,
}) : super(listenable: controller); }) : super(listenable: controller);
/// The PageController for which the indicator is displayed.
final PageController controller; final PageController controller;
/// The color of the dashes.
final Color color; final Color color;
/// The color of the selected dash.
final Color selectedColor; final Color selectedColor;
/// The total number of items managed by the PageController.
final int itemCount; final int itemCount;
/// Callback function called when a dash is selected.
final Function(int) onPageSelected; final Function(int) onPageSelected;
int _getPage() { int _getPage() {
@ -164,15 +137,16 @@ class DotsIndicator extends AnimatedWidget {
const DotsIndicator({ const DotsIndicator({
required this.controller, required this.controller,
this.color = Colors.white, this.color = Colors.white,
this.dotcolor = Colors.green,
this.itemCount, this.itemCount,
this.onPageSelected, this.onPageSelected,
this.dotSize = 8.0,
this.dotSpacing = 24.0,
super.key, super.key,
}) : super( }) : super(
listenable: controller, listenable: controller,
); );
/// The PageController that this DotsIndicator is representing.
final Color? dotcolor;
final PageController controller; final PageController controller;
/// The number of items managed by the PageController /// The number of items managed by the PageController
@ -187,24 +161,39 @@ class DotsIndicator extends AnimatedWidget {
final Color color; final Color color;
// The base size of the dots // The base size of the dots
final double dotSize; static const double _kDotSize = 4.0;
final double dotSpacing;
Widget _buildDot(int index) => SizedBox( // The increase in the size of the selected dot
width: dotSpacing, static const double _kMaxZoom = 2.0;
// The distance between the center of each dot
static const double _kDotSpacing = 12.0;
Widget _buildDot(int index) {
var selectedness = Curves.easeOut.transform(
max(
0.0,
1.0 -
((controller.page ?? controller.initialPage).round() - index).abs(),
),
);
var zoom = 1.0 + (_kMaxZoom - 1.0) * selectedness;
return SizedBox(
width: _kDotSpacing,
child: Center( child: Center(
child: Material( child: Material(
color: color: (((controller.page ?? controller.initialPage).round()) == index
(((controller.page ?? controller.initialPage).round()) == index
? color ? color
: color.withAlpha(62)), : color.withAlpha(125)),
type: MaterialType.circle, type: MaterialType.circle,
child: Container( child: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(width: 2, color: dotcolor!),
), ),
width: dotSize, width: _kDotSize * 2 * zoom,
height: dotSize, height: _kDotSize * 2 * zoom,
child: InkWell( child: InkWell(
onTap: () => onPageSelected!.call(index), onTap: () => onPageSelected!.call(index),
), ),
@ -212,6 +201,7 @@ class DotsIndicator extends AnimatedWidget {
), ),
), ),
); );
}
@override @override
Widget build(BuildContext context) => Row( Widget build(BuildContext context) => Row(

View file

@ -6,9 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_introduction_widget/src/config/introduction.dart'; import 'package:flutter_introduction_widget/src/config/introduction.dart';
/// Widget representing the content of an introduction page.
class IntroductionPageContent extends StatelessWidget { class IntroductionPageContent extends StatelessWidget {
/// Constructs an IntroductionPageContent widget.
const IntroductionPageContent({ const IntroductionPageContent({
required this.title, required this.title,
required this.text, required this.text,
@ -18,19 +16,10 @@ class IntroductionPageContent extends StatelessWidget {
super.key, super.key,
}); });
/// The title widget.
final Widget? title; final Widget? title;
/// The text widget.
final Widget? text; final Widget? text;
/// The graphic widget.
final Widget? graphic; final Widget? graphic;
/// The layout style of the content.
final IntroductionLayoutStyle layoutStyle; final IntroductionLayoutStyle layoutStyle;
/// Callback function called when the content is tapped.
final VoidCallback onTap; final VoidCallback onTap;
@override @override

View file

@ -1,12 +1,10 @@
name: flutter_introduction_widget name: flutter_introduction_widget
description: Flutter Introduction Widget for showing a list of introduction pages on a single scrollable page or horizontal pageview description: Flutter Introduction Widget for showing a list of introduction pages on a single scrollable page or horizontal pageview
version: 5.0.0 version: 2.1.0
homepage: https://github.com/Iconica-Development/flutter_introduction_widget homepage: https://github.com/Iconica-Development/flutter_introduction_widget
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=2.18.0 <3.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
@ -22,5 +20,3 @@ dev_dependencies:
ref: 6.0.0 ref: 6.0.0
flutter: flutter:
assets:
- assets/

View file

@ -1,10 +1,7 @@
name: flutter_introduction_workspace name: flutter_introduction_workspace
description: The use case level package using both the flutter_introduction_widget and the flutter_introduction_service combined version: 2.1.0
version: 5.0.0
publish_to: None
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'
dev_dependencies: dev_dependencies:
melos: ">=3.0.1 <7.0.0" melos: ^3.0.1