Compare commits

..

1 commit

33 changed files with 221 additions and 709 deletions

3
.fvmrc Normal file
View file

@ -0,0 +1,3 @@
{
"flutter": "3.22.1"
}

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

1
.gitignore vendored
View file

@ -43,4 +43,3 @@ example/macos
# FVM Version Cache
.fvm/
.fvmrc

View file

@ -1,28 +1,6 @@
## 5.0.0
## 2.0.1
* 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
* Add introductionButtonTextstyles option to IntroductionOptions for styling the introduction button text
* Ready for publishing
## 2.0.0

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

@ -20,9 +20,6 @@ scripts:
upgrade:
run: melos exec -c 1 -- "flutter pub upgrade"
upgrade-major:
run: melos exec -c 1 -- "flutter pub upgrade --major-versions"
create:
# run create in the example folder of flutter_introduction, flutter_introduction_firebase
run: melos exec --scope="*example*" -c 1 -- "flutter create ."

View file

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

View file

@ -17,11 +17,15 @@ dependencies:
flutter_introduction:
path: ../
flutter_introduction_shared_preferences:
path: ../../flutter_introduction_shared_preferences
git:
url: https://github.com/Iconica-Development/flutter_introduction
ref: 2.0.0
path: packages/flutter_introduction_shared_preferences
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis

View file

@ -19,19 +19,10 @@ 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
@ -56,9 +47,7 @@ class _IntroductionState extends State<Introduction> {
// ignore: discarded_futures
future: _service.shouldShow(),
builder: (context, snapshot) {
if (snapshot.data == null ||
snapshot.data! ||
widget.options.mode == IntroductionScreenMode.showAlways) {
if (snapshot.data == null || snapshot.data!) {
return IntroductionScreen(
options: widget.options,
onComplete: () async {

View file

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

View file

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

View file

@ -1,6 +1,6 @@
name: flutter_introduction_firebase
description: Flutter Introduction Page that uses firebase for the pages and some settings
version: 5.0.0
version: 2.0.1
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
@ -9,15 +9,15 @@ environment:
dependencies:
flutter:
sdk: flutter
cloud_firestore: "^4.12.2"
cached_network_image: "^3.3.0"
cloud_firestore: ^5.6.6
cached_network_image: ^3.3.0
flutter_introduction_widget:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: "^5.0.0"
version: ^2.0.1
flutter_introduction_service:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: "^5.0.0"
version: ^2.0.1
dev_dependencies:
flutter_test:

View file

@ -6,35 +6,20 @@ 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,17 +4,9 @@
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,22 +1,22 @@
name: flutter_introduction_interface
description: A new Flutter package project.
version: 5.0.0
version: 2.0.1
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter_data_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: "^1.0.0"
version: ^1.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

View file

@ -4,36 +4,15 @@
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,22 +1,22 @@
name: flutter_introduction_service
description: A new Flutter package project.
version: 5.0.0
version: 2.0.1
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter_introduction_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: "^5.0.0"
version: ^2.0.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis

View file

@ -5,27 +5,16 @@
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,23 +1,23 @@
name: flutter_introduction_shared_preferences
description: A new Flutter package project.
version: 5.0.0
version: 2.0.1
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter_introduction_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: "^5.0.0"
shared_preferences: "^2.2.0"
version: ^2.0.0
shared_preferences: ^2.2.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis

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(
options: IntroductionOptions(
pages: (context) => [
const IntroductionPage(
title: Text('Basic Page'),
text: Text(
pages: [
IntroductionPage(
title: const Text('Basic Page'),
text: const Text(
'A page with some text and a widget in the middle.',
),
graphic: FlutterLogo(size: 100),
graphic: const FlutterLogo(size: 100),
),
const IntroductionPage(
title: Text('Layout Shift'),
text: Text(
IntroductionPage(
title: const Text('Layout Shift'),
text: const Text(
'You can change the layout of a page to mix things up.',
),
graphic: FlutterLogo(size: 100),
graphic: const FlutterLogo(size: 100),
layoutStyle: IntroductionLayoutStyle.imageTop,
),
const IntroductionPage(
title: Text(
IntroductionPage(
title: const Text(
'Decoration',
style: TextStyle(
color: Colors.white,
),
),
decoration: BoxDecoration(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
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',
style: TextStyle(
color: Colors.white,
),
),
graphic: FlutterLogo(
graphic: const FlutterLogo(
size: 100,
),
),
const IntroductionPage(
title: Text(
IntroductionPage(
title: const Text(
'Background Image',
),
text: Text(
text: const Text(
'Add a Decoration with a DecorationImage, to add an background image',
),
decoration: BoxDecoration(
decoration: const BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage(

View file

@ -18,7 +18,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
flutter_lints: ^2.0.0
flutter:

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
import 'package:flutter/material.dart';
import 'package:flutter_introduction_widget/src/config/default_introduction_pages.dart';
enum IntroductionScreenMode { showNever, showAlways, showOnce }
@ -43,7 +42,7 @@ class IntroductionPage {
///
/// The [background] is fully optional and if not provided will show the
/// [ThemeData.colorScheme.background] as default.
const IntroductionPage({
IntroductionPage({
this.title,
this.text,
this.graphic,
@ -60,12 +59,11 @@ class IntroductionPage {
class IntroductionOptions {
const IntroductionOptions({
this.introductionTranslations = const IntroductionTranslations(),
this.introductionButtonTextstyles = const IntroductionButtonTextstyles(),
this.indicatorMode = IndicatorMode.dot,
this.indicatorMode = IndicatorMode.dash,
this.indicatorBuilder,
this.layoutStyle = IntroductionLayoutStyle.imageBottom,
this.pages = defaultIntroductionPages,
this.buttonMode = IntroductionScreenButtonMode.text,
this.layoutStyle = IntroductionLayoutStyle.imageCenter,
this.pages = const [],
this.buttonMode = IntroductionScreenButtonMode.disabled,
this.tapEnabled = false,
this.mode = IntroductionScreenMode.showNever,
this.textAlign = TextAlign.center,
@ -73,9 +71,6 @@ 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),
@ -96,7 +91,7 @@ class IntroductionOptions {
/// List of introduction pages to set the text, icons or images for the
/// 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
/// introduction screen.
@ -197,29 +192,9 @@ class IntroductionOptions {
/// - Finish
final IntroductionTranslations introductionTranslations;
/// The textstyles for all buttons on the introductionpages
///
/// See [IntroductionButtonTextstyles] for more information
/// The following buttons have a textstyle:
/// - Skip
/// - Next
/// - Previous
/// - 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> Function(BuildContext context)? pages,
List<IntroductionPage>? pages,
bool? tapEnabled,
IntroductionScreenButtonMode? buttonMode,
IntroductionLayoutStyle? layoutStyle,
@ -237,7 +212,6 @@ class IntroductionOptions {
Widget Function(BuildContext, VoidCallback, Widget, IntroductionButtonType)?
buttonBuilder,
IntroductionTranslations? introductionTranslations,
IntroductionButtonTextstyles? introductionButtonTextstyles,
}) =>
IntroductionOptions(
mode: mode ?? this.mode,
@ -254,33 +228,19 @@ class IntroductionOptions {
buttonBuilder: buttonBuilder ?? this.buttonBuilder,
introductionTranslations:
introductionTranslations ?? this.introductionTranslations,
introductionButtonTextstyles:
introductionButtonTextstyles ?? this.introductionButtonTextstyles,
);
}
///
class IntroductionTranslations {
const IntroductionTranslations({
this.skipButton = 'Skip',
this.nextButton = 'Next',
this.previousButton = 'Previous',
this.finishButton = 'Get started',
this.skipButton = 'skip',
this.nextButton = 'next',
this.previousButton = 'previous',
this.finishButton = 'finish',
});
final String skipButton;
final String nextButton;
final String previousButton;
final String finishButton;
}
class IntroductionButtonTextstyles {
const IntroductionButtonTextstyles({
this.skipButtonStyle,
this.nextButtonStyle,
this.previousButtonStyle,
this.finishButtonStyle,
});
final TextStyle? skipButtonStyle;
final TextStyle? nextButtonStyle;
final TextStyle? previousButtonStyle;
final TextStyle? finishButtonStyle;
}

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/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,
@ -27,22 +22,12 @@ class MultiPageIntroductionScreen extends StatefulWidget {
super.key,
});
/// Callback function triggered when the introduction is completed.
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;
/// Physics for the scrolling behavior.
final void Function(IntroductionPage)? onNext;
final void Function(IntroductionPage)? onPrevious;
final ScrollPhysics? physics;
/// Introduction options specifying the configuration of the introduction.
final IntroductionOptions options;
@override
@ -50,9 +35,6 @@ 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();
@ -77,7 +59,7 @@ class _MultiPageIntroductionScreenState
@override
Widget build(BuildContext context) {
var pages = widget.options.pages.call(context);
var pages = widget.options.pages;
var translations = widget.options.introductionTranslations;
return Stack(
children: [
@ -150,21 +132,15 @@ 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.symmetric(
vertical: 40,
horizontal: 20,
),
padding: const EdgeInsets.all(32),
child: AnimatedBuilder(
animation: _controller,
builder: (context, _) {
@ -276,14 +252,14 @@ class ExplainerPage extends StatelessWidget {
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: DefaultTextStyle(
style: theme.textTheme.titleMedium!,
style: theme.textTheme.displayMedium!,
child: page.title ?? Text('introduction.$index.title'),
),
),
text: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: DefaultTextStyle(
style: theme.textTheme.bodyMedium!,
style: theme.textTheme.bodyLarge!,
child: page.text ?? Text('introduction.$index.description'),
),
),
@ -342,158 +318,48 @@ 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) ...[
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(
if (previous) ...[
options.buttonBuilder?.call(
context,
_previous,
Text(
translations.previousButton,
style: options.introductionButtonTextstyles
.previousButtonStyle ??
theme.textTheme.bodyMedium,
),
Text(translations.previousButton),
IntroductionButtonType.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 ??
theme.textTheme.bodyMedium,
),
),
),
),
),
),
),
),
),
TextButton(
onPressed: _previous,
child: Text(translations.previousButton),
),
] else
const SizedBox.shrink(),
if (next) ...[
Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 6),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 180,
),
child: options.buttonBuilder?.call(
options.buttonBuilder?.call(
context,
_next,
Text(
translations.nextButton,
style: options.introductionButtonTextstyles
.nextButtonStyle ??
theme.textTheme.bodyMedium,
),
Text(translations.nextButton),
IntroductionButtonType.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 ??
theme.textTheme.bodyMedium,
),
),
),
),
),
),
),
TextButton(
onPressed: _next,
child: Text(translations.nextButton),
),
] else if (last) ...[
Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 6),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 180,
),
child: options.buttonBuilder?.call(
options.buttonBuilder?.call(
context,
() {
onFinish?.call();
},
Text(
translations.finishButton,
style: options.introductionButtonTextstyles
.finishButtonStyle ??
theme.textTheme.bodyMedium,
),
Text(translations.finishButton),
IntroductionButtonType.finish,
) ??
InkWell(
onTap: () {
TextButton(
onPressed: () {
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 ??
theme.textTheme.bodyMedium,
),
),
),
),
),
),
),
child: Text(translations.finishButton),
),
] else ...[
const SizedBox.shrink(),
@ -508,52 +374,19 @@ class IntroductionTwoButtons extends StatelessWidget {
maintainState: true,
maintainInteractivity: false,
child: Align(
child: Flexible(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 180,
),
child: options.buttonBuilder?.call(
context,
() {
onFinish?.call();
},
Text(
translations.finishButton,
style: options.introductionButtonTextstyles
.finishButtonStyle ??
theme.textTheme.bodyMedium,
),
Text(translations.finishButton),
IntroductionButtonType.finish,
) ??
InkWell(
onTap: () {
ElevatedButton(
onPressed: () {
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 ??
theme.textTheme.bodyMedium,
),
),
),
),
),
),
child: Text(translations.finishButton),
),
),
),
@ -586,31 +419,16 @@ 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,
@ -632,59 +450,37 @@ class IntroductionOneButton extends StatelessWidget {
() {
onFinish?.call();
},
Text(
translations.finishButton,
style:
options.introductionButtonTextstyles.finishButtonStyle,
),
Text(translations.finishButton),
IntroductionButtonType.finish,
) ??
TextButton(
onPressed: () {
onFinish?.call();
},
child: Text(
translations.finishButton,
style:
options.introductionButtonTextstyles.finishButtonStyle,
),
child: Text(translations.finishButton),
),
] else ...[
options.buttonBuilder?.call(
context,
_next,
Text(
translations.nextButton,
style: options.introductionButtonTextstyles.nextButtonStyle,
),
Text(translations.nextButton),
IntroductionButtonType.next,
) ??
TextButton(
onPressed: _next,
child: Text(
translations.nextButton,
style: options.introductionButtonTextstyles.nextButtonStyle,
),
child: Text(translations.nextButton),
),
],
if (previous) ...[
options.buttonBuilder?.call(
context,
_previous,
Text(
translations.previousButton,
style: options
.introductionButtonTextstyles.previousButtonStyle,
),
Text(translations.previousButton),
IntroductionButtonType.previous,
) ??
TextButton(
onPressed: _previous,
child: Text(
translations.previousButton,
style: options
.introductionButtonTextstyles.previousButtonStyle,
),
child: Text(translations.previousButton),
),
] else ...[
options.buttonBuilder?.call(
@ -692,22 +488,14 @@ class IntroductionOneButton extends StatelessWidget {
() {
onFinish?.call();
},
Text(
translations.finishButton,
style:
options.introductionButtonTextstyles.finishButtonStyle,
),
Text(translations.finishButton),
IntroductionButtonType.skip,
) ??
TextButton(
onPressed: () {
onFinish?.call();
},
child: Text(
translations.finishButton,
style:
options.introductionButtonTextstyles.finishButtonStyle,
),
child: Text(translations.finishButton),
),
],
],
@ -737,31 +525,16 @@ 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,15 +6,12 @@ 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,28 +4,21 @@
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
Widget build(BuildContext context) {
var theme = Theme.of(context);
var background = this.background ??
BoxDecoration(
color: theme.colorScheme.surface,
);
var background =
this.background ?? BoxDecoration(color: theme.colorScheme.surface);
var size = MediaQuery.of(context).size;
return Container(
width: size.width,

View file

@ -3,6 +3,7 @@
// 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';
@ -15,9 +16,6 @@ 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),
@ -25,29 +23,16 @@ 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;
/// The total number of items managed by the PageController.
final int count;
/// The index of the current item in the PageController.
final Widget Function(
BuildContext,
PageController,
int,
int,
)? indicatorBuilder;
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;
final int count;
@override
Widget build(BuildContext context) {
@ -57,10 +42,9 @@ class Indicator extends StatelessWidget {
return indicatorBuilder!.call(context, controller, index, count);
case IndicatorMode.dot:
return DotsIndicator(
dotSize: dotSize,
dotSpacing: dotSpacing,
controller: controller,
color: options.dotColor ?? theme.colorScheme.primary,
color: theme.colorScheme.primary,
dotcolor: theme.colorScheme.secondary,
itemCount: count,
onPageSelected: (int page) {
unawaited(
@ -74,9 +58,8 @@ class Indicator extends StatelessWidget {
);
case IndicatorMode.dash:
return DashIndicator(
color: theme.colorScheme.primary,
controller: controller,
selectedColor: options.dotColor ?? theme.colorScheme.primary,
selectedColor: theme.colorScheme.primary,
itemCount: count,
onPageSelected: (int page) {
unawaited(
@ -101,20 +84,10 @@ 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() {
@ -164,15 +137,16 @@ 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
@ -187,24 +161,39 @@ class DotsIndicator extends AnimatedWidget {
final Color color;
// The base size of the dots
final double dotSize;
final double dotSpacing;
static const double _kDotSize = 4.0;
Widget _buildDot(int index) => SizedBox(
width: 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,
child: Center(
child: Material(
color:
(((controller.page ?? controller.initialPage).round()) == index
color: (((controller.page ?? controller.initialPage).round()) == index
? color
: color.withAlpha(62)),
: color.withAlpha(125)),
type: MaterialType.circle,
child: Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(width: 2, color: dotcolor!),
),
width: dotSize,
height: dotSize,
width: _kDotSize * 2 * zoom,
height: _kDotSize * 2 * zoom,
child: InkWell(
onTap: () => onPageSelected!.call(index),
),
@ -212,6 +201,7 @@ class DotsIndicator extends AnimatedWidget {
),
),
);
}
@override
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';
/// Widget representing the content of an introduction page.
class IntroductionPageContent extends StatelessWidget {
/// Constructs an IntroductionPageContent widget.
const IntroductionPageContent({
required this.title,
required this.text,
@ -18,19 +16,10 @@ 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,13 +1,11 @@
name: flutter_introduction_widget
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.0.1
homepage: https://github.com/Iconica-Development/flutter_introduction_widget
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
@ -16,11 +14,10 @@ dependencies:
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:
assets:
- assets/

View file

@ -1,10 +1,7 @@
name: flutter_introduction_workspace
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
version: 2.0.0
environment:
sdk: '>=3.1.0 <4.0.0'
dev_dependencies:
melos: ">=3.0.1 <7.0.0"
melos: ^3.0.1