Compare commits

...

22 commits

Author SHA1 Message Date
Gorter-dev
2333689471
Merge pull request #21 from Iconica-Development/bugfix/design
fix: design
2024-07-30 11:24:30 +02:00
mike doornenbal
9bb789c6da fix: design 2024-07-26 16:12:46 +02:00
Gorter-dev
48a4ea0e66
Merge pull request #19 from Iconica-Development/chore/deploy
chore: ready the package for deployment to the pub server
2024-07-22 15:12:30 +02:00
Bart Ribbers
21c2efa98a chore: ready the package for deployment to the pub server 2024-07-22 15:06:58 +02:00
Bart Ribbers
a5f9d76f79 chore: add fvm configuration to gitignore 2024-07-22 15:02:41 +02:00
mike doornenbal
b3fd9df897
Merge pull request #20 from Iconica-Development/bugfix/default_style
fix: default styling
2024-07-12 16:07:06 +02:00
mike doornenbal
4868f3c548 fix: default styling
added buildcontext to pages so theme can be used and other small additions
2024-07-12 16:00:20 +02:00
Freek van de Ven
89bc007236 Merge pull request #17 from Iconica-Development/dependabot/pub/melos-6.0.0
build(deps): bump melos from 4.1.0 to 6.0.0
2024-06-06 22:18:31 +02:00
Gorter-dev
23569d0e05
Merge pull request #18 from Iconica-Development/bugfix/feedback_userstory
fix: feedback userstory
2024-06-04 16:57:49 +02:00
mike doornenbal
6808ee972d fix: feedback userstory 2024-06-04 16:46:28 +02:00
dependabot[bot]
43a56166c9
build(deps): bump melos from 4.1.0 to 6.0.0
Bumps [melos](https://github.com/invertase/melos/tree/main/packages) from 4.1.0 to 6.0.0.
- [Release notes](https://github.com/invertase/melos/releases)
- [Changelog](https://github.com/invertase/melos/blob/main/CHANGELOG.md)
- [Commits](https://github.com/invertase/melos/commits/melos-v6.0.0/packages)

---
updated-dependencies:
- dependency-name: melos
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 13:22:33 +00:00
Gorter-dev
55727fa76d
Merge pull request #16 from Iconica-Development/3.0.0
feat: add default styling
2024-04-19 11:20:48 +02:00
mike doornenbal
690642ac9d feat: add default styling 2024-04-19 10:05:08 +02:00
Freek van de Ven
15bebcd675
Merge pull request #9 from Iconica-Development/doc/improve-documentation
doc: create documentation for file
2024-03-05 16:51:27 +01:00
Vick Top
dd45f1d9ce doc: create documentation for file 2024-03-05 11:16:42 +01:00
Freek van de Ven
c02d8fe90b
Merge pull request #6 from Iconica-Development/dependabot/pub/melos-4.1.0
build(deps): bump melos from 3.4.0 to 4.1.0
2024-02-18 18:29:38 +01:00
Freek van de Ven
46c0d17ad1
Merge pull request #8 from Iconica-Development/update-component-documentation-workflow-correct
Add component-documentation.yml correct
2024-02-14 08:14:59 +01:00
Vick Top
6e7b76b21e feat(documentation): Create component-documentation.yml workflow file 2024-02-13 13:39:11 +01:00
Vick Top
397974fa07 chore: Remove old component-documentation.yml 2024-02-13 13:39:11 +01:00
Freek van de Ven
d8473c3245
Merge pull request #7 from Iconica-Development/update-component-documentation-workflow
Add component-documentation.yml
2024-02-12 20:23:11 +01:00
Vick Top
3a3af593ad feat(documentation): Create component-documentation.yml workflow file 2024-02-12 19:11:14 +01:00
dependabot[bot]
8258e4d754
build(deps): bump melos from 3.4.0 to 4.1.0
Bumps [melos](https://github.com/invertase/melos/tree/main/packages) from 3.4.0 to 4.1.0.
- [Release notes](https://github.com/invertase/melos/releases)
- [Changelog](https://github.com/invertase/melos/blob/main/CHANGELOG.md)
- [Commits](https://github.com/invertase/melos/commits/HEAD/packages)

---
updated-dependencies:
- dependency-name: melos
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 13:31:35 +00:00
45 changed files with 637 additions and 228 deletions

View file

@ -0,0 +1,14 @@
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,7 +31,6 @@ build/
.metadata
pubspec.lock
pubspec_overrides.yaml
@ -41,3 +40,7 @@ example/web
example/android
example/linux
example/macos
# FVM Version Cache
.fvm/
.fvmrc

View file

@ -1,3 +1,24 @@
## 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
* 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
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,20 +6,35 @@ import 'package:flutter_data_interface/flutter_data_interface.dart';
import 'package:flutter_introduction_interface/src/local_introduction.dart';
abstract class IntroductionInterface extends DataInterface {
/// Constructs an instance of [IntroductionInterface].
///
/// The [token] is used for verification purposes.
IntroductionInterface() : super(token: _token);
static final Object _token = Object();
static IntroductionInterface _instance = LocalIntroductionDataProvider();
/// Retrieves the current instance of [IntroductionInterface].
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) {
DataInterface.verify(instance, _token);
_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});
/// Checks if the introduction should be shown.
///
/// Returns `true` if the introduction should be shown;
/// otherwise, returns `false`.
Future<bool> shouldShow();
}

View file

@ -4,9 +4,17 @@
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 {
/// Constructs an instance of [LocalIntroductionDataProvider].
///
/// Initializes the [hasViewed] flag to `false`.
LocalIntroductionDataProvider();
/// Flag indicating whether the introduction has been viewed or not.
bool hasViewed = false;
@override

View file

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

View file

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

View file

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

View file

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

View file

@ -4,15 +4,36 @@
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 {
/// 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])
: _dataProvider = dataProvider ?? LocalIntroductionDataProvider();
late final IntroductionInterface _dataProvider;
/// Marks the introduction as skipped.
///
/// Calls [_dataProvider.setCompleted] with the 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);
/// Checks whether the introduction should be shown.
///
/// Returns a `Future<bool>` indicating whether the
/// introduction should be shown.
Future<bool> shouldShow() => _dataProvider.shouldShow();
}

View file

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

View file

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

View file

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

View file

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

View file

@ -5,16 +5,27 @@
import 'package:flutter_introduction_interface/flutter_introduction_interface.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 {
/// Constructs an instance of [SharedPreferencesIntroductionDataProvider].
SharedPreferencesIntroductionDataProvider();
SharedPreferences? _prefs;
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 {
await _prefs!.setBool(key, value);
}
/// Initializes the SharedPreferences instance.
Future<void> _init() async {
_prefs ??= await SharedPreferences.getInstance();
}

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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