refactor(learning): Enhance data loading and navigation flow

- Implement comprehensive loading state management
- Add error handling for topics and sections data fetching
- Improve user experience with loading indicators
- Prevent premature navigation before data is fully loaded
- Add safety checks for topic and section interaction
This commit is contained in:
Naresh Pratista 2024-11-13 09:03:40 +07:00
parent eb18fe4bac
commit e3b522bd33
43 changed files with 365 additions and 289 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";

View File

@ -1,122 +1 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -5,14 +5,56 @@ import 'package:english_learning/features/history/provider/history_provider.dart
import 'package:english_learning/features/history/widgets/custom_tab_bar.dart';
import 'package:english_learning/core/utils/styles/theme.dart';
import 'package:english_learning/features/history/widgets/exercise_history_card.dart';
import 'package:english_learning/features/learning/screens/learning_screen.dart';
import 'package:english_learning/features/home/screens/home_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class HistoryScreen extends StatelessWidget {
class HistoryScreen extends StatefulWidget {
const HistoryScreen({super.key});
@override
State<HistoryScreen> createState() => _HistoryScreenState();
}
class _HistoryScreenState extends State<HistoryScreen> {
@override
void initState() {
super.initState();
// Memuat data saat HistoryScreen diinisialisasi
WidgetsBinding.instance.addPostFrameCallback((_) {
final historyProvider =
Provider.of<HistoryProvider>(context, listen: false);
final userProvider = Provider.of<UserProvider>(context, listen: false);
historyProvider.fetchLearningHistory(userProvider.jwtToken!);
});
}
// Tambahkan method untuk shimmer loading
Widget _buildShimmerLoading() {
return ListView.builder(
itemCount: 5, // Jumlah item shimmer
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
width: double.infinity,
height: 100, // Sesuaikan dengan tinggi card history
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
),
),
);
},
);
}
bool isNotFoundError(String error) {
return error.toLowerCase().contains('no learning history found') ||
error.toLowerCase().contains('not found');
@ -52,7 +94,7 @@ class HistoryScreen extends StatelessWidget {
Widget _buildContent(BuildContext context, HistoryProvider historyProvider) {
if (historyProvider.isLoading) {
return const Center(child: CircularProgressIndicator());
return _buildShimmerLoading();
}
if (historyProvider.error != null) {
@ -186,12 +228,7 @@ class HistoryScreen extends StatelessWidget {
backgroundColor: AppColors.yellowButtonColor,
textColor: AppColors.blackColor,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LearningScreen(),
),
);
HomeScreen.navigateToTab(context, 1);
},
),
],

View File

@ -16,17 +16,28 @@ class CompletedTopicsProvider with ChangeNotifier {
bool get isLoading => _isLoading;
String? get error => _error;
Future<void> fetchCompletedTopics(String token) async {
_isLoading = true;
void resetData() {
_completedTopics = [];
_isLoading = false;
_error = null;
notifyListeners();
}
Future<void> fetchCompletedTopics(String token) async {
resetData();
_isLoading = true;
notifyListeners();
try {
_completedTopics = await _repository.getCompletedTopics(token);
final result = await _repository.getCompletedTopics(token);
_completedTopics = result;
_error = null;
} catch (e) {
// Tangani error
_completedTopics = [];
_error = e.toString();
print('Error fetching completed topics: $_error');
} finally {
// Selalu set loading ke false
_isLoading = false;
notifyListeners();
}

View File

@ -25,6 +25,13 @@ class HomeScreen extends StatefulWidget {
@override
State<HomeScreen> createState() => _HomeScreenState();
static void navigateToTab(BuildContext context, int index) {
final state = context.findAncestorStateOfType<_HomeScreenState>();
if (state != null) {
state.navigateToTab(index);
}
}
static void navigateReplacing(BuildContext context) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const HomeScreen()),
@ -43,9 +50,18 @@ class _HomeScreenState extends State<HomeScreen> {
const SettingsScreen(),
];
void navigateToTab(int index) {
setState(() {
_selectedIndex = index;
_pageController.jumpToPage(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
return DefaultTabController(
length: 4,
child: Scaffold(
body: Container(
color: AppColors.bgSoftColor,
child: PageView(
@ -91,14 +107,12 @@ class _HomeScreenState extends State<HomeScreen> {
Provider.of<UserProvider>(context, listen: false);
if (!historyProvider.isInitialized) {
await historyProvider.loadInitialData(userProvider.jwtToken!);
await historyProvider
.loadInitialData(userProvider.jwtToken!);
}
}
setState(() {
_selectedIndex = index;
_pageController.jumpToPage(index);
});
navigateToTab(index);
},
padding: const EdgeInsets.symmetric(
horizontal: 16,
@ -125,6 +139,7 @@ class _HomeScreenState extends State<HomeScreen> {
),
),
),
),
);
}
}
@ -148,6 +163,7 @@ class _HomeContentState extends State<HomeContent> {
final userProvider = Provider.of<UserProvider>(context, listen: false);
final completedTopicsProvider =
Provider.of<CompletedTopicsProvider>(context, listen: false);
completedTopicsProvider.resetData();
completedTopicsProvider.fetchCompletedTopics(userProvider.jwtToken!);
});
}
@ -178,7 +194,9 @@ class _HomeContentState extends State<HomeContent> {
width: double.infinity,
height: 44,
color: AppColors.yellowButtonColor,
onPressed: () {},
onPressed: () {
HomeScreen.navigateToTab(context, 1);
},
),
],
),
@ -421,29 +439,11 @@ class _HomeContentState extends State<HomeContent> {
),
completedTopicsProvider.isLoading
? _buildShimmerEffect()
: completedTopicsProvider.completedTopics.isEmpty
: completedTopicsProvider.completedTopics == null ||
completedTopicsProvider.completedTopics.isEmpty
? _buildNoDataWidget()
: _buildCompletedTopicsContent(
completedTopicsProvider),
// completedTopicsProvider.isLoading
// ? _buildShimmerEffect()
// : completedTopicsProvider.completedTopics.isEmpty
// ? _buildNoDataWidget()
// : ListView.builder(
// shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
// padding: const EdgeInsets.symmetric(
// horizontal: 16.0,
// ),
// itemCount: 1,
// itemBuilder: (context, index) {
// return ProgressCard(
// completedTopic: completedTopicsProvider
// .completedTopics, // Kirim seluruh list
// );
// },
// ),
],
),
),

View File

@ -1,8 +1,12 @@
import 'package:english_learning/core/services/constants.dart';
import 'package:english_learning/core/utils/styles/theme.dart';
import 'package:english_learning/features/auth/provider/user_provider.dart';
import 'package:english_learning/features/home/models/completed_topics_model.dart';
import 'package:english_learning/features/home/widgets/progress_bar.dart';
import 'package:english_learning/features/learning/modules/topics/screens/topics_list_screen.dart';
import 'package:english_learning/features/learning/provider/section_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProgressCard extends StatelessWidget {
final List<CompletedTopic> completedTopic;
@ -15,6 +19,68 @@ class ProgressCard extends StatelessWidget {
: '${baseUrl}uploads/section/$thumbnail';
}
Future<void> _navigateToTopics(
BuildContext context, CompletedTopic topic) async {
// Get the SectionProvider
final sectionProvider =
Provider.of<SectionProvider>(context, listen: false);
// Show loading indicator while checking/loading data
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(
child: CircularProgressIndicator(),
),
);
try {
// If sections aren't loaded yet, load them
if (sectionProvider.sections.isEmpty) {
final userProvider = Provider.of<UserProvider>(context, listen: false);
final token = await userProvider.getValidToken();
if (token != null) {
await sectionProvider.fetchSections(token);
} else {
throw Exception('No valid token found');
}
}
// Remove loading indicator
Navigator.pop(context);
// Navigate to topics screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TopicsListScreen(
sectionId: topic.idSection,
),
),
);
} catch (e) {
// Remove loading indicator
Navigator.pop(context);
// Show error dialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: const Text(
'Unable to load section data. Please try again later.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
}
@override
Widget build(BuildContext context) {
return Column(
@ -25,7 +91,7 @@ class ProgressCard extends StatelessWidget {
CompletedTopic topic = entry.value;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildTopicItem(topic),
child: _buildTopicItem(context, topic),
);
},
),
@ -33,8 +99,10 @@ class ProgressCard extends StatelessWidget {
);
}
Widget _buildTopicItem(CompletedTopic topic) {
return Container(
Widget _buildTopicItem(BuildContext context, CompletedTopic topic) {
return GestureDetector(
onTap: () => _navigateToTopics(context, topic),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
@ -97,6 +165,7 @@ class ProgressCard extends StatelessWidget {
],
),
),
),
);
}
}

View File

@ -20,7 +20,9 @@ class _LearningScreenState extends State<LearningScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_fetchSections();
});
}
Future<void> _fetchSections() async {

View File

@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
@ -145,6 +153,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
chewie:
dependency: transitive
description:
@ -153,6 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.5"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
@ -382,6 +406,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77"
url: "https://pub.dev"
source: hosted
version: "0.14.1"
flutter_lints:
dependency: "direct dev"
description:
@ -568,6 +600,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.0"
image:
dependency: transitive
description:
name: image
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
url: "https://pub.dev"
source: hosted
version: "4.3.0"
image_picker:
dependency: "direct main"
description:
@ -648,6 +688,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
just_audio:
dependency: transitive
description:
@ -1301,6 +1349,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
youtube_player_flutter:
dependency: "direct main"
description:

View File

@ -58,6 +58,15 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_launcher_icons: ^0.14.1
flutter_launcher_icons:
android: true
ios: true
image_path: 'assets/logo.png'
adaptive_icon_background: '#FFFFFF'
adaptive_icon_foreground: 'assets/logo.png'
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.