diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..e69de29 diff --git a/lib/design_course/category_list_view.dart b/lib/design_course/category_list_view.dart new file mode 100644 index 0000000..73dec24 --- /dev/null +++ b/lib/design_course/category_list_view.dart @@ -0,0 +1,286 @@ +import 'package:best_flutter_ui_templates/design_course/design_course_app_theme.dart'; +import 'package:best_flutter_ui_templates/design_course/models/category.dart'; +import 'package:best_flutter_ui_templates/main.dart'; +import 'package:flutter/material.dart'; + +class CategoryListView extends StatefulWidget { + const CategoryListView({Key? key, this.callBack}) : super(key: key); + + final Function()? callBack; + @override + _CategoryListViewState createState() => _CategoryListViewState(); +} + +class _CategoryListViewState extends State + with TickerProviderStateMixin { + AnimationController? animationController; + + @override + void initState() { + animationController = AnimationController( + duration: const Duration(milliseconds: 2000), vsync: this); + super.initState(); + } + + Future getData() async { + await Future.delayed(const Duration(milliseconds: 50)); + return true; + } + + @override + void dispose() { + animationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 16, bottom: 16), + child: Container( + height: 134, + width: double.infinity, + child: FutureBuilder( + future: getData(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } else { + return ListView.builder( + padding: const EdgeInsets.only( + top: 0, bottom: 0, right: 16, left: 16), + itemCount: Category.categoryList.length, + scrollDirection: Axis.horizontal, + itemBuilder: (BuildContext context, int index) { + final int count = Category.categoryList.length > 10 + ? 10 + : Category.categoryList.length; + final Animation animation = + Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: animationController!, + curve: Interval((1 / count) * index, 1.0, + curve: Curves.fastOutSlowIn))); + animationController?.forward(); + + return CategoryView( + category: Category.categoryList[index], + animation: animation, + animationController: animationController, + callback: widget.callBack, + ); + }, + ); + } + }, + ), + ), + ); + } +} + +class CategoryView extends StatelessWidget { + const CategoryView( + {Key? key, + this.category, + this.animationController, + this.animation, + this.callback}) + : super(key: key); + + final VoidCallback? callback; + final Category? category; + final AnimationController? animationController; + final Animation? animation; + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animationController!, + builder: (BuildContext context, Widget? child) { + return FadeTransition( + opacity: animation!, + child: Transform( + transform: Matrix4.translationValues( + 100 * (1.0 - animation!.value), 0.0, 0.0), + child: InkWell( + splashColor: Colors.transparent, + onTap: callback, + child: SizedBox( + width: 280, + child: Stack( + children: [ + Container( + child: Row( + children: [ + const SizedBox( + width: 48, + ), + Expanded( + child: Container( + decoration: BoxDecoration( + color: HexColor('#F8FAFB'), + borderRadius: const BorderRadius.all( + Radius.circular(16.0)), + ), + child: Row( + children: [ + const SizedBox( + width: 48 + 24.0, + ), + Expanded( + child: Container( + child: Column( + children: [ + Padding( + padding: + const EdgeInsets.only(top: 16), + child: Text( + category!.title, + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + letterSpacing: 0.27, + color: DesignCourseAppTheme + .darkerText, + ), + ), + ), + const Expanded( + child: SizedBox(), + ), + Padding( + padding: const EdgeInsets.only( + right: 16, bottom: 8), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Text( + '${category!.lessonCount} lesson', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w200, + fontSize: 12, + letterSpacing: 0.27, + color: DesignCourseAppTheme + .grey, + ), + ), + Container( + child: Row( + children: [ + Text( + '${category!.rating}', + textAlign: + TextAlign.left, + style: TextStyle( + fontWeight: + FontWeight.w200, + fontSize: 18, + letterSpacing: 0.27, + color: + DesignCourseAppTheme + .grey, + ), + ), + Icon( + Icons.star, + color: + DesignCourseAppTheme + .nearlyBlue, + size: 20, + ), + ], + ), + ) + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + bottom: 16, right: 16), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '\$${category!.money}', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 18, + letterSpacing: 0.27, + color: DesignCourseAppTheme + .nearlyBlue, + ), + ), + Container( + decoration: BoxDecoration( + color: DesignCourseAppTheme + .nearlyBlue, + borderRadius: + const BorderRadius.all( + Radius.circular( + 8.0)), + ), + child: Padding( + padding: + const EdgeInsets.all( + 4.0), + child: Icon( + Icons.add, + color: + DesignCourseAppTheme + .nearlyWhite, + ), + ), + ) + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ) + ], + ), + ), + Container( + child: Padding( + padding: const EdgeInsets.only( + top: 24, bottom: 24, left: 16), + child: Row( + children: [ + ClipRRect( + borderRadius: + const BorderRadius.all(Radius.circular(16.0)), + child: AspectRatio( + aspectRatio: 1.0, + child: Image.asset(category!.imagePath)), + ) + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/design_course/course_info_screen.dart b/lib/design_course/course_info_screen.dart new file mode 100644 index 0000000..b02bee1 --- /dev/null +++ b/lib/design_course/course_info_screen.dart @@ -0,0 +1,364 @@ +import 'package:flutter/material.dart'; +import 'design_course_app_theme.dart'; + +class CourseInfoScreen extends StatefulWidget { + @override + _CourseInfoScreenState createState() => _CourseInfoScreenState(); +} + +class _CourseInfoScreenState extends State + with TickerProviderStateMixin { + final double infoHeight = 364.0; + AnimationController? animationController; + Animation? animation; + double opacity1 = 0.0; + double opacity2 = 0.0; + double opacity3 = 0.0; + @override + void initState() { + animationController = AnimationController( + duration: const Duration(milliseconds: 1000), vsync: this); + animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: animationController!, + curve: Interval(0, 1.0, curve: Curves.fastOutSlowIn))); + setData(); + super.initState(); + } + + Future setData() async { + animationController?.forward(); + await Future.delayed(const Duration(milliseconds: 200)); + setState(() { + opacity1 = 1.0; + }); + await Future.delayed(const Duration(milliseconds: 200)); + setState(() { + opacity2 = 1.0; + }); + await Future.delayed(const Duration(milliseconds: 200)); + setState(() { + opacity3 = 1.0; + }); + } + + @override + Widget build(BuildContext context) { + final double tempHeight = MediaQuery.of(context).size.height - + (MediaQuery.of(context).size.width / 1.2) + + 24.0; + return Container( + color: DesignCourseAppTheme.nearlyWhite, + child: Scaffold( + backgroundColor: Colors.transparent, + body: Stack( + children: [ + Column( + children: [ + AspectRatio( + aspectRatio: 1.2, + child: Image.asset('assets/design_course/webInterFace.png'), + ), + ], + ), + Positioned( + top: (MediaQuery.of(context).size.width / 1.2) - 24.0, + bottom: 0, + left: 0, + right: 0, + child: Container( + decoration: BoxDecoration( + color: DesignCourseAppTheme.nearlyWhite, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(32.0), + topRight: Radius.circular(32.0)), + boxShadow: [ + BoxShadow( + color: DesignCourseAppTheme.grey.withOpacity(0.2), + offset: const Offset(1.1, 1.1), + blurRadius: 10.0), + ], + ), + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: SingleChildScrollView( + child: Container( + constraints: BoxConstraints( + minHeight: infoHeight, + maxHeight: tempHeight > infoHeight + ? tempHeight + : infoHeight), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 32.0, left: 18, right: 16), + child: Text( + 'Web Design\nCourse', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 22, + letterSpacing: 0.27, + color: DesignCourseAppTheme.darkerText, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 16, right: 16, bottom: 8, top: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '\$28.99', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w200, + fontSize: 22, + letterSpacing: 0.27, + color: DesignCourseAppTheme.nearlyBlue, + ), + ), + Container( + child: Row( + children: [ + Text( + '4.3', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w200, + fontSize: 22, + letterSpacing: 0.27, + color: DesignCourseAppTheme.grey, + ), + ), + Icon( + Icons.star, + color: DesignCourseAppTheme.nearlyBlue, + size: 24, + ), + ], + ), + ) + ], + ), + ), + AnimatedOpacity( + duration: const Duration(milliseconds: 500), + opacity: opacity1, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + getTimeBoxUI('24', 'Classe'), + getTimeBoxUI('2hours', 'Time'), + getTimeBoxUI('24', 'Seat'), + ], + ), + ), + ), + Expanded( + child: AnimatedOpacity( + duration: const Duration(milliseconds: 500), + opacity: opacity2, + child: Padding( + padding: const EdgeInsets.only( + left: 16, right: 16, top: 8, bottom: 8), + child: Text( + 'Lorem ipsum is simply dummy text of printing & typesetting industry, Lorem ipsum is simply dummy text of printing & typesetting industry.', + textAlign: TextAlign.justify, + style: TextStyle( + fontWeight: FontWeight.w200, + fontSize: 14, + letterSpacing: 0.27, + color: DesignCourseAppTheme.grey, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + AnimatedOpacity( + duration: const Duration(milliseconds: 500), + opacity: opacity3, + child: Padding( + padding: const EdgeInsets.only( + left: 16, bottom: 16, right: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 48, + height: 48, + child: Container( + decoration: BoxDecoration( + color: DesignCourseAppTheme.nearlyWhite, + borderRadius: const BorderRadius.all( + Radius.circular(16.0), + ), + border: Border.all( + color: DesignCourseAppTheme.grey + .withOpacity(0.2)), + ), + child: Icon( + Icons.add, + color: DesignCourseAppTheme.nearlyBlue, + size: 28, + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Container( + height: 48, + decoration: BoxDecoration( + color: DesignCourseAppTheme.nearlyBlue, + borderRadius: const BorderRadius.all( + Radius.circular(16.0), + ), + boxShadow: [ + BoxShadow( + color: DesignCourseAppTheme + .nearlyBlue + .withOpacity(0.5), + offset: const Offset(1.1, 1.1), + blurRadius: 10.0), + ], + ), + child: Center( + child: Text( + 'Join Course', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 18, + letterSpacing: 0.0, + color: DesignCourseAppTheme + .nearlyWhite, + ), + ), + ), + ), + ) + ], + ), + ), + ), + SizedBox( + height: MediaQuery.of(context).padding.bottom, + ) + ], + ), + ), + ), + ), + ), + ), + Positioned( + top: (MediaQuery.of(context).size.width / 1.2) - 24.0 - 35, + right: 35, + child: ScaleTransition( + alignment: Alignment.center, + scale: CurvedAnimation( + parent: animationController!, curve: Curves.fastOutSlowIn), + child: Card( + color: DesignCourseAppTheme.nearlyBlue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50.0)), + elevation: 10.0, + child: Container( + width: 60, + height: 60, + child: Center( + child: Icon( + Icons.favorite, + color: DesignCourseAppTheme.nearlyWhite, + size: 30, + ), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + child: SizedBox( + width: AppBar().preferredSize.height, + height: AppBar().preferredSize.height, + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: + BorderRadius.circular(AppBar().preferredSize.height), + child: Icon( + Icons.arrow_back_ios, + color: DesignCourseAppTheme.nearlyBlack, + ), + onTap: () { + Navigator.pop(context); + }, + ), + ), + ), + ) + ], + ), + ), + ); + } + + Widget getTimeBoxUI(String text1, String txt2) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + decoration: BoxDecoration( + color: DesignCourseAppTheme.nearlyWhite, + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + boxShadow: [ + BoxShadow( + color: DesignCourseAppTheme.grey.withOpacity(0.2), + offset: const Offset(1.1, 1.1), + blurRadius: 8.0), + ], + ), + child: Padding( + padding: const EdgeInsets.only( + left: 18.0, right: 18.0, top: 12.0, bottom: 12.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + text1, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + letterSpacing: 0.27, + color: DesignCourseAppTheme.nearlyBlue, + ), + ), + Text( + txt2, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w200, + fontSize: 14, + letterSpacing: 0.27, + color: DesignCourseAppTheme.grey, + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/design_course/design_course_app_theme.dart b/lib/design_course/design_course_app_theme.dart new file mode 100644 index 0000000..0a66e41 --- /dev/null +++ b/lib/design_course/design_course_app_theme.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; + +class DesignCourseAppTheme { + DesignCourseAppTheme._(); + + static const Color notWhite = Color(0xFFEDF0F2); + static const Color nearlyWhite = Color(0xFFFFFFFF); + static const Color nearlyBlue = Color(0xFF00B6F0); + static const Color nearlyBlack = Color(0xFF213333); + static const Color grey = Color(0xFF3A5160); + static const Color dark_grey = Color(0xFF313A44); + + static const Color darkText = Color(0xFF253840); + static const Color darkerText = Color(0xFF17262A); + static const Color lightText = Color(0xFF4A6572); + static const Color deactivatedText = Color(0xFF767676); + static const Color dismissibleBackground = Color(0xFF364A54); + static const Color chipBackground = Color(0xFFEEF1F3); + static const Color spacer = Color(0xFFF2F2F2); + + static const TextTheme textTheme = TextTheme( + headline4: display1, + headline5: headline, + headline6: title, + subtitle2: subtitle, + bodyText1: body2, + bodyText2: body1, + caption: caption, + ); + + static const TextStyle display1 = TextStyle( + // h4 -> display1 + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 36, + letterSpacing: 0.4, + height: 0.9, + color: darkerText, + ); + + static const TextStyle headline = TextStyle( + // h5 -> headline + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 24, + letterSpacing: 0.27, + color: darkerText, + ); + + static const TextStyle title = TextStyle( + // h6 -> title + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 16, + letterSpacing: 0.18, + color: darkerText, + ); + + static const TextStyle subtitle = TextStyle( + // subtitle2 -> subtitle + fontFamily: 'WorkSans', + fontWeight: FontWeight.w400, + fontSize: 14, + letterSpacing: -0.04, + color: darkText, + ); + + static const TextStyle body2 = TextStyle( + // body1 -> body2 + fontFamily: 'WorkSans', + fontWeight: FontWeight.w400, + fontSize: 14, + letterSpacing: 0.2, + color: darkText, + ); + + static const TextStyle body1 = TextStyle( + // body2 -> body1 + fontFamily: 'WorkSans', + fontWeight: FontWeight.w400, + fontSize: 16, + letterSpacing: -0.05, + color: darkText, + ); + + static const TextStyle caption = TextStyle( + // Caption -> caption + fontFamily: 'WorkSans', + fontWeight: FontWeight.w400, + fontSize: 12, + letterSpacing: 0.2, + color: lightText, // was lightText + ); +} \ No newline at end of file diff --git a/lib/design_course/home_design_course.dart b/lib/design_course/home_design_course.dart new file mode 100644 index 0000000..ac69f46 --- /dev/null +++ b/lib/design_course/home_design_course.dart @@ -0,0 +1,310 @@ +import 'package:best_flutter_ui_templates/design_course/category_list_view.dart'; +import 'package:best_flutter_ui_templates/design_course/course_info_screen.dart'; +import 'package:best_flutter_ui_templates/design_course/popular_course_list_view.dart'; +import 'package:best_flutter_ui_templates/main.dart'; +import 'package:flutter/material.dart'; +import 'design_course_app_theme.dart'; + +class DesignCourseHomeScreen extends StatefulWidget { + @override + _DesignCourseHomeScreenState createState() => _DesignCourseHomeScreenState(); +} + +class _DesignCourseHomeScreenState extends State { + CategoryType categoryType = CategoryType.ui; + + @override + Widget build(BuildContext context) { + return Container( + color: DesignCourseAppTheme.nearlyWhite, + child: Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + SizedBox( + height: MediaQuery.of(context).padding.top, + ), + getAppBarUI(), + Expanded( + child: SingleChildScrollView( + child: Container( + height: MediaQuery.of(context).size.height, + child: Column( + children: [ + getSearchBarUI(), + getCategoryUI(), + Flexible( + child: getPopularCourseUI(), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget getCategoryUI() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 8.0, left: 18, right: 16), + child: Text( + 'Category', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 22, + letterSpacing: 0.27, + color: DesignCourseAppTheme.darkerText, + ), + ), + ), + const SizedBox( + height: 16, + ), + Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + getButtonUI(CategoryType.ui, categoryType == CategoryType.ui), + const SizedBox( + width: 16, + ), + getButtonUI( + CategoryType.coding, categoryType == CategoryType.coding), + const SizedBox( + width: 16, + ), + getButtonUI( + CategoryType.basic, categoryType == CategoryType.basic), + ], + ), + ), + const SizedBox( + height: 16, + ), + CategoryListView( + callBack: () { + moveTo(); + }, + ), + ], + ); + } + + Widget getPopularCourseUI() { + return Padding( + padding: const EdgeInsets.only(top: 8.0, left: 18, right: 16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Popular Course', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 22, + letterSpacing: 0.27, + color: DesignCourseAppTheme.darkerText, + ), + ), + Flexible( + child: PopularCourseListView( + callBack: () { + moveTo(); + }, + ), + ) + ], + ), + ); + } + + void moveTo() { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => CourseInfoScreen(), + ), + ); + } + + Widget getButtonUI(CategoryType categoryTypeData, bool isSelected) { + String txt = ''; + if (CategoryType.ui == categoryTypeData) { + txt = 'Ui/Ux'; + } else if (CategoryType.coding == categoryTypeData) { + txt = 'Coding'; + } else if (CategoryType.basic == categoryTypeData) { + txt = 'Basic UI'; + } + return Expanded( + child: Container( + decoration: BoxDecoration( + color: isSelected + ? DesignCourseAppTheme.nearlyBlue + : DesignCourseAppTheme.nearlyWhite, + borderRadius: const BorderRadius.all(Radius.circular(24.0)), + border: Border.all(color: DesignCourseAppTheme.nearlyBlue)), + child: Material( + color: Colors.transparent, + child: InkWell( + splashColor: Colors.white24, + borderRadius: const BorderRadius.all(Radius.circular(24.0)), + onTap: () { + setState(() { + categoryType = categoryTypeData; + }); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 12, bottom: 12, left: 18, right: 18), + child: Center( + child: Text( + txt, + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 12, + letterSpacing: 0.27, + color: isSelected + ? DesignCourseAppTheme.nearlyWhite + : DesignCourseAppTheme.nearlyBlue, + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget getSearchBarUI() { + return Padding( + padding: const EdgeInsets.only(top: 8.0, left: 18), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.75, + height: 64, + child: Padding( + padding: const EdgeInsets.only(top: 8, bottom: 8), + child: Container( + decoration: BoxDecoration( + color: HexColor('#F8FAFB'), + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(13.0), + bottomLeft: Radius.circular(13.0), + topLeft: Radius.circular(13.0), + topRight: Radius.circular(13.0), + ), + ), + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.only(left: 16, right: 16), + child: TextFormField( + style: TextStyle( + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 16, + color: DesignCourseAppTheme.nearlyBlue, + ), + keyboardType: TextInputType.text, + decoration: InputDecoration( + labelText: 'Search for course', + border: InputBorder.none, + helperStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: HexColor('#B9BABC'), + ), + labelStyle: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + letterSpacing: 0.2, + color: HexColor('#B9BABC'), + ), + ), + onEditingComplete: () {}, + ), + ), + ), + SizedBox( + width: 60, + height: 60, + child: Icon(Icons.search, color: HexColor('#B9BABC')), + ) + ], + ), + ), + ), + ), + const Expanded( + child: SizedBox(), + ) + ], + ), + ); + } + + Widget getAppBarUI() { + return Padding( + padding: const EdgeInsets.only(top: 8.0, left: 18, right: 18), + child: Row( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Choose your', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 14, + letterSpacing: 0.2, + color: DesignCourseAppTheme.grey, + ), + ), + Text( + 'Design Course', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + letterSpacing: 0.27, + color: DesignCourseAppTheme.darkerText, + ), + ), + ], + ), + ), + Container( + width: 60, + height: 60, + child: Image.asset('assets/design_course/userImage.png'), + ) + ], + ), + ); + } +} + +enum CategoryType { + ui, + coding, + basic, +} \ No newline at end of file diff --git a/lib/design_course/model/category.dart b/lib/design_course/model/category.dart new file mode 100644 index 0000000..99ee5f4 --- /dev/null +++ b/lib/design_course/model/category.dart @@ -0,0 +1,77 @@ +class Category { + Category({ + this.title = '', + this.imagePath = '', + this.lessonCount = 0, + this.money = 0, + this.rating = 0.0, + }); + + String title; + int lessonCount; + int money; + double rating; + String imagePath; + + static List categoryList = [ + Category( + imagePath: 'assets/design_course/interFace1.png', + title: 'User interface Design', + lessonCount: 24, + money: 25, + rating: 4.3, + ), + Category( + imagePath: 'assets/design_course/interFace2.png', + title: 'User interface Design', + lessonCount: 22, + money: 18, + rating: 4.6, + ), + Category( + imagePath: 'assets/design_course/interFace1.png', + title: 'User interface Design', + lessonCount: 24, + money: 25, + rating: 4.3, + ), + Category( + imagePath: 'assets/design_course/interFace2.png', + title: 'User interface Design', + lessonCount: 22, + money: 18, + rating: 4.6, + ), + ]; + + static List popularCourseList = [ + Category( + imagePath: 'assets/design_course/interFace3.png', + title: 'App Design Course', + lessonCount: 12, + money: 25, + rating: 4.8, + ), + Category( + imagePath: 'assets/design_course/interFace4.png', + title: 'Web Design Course', + lessonCount: 28, + money: 208, + rating: 4.9, + ), + Category( + imagePath: 'assets/design_course/interFace3.png', + title: 'App Design Course', + lessonCount: 12, + money: 25, + rating: 4.8, + ), + Category( + imagePath: 'assets/design_course/interFace4.png', + title: 'Web Design Course', + lessonCount: 28, + money: 208, + rating: 4.9, + ), + ]; +} \ No newline at end of file diff --git a/lib/design_course/popular_course_list_view.dart b/lib/design_course/popular_course_list_view.dart new file mode 100644 index 0000000..9fb97b1 --- /dev/null +++ b/lib/design_course/popular_course_list_view.dart @@ -0,0 +1,249 @@ +import 'package:design_course/design_course_app_theme.dart'; +import 'package:design_course/models/category.dart'; +import 'package:main.dart'; +import 'package:flutter/material.dart'; + +class PopularCourseListView extends StatefulWidget { + const PopularCourseListView({Key? key, this.callBack}) : super(key: key); + + final Function()? callBack; + @override + _PopularCourseListViewState createState() => _PopularCourseListViewState(); +} + +class _PopularCourseListViewState extends State + with TickerProviderStateMixin { + AnimationController? animationController; + @override + void initState() { + animationController = AnimationController( + duration: const Duration(milliseconds: 2000), vsync: this); + super.initState(); + } + + Future getData() async { + await Future.delayed(const Duration(milliseconds: 200)); + return true; + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 8), + child: FutureBuilder( + future: getData(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } else { + return GridView( + padding: const EdgeInsets.all(8), + physics: const BouncingScrollPhysics(), + scrollDirection: Axis.vertical, + children: List.generate( + Category.popularCourseList.length, + (int index) { + final int count = Category.popularCourseList.length; + final Animation animation = + Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: animationController!, + curve: Interval((1 / count) * index, 1.0, + curve: Curves.fastOutSlowIn), + ), + ); + animationController?.forward(); + return CategoryView( + callback: widget.callBack, + category: Category.popularCourseList[index], + animation: animation, + animationController: animationController, + ); + }, + ), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 32.0, + crossAxisSpacing: 32.0, + childAspectRatio: 0.8, + ), + ); + } + }, + ), + ); + } +} + +class CategoryView extends StatelessWidget { + const CategoryView( + {Key? key, + this.category, + this.animationController, + this.animation, + this.callback}) + : super(key: key); + + final VoidCallback? callback; + final Category? category; + final AnimationController? animationController; + final Animation? animation; + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animationController!, + builder: (BuildContext context, Widget? child) { + return FadeTransition( + opacity: animation!, + child: Transform( + transform: Matrix4.translationValues( + 0.0, 50 * (1.0 - animation!.value), 0.0), + child: InkWell( + splashColor: Colors.transparent, + onTap: callback, + child: SizedBox( + height: 280, + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + Container( + child: Column( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: HexColor('#F8FAFB'), + borderRadius: const BorderRadius.all( + Radius.circular(16.0)), + // border: new Border.all( + // color: DesignCourseAppTheme.notWhite), + ), + child: Column( + children: [ + Expanded( + child: Container( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16, left: 16, right: 16), + child: Text( + category!.title, + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + letterSpacing: 0.27, + color: DesignCourseAppTheme + .darkerText, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 8, + left: 16, + right: 16, + bottom: 8), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Text( + '${category!.lessonCount} lesson', + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w200, + fontSize: 12, + letterSpacing: 0.27, + color: DesignCourseAppTheme + .grey, + ), + ), + Container( + child: Row( + children: [ + Text( + '${category!.rating}', + textAlign: + TextAlign.left, + style: TextStyle( + fontWeight: + FontWeight.w200, + fontSize: 18, + letterSpacing: 0.27, + color: + DesignCourseAppTheme + .grey, + ), + ), + Icon( + Icons.star, + color: + DesignCourseAppTheme + .nearlyBlue, + size: 20, + ), + ], + ), + ) + ], + ), + ), + ], + ), + ), + ), + const SizedBox( + width: 48, + ), + ], + ), + ), + ), + const SizedBox( + height: 48, + ), + ], + ), + ), + Container( + child: Padding( + padding: + const EdgeInsets.only(top: 24, right: 16, left: 16), + child: Container( + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all(Radius.circular(16.0)), + boxShadow: [ + BoxShadow( + color: DesignCourseAppTheme.grey + .withOpacity(0.2), + offset: const Offset(0.0, 0.0), + blurRadius: 6.0), + ], + ), + child: ClipRRect( + borderRadius: + const BorderRadius.all(Radius.circular(16.0)), + child: AspectRatio( + aspectRatio: 1.28, + child: Image.asset(category!.imagePath)), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..700682f --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,88 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + return macos; + case TargetPlatform.windows: + return windows; + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyD4jQcehJ0ekAj1z3Or8qZcRczuji0BcgE', + appId: '1:510959666422:web:b4a1c18ab247c6fe6798d7', + messagingSenderId: '510959666422', + projectId: 'speakfree-28159', + authDomain: 'speakfree-28159.firebaseapp.com', + storageBucket: 'speakfree-28159.appspot.com', + measurementId: 'G-MTF7P6XSW7', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyCxtUZw3acxp1oK0lZ-DTN42ER4vAn7jTM', + appId: '1:510959666422:android:3d8ef45330fafca36798d7', + messagingSenderId: '510959666422', + projectId: 'speakfree-28159', + storageBucket: 'speakfree-28159.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyAZVso5ke2M_7y6yfaruPJNoa_fQMmUc0M', + appId: '1:510959666422:ios:e743c9e77c01453a6798d7', + messagingSenderId: '510959666422', + projectId: 'speakfree-28159', + storageBucket: 'speakfree-28159.appspot.com', + iosBundleId: 'com.example.modernlogintute', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyAZVso5ke2M_7y6yfaruPJNoa_fQMmUc0M', + appId: '1:510959666422:ios:e743c9e77c01453a6798d7', + messagingSenderId: '510959666422', + projectId: 'speakfree-28159', + storageBucket: 'speakfree-28159.appspot.com', + iosBundleId: 'com.example.modernlogintute', + ); + + static const FirebaseOptions windows = FirebaseOptions( + apiKey: 'AIzaSyD4jQcehJ0ekAj1z3Or8qZcRczuji0BcgE', + appId: '1:510959666422:web:7f5371cbe65bada26798d7', + messagingSenderId: '510959666422', + projectId: 'speakfree-28159', + authDomain: 'speakfree-28159.firebaseapp.com', + storageBucket: 'speakfree-28159.appspot.com', + measurementId: 'G-PLVMX5NPFP', + ); +} diff --git a/lib/main.dart b/lib/main.dart index c1943f1..4bb2169 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,13 @@ import 'package:flutter/material.dart'; -import 'pages/login_page.dart'; +import 'pages/auth_page.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'firebase_options.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); runApp(const MyApp()); } @@ -10,9 +16,9 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( debugShowCheckedModeBanner: false, - home: LoginPage(), + home: AuthPage(), ); } } diff --git a/lib/pages/auth_page.dart b/lib/pages/auth_page.dart new file mode 100644 index 0000000..83358a3 --- /dev/null +++ b/lib/pages/auth_page.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; // Ensure FirebaseAuth is imported + +import 'home_page.dart'; +import 'login_or_register_page.dart'; // Import LoginOrRegisterPage + +class AuthPage extends StatelessWidget { + const AuthPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + // Check if the user is logged in + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), // Show loading while waiting + ); + } else if (snapshot.hasData) { + // User is logged in + return HomePage(); + } else { + // User is NOT logged in + return const LoginOrRegisterPage(); + } + }, + ), + ); + } +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/pages/login_or_register.dart b/lib/pages/login_or_register.dart new file mode 100644 index 0000000..1ce1c7b --- /dev/null +++ b/lib/pages/login_or_register.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'login_page.dart'; +import 'register_page.dart'; // Make sure you have this import for the register page + +class LoginOrRegisterPage extends StatefulWidget { + const LoginOrRegisterPage({super.key}); + + @override + State createState() => _LoginOrRegisterPageState(); +} + +class _LoginOrRegisterPageState extends State { + // Initially show login page + bool showLoginPage = true; + + // Toggle between login and register page + void togglePages() { + setState(() { + showLoginPage = !showLoginPage; + }); + } + + @override + Widget build(BuildContext context) { + if (showLoginPage) { + return LoginPage( + onTap: togglePages, // toggle to register page + ); + } else { + return RegisterPage( + onTap: togglePages, // toggle to login page + ); + } + } +} diff --git a/lib/pages/register_page.dart b/lib/pages/register_page.dart new file mode 100644 index 0000000..79423a5 --- /dev/null +++ b/lib/pages/register_page.dart @@ -0,0 +1,224 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:modernlogintute/components/my_button.dart'; +import 'package:modernlogintute/components/my_textfield.dart'; +import 'package:modernlogintute/components/square_tile.dart'; + +class RegisterPage extends StatefulWidget { + final Function()? onTap; + const RegisterPage({super.key, required this.onTap}); + + @override + State createState() => _RegisterPageState(); +} + +class _RegisterPageState extends State { + // text editing controllers + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + final confirmPasswordController = TextEditingController(); + + // Sign user up method + void signUserUp() async { + // Show loading indicator + showDialog( + context: context, + barrierDismissible: false, // Prevent dismissing by tapping outside + builder: (context) { + return const Center( + child: CircularProgressIndicator(), + ); + }, + ); + + // Check if passwords match + if (passwordController.text != confirmPasswordController.text) { + // Dismiss the loading indicator first + Navigator.pop(context); + + // Show error message if passwords don't match + showErrorMessage('Passwords do not match'); + return; + } + + try { + // Attempt to create the user + await FirebaseAuth.instance.createUserWithEmailAndPassword( + email: emailController.text, + password: passwordController.text, + ); + + // If successful, pop the loading dialog + Navigator.pop(context); // Close the loading dialog + + } on FirebaseAuthException catch (e) { + // If an error occurs, pop the loading dialog first + Navigator.pop(context); + + // Handle specific FirebaseAuthException errors + showErrorMessage(e.message ?? 'An error occurred'); + } + } + + // Display error message + void showErrorMessage(String message) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Center( + child: Text( + message, + style: const TextStyle(color: Colors.black), + ), + ), + ); + }, + ); + } + + @override + void dispose() { + // Dispose controllers to avoid memory leaks + emailController.dispose(); + passwordController.dispose(); + confirmPasswordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey[300], + body: SafeArea( + child: Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 50), + + // Logo + const Icon( + Icons.lock, + size: 50, + ), + + const SizedBox(height: 25), + + // Welcome message + Text( + 'Let\'s Create An Account For You!', + style: TextStyle( + color: Colors.grey[700], + fontSize: 16, + ), + ), + + const SizedBox(height: 25), + + // Email textfield + MyTextField( + controller: emailController, + hintText: 'Email', + obscureText: false, + ), + + const SizedBox(height: 10), + + // Password textfield + MyTextField( + controller: passwordController, + hintText: 'Password', + obscureText: true, + ), + + const SizedBox(height: 10), + + // Confirm password textfield + MyTextField( + controller: confirmPasswordController, + hintText: 'Confirm Password', + obscureText: true, + ), + + const SizedBox(height: 25), + + // Sign up button + MyButton( + onTap: signUserUp, + ), + + const SizedBox(height: 50), + + // Or continue with divider + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25.0), + child: Row( + children: [ + Expanded( + child: Divider( + thickness: 0.5, + color: Colors.grey[400], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Text( + 'Or continue with', + style: TextStyle(color: Colors.grey[700]), + ), + ), + Expanded( + child: Divider( + thickness: 0.5, + color: Colors.grey[400], + ), + ), + ], + ), + ), + + const SizedBox(height: 50), + + // Google and Apple sign in buttons + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + SquareTile(imagePath: 'lib/images/google.png'), + SizedBox(width: 25), + SquareTile(imagePath: 'lib/images/apple.png'), + ], + ), + + const SizedBox(height: 50), + + // Already a member? Login now + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Already a member?', + style: TextStyle(color: Colors.grey[700]), + ), + const SizedBox(width: 4), + GestureDetector( + onTap: widget.onTap, + child: const Text( + 'Login now', + style: TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 5c952a8..e453be7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,55 +1,118 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "5534e701a2c505fed1f0799e652dd6ae23bd4d2c4cf797220e5ced5764a7c1c2" + url: "https://pub.dev" + source: hosted + version: "1.3.44" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.18.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted version: "1.0.5" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: d453acec0d958ba0e25d41a9901b32cb77d1535766903dea7a61b2788c304596 + url: "https://pub.dev" + source: hosted + version: "5.3.1" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "78966c2ef774f5bf2a8381a307222867e9ece3509110500f7a138c115926aa65" + url: "https://pub.dev" + source: hosted + version: "7.4.7" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "77ad3b252badedd3f08dfa21a4c7fe244be96c6da3a4067f253b13ea5d32424c" + url: "https://pub.dev" + source: hosted + version: "5.13.2" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "51dfe2fbf3a984787a2e7b8592f2f05c986bfedd6fdacea3f9e0a7beb334de96" + url: "https://pub.dev" + source: hosted + version: "3.6.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 + url: "https://pub.dev" + source: hosted + version: "5.3.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 + url: "https://pub.dev" + source: hosted + version: "2.18.1" flutter: dependency: "direct main" description: flutter @@ -59,7 +122,8 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_test: @@ -67,41 +131,91 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" source: hosted version: "2.0.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.11.1" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.15.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -111,50 +225,82 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "1.3.2" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index b7229b3..9a1aeb1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + firebase_core: ^3.6.0 + firebase_auth: ^5.3.1 dev_dependencies: flutter_test: