mirror of
https://github.com/Iconica-Development/flutter_introduction.git
synced 2025-05-18 19:43:44 +02:00
Merge pull request #3 from Iconica-Development/feature/melos-variant-flutter-introduction
Feature/melos variant flutter introduction
This commit is contained in:
commit
d4037160ce
66 changed files with 2807 additions and 767 deletions
10
.github/dependabot.yaml
vendored
Normal file
10
.github/dependabot.yaml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
12
.github/workflows/melos-component-ci.yml
vendored
Normal file
12
.github/workflows/melos-component-ci.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: Iconica Standard Melos CI Workflow
|
||||
# Workflow Caller version: 1.0.0
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-global-iconica-workflow:
|
||||
uses: Iconica-Development/.github/.github/workflows/melos-ci.yml@master
|
||||
secrets: inherit
|
||||
permissions: write-all
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -31,4 +31,13 @@ build/
|
|||
|
||||
.metadata
|
||||
|
||||
|
||||
pubspec.lock
|
||||
|
||||
pubspec_overrides.yaml
|
||||
|
||||
example/ios
|
||||
example/web
|
||||
example/android
|
||||
example/linux
|
||||
example/macos
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
## 1.0.0
|
||||
## 2.0.0
|
||||
|
||||
* Update introduction_widget and introduction_service
|
||||
* Initial release of working flutter_introduction mono project.
|
||||
|
||||
## 0.0.1
|
||||
|
||||
* Initial release.
|
||||
* Initial release of combined flutter_introduction melos project
|
||||
|
|
194
CONTRIBUTING.md
Normal file
194
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,194 @@
|
|||
# Contributing
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
All types of contributions are encouraged and valued.
|
||||
See the [Table of Contents](#table-of-contents) for different ways to help and details about how we handle them.
|
||||
Please make sure to read the relevant section before making your contribution.
|
||||
It will make it a lot easier for us maintainers and smooth out the experience for all involved.
|
||||
Iconica looks forward to your contributions. 🎉
|
||||
|
||||
## Table of contents
|
||||
- [Code of conduct](#code-of-conduct)
|
||||
- [I Have a Question](#i-have-a-question)
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Contributing code](#contributing-code)
|
||||
|
||||
## Code of conduct
|
||||
|
||||
### Legal notice
|
||||
When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
|
||||
All accepted pull requests and other additions to this project will be considered intellectual property of Iconica.
|
||||
|
||||
All repositories should be kept clean of jokes, easter eggs and other unnecessary additions.
|
||||
|
||||
## I have a question
|
||||
|
||||
If you want to ask a question, we assume that you have read the available documentation found within the code.
|
||||
Before you ask a question, it is best to search for existing issues that might help you.
|
||||
In case you have found a suitable issue but still need clarification, you can ask your question
|
||||
It is also advisable to search the internet for answers first.
|
||||
|
||||
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||
|
||||
- Open an issue.
|
||||
- Provide as much context as you can about what you're running into.
|
||||
|
||||
We will then take care of the issue as soon as possible.
|
||||
|
||||
## I want to contribute
|
||||
|
||||
### Reporting bugs
|
||||
|
||||
<!-- omit in toc -->
|
||||
**Before submitting a bug report**
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information.
|
||||
Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report.
|
||||
Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error.
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if users outside of Iconica have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Time and date of occurance
|
||||
- Describe the expected result and actual result
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions? Describe all steps that lead to the bug.
|
||||
|
||||
Once it's filed:
|
||||
|
||||
- The project team will label the issue accordingly.
|
||||
- A team member will try to reproduce the issue with your provided steps.
|
||||
If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for additional information.
|
||||
- If the team is able to reproduce the issue, it will be moved into the backlog, as well as marked with a priority, and the issue will be left to be [implemented by someone](#contributing-code).
|
||||
|
||||
### Contributing code
|
||||
|
||||
When you start working on your contribution, make sure you are aware of the relevant documentation and the functionality of the component you are working on.
|
||||
|
||||
When writing code, follow the style guidelines set by Dart: [Effective Dart](https://Dart.dev/guides/language/effective-Dart). This contains most information you will need to write clean Dart code that is well documented.
|
||||
|
||||
**Documentation**
|
||||
|
||||
As Effective Dart indicates, documenting your public methods with Dart doc comments is recommended.
|
||||
Aside from Effective Dart, we require specific information in the documentation of a method:
|
||||
|
||||
At the very least, your documentation should first name what the code does, then followed below by requirements for calling the method, the result of the method.
|
||||
Any references to internal variables or other methods should be done through [var] to indicate a reference.
|
||||
|
||||
If the method or class is complex enough (determined by the reviewers) an example is required.
|
||||
If unsure, add an example in the docs using code blocks.
|
||||
|
||||
For classes and methods, document the individual parameters with their implications.
|
||||
|
||||
> Tip: Remember that the shortest documentation can be written by having good descriptive names in the first place.
|
||||
|
||||
An example:
|
||||
```Dart
|
||||
library iconica_utilities.bidirectional_sorter;
|
||||
|
||||
part 'sorter.Dart';
|
||||
part 'enum.Dart';
|
||||
|
||||
/// Generic sort method, allow sorting of list with primitives or complex types.
|
||||
/// Uses [SortDirection] to determine the direction, either Ascending or Descending,
|
||||
/// Gets called on [List] toSort of type [T] which cannot be shorter than 2.
|
||||
/// Optionally for complex types a [Comparable] [Function] can be given to compare complex types.
|
||||
/// ```
|
||||
/// List<TestObject> objects = [];
|
||||
/// for (int i = 0; i < 10; i++) {
|
||||
/// objects.add(TestObject(name: "name", id: i));
|
||||
/// }
|
||||
///
|
||||
/// sort<TestObject>(
|
||||
/// SortDirection.descending, objects, (object) => object.id);
|
||||
///
|
||||
/// ```
|
||||
/// In the above example a list of TestObjects is created, and then sorted in descending order.
|
||||
/// If the implementation of TestObject is as following:
|
||||
/// ```
|
||||
/// class TestObject {
|
||||
/// final String name;
|
||||
/// final int id;
|
||||
///
|
||||
/// TestObject({required this.name, required this.id});
|
||||
/// }
|
||||
/// ```
|
||||
/// And the list is logged to the console, the following will appear:
|
||||
/// ```
|
||||
/// [name9, name8, name7, name6, name5, name4, name3, name2, name1, name0]
|
||||
/// ```
|
||||
|
||||
void sort<T>(
|
||||
/// Determines the sorting direction, can be either Ascending or Descending
|
||||
SortDirection sortDirection,
|
||||
|
||||
/// Incoming list, which gets sorted
|
||||
List<T> toSort, [
|
||||
|
||||
/// Optional comparable, which is only necessary for complex types
|
||||
SortFieldGetter<T>? sortValueCallback,
|
||||
]) {
|
||||
if (toSort.length < 2) return;
|
||||
assert(
|
||||
toSort.whereType<Comparable>().isNotEmpty || sortValueCallback != null);
|
||||
BidirectionalSorter<T>(
|
||||
sortInstructions: <SortInstruction<T>>[
|
||||
SortInstruction(
|
||||
sortValueCallback ?? (t) => t as Comparable, sortDirection),
|
||||
],
|
||||
).sort(toSort);
|
||||
}
|
||||
|
||||
/// same functionality as [sort] but with the added functionality
|
||||
/// of sorting multiple values
|
||||
void sortMulti<T>(
|
||||
/// Incoming list, which gets sorted
|
||||
List<T> toSort,
|
||||
|
||||
/// list of comparables to sort multiple values at once,
|
||||
/// priority based on index
|
||||
List<SortInstruction<T>> sortValueCallbacks,
|
||||
) {
|
||||
if (toSort.length < 2) return;
|
||||
assert(sortValueCallbacks.isNotEmpty);
|
||||
BidirectionalSorter<T>(
|
||||
sortInstructions: sortValueCallbacks,
|
||||
).sort(toSort);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Tests**
|
||||
|
||||
For each public method that was created, excluding widgets, which contains any form of logic (e.g. Calculations, predicates or major side-effects) tests are required.
|
||||
|
||||
A set of tests is written for each method, covering at least each path within the method. For example:
|
||||
|
||||
```Dart
|
||||
void foo() {
|
||||
try {
|
||||
var bar = doSomething();
|
||||
if (bar) {
|
||||
doSomethingElse();
|
||||
} else {
|
||||
doSomethingCool();
|
||||
}
|
||||
} catch (_) {
|
||||
displayError();
|
||||
}
|
||||
}
|
||||
```
|
||||
The method above should result in 3 tests:
|
||||
|
||||
1. A test for the path leading to displayError by the cause of an exception
|
||||
2. A test for if bar is true, resulting in doSomethingElse()
|
||||
3. A test for if bar is false, resulting in the doSomethingCool() method being called.
|
||||
|
||||
This means that we require 100% coverage of each method you test.
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2022 Iconica, All rights reserved.
|
||||
Copyright (c) 2023 Iconica, All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
|
|
54
README.md
54
README.md
|
@ -1,39 +1,39 @@
|
|||
<!--
|
||||
This README describes the package. If you publish this package to pub.dev,
|
||||
this README's contents appear on the landing page for your package.
|
||||
# Flutter Introduction
|
||||
|
||||
For information about how to write a good package README, see the guide for
|
||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||

|
||||
|
||||
For general information about developing packages, see the Dart guide for
|
||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||
and the Flutter guide for
|
||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||
-->
|
||||
Monorepo for the Flutter introduction package. Including the following packages:
|
||||
- Flutter Introduction
|
||||
Main package for Flutter Introduction including an example.
|
||||
|
||||
TODO: Put a short description of the package here that helps potential users
|
||||
know whether this package might be useful for them.
|
||||
- Flutter Introduction Firebase
|
||||
Package to provide content from firebase.
|
||||
|
||||
## Features
|
||||
- Flutter Introduction Interface
|
||||
Interface regarding data for the Introduction widget, like whether to show the introduction or not.
|
||||
|
||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||
- Flutter Introduction Service
|
||||
Service to handle actions done in the Introduction widget.
|
||||
|
||||
## Getting started
|
||||
- Flutter Introduction Shared Preferences
|
||||
Implementation of the interface with the use of shared preferences.
|
||||
|
||||
TODO: List prerequisites and provide or point to information on how to
|
||||
start using the package.
|
||||
- Flutter Introduction Widget
|
||||
The actual widget showing the Introduction widget.
|
||||
|
||||
## Usage
|
||||
## How to use
|
||||
The simple way to use this package is by using the flutter_introduction package. An example is included if needed.
|
||||
|
||||
TODO: Include short and useful examples for package users. Add longer examples
|
||||
to `/example` folder.
|
||||
If needed a custom implementation can be made on the interface if the shared preferences doesn't suffice.
|
||||
|
||||
```dart
|
||||
const like = 'sample';
|
||||
```
|
||||
## Issues
|
||||
|
||||
## Additional information
|
||||
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_introduction/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
|
||||
|
||||
TODO: Tell users more about the package: where to find more information, how to
|
||||
contribute to the package, how to file issues, what response they can expect
|
||||
from the package authors, and more.
|
||||
## 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).
|
||||
|
||||
## Author
|
||||
|
||||
This `flutter_introduction` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>
|
|
@ -1,4 +0,0 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
|
@ -1,100 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction/flutter_introduction.dart';
|
||||
import 'package:flutter_introduction_shared_preferences/flutter_introduction_shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key});
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
IntroductionService service =
|
||||
IntroductionService(SharedPreferencesIntroductionDataProvider());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Introduction(
|
||||
options: IntroductionOptions(
|
||||
pages: [
|
||||
IntroductionPage(
|
||||
title: const Text('First page'),
|
||||
text: const Text('Wow a page'),
|
||||
graphic: const FlutterLogo(),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text('Second page'),
|
||||
text: const Text('Another page'),
|
||||
graphic: const FlutterLogo(),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text('Third page'),
|
||||
text: const Text('The final page of this app'),
|
||||
graphic: const FlutterLogo(),
|
||||
),
|
||||
],
|
||||
introductionTranslations: const IntroductionTranslations(
|
||||
skipButton: 'Skip it!',
|
||||
nextButton: 'Previous',
|
||||
previousButton: 'Next',
|
||||
finishButton: 'To the app!',
|
||||
),
|
||||
tapEnabled: true,
|
||||
displayMode: IntroductionDisplayMode.multiPageHorizontal,
|
||||
buttonMode: IntroductionScreenButtonMode.text,
|
||||
indicatorMode: IndicatorMode.dash,
|
||||
skippable: true,
|
||||
buttonBuilder: (context, onPressed, child) =>
|
||||
ElevatedButton(onPressed: onPressed, child: child),
|
||||
),
|
||||
service: service,
|
||||
navigateTo: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return const Home();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Home(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
const Home({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
|
@ -1,542 +0,0 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "61.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: b003c3098049a51720352d219b0bb5f219b60fbfb68e7a4748139a06a5676515
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: d7a9cd57c215bdf8d502772447aa6b52a8ab3f956d25d5fdea6ef1df2d2dad60
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.4.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "02ce3596b459c666530f045ad6f96209474e8fee6e4855940a3cee65fb872ec5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: "196284f26f69444b7f5c50692b55ec25da86d9e500451dc09333bf2e3ad69259"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_data_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "1.0.0"
|
||||
resolved-ref: "500ed1d08095b33387ae3aa4ed1a2ad4d2fb2ac3"
|
||||
url: "https://github.com/Iconica-Development/flutter_data_interface.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
flutter_introduction:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
flutter_introduction_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "1.0.0"
|
||||
resolved-ref: "2bb986c60a4ce7370a46c5db4cc3bc82a7f96884"
|
||||
url: "https://github.com/Iconica-Development/flutter_introduction_interface.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
flutter_introduction_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "1.0.0"
|
||||
resolved-ref: d8af4b73f1c951dd5fb72d24b07d854ee64a7ee1
|
||||
url: "https://github.com/Iconica-Development/flutter_introduction_service.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
flutter_introduction_shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "1.0.0"
|
||||
resolved-ref: fd976b68e0b44bc6fec7d6570f1e410a98ae3d61
|
||||
url: "https://github.com/Iconica-Development/flutter_introduction_shared_preferences.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
flutter_introduction_widget:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "3.0.0"
|
||||
resolved-ref: ae72ec10ea33eea5afbe62913992bf5215b4ad78
|
||||
url: "https://github.com/Iconica-Development/flutter_introduction_widget.git"
|
||||
source: git
|
||||
version: "3.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c51b4fdfee4d281f49b8c957f1add91b815473597f76bcf07377987f66a55729
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c0bbfe94d46aedf9b8b3e695cf3bd48c8e14b35e3b2c639e0aa7755d589ba946
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
mockito:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mockito
|
||||
sha256: "8b46d7eb40abdda92d62edd01546051f0c27365e65608c284de336dccfef88cc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.1"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.11"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.6"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "816c1a640e952d213ddd223b3e7aafae08cd9f8e1f6864eed304cc13b0272b07"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "85f8c7d6425dff95475db618404732f034c87fe23efe05478cea50520a2517a3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.5"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: e42dfcc48f67618344da967b10f62de57e04bae01d9d3af4c2596f3712a88c99
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.4"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=3.0.0-0 <4.0.0"
|
||||
flutter: ">=3.3.0"
|
|
@ -1,34 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
BIN
flutter_introduction_widget.gif
Normal file
BIN
flutter_introduction_widget.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 MiB |
39
melos.yaml
Normal file
39
melos.yaml
Normal file
|
@ -0,0 +1,39 @@
|
|||
name: flutter_introduction
|
||||
|
||||
packages:
|
||||
- packages/**
|
||||
|
||||
command:
|
||||
version:
|
||||
branch: master
|
||||
|
||||
scripts:
|
||||
lint:all:
|
||||
run: dart run melos run analyze && dart run melos run format-check
|
||||
description: Run all static analysis checks.
|
||||
|
||||
get:
|
||||
run: |
|
||||
melos exec -c 1 -- "flutter pub get"
|
||||
melos exec --scope="*example*" -c 1 -- "flutter pub get"
|
||||
|
||||
upgrade:
|
||||
run: melos exec -c 1 -- "flutter pub upgrade"
|
||||
|
||||
create:
|
||||
# run create in the example folder of flutter_introduction, flutter_introduction_firebase
|
||||
run: melos exec --scope="*example*" -c 1 -- "flutter create ."
|
||||
|
||||
analyze:
|
||||
run: |
|
||||
dart run melos exec -c 1 -- \
|
||||
flutter analyze --fatal-infos
|
||||
description: Run `flutter analyze` for all packages.
|
||||
|
||||
format:
|
||||
run: dart run melos exec dart format .
|
||||
description: Run `dart format` for all packages.
|
||||
|
||||
format-check:
|
||||
run: dart run melos exec dart format . --set-exit-if-changed
|
||||
description: Run `dart format` checks for all packages.
|
41
packages/flutter_introduction/.gitignore
vendored
Normal file
41
packages/flutter_introduction/.gitignore
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# SPDX-FileCopyrightText: 2022 Iconica
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
# /pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
.flutter-plugins-dependencies
|
||||
.flutter-plugins
|
||||
.metadata
|
||||
|
||||
pubspec.lock
|
||||
|
||||
pubspec_overrides.yaml
|
9
packages/flutter_introduction/analysis_options.yaml
Normal file
9
packages/flutter_introduction/analysis_options.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
92
packages/flutter_introduction/example/lib/main.dart
Normal file
92
packages/flutter_introduction/example/lib/main.dart
Normal file
|
@ -0,0 +1,92 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction/flutter_introduction.dart';
|
||||
import 'package:flutter_introduction_shared_preferences/flutter_introduction_shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key});
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
IntroductionService service =
|
||||
IntroductionService(SharedPreferencesIntroductionDataProvider());
|
||||
|
||||
@override
|
||||
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(),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text('Second page'),
|
||||
text: const Text('Another page'),
|
||||
graphic: const FlutterLogo(),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text('Third page'),
|
||||
text: const Text('The final page of this app'),
|
||||
graphic: const FlutterLogo(),
|
||||
),
|
||||
],
|
||||
introductionTranslations: const IntroductionTranslations(
|
||||
skipButton: 'Skip it!',
|
||||
nextButton: 'Next',
|
||||
previousButton: 'Previous',
|
||||
finishButton: 'To the app!',
|
||||
),
|
||||
tapEnabled: true,
|
||||
displayMode: IntroductionDisplayMode.multiPageHorizontal,
|
||||
buttonMode: IntroductionScreenButtonMode.text,
|
||||
indicatorMode: IndicatorMode.dash,
|
||||
skippable: true,
|
||||
buttonBuilder: (context, onPressed, child, type) =>
|
||||
ElevatedButton(onPressed: onPressed, child: child),
|
||||
),
|
||||
service: service,
|
||||
navigateTo: () async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const Home(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Home(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
const Home({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container();
|
||||
}
|
|
@ -18,13 +18,18 @@ dependencies:
|
|||
path: ../
|
||||
flutter_introduction_shared_preferences:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction_shared_preferences.git
|
||||
ref: 1.0.0
|
||||
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
|
||||
ref: 6.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
|
@ -2,14 +2,12 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
library flutter_introduction;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction_service/flutter_introduction_service.dart';
|
||||
import 'package:flutter_introduction_widget/flutter_introduction_widget.dart';
|
||||
|
||||
export 'package:flutter_introduction_widget/flutter_introduction_widget.dart';
|
||||
export 'package:flutter_introduction_service/flutter_introduction_service.dart';
|
||||
export 'package:flutter_introduction_widget/flutter_introduction_widget.dart';
|
||||
|
||||
class Introduction extends StatefulWidget {
|
||||
const Introduction({
|
||||
|
@ -21,7 +19,7 @@ class Introduction extends StatefulWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
final Function navigateTo;
|
||||
final VoidCallback navigateTo;
|
||||
final IntroductionService? service;
|
||||
final IntroductionOptions options;
|
||||
final ScrollPhysics? physics;
|
||||
|
@ -45,20 +43,20 @@ class _IntroductionState extends State<Introduction> {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
Widget build(BuildContext context) => FutureBuilder(
|
||||
// ignore: discarded_futures
|
||||
future: _service.shouldShow(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null || snapshot.data!) {
|
||||
return IntroductionScreen(
|
||||
options: widget.options,
|
||||
onComplete: () async {
|
||||
_service.onComplete();
|
||||
await _service.onComplete();
|
||||
widget.navigateTo();
|
||||
},
|
||||
physics: widget.physics,
|
||||
onSkip: () async {
|
||||
_service.onComplete();
|
||||
await _service.onComplete();
|
||||
widget.navigateTo();
|
||||
},
|
||||
);
|
||||
|
@ -70,5 +68,4 @@ class _IntroductionState extends State<Introduction> {
|
|||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
33
packages/flutter_introduction/pubspec.yaml
Normal file
33
packages/flutter_introduction/pubspec.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: flutter_introduction
|
||||
description: Combined Package of Flutter Introduction Widget and Flutter Introduction Service
|
||||
version: 2.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_introduction_widget:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction
|
||||
ref: 2.0.0
|
||||
path: packages/flutter_introduction_widget
|
||||
flutter_introduction_service:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction
|
||||
ref: 2.0.0
|
||||
path: packages/flutter_introduction_service
|
||||
|
||||
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:
|
41
packages/flutter_introduction_firebase/.gitignore
vendored
Normal file
41
packages/flutter_introduction_firebase/.gitignore
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# SPDX-FileCopyrightText: 2022 Iconica
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
# /pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
.flutter-plugins-dependencies
|
||||
.flutter-plugins
|
||||
.metadata
|
||||
|
||||
pubspec.lock
|
||||
|
||||
pubspec_overrides.yaml
|
0
packages/flutter_introduction_firebase/README.md
Normal file
0
packages/flutter_introduction_firebase/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -0,0 +1,9 @@
|
|||
// SPDX-FileCopyrightText: 2023 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
///
|
||||
library flutter_introduction_firebase;
|
||||
|
||||
export 'src/firebase_service.dart';
|
||||
export 'src/introduction_widget.dart';
|
|
@ -0,0 +1,75 @@
|
|||
// SPDX-FileCopyrightText: 2023 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction_firebase/src/introduction_page.dart';
|
||||
|
||||
const _introductionDocumentRef = 'introduction/introduction';
|
||||
|
||||
class FirebaseIntroductionService {
|
||||
FirebaseIntroductionService({
|
||||
DocumentReference<Map<String, dynamic>>? documentRef,
|
||||
}) : _documentRef = documentRef ??
|
||||
FirebaseFirestore.instance.doc(_introductionDocumentRef);
|
||||
|
||||
final DocumentReference<Map<String, dynamic>> _documentRef;
|
||||
List<IntroductionPageData> _pages = [];
|
||||
|
||||
Future<List<IntroductionPageData>> getIntroductionPages() async {
|
||||
if (_pages.isNotEmpty) return _pages;
|
||||
var pagesDocuments =
|
||||
await _documentRef.collection('pages').orderBy('order').get();
|
||||
return _pages = pagesDocuments.docs.map((document) {
|
||||
var data = document.data();
|
||||
// convert Map<String, dynamic> to Map<String, String>
|
||||
var title = data['title'] != null
|
||||
? (data['title'] as Map<String, dynamic>).cast<String, String>()
|
||||
: <String, String>{};
|
||||
var content = data['content'] != null
|
||||
? (data['content'] as Map<String, dynamic>).cast<String, String>()
|
||||
: <String, String>{};
|
||||
return IntroductionPageData(
|
||||
title: title,
|
||||
content: content,
|
||||
contentImage: data['image'] as String?,
|
||||
backgroundImage: data['background_image'] as String?,
|
||||
// the color is stored as a hex string
|
||||
backgroundColor: data['background_color'] != null
|
||||
? Color(int.parse(data['background_color'] as String, radix: 16))
|
||||
: null,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<bool> introductionIsDisabled() async {
|
||||
var document = await _documentRef.get();
|
||||
return document.data()!['disabled'] as bool? ?? false;
|
||||
}
|
||||
|
||||
Future<bool> shouldAlwaysShowIntroduction() async {
|
||||
var document = await _documentRef.get();
|
||||
return document.data()!['always_show'] as bool? ?? false;
|
||||
}
|
||||
|
||||
Future<void> loadIntroductionPages(
|
||||
BuildContext context,
|
||||
) async {
|
||||
for (var page in _pages) {
|
||||
if (context.mounted && page.backgroundImage != null) {
|
||||
await precacheImage(
|
||||
CachedNetworkImageProvider(page.backgroundImage!),
|
||||
context,
|
||||
);
|
||||
}
|
||||
if (context.mounted && page.contentImage != null) {
|
||||
await precacheImage(
|
||||
CachedNetworkImageProvider(page.contentImage!),
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-FileCopyrightText: 2023 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@immutable
|
||||
class IntroductionPageData {
|
||||
const IntroductionPageData({
|
||||
required this.title,
|
||||
required this.content,
|
||||
this.contentImage,
|
||||
this.backgroundImage,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The title of the introduction page in different languages
|
||||
final Map<String, String> title;
|
||||
|
||||
/// The content of the introduction page in different languages
|
||||
final Map<String, String> content;
|
||||
|
||||
/// The imageUrl of the graphic on the introduction page
|
||||
final String? contentImage;
|
||||
|
||||
/// The imageUrl of the background image of the introduction page
|
||||
final String? backgroundImage;
|
||||
|
||||
/// Optional background color of the introduction page
|
||||
/// (defaults to transparent)
|
||||
final Color? backgroundColor;
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
// SPDX-FileCopyrightText: 2023 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// ignore_for_file: discarded_futures
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction_firebase/flutter_introduction_firebase.dart';
|
||||
import 'package:flutter_introduction_service/flutter_introduction_service.dart';
|
||||
|
||||
export 'package:flutter_introduction_firebase/src/introduction_page.dart';
|
||||
export 'package:flutter_introduction_widget/flutter_introduction_widget.dart';
|
||||
|
||||
class IntroductionFirebase extends StatefulWidget {
|
||||
const IntroductionFirebase({
|
||||
required this.options,
|
||||
required this.onComplete,
|
||||
this.decoration,
|
||||
this.layoutStyle,
|
||||
this.titleBuilder,
|
||||
this.contentBuilder,
|
||||
this.imageBuilder,
|
||||
this.onSkip,
|
||||
this.firebaseService,
|
||||
this.introductionService,
|
||||
this.physics,
|
||||
this.child,
|
||||
this.languageCodeOverride,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The options used to build the introduction screen
|
||||
final IntroductionOptions options;
|
||||
|
||||
/// The service used to determine if the introduction screen should be shown
|
||||
final IntroductionService? introductionService;
|
||||
|
||||
/// The service used to get the introduction pages
|
||||
final FirebaseIntroductionService? firebaseService;
|
||||
|
||||
/// A function called when the introductionSceen changes
|
||||
final VoidCallback onComplete;
|
||||
|
||||
/// A function called when the introductionScreen is skipped
|
||||
final VoidCallback? onSkip;
|
||||
|
||||
/// How the single child scroll view should respond to scrolling
|
||||
final ScrollPhysics? physics;
|
||||
|
||||
/// The widget to show when the introduction screen is loading
|
||||
final Widget? child;
|
||||
|
||||
/// The decoration of an introduction page if it doesn't have
|
||||
/// a backgroundImage or backgroundColor
|
||||
final BoxDecoration? decoration;
|
||||
|
||||
/// The layout style of all the introduction pages
|
||||
final IntroductionLayoutStyle? layoutStyle;
|
||||
|
||||
/// The builder used to build the title of the introduction page
|
||||
final Widget Function(String)? titleBuilder;
|
||||
|
||||
/// The builder used to build the content of the introduction page
|
||||
final Widget Function(String)? contentBuilder;
|
||||
|
||||
/// The builder used to build the image of the introduction page
|
||||
final Widget Function(String)? imageBuilder;
|
||||
|
||||
/// Use this to override the language code that is in the context
|
||||
/// used for showing the introduction in a different language
|
||||
final String? languageCodeOverride;
|
||||
|
||||
@override
|
||||
State<IntroductionFirebase> createState() => _IntroductionState();
|
||||
}
|
||||
|
||||
class _IntroductionState extends State<IntroductionFirebase> {
|
||||
late IntroductionService _service;
|
||||
late FirebaseIntroductionService _firebaseService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.introductionService == null) {
|
||||
_service = IntroductionService();
|
||||
} else {
|
||||
_service = widget.introductionService!;
|
||||
}
|
||||
if (widget.firebaseService == null) {
|
||||
_firebaseService = FirebaseIntroductionService();
|
||||
} else {
|
||||
_firebaseService = widget.firebaseService!;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Future<bool> shouldShow() async =>
|
||||
!await _firebaseService.introductionIsDisabled() &&
|
||||
(await _service.shouldShow() ||
|
||||
await _firebaseService.shouldAlwaysShowIntroduction());
|
||||
var languageCode = widget.languageCodeOverride ??
|
||||
Localizations.localeOf(context).languageCode;
|
||||
|
||||
return FutureBuilder(
|
||||
future: shouldShow(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data != null && snapshot.data!) {
|
||||
return FutureBuilder(
|
||||
future: _firebaseService.getIntroductionPages(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.data != null &&
|
||||
snapshot.data is List<IntroductionPageData>) {
|
||||
return IntroductionScreen(
|
||||
options: widget.options.copyWith(
|
||||
pages: snapshot.data?.map(
|
||||
(e) {
|
||||
var title = e.title.isEmpty
|
||||
? ''
|
||||
: e.title.containsKey(languageCode)
|
||||
? e.title[languageCode]!
|
||||
: e.title.values.first;
|
||||
var content = e.content.isEmpty
|
||||
? ''
|
||||
: e.content.containsKey(languageCode)
|
||||
? e.content[languageCode]!
|
||||
: e.content.values.first;
|
||||
return IntroductionPage(
|
||||
title:
|
||||
widget.titleBuilder?.call(title) ?? Text(title),
|
||||
graphic: e.contentImage != null &&
|
||||
e.contentImage!.isNotEmpty
|
||||
? widget.imageBuilder?.call(e.contentImage!) ??
|
||||
CachedNetworkImage(imageUrl: e.contentImage!)
|
||||
: null,
|
||||
text: widget.contentBuilder?.call(content) ??
|
||||
Text(content),
|
||||
decoration: widget.decoration?.copyWith(
|
||||
color: e.backgroundColor,
|
||||
image: e.backgroundImage != null &&
|
||||
e.backgroundImage!.isNotEmpty
|
||||
? DecorationImage(
|
||||
image: CachedNetworkImageProvider(
|
||||
e.backgroundImage!,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
layoutStyle: widget.layoutStyle,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
onComplete: () async {
|
||||
await _service.onComplete();
|
||||
widget.onComplete();
|
||||
},
|
||||
physics: widget.physics,
|
||||
onSkip: () async {
|
||||
await _service.onSkip();
|
||||
widget.onComplete();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return widget.child ?? const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (snapshot.hasData && snapshot.data != null && !snapshot.data!) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _service.onComplete();
|
||||
widget.onComplete();
|
||||
});
|
||||
}
|
||||
return widget.child ?? const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
34
packages/flutter_introduction_firebase/pubspec.yaml
Normal file
34
packages/flutter_introduction_firebase/pubspec.yaml
Normal file
|
@ -0,0 +1,34 @@
|
|||
name: flutter_introduction_firebase
|
||||
description: Flutter Introduction Page that uses firebase for the pages and some settings
|
||||
version: 2.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=3.1.5 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
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.0.0
|
||||
path: packages/flutter_introduction_widget
|
||||
flutter_introduction_service:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction
|
||||
ref: 2.0.0
|
||||
path: packages/flutter_introduction_service
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_iconica_analysis:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||
ref: 6.0.0
|
||||
|
||||
flutter:
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-FileCopyrightText: 2023 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('test', () {
|
||||
expect(true, true);
|
||||
});
|
||||
}
|
32
packages/flutter_introduction_interface/.gitignore
vendored
Normal file
32
packages/flutter_introduction_interface/.gitignore
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
.metadata
|
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -0,0 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
export './src/introduction_interface.dart';
|
||||
export './src/local_introduction.dart';
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter_data_interface/flutter_data_interface.dart';
|
||||
import 'package:flutter_introduction_interface/src/local_introduction.dart';
|
||||
|
||||
abstract class IntroductionInterface extends DataInterface {
|
||||
IntroductionInterface() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static IntroductionInterface _instance = LocalIntroductionDataProvider();
|
||||
|
||||
static IntroductionInterface get instance => _instance;
|
||||
|
||||
static set instance(IntroductionInterface instance) {
|
||||
DataInterface.verify(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<void> setCompleted({bool value = true});
|
||||
|
||||
Future<bool> shouldShow();
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter_introduction_interface/src/introduction_interface.dart';
|
||||
|
||||
class LocalIntroductionDataProvider extends IntroductionInterface {
|
||||
LocalIntroductionDataProvider();
|
||||
|
||||
bool hasViewed = false;
|
||||
|
||||
@override
|
||||
Future<void> setCompleted({bool value = true}) async {
|
||||
hasViewed = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> shouldShow() async => hasViewed;
|
||||
}
|
27
packages/flutter_introduction_interface/pubspec.yaml
Normal file
27
packages/flutter_introduction_interface/pubspec.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: flutter_introduction_interface
|
||||
description: A new Flutter package project.
|
||||
version: 2.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.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
|
||||
|
||||
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:
|
32
packages/flutter_introduction_service/.gitignore
vendored
Normal file
32
packages/flutter_introduction_service/.gitignore
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
.metadata
|
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
export 'package:flutter_introduction_interface/flutter_introduction_interface.dart';
|
||||
|
||||
export './src/introduction_service.dart';
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter_introduction_interface/flutter_introduction_interface.dart';
|
||||
|
||||
class IntroductionService {
|
||||
IntroductionService([IntroductionInterface? dataProvider])
|
||||
: _dataProvider = dataProvider ?? LocalIntroductionDataProvider();
|
||||
|
||||
late final IntroductionInterface _dataProvider;
|
||||
|
||||
Future<void> onSkip() => _dataProvider.setCompleted(value: true);
|
||||
|
||||
Future<void> onComplete() => _dataProvider.setCompleted(value: true);
|
||||
|
||||
Future<bool> shouldShow() => _dataProvider.shouldShow();
|
||||
}
|
28
packages/flutter_introduction_service/pubspec.yaml
Normal file
28
packages/flutter_introduction_service/pubspec.yaml
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: flutter_introduction_service
|
||||
description: A new Flutter package project.
|
||||
version: 2.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_introduction_interface:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction
|
||||
ref: 2.0.0
|
||||
path: packages/flutter_introduction_interface
|
||||
|
||||
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:
|
34
packages/flutter_introduction_shared_preferences/.gitignore
vendored
Normal file
34
packages/flutter_introduction_shared_preferences/.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
.metadata
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter_introduction_interface/flutter_introduction_interface.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SharedPreferencesIntroductionDataProvider extends IntroductionInterface {
|
||||
SharedPreferencesIntroductionDataProvider();
|
||||
|
||||
SharedPreferences? _prefs;
|
||||
String key = '_completedIntroduction';
|
||||
|
||||
Future<void> _writeKeyValue(String key, bool value) async {
|
||||
await _prefs!.setBool(key, value);
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
_prefs ??= await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setCompleted({bool value = true}) async {
|
||||
await _init();
|
||||
await _writeKeyValue(key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> shouldShow() async {
|
||||
await _init();
|
||||
return !(_prefs!.getBool(key) ?? false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
name: flutter_introduction_shared_preferences
|
||||
description: A new Flutter package project.
|
||||
version: 2.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_introduction_interface:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction
|
||||
ref: 2.0.0
|
||||
path: packages/flutter_introduction_interface
|
||||
shared_preferences: any
|
||||
|
||||
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:
|
43
packages/flutter_introduction_widget/.gitignore
vendored
Normal file
43
packages/flutter_introduction_widget/.gitignore
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
example/android/
|
||||
example/ios/
|
||||
example/macos/
|
||||
example/web/
|
||||
example/windows/
|
||||
example/linux/
|
||||
example/.metadata
|
||||
example/pubspec.lock
|
||||
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.metadata
|
65
packages/flutter_introduction_widget/README.md
Normal file
65
packages/flutter_introduction_widget/README.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
[](https://github.com/Iconica-Development) [](https://github.com/Iconica-Development/flutter_introduction_widget/actions/new) [](https://github.com/tenhobi/effective_dart)
|
||||
|
||||
# Introduction Widget
|
||||
Flutter Introduction Widget for showing a list of introduction pages on a single scrollable page or horizontal pageview.
|
||||
|
||||

|
||||
|
||||
## Setup
|
||||
|
||||
To use this package, add `flutter_introduction_widget` as a dependency in your pubspec.yaml file.
|
||||
|
||||
## How to use
|
||||
|
||||
Simple way to use the introduction widget:
|
||||
```dart
|
||||
IntroductionScreen(
|
||||
options: IntroductionOptions(
|
||||
pages: [
|
||||
IntroductionPage(
|
||||
title: const Text('First page'),
|
||||
text: const Text('Wow a page'),
|
||||
graphic: const FlutterLogo(),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text('Second page'),
|
||||
text: const Text('Another page'),
|
||||
graphic: const FlutterLogo(),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text('Third page'),
|
||||
text: const Text('The final page of this app'),
|
||||
graphic: const FlutterLogo(),
|
||||
backgroundImage: const AssetImage(
|
||||
'assets/flutter_introduction_background.jpeg'),
|
||||
),
|
||||
],
|
||||
introductionTranslations: const IntroductionTranslations(
|
||||
skipButton: 'Skip it!',
|
||||
nextButton: 'Next',
|
||||
previousButton: 'Previous',
|
||||
finishButton: 'Finish',
|
||||
),
|
||||
buttonMode: IntroductionScreenButtonMode.text,
|
||||
buttonBuilder: (context, onPressed, child) =>
|
||||
ElevatedButton(onPressed: onPressed, child: child),
|
||||
),
|
||||
onComplete: () {
|
||||
debugPrint('We completed the cycle');
|
||||
},
|
||||
),
|
||||
```
|
||||
|
||||
See the [Example Code](example/lib/main.dart) for an example on how to use this package.
|
||||
|
||||
## Issues
|
||||
|
||||
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_introduction_widget) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
|
||||
|
||||
## Want to contribute
|
||||
|
||||
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_widget/pulls).
|
||||
|
||||
## Author
|
||||
|
||||
This `flutter_introduction_widget` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>
|
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
44
packages/flutter_introduction_widget/example/.gitignore
vendored
Normal file
44
packages/flutter_introduction_widget/example/.gitignore
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
0
packages/flutter_introduction_widget/example/README.md
Normal file
0
packages/flutter_introduction_widget/example/README.md
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
116
packages/flutter_introduction_widget/example/lib/main.dart
Normal file
116
packages/flutter_introduction_widget/example/lib/main.dart
Normal file
|
@ -0,0 +1,116 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction_widget/flutter_introduction_widget.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: IntroductionScreen(
|
||||
options: IntroductionOptions(
|
||||
pages: [
|
||||
IntroductionPage(
|
||||
title: const Text('Basic Page'),
|
||||
text: const Text(
|
||||
'A page with some text and a widget in the middle.',
|
||||
),
|
||||
graphic: const FlutterLogo(size: 100),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text('Layout Shift'),
|
||||
text: const Text(
|
||||
'You can change the layout of a page to mix things up.',
|
||||
),
|
||||
graphic: const FlutterLogo(size: 100),
|
||||
layoutStyle: IntroductionLayoutStyle.imageTop,
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text(
|
||||
'Decoration',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
colors: [
|
||||
Colors.yellow,
|
||||
Colors.red,
|
||||
Colors.indigo,
|
||||
Colors.teal,
|
||||
],
|
||||
),
|
||||
),
|
||||
text: const Text(
|
||||
'Add a Decoration to make a custom background, like a LinearGradient',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
graphic: const FlutterLogo(
|
||||
size: 100,
|
||||
),
|
||||
),
|
||||
IntroductionPage(
|
||||
title: const Text(
|
||||
'Background Image',
|
||||
),
|
||||
text: const Text(
|
||||
'Add a Decoration with a DecorationImage, to add an background image',
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: AssetImage(
|
||||
'assets/flutter_introduction_background.jpeg',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
introductionTranslations: const IntroductionTranslations(
|
||||
skipButton: 'Skip it!',
|
||||
nextButton: 'Next',
|
||||
previousButton: 'Previous',
|
||||
finishButton: 'Finish',
|
||||
),
|
||||
tapEnabled: true,
|
||||
displayMode: IntroductionDisplayMode.multiPageHorizontal,
|
||||
buttonMode: IntroductionScreenButtonMode.text,
|
||||
indicatorMode: IndicatorMode.dash,
|
||||
skippable: true,
|
||||
buttonBuilder: (context, onPressed, child, buttonType) =>
|
||||
ElevatedButton(onPressed: onPressed, child: child),
|
||||
),
|
||||
onComplete: () {
|
||||
debugPrint('We completed the cycle');
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
60
packages/flutter_introduction_widget/example/pubspec.yaml
Normal file
60
packages/flutter_introduction_widget/example/pubspec.yaml
Normal file
|
@ -0,0 +1,60 @@
|
|||
name: example_widget
|
||||
description: A new Flutter project.
|
||||
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.1 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_introduction_widget:
|
||||
path: ../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
|
@ -0,0 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
export 'src/config/introduction.dart';
|
||||
export 'src/introduction.dart';
|
|
@ -0,0 +1,246 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum IntroductionScreenMode { showNever, showAlways, showOnce }
|
||||
|
||||
enum IntroductionScreenButtonMode { text, icon, disabled, singleFinish }
|
||||
|
||||
enum IntroductionLayoutStyle {
|
||||
imageCenter,
|
||||
imageTop,
|
||||
imageBottom,
|
||||
}
|
||||
|
||||
enum IndicatorMode { dot, dash, custom }
|
||||
|
||||
enum IntroductionDisplayMode {
|
||||
singleScrollablePageVertical,
|
||||
multiPageHorizontal
|
||||
}
|
||||
|
||||
enum IntroductionControlMode {
|
||||
previousNextButton,
|
||||
singleButton,
|
||||
}
|
||||
|
||||
enum IntroductionButtonType {
|
||||
next,
|
||||
previous,
|
||||
finish,
|
||||
skip,
|
||||
}
|
||||
|
||||
class IntroductionPage {
|
||||
/// Creates an introduction page with data used in the introduction screen for
|
||||
/// each page.
|
||||
///
|
||||
/// The values for [title] and [text] are optional in this, but will use a
|
||||
/// default translation key when built.
|
||||
///
|
||||
/// The [background] is fully optional and if not provided will show the
|
||||
/// [ThemeData.colorScheme.background] as default.
|
||||
IntroductionPage({
|
||||
this.title,
|
||||
this.text,
|
||||
this.graphic,
|
||||
this.decoration,
|
||||
this.layoutStyle,
|
||||
});
|
||||
final Widget? title;
|
||||
final Widget? text;
|
||||
final Widget? graphic;
|
||||
final BoxDecoration? decoration;
|
||||
final IntroductionLayoutStyle? layoutStyle;
|
||||
}
|
||||
|
||||
class IntroductionOptions {
|
||||
const IntroductionOptions({
|
||||
this.introductionTranslations = const IntroductionTranslations(),
|
||||
this.indicatorMode = IndicatorMode.dash,
|
||||
this.indicatorBuilder,
|
||||
this.layoutStyle = IntroductionLayoutStyle.imageCenter,
|
||||
this.pages = const [],
|
||||
this.buttonMode = IntroductionScreenButtonMode.disabled,
|
||||
this.tapEnabled = false,
|
||||
this.mode = IntroductionScreenMode.showNever,
|
||||
this.textAlign = TextAlign.center,
|
||||
this.displayMode = IntroductionDisplayMode.multiPageHorizontal,
|
||||
this.skippable = false,
|
||||
this.buttonBuilder,
|
||||
this.controlMode = IntroductionControlMode.previousNextButton,
|
||||
}) : assert(
|
||||
!(identical(indicatorMode, IndicatorMode.custom) &&
|
||||
indicatorBuilder == null),
|
||||
'When indicator mode is set to custom, '
|
||||
'make sure to define indicatorBuilder',
|
||||
);
|
||||
|
||||
/// Determine when the introduction screens needs to be shown.
|
||||
///
|
||||
/// [IntroductionScreenMode.showNever] To disable introduction screens.
|
||||
///
|
||||
/// [IntroductionScreenMode.showAlways] To always show the introduction
|
||||
/// screens on startup.
|
||||
///
|
||||
/// [IntroductionScreenMode.showOnce] To only show the introduction screens
|
||||
/// once on startup.
|
||||
final IntroductionScreenMode mode;
|
||||
|
||||
/// List of introduction pages to set the text, icons or images for the
|
||||
/// introduction screens.
|
||||
final List<IntroductionPage> pages;
|
||||
|
||||
/// Determines whether the user can tap the screen to go to the next
|
||||
/// introduction screen.
|
||||
final bool tapEnabled;
|
||||
|
||||
/// Determines what kind of buttons are used to navigate to the next
|
||||
/// introduction screen.
|
||||
/// Introduction screens can always be navigated by swiping (or tapping if
|
||||
/// [tapEnabled] is enabled).
|
||||
///
|
||||
/// [IntroductionScreenButtonMode.text] Use text buttons (text can be set by
|
||||
/// setting the translation key or using the default appshell translations).
|
||||
///
|
||||
/// [IntroductionScreenButtonMode.icon] Use icon buttons (icons can be
|
||||
/// changed by providing a icon library)
|
||||
///
|
||||
/// [IntroductionScreenButtonMode.disabled] Disable buttons.
|
||||
final IntroductionScreenButtonMode buttonMode;
|
||||
|
||||
/// Determines the position of the provided images or icons that are set
|
||||
/// using [pages].
|
||||
/// Every introduction page provided with a image or icon will use the same
|
||||
/// layout setting.
|
||||
///
|
||||
/// [IntroductionLayoutStyle.imageCenter] Image/icon will be at the center of the introduction page.
|
||||
///
|
||||
/// [IntroductionLayoutStyle.imageTop] Image/icon will be at the top of the introduction page.
|
||||
///
|
||||
/// [IntroductionLayoutStyle.imageBottom] Image/icon will be at the bottom of the introduction page.
|
||||
final IntroductionLayoutStyle layoutStyle;
|
||||
|
||||
/// Determines the style of the page indicator shown at the bottom on the
|
||||
/// introduction pages.
|
||||
///
|
||||
/// [IndicatorMode.dot] Shows a dot for each page.
|
||||
///
|
||||
/// [IndicatorMode.dash] Shows a dash for each page.
|
||||
///
|
||||
/// [IndicatorMode.custom] calls indicatorBuilder for the indicator
|
||||
final IndicatorMode indicatorMode;
|
||||
|
||||
/// Builder that is called when [indicatorMode] is set
|
||||
/// to [IndicatorMode.custom]
|
||||
final Widget Function(
|
||||
BuildContext,
|
||||
PageController,
|
||||
int,
|
||||
int,
|
||||
)? indicatorBuilder;
|
||||
|
||||
/// Determines whether the user can skip the introduction pages using a button
|
||||
/// in the header
|
||||
final bool skippable;
|
||||
|
||||
/// Determines whether the introduction screens should be shown in a single
|
||||
final TextAlign textAlign;
|
||||
|
||||
/// [IntroductionDisplayMode.multiPageHorizontal] Configured introduction
|
||||
/// pages will be shown on seperate screens and can be navigated using using
|
||||
/// buttons (if enabled) or swiping.
|
||||
///
|
||||
/// !Unimplemented! [IntroductionDisplayMode.singleScrollablePageVertical]
|
||||
/// All configured introduction pages will be shown on a single scrollable
|
||||
/// page.
|
||||
///
|
||||
final IntroductionDisplayMode displayMode;
|
||||
|
||||
/// When [IntroductionDisplayMode.multiPageHorizontal] is selected multiple
|
||||
/// controlMode can be selected.
|
||||
///
|
||||
/// [IntroductionControlMode.previousNextButton] shows two buttons at the
|
||||
/// bottom of the screen to return or proceed. The skip button is placed at
|
||||
/// the top left of the screen.
|
||||
///
|
||||
/// [IntroductionControlMode.singleButton] contains one button at the bottom
|
||||
/// of the screen to proceed. Underneath is clickable text to skip if the
|
||||
/// current page is the first page. If the current page is any different it
|
||||
/// return to the previous screen.
|
||||
///
|
||||
final IntroductionControlMode controlMode;
|
||||
|
||||
/// A builder that can be used to replace the default text buttons when
|
||||
/// [IntroductionScreenButtonMode.text] is provided to [buttonMode]
|
||||
final Widget Function(
|
||||
BuildContext,
|
||||
VoidCallback,
|
||||
Widget,
|
||||
IntroductionButtonType,
|
||||
)? buttonBuilder;
|
||||
|
||||
/// The translations for all buttons on the introductionpages
|
||||
///
|
||||
/// See [IntroductionTranslations] for more information
|
||||
/// The following buttons have a translation:
|
||||
/// - Skip
|
||||
/// - Next
|
||||
/// - Previous
|
||||
/// - Finish
|
||||
final IntroductionTranslations introductionTranslations;
|
||||
|
||||
IntroductionOptions copyWith({
|
||||
IntroductionScreenMode? mode,
|
||||
List<IntroductionPage>? pages,
|
||||
bool? tapEnabled,
|
||||
IntroductionScreenButtonMode? buttonMode,
|
||||
IntroductionLayoutStyle? layoutStyle,
|
||||
IndicatorMode? indicatorMode,
|
||||
Widget Function(
|
||||
BuildContext,
|
||||
PageController,
|
||||
int,
|
||||
int,
|
||||
)? indicatorBuilder,
|
||||
bool? skippable,
|
||||
TextAlign? textAlign,
|
||||
IntroductionDisplayMode? displayMode,
|
||||
IntroductionControlMode? controlMode,
|
||||
Widget Function(BuildContext, VoidCallback, Widget, IntroductionButtonType)?
|
||||
buttonBuilder,
|
||||
IntroductionTranslations? introductionTranslations,
|
||||
}) =>
|
||||
IntroductionOptions(
|
||||
mode: mode ?? this.mode,
|
||||
pages: pages ?? this.pages,
|
||||
tapEnabled: tapEnabled ?? this.tapEnabled,
|
||||
buttonMode: buttonMode ?? this.buttonMode,
|
||||
layoutStyle: layoutStyle ?? this.layoutStyle,
|
||||
indicatorMode: indicatorMode ?? this.indicatorMode,
|
||||
indicatorBuilder: indicatorBuilder ?? this.indicatorBuilder,
|
||||
skippable: skippable ?? this.skippable,
|
||||
textAlign: textAlign ?? this.textAlign,
|
||||
displayMode: displayMode ?? this.displayMode,
|
||||
controlMode: controlMode ?? this.controlMode,
|
||||
buttonBuilder: buttonBuilder ?? this.buttonBuilder,
|
||||
introductionTranslations:
|
||||
introductionTranslations ?? this.introductionTranslations,
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
class IntroductionTranslations {
|
||||
const IntroductionTranslations({
|
||||
this.skipButton = 'skip',
|
||||
this.nextButton = 'next',
|
||||
this.previousButton = 'previous',
|
||||
this.finishButton = 'finish',
|
||||
});
|
||||
final String skipButton;
|
||||
final String nextButton;
|
||||
final String previousButton;
|
||||
final String finishButton;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction_widget/src/config/introduction.dart';
|
||||
import 'package:flutter_introduction_widget/src/types/page_introduction.dart';
|
||||
import 'package:flutter_introduction_widget/src/types/single_introduction.dart';
|
||||
|
||||
const kAnimationDuration = Duration(milliseconds: 300);
|
||||
|
||||
class IntroductionScreen extends StatelessWidget {
|
||||
const IntroductionScreen({
|
||||
required this.options,
|
||||
required this.onComplete,
|
||||
super.key,
|
||||
this.physics,
|
||||
this.onNext,
|
||||
this.onPrevious,
|
||||
this.onSkip,
|
||||
});
|
||||
|
||||
/// The options used to build the introduction screen
|
||||
final IntroductionOptions options;
|
||||
|
||||
/// A function called when the introductionSceen changes
|
||||
final VoidCallback onComplete;
|
||||
|
||||
/// A function called when the introductionScreen is skipped
|
||||
final VoidCallback? onSkip;
|
||||
final ScrollPhysics? physics;
|
||||
|
||||
/// A function called when the introductionScreen moved to the next page
|
||||
/// where the page provided is the page where the user currently moved to
|
||||
final void Function(IntroductionPage)? onNext;
|
||||
|
||||
/// A function called when the introductionScreen moved to the previous page
|
||||
/// where the page provided is the page where the user currently moved to
|
||||
final void Function(IntroductionPage)? onPrevious;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
switch (options.displayMode) {
|
||||
case IntroductionDisplayMode.multiPageHorizontal:
|
||||
return MultiPageIntroductionScreen(
|
||||
onComplete: onComplete,
|
||||
physics: physics,
|
||||
onSkip: onSkip,
|
||||
onPrevious: onPrevious,
|
||||
onNext: onNext,
|
||||
options: options,
|
||||
);
|
||||
case IntroductionDisplayMode.singleScrollablePageVertical:
|
||||
return SingleIntroductionPage(
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,583 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_introduction_widget/src/config/introduction.dart';
|
||||
import 'package:flutter_introduction_widget/src/introduction.dart';
|
||||
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';
|
||||
|
||||
class MultiPageIntroductionScreen extends StatefulWidget {
|
||||
const MultiPageIntroductionScreen({
|
||||
required this.options,
|
||||
required this.onComplete,
|
||||
this.physics,
|
||||
this.onNext,
|
||||
this.onPrevious,
|
||||
this.onSkip,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final VoidCallback onComplete;
|
||||
final VoidCallback? onSkip;
|
||||
final void Function(IntroductionPage)? onNext;
|
||||
final void Function(IntroductionPage)? onPrevious;
|
||||
final ScrollPhysics? physics;
|
||||
|
||||
final IntroductionOptions options;
|
||||
|
||||
@override
|
||||
State<MultiPageIntroductionScreen> createState() =>
|
||||
_MultiPageIntroductionScreenState();
|
||||
}
|
||||
|
||||
class _MultiPageIntroductionScreenState
|
||||
extends State<MultiPageIntroductionScreen> {
|
||||
final PageController _controller = PageController();
|
||||
final ValueNotifier<int> _currentPage = ValueNotifier(0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.addListener(_handleScroll);
|
||||
}
|
||||
|
||||
void _handleScroll() {
|
||||
_currentPage.value = _controller.page?.round() ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.removeListener(_handleScroll);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var pages = widget.options.pages;
|
||||
var translations = widget.options.introductionTranslations;
|
||||
return Stack(
|
||||
children: [
|
||||
NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification is OverscrollNotification) {
|
||||
if (notification.overscroll > 8) {
|
||||
widget.onComplete.call();
|
||||
}
|
||||
}
|
||||
// add bouncing scroll physics support
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
var offset = notification.metrics.pixels;
|
||||
if (offset > notification.metrics.maxScrollExtent + 8) {
|
||||
widget.onComplete.call();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: PageView(
|
||||
controller: _controller,
|
||||
physics: widget.physics,
|
||||
children: List.generate(
|
||||
pages.length,
|
||||
(index) => ExplainerPage(
|
||||
onTap: () {
|
||||
widget.onNext?.call(pages[_currentPage.value]);
|
||||
},
|
||||
controller: _controller,
|
||||
page: pages[index],
|
||||
options: widget.options,
|
||||
index: index,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (widget.options.controlMode ==
|
||||
IntroductionControlMode.previousNextButton) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: SizedBox(
|
||||
height: 64,
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, _) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (widget.options.skippable && !_isLast(pages)) ...[
|
||||
TextButton(
|
||||
onPressed: widget.onComplete,
|
||||
child: Text(translations.skipButton),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Column(
|
||||
children: [
|
||||
AnimatedBuilder(
|
||||
animation: _currentPage,
|
||||
builder: (context, _) => Indicator(
|
||||
indicatorBuilder: widget.options.indicatorBuilder,
|
||||
mode: widget.options.indicatorMode,
|
||||
controller: _controller,
|
||||
count: pages.length,
|
||||
index: _currentPage.value,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, _) {
|
||||
if (widget.options.controlMode ==
|
||||
IntroductionControlMode.singleButton) {
|
||||
return IntroductionOneButton(
|
||||
controller: _controller,
|
||||
next: _isNext(pages),
|
||||
previous: _isPrevious,
|
||||
last: _isLast(pages),
|
||||
options: widget.options,
|
||||
onFinish: widget.onComplete,
|
||||
onNext: () {
|
||||
widget.onNext?.call(pages[_currentPage.value]);
|
||||
},
|
||||
onPrevious: () {
|
||||
widget.onNext?.call(pages[_currentPage.value]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return IntroductionTwoButtons(
|
||||
controller: _controller,
|
||||
next: _isNext(pages),
|
||||
previous: _isPrevious,
|
||||
last: _isLast(pages),
|
||||
options: widget.options,
|
||||
onFinish: widget.onComplete,
|
||||
onNext: () {
|
||||
widget.onNext?.call(pages[_currentPage.value]);
|
||||
},
|
||||
onPrevious: () {
|
||||
widget.onNext?.call(pages[_currentPage.value]);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, _) => IntroductionIconButtons(
|
||||
controller: _controller,
|
||||
next: _isNext(pages),
|
||||
previous: _isPrevious,
|
||||
last: _isLast(pages),
|
||||
options: widget.options,
|
||||
onFinish: widget.onComplete,
|
||||
onNext: () {
|
||||
widget.onNext?.call(pages[_currentPage.value]);
|
||||
},
|
||||
onPrevious: () {
|
||||
widget.onNext?.call(pages[_currentPage.value]);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool _isLast(List<IntroductionPage> pages) =>
|
||||
_currentPage.value == pages.length - 1;
|
||||
|
||||
bool get _isPrevious => _currentPage.value > 0;
|
||||
|
||||
bool _isNext(List<IntroductionPage> pages) =>
|
||||
_currentPage.value < pages.length - 1;
|
||||
}
|
||||
|
||||
class ExplainerPage extends StatelessWidget {
|
||||
const ExplainerPage({
|
||||
required this.page,
|
||||
required this.options,
|
||||
required this.index,
|
||||
required this.controller,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final IntroductionPage page;
|
||||
final IntroductionOptions options;
|
||||
final PageController controller;
|
||||
final int index;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
return Background(
|
||||
background: page.decoration,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 64,
|
||||
),
|
||||
Expanded(
|
||||
child: IntroductionPageContent(
|
||||
onTap: () {
|
||||
if (options.tapEnabled) {
|
||||
onTap?.call();
|
||||
}
|
||||
},
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: DefaultTextStyle(
|
||||
style: theme.textTheme.displayMedium!,
|
||||
child: page.title ?? Text('introduction.$index.title'),
|
||||
),
|
||||
),
|
||||
text: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: DefaultTextStyle(
|
||||
style: theme.textTheme.bodyLarge!,
|
||||
child: page.text ?? Text('introduction.$index.description'),
|
||||
),
|
||||
),
|
||||
graphic: Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: page.graphic,
|
||||
),
|
||||
),
|
||||
layoutStyle: page.layoutStyle ?? options.layoutStyle,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 144,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IntroductionTwoButtons extends StatelessWidget {
|
||||
const IntroductionTwoButtons({
|
||||
required this.options,
|
||||
required this.controller,
|
||||
required this.next,
|
||||
required this.last,
|
||||
required this.previous,
|
||||
required this.onFinish,
|
||||
required this.onNext,
|
||||
required this.onPrevious,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final IntroductionOptions options;
|
||||
final PageController controller;
|
||||
final VoidCallback? onFinish;
|
||||
final VoidCallback? onNext;
|
||||
final VoidCallback? onPrevious;
|
||||
|
||||
final bool previous;
|
||||
final bool next;
|
||||
final bool last;
|
||||
|
||||
Future<void> _previous() async {
|
||||
await controller.previousPage(
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
onPrevious?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var translations = options.introductionTranslations;
|
||||
var showFinishButton =
|
||||
options.buttonMode == IntroductionScreenButtonMode.singleFinish;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (options.buttonMode == IntroductionScreenButtonMode.text) ...[
|
||||
if (previous) ...[
|
||||
options.buttonBuilder?.call(
|
||||
context,
|
||||
_previous,
|
||||
Text(translations.previousButton),
|
||||
IntroductionButtonType.previous,
|
||||
) ??
|
||||
TextButton(
|
||||
onPressed: _previous,
|
||||
child: Text(translations.previousButton),
|
||||
),
|
||||
] else
|
||||
const SizedBox.shrink(),
|
||||
if (next) ...[
|
||||
options.buttonBuilder?.call(
|
||||
context,
|
||||
_next,
|
||||
Text(translations.nextButton),
|
||||
IntroductionButtonType.next,
|
||||
) ??
|
||||
TextButton(
|
||||
onPressed: _next,
|
||||
child: Text(translations.nextButton),
|
||||
),
|
||||
] else if (last) ...[
|
||||
options.buttonBuilder?.call(
|
||||
context,
|
||||
() {
|
||||
onFinish?.call();
|
||||
},
|
||||
Text(translations.finishButton),
|
||||
IntroductionButtonType.finish,
|
||||
) ??
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onFinish?.call();
|
||||
},
|
||||
child: Text(translations.finishButton),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
] else if (showFinishButton) ...[
|
||||
const SizedBox.shrink(),
|
||||
Expanded(
|
||||
child: Visibility(
|
||||
visible: last,
|
||||
maintainSize: true,
|
||||
maintainAnimation: true,
|
||||
maintainState: true,
|
||||
maintainInteractivity: false,
|
||||
child: Align(
|
||||
child: options.buttonBuilder?.call(
|
||||
context,
|
||||
() {
|
||||
onFinish?.call();
|
||||
},
|
||||
Text(translations.finishButton),
|
||||
IntroductionButtonType.finish,
|
||||
) ??
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
onFinish?.call();
|
||||
},
|
||||
child: Text(translations.finishButton),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _next() async {
|
||||
await controller.nextPage(
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
onNext?.call();
|
||||
}
|
||||
}
|
||||
|
||||
class IntroductionOneButton extends StatelessWidget {
|
||||
const IntroductionOneButton({
|
||||
required this.options,
|
||||
required this.controller,
|
||||
required this.next,
|
||||
required this.last,
|
||||
required this.previous,
|
||||
required this.onFinish,
|
||||
required this.onNext,
|
||||
required this.onPrevious,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final IntroductionOptions options;
|
||||
final PageController controller;
|
||||
final VoidCallback? onFinish;
|
||||
final VoidCallback? onNext;
|
||||
final VoidCallback? onPrevious;
|
||||
|
||||
final bool previous;
|
||||
final bool next;
|
||||
final bool last;
|
||||
|
||||
Future<void> _previous() async {
|
||||
await controller.previousPage(
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
onPrevious?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var translations = options.introductionTranslations;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (options.buttonMode == IntroductionScreenButtonMode.text) ...[
|
||||
if (last) ...[
|
||||
options.buttonBuilder?.call(
|
||||
context,
|
||||
() {
|
||||
onFinish?.call();
|
||||
},
|
||||
Text(translations.finishButton),
|
||||
IntroductionButtonType.finish,
|
||||
) ??
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onFinish?.call();
|
||||
},
|
||||
child: Text(translations.finishButton),
|
||||
),
|
||||
] else ...[
|
||||
options.buttonBuilder?.call(
|
||||
context,
|
||||
_next,
|
||||
Text(translations.nextButton),
|
||||
IntroductionButtonType.next,
|
||||
) ??
|
||||
TextButton(
|
||||
onPressed: _next,
|
||||
child: Text(translations.nextButton),
|
||||
),
|
||||
],
|
||||
if (previous) ...[
|
||||
options.buttonBuilder?.call(
|
||||
context,
|
||||
_previous,
|
||||
Text(translations.previousButton),
|
||||
IntroductionButtonType.previous,
|
||||
) ??
|
||||
TextButton(
|
||||
onPressed: _previous,
|
||||
child: Text(translations.previousButton),
|
||||
),
|
||||
] else ...[
|
||||
options.buttonBuilder?.call(
|
||||
context,
|
||||
() {
|
||||
onFinish?.call();
|
||||
},
|
||||
Text(translations.finishButton),
|
||||
IntroductionButtonType.skip,
|
||||
) ??
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onFinish?.call();
|
||||
},
|
||||
child: Text(translations.finishButton),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _next() async {
|
||||
await controller.nextPage(
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
onNext?.call();
|
||||
}
|
||||
}
|
||||
|
||||
class IntroductionIconButtons extends StatelessWidget {
|
||||
const IntroductionIconButtons({
|
||||
required this.options,
|
||||
required this.controller,
|
||||
required this.next,
|
||||
required this.last,
|
||||
required this.previous,
|
||||
required this.onFinish,
|
||||
required this.onNext,
|
||||
required this.onPrevious,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final IntroductionOptions options;
|
||||
final PageController controller;
|
||||
final VoidCallback? onFinish;
|
||||
final VoidCallback? onNext;
|
||||
final VoidCallback? onPrevious;
|
||||
|
||||
final bool previous;
|
||||
final bool next;
|
||||
final bool last;
|
||||
|
||||
Future<void> _previous() async {
|
||||
await controller.previousPage(
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
onPrevious?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (options.buttonMode == IntroductionScreenButtonMode.icon) ...[
|
||||
if (previous) ...[
|
||||
IconButton(
|
||||
iconSize: 70,
|
||||
onPressed: _previous,
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
),
|
||||
] else
|
||||
const SizedBox.shrink(),
|
||||
IconButton(
|
||||
iconSize: 70,
|
||||
onPressed: () {
|
||||
if (next) {
|
||||
unawaited(_next());
|
||||
} else {
|
||||
onFinish?.call();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _next() async {
|
||||
await controller.nextPage(
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
onNext?.call();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import 'package:flutter_introduction_widget/src/config/introduction.dart';
|
||||
|
||||
class SingleIntroductionPage extends StatelessWidget {
|
||||
const SingleIntroductionPage({
|
||||
required this.options,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final IntroductionOptions options;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Background extends StatelessWidget {
|
||||
const Background({
|
||||
required this.child,
|
||||
this.background,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final BoxDecoration? background;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
var background = this.background ??
|
||||
BoxDecoration(
|
||||
color: theme.colorScheme.background,
|
||||
);
|
||||
var size = MediaQuery.of(context).size;
|
||||
return Container(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
decoration: background,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// 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';
|
||||
import 'package:flutter_introduction_widget/src/introduction.dart';
|
||||
|
||||
class Indicator extends StatelessWidget {
|
||||
const Indicator({
|
||||
required this.mode,
|
||||
required this.controller,
|
||||
required this.count,
|
||||
required this.index,
|
||||
required this.indicatorBuilder,
|
||||
super.key,
|
||||
}) : assert(
|
||||
!(mode == IndicatorMode.custom && indicatorBuilder == null),
|
||||
'When a custom indicator is used the indicatorBuilder '
|
||||
'must be provided',
|
||||
);
|
||||
|
||||
final IndicatorMode mode;
|
||||
final PageController controller;
|
||||
final Widget Function(
|
||||
BuildContext,
|
||||
PageController,
|
||||
int,
|
||||
int,
|
||||
)? indicatorBuilder;
|
||||
final int index;
|
||||
final int count;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
switch (mode) {
|
||||
case IndicatorMode.custom:
|
||||
return indicatorBuilder!.call(context, controller, index, count);
|
||||
case IndicatorMode.dot:
|
||||
return DotsIndicator(
|
||||
controller: controller,
|
||||
color: theme.colorScheme.primary,
|
||||
dotcolor: theme.colorScheme.secondary,
|
||||
itemCount: count,
|
||||
onPageSelected: (int page) {
|
||||
unawaited(
|
||||
controller.animateToPage(
|
||||
page,
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
case IndicatorMode.dash:
|
||||
return DashIndicator(
|
||||
controller: controller,
|
||||
selectedColor: theme.colorScheme.primary,
|
||||
itemCount: count,
|
||||
onPageSelected: (int page) {
|
||||
unawaited(
|
||||
controller.animateToPage(
|
||||
page,
|
||||
duration: kAnimationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DashIndicator extends AnimatedWidget {
|
||||
const DashIndicator({
|
||||
required this.controller,
|
||||
required this.selectedColor,
|
||||
required this.itemCount,
|
||||
required this.onPageSelected,
|
||||
this.color = Colors.white,
|
||||
super.key,
|
||||
}) : super(listenable: controller);
|
||||
final PageController controller;
|
||||
final Color color;
|
||||
final Color selectedColor;
|
||||
final int itemCount;
|
||||
final Function(int) onPageSelected;
|
||||
|
||||
int _getPage() {
|
||||
try {
|
||||
return controller.page?.round() ?? 0;
|
||||
} on Exception catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var index = _getPage();
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
for (int i = 0; i < itemCount; i++) ...[
|
||||
buildDash(i, selected: index == i),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDash(int index, {required bool selected}) => SizedBox(
|
||||
width: 20,
|
||||
child: Center(
|
||||
child: Material(
|
||||
color: selected ? color : color.withAlpha(125),
|
||||
type: MaterialType.card,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2.5),
|
||||
),
|
||||
width: 16,
|
||||
height: 5,
|
||||
child: InkWell(
|
||||
onTap: () => onPageSelected.call(index),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// An indicator showing the currently selected page of a PageController
|
||||
class DotsIndicator extends AnimatedWidget {
|
||||
const DotsIndicator({
|
||||
required this.controller,
|
||||
this.color = Colors.white,
|
||||
this.dotcolor = Colors.green,
|
||||
this.itemCount,
|
||||
this.onPageSelected,
|
||||
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
|
||||
final int? itemCount;
|
||||
|
||||
/// Called when a dot is tapped
|
||||
final ValueChanged<int>? onPageSelected;
|
||||
|
||||
/// The color of the dots.
|
||||
///
|
||||
/// Defaults to `Colors.white`.
|
||||
final Color color;
|
||||
|
||||
// The base size of the dots
|
||||
static const double _kDotSize = 4.0;
|
||||
|
||||
// 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
|
||||
: color.withAlpha(125)),
|
||||
type: MaterialType.circle,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(width: 2, color: dotcolor!),
|
||||
),
|
||||
width: _kDotSize * 2 * zoom,
|
||||
height: _kDotSize * 2 * zoom,
|
||||
child: InkWell(
|
||||
onTap: () => onPageSelected!.call(index),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List<Widget>.generate(itemCount!, _buildDot),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_introduction_widget/src/config/introduction.dart';
|
||||
|
||||
class IntroductionPageContent extends StatelessWidget {
|
||||
const IntroductionPageContent({
|
||||
required this.title,
|
||||
required this.text,
|
||||
required this.graphic,
|
||||
required this.layoutStyle,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget? title;
|
||||
final Widget? text;
|
||||
final Widget? graphic;
|
||||
final IntroductionLayoutStyle layoutStyle;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
children: [
|
||||
if (graphic != null &&
|
||||
layoutStyle == IntroductionLayoutStyle.imageTop)
|
||||
graphic!,
|
||||
if (title != null) title!,
|
||||
if (graphic != null &&
|
||||
layoutStyle == IntroductionLayoutStyle.imageCenter)
|
||||
graphic!,
|
||||
if (text != null) text!,
|
||||
if (graphic != null &&
|
||||
layoutStyle == IntroductionLayoutStyle.imageBottom)
|
||||
graphic!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
23
packages/flutter_introduction_widget/pubspec.yaml
Normal file
23
packages/flutter_introduction_widget/pubspec.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
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.0.0
|
||||
homepage: https://github.com/Iconica-Development/flutter_introduction_widget
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
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:
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('test', () {
|
||||
expect(true, true);
|
||||
});
|
||||
}
|
28
pubspec.yaml
28
pubspec.yaml
|
@ -1,27 +1,7 @@
|
|||
name: flutter_introduction
|
||||
description: Combined Package of Flutter Introduction Widget and Flutter Introduction Service
|
||||
version: 1.0.0
|
||||
publish_to: none
|
||||
name: flutter_introduction_workspace
|
||||
version: 2.0.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_introduction_widget:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction_widget.git
|
||||
ref: 3.0.0
|
||||
flutter_introduction_service:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_introduction_service.git
|
||||
ref: 1.0.0
|
||||
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
flutter:
|
||||
melos: ^3.0.1
|
||||
|
|
Loading…
Reference in a new issue