diff --git a/assets b/assets index 1111b93..52574f7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 1111b93f2f53913be80a1dfe127392dc50dab714 +Subproject commit 52574f712bbf7cad61a438bb84b52b0453699714 diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..7e7e7f6 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1 @@ +extensions: diff --git a/lib/analytics/analytics_constants.dart b/lib/analytics/analytics_constants.dart index 1082787..098ced5 100644 --- a/lib/analytics/analytics_constants.dart +++ b/lib/analytics/analytics_constants.dart @@ -5,6 +5,7 @@ const String kHeroPagesClass = 'hero_pages'; const String kBloonPagesClass = 'bloon_pages'; const String kBossPagesClass = 'boss_pages'; const String kMapPagesClass = 'map_pages'; +const String kFavoritesClass = 'favorites_page'; // ------- Const for event names ------- const String widgetEngagement = 'widget_engagement'; @@ -33,5 +34,6 @@ const String kHeroSkinsPage = 'hero_skins_page'; // ---- General ---- const String expansionTile = 'expansion_tile'; const String listTile = 'list_tile'; +const String card = 'card'; const String buttonOpen = 'opened'; const String buttonClose = 'closed'; diff --git a/lib/hive/favorite_model.dart b/lib/hive/favorite_model.dart new file mode 100644 index 0000000..18fe035 --- /dev/null +++ b/lib/hive/favorite_model.dart @@ -0,0 +1,20 @@ +import 'package:hive_flutter/hive_flutter.dart'; + +part 'favorite_model.g.dart'; + +@HiveType(typeId: 1) +class FavoriteModel extends HiveObject { + @HiveField(0) + late final String id; + + @HiveField(1) + late final String name; + + @HiveField(2) + late final String image; + + @HiveField(3) + late final String type; + + FavoriteModel(this.id, this.name, this.image, this.type); +} diff --git a/lib/hive/favorite_model.g.dart b/lib/hive/favorite_model.g.dart new file mode 100644 index 0000000..a930c81 --- /dev/null +++ b/lib/hive/favorite_model.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'favorite_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class FavoriteModelAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + FavoriteModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return FavoriteModel( + fields[0] as String, + fields[1] as String, + fields[2] as String, + fields[3] as String, + ); + } + + @override + void write(BinaryWriter writer, FavoriteModel obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.name) + ..writeByte(2) + ..write(obj.image) + ..writeByte(3) + ..write(obj.type); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FavoriteModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/hive/favorite_model_adapter.dart b/lib/hive/favorite_model_adapter.dart new file mode 100644 index 0000000..aea2597 --- /dev/null +++ b/lib/hive/favorite_model_adapter.dart @@ -0,0 +1,29 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import '/hive/favorite_model.dart'; + +class FavoriteModelBoxAdapter extends TypeAdapter { + @override + final typeId = 1; + + @override + FavoriteModel read(BinaryReader reader) { + return FavoriteModel( + // id + reader.readString(), + // name + reader.readString(), + // image + reader.readString(), + // type + reader.readString(), + ); + } + + @override + void write(BinaryWriter writer, FavoriteModel obj) { + writer.writeString(obj.id); + writer.writeString(obj.name); + writer.writeString(obj.image); + writer.writeString(obj.type); + } +} diff --git a/lib/main.dart b/lib/main.dart index 119d069..bcd14b3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,21 +1,25 @@ import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; import '/firebase_options.dart'; +import '/hive/favorite_model.dart'; import '/models/base/base_tower.dart'; import '/models/base/base_hero.dart'; import '/models/base/base_map.dart'; import '/models/base_model.dart'; -import '/presentation/widgets/drawer_content.dart'; +import '/presentation/screens/misc/favorite_screen.dart'; +import '/presentation/widgets/misc/drawer_content.dart'; import '/presentation/screens/tower/towers.dart'; import '/presentation/screens/bloon/bloons.dart'; import '/presentation/screens/hero/heroes.dart'; import '/presentation/screens/maps/maps.dart'; -import '/presentation/widgets/loader.dart'; -import 'analytics/analytics_constants.dart'; +import 'presentation/widgets/common/loader.dart'; +import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/global_state.dart'; import '/utilities/constants.dart'; import '/utilities/requests.dart'; @@ -29,29 +33,43 @@ Future main() async { options: DefaultFirebaseOptions.currentPlatform, ); final analytics = FirebaseAnalytics.instance; - + await Hive.initFlutter(); + Hive.registerAdapter(FavoriteModelAdapter()); + await Hive.openBox>( + kFavorite, + keyComparator: desiredCategoryOrder, + ); runApp(MyApp(analytics: analytics)); } class MyApp extends StatelessWidget { const MyApp({super.key, required this.analytics}); final FirebaseAnalytics analytics; + @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (BuildContext context) => GlobalState(), + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (BuildContext context) => GlobalState(), + ), + ChangeNotifierProvider( + create: (BuildContext context) => FavoriteState(), + ), + ], child: AdaptiveTheme( - light: Themes.lightTheme, - dark: Themes.darkTheme, - initial: AdaptiveThemeMode.system, - builder: (theme, darkTheme) => MaterialApp( - theme: theme, - darkTheme: darkTheme, - home: MyHomePage( - analytics: analytics, - ), - debugShowCheckedModeBanner: false, - )), + light: Themes.lightTheme, + dark: Themes.darkTheme, + initial: AdaptiveThemeMode.system, + builder: (theme, darkTheme) => MaterialApp( + theme: theme, + darkTheme: darkTheme, + home: MyHomePage( + analytics: analytics, + ), + debugShowCheckedModeBanner: false, + ), + ), ); } } @@ -152,30 +170,64 @@ class _MyHomePageState extends State { : Container(); }, ), - Consumer(builder: (context, globalState, child) { - return IconButton( - onPressed: () { - globalState.switchSearch(); - String value; - if (globalState.isSearchEnabled) { - value = searchOn; - } else { - globalState.updateCurrentQuery(''); - value = searchOff; - } - analyticsHelper.logEvent( - name: widgetEngagement, - parameters: { - 'screen': globalState.activeCategory, - 'widget': searchButton, - 'value': value, + Consumer( + builder: (context, globalState, child) { + return IconButton( + onPressed: () { + globalState.switchSearch(); + String value; + if (globalState.isSearchEnabled) { + value = searchOn; + } else { + globalState.updateCurrentQuery(''); + value = searchOff; + } + analyticsHelper.logEvent( + name: widgetEngagement, + parameters: { + 'screen': globalState.activeCategory, + 'widget': searchButton, + 'value': value, + }, + ); + }, + icon: Icon( + !globalState.isSearchEnabled ? Icons.search : Icons.close), + ); + }, + ), + Consumer( + builder: (context, favoriteState, child) { + IconData favIcon = + favoriteState.multiSelect ? Icons.add_task_sharp : Icons.star; + return GestureDetector( + onLongPress: () { + favoriteState.toggleMultiSelect(); + }, + child: IconButton( + onPressed: () { + if (!favoriteState.multiSelect) { + analyticsHelper.logScreenView( + screenClass: kFavoritesClass, + screenName: kFavoritesClass, + ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FavoriteScreen( + analyticsHelper: analyticsHelper, + ), + ), + ); + } else { + favoriteState.toggleMultiSelect(); + } }, - ); - }, - icon: Icon( - !globalState.isSearchEnabled ? Icons.search : Icons.close), - ); - }) + icon: Icon(favIcon), + ), + ); + }, + ), ], ), body: !isLoading diff --git a/lib/presentation/screens/bloon/bloons.dart b/lib/presentation/screens/bloon/bloons.dart index ca77ea1..a131760 100644 --- a/lib/presentation/screens/bloon/bloons.dart +++ b/lib/presentation/screens/bloon/bloons.dart @@ -1,16 +1,14 @@ import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; import '/models/base_model.dart'; -import '/presentation/widgets/search_widget.dart'; -import '/presentation/widgets/image_outline.dart'; +import '/presentation/widgets/bloons/bloons_grid.dart'; +import '/presentation/widgets/bloons/bosses_grid.dart'; +import '/presentation/widgets/misc/search_widget.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; import '/utilities/global_state.dart'; -import '/utilities/images_url.dart'; import '/utilities/constants.dart'; import '/utilities/utils.dart'; -import 'single_bloon.dart'; -import 'boss_bloon.dart'; class Bloons extends StatefulWidget { const Bloons({ @@ -124,137 +122,3 @@ class _BloonsState extends State { ); } } - -class BloonsGrid extends StatelessWidget { - const BloonsGrid({ - super.key, - required this.analyticsHelper, - required this.bloons, - required this.constraintsValues, - }); - - final AnalyticsHelper analyticsHelper; - final List bloons; - final Map constraintsValues; - - @override - Widget build(BuildContext context) { - return GridView.builder( - itemCount: bloons.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: constraintsValues[bloonCrossCount], - childAspectRatio: constraintsValues[bloonAspectRatio], - ), - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - primary: false, - itemBuilder: (context, index) { - final bloon = bloons[index]; - return Card( - margin: const EdgeInsets.symmetric( - vertical: 3, - horizontal: 7, - ), - child: Center( - child: ListTile( - titleAlignment: ListTileTitleAlignment.center, - leading: ImageOutliner( - imageName: bloon.image, - imagePath: bloonImage(bloon.image), - width: constraintsValues[bloonImageWidth], - ), - title: Text( - bloon.name, - maxLines: 1, - style: constraintsValues[bloonTitleStyle], - ), - onTap: () { - analyticsHelper.logEvent( - name: widgetEngagement, - parameters: { - 'screen': kBloonPagesClass, - 'widget': listTile, - 'value': bloon.id, - }, - ); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SingleBloon( - analyticsHelper: analyticsHelper, - bloonId: bloon.id, - ), - ), - ); - }, - ), - ), - ); - }, - ); - } -} - -class BossesGrid extends StatelessWidget { - const BossesGrid({ - super.key, - required this.analyticsHelper, - required this.bossesList, - required this.constraintsValues, - }); - final AnalyticsHelper analyticsHelper; - final List bossesList; - final Map constraintsValues; - - @override - Widget build(BuildContext context) { - return GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: constraintsValues[bossCrossCount], - childAspectRatio: constraintsValues[bossAspectRatio], - ), - physics: const NeverScrollableScrollPhysics(), - primary: false, - itemCount: bossesList.length, - shrinkWrap: true, - itemBuilder: (context, index) { - final boss = bossesList[index]; - - return Card( - child: Center( - child: ListTile( - titleAlignment: ListTileTitleAlignment.center, - leading: ImageOutliner( - imageName: boss.image, - imagePath: bossImage(boss.image), - ), - title: Text( - boss.name, - style: constraintsValues[bossTitleStyle], - ), - onTap: () { - analyticsHelper.logEvent( - name: widgetEngagement, - parameters: { - 'screen': kBossPagesClass, - 'widget': listTile, - 'value': boss.id, - }, - ); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => BossBloon( - analyticsHelper: analyticsHelper, - bossId: boss.id, - ), - ), - ); - }, - ), - ), - ); - }, - ); - } -} diff --git a/lib/presentation/screens/bloon/boss_bloon.dart b/lib/presentation/screens/bloon/boss_bloon.dart index e43e501..31f1c2f 100644 --- a/lib/presentation/screens/bloon/boss_bloon.dart +++ b/lib/presentation/screens/bloon/boss_bloon.dart @@ -1,13 +1,15 @@ import 'dart:convert'; +import 'package:provider/provider.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '/models/bloons/boss/boss_health_class.dart'; import '/models/bloons/boss/boss_bloon.dart'; -import '/presentation/widgets/bloon_aid_widget.dart'; +import '/presentation/widgets/bloons/bloon_aid_widget.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; @@ -58,9 +60,24 @@ class _BossBloonState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(!loading ? boss.name : ''), - ), + appBar: !loading + ? AppBar( + title: Text(boss.name), + actions: [ + Consumer( + builder: (context, favoriteState, child) { + return IconButton( + onPressed: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, boss), + icon: favoriteState.isFavorite(boss.type, boss.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ); + }, + ), + ], + ) + : AppBar(), body: !loading ? SingleChildScrollView( child: Padding( diff --git a/lib/presentation/screens/bloon/minion_bloon.dart b/lib/presentation/screens/bloon/minion_bloon.dart index f59691f..3962701 100644 --- a/lib/presentation/screens/bloon/minion_bloon.dart +++ b/lib/presentation/screens/bloon/minion_bloon.dart @@ -4,7 +4,7 @@ import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '/models/bloons/boss/minion_bloon.dart'; -import '/presentation/widgets/bloon_aid_widget.dart'; +import '/presentation/widgets/bloons/bloon_aid_widget.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; import '/utilities/images_url.dart'; diff --git a/lib/presentation/screens/bloon/single_bloon.dart b/lib/presentation/screens/bloon/single_bloon.dart index 8c34a3f..e33cb12 100644 --- a/lib/presentation/screens/bloon/single_bloon.dart +++ b/lib/presentation/screens/bloon/single_bloon.dart @@ -1,10 +1,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; import '/models/bloons/bloon/bloon.dart'; -import '/presentation/widgets/bloon_aid_widget.dart'; +import '/presentation/widgets/bloons/bloon_aid_widget.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; import '/utilities/utils.dart'; @@ -49,9 +51,24 @@ class _SingleBloonState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(!loading ? bloon.fullName : ''), - ), + appBar: !loading + ? AppBar( + title: Text(bloon.fullName), + actions: [ + Consumer( + builder: (context, favoriteState, child) { + return IconButton( + onPressed: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, bloon), + icon: favoriteState.isFavorite(bloon.type, bloon.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ); + }, + ), + ], + ) + : AppBar(), body: !loading ? SingleChildScrollView( child: Padding( diff --git a/lib/presentation/screens/hero/heroes.dart b/lib/presentation/screens/hero/heroes.dart index e3546b7..67dbcee 100644 --- a/lib/presentation/screens/hero/heroes.dart +++ b/lib/presentation/screens/hero/heroes.dart @@ -1,11 +1,12 @@ import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; import '/models/base/base_hero.dart'; +import '/presentation/widgets/misc/search_widget.dart'; +import '/presentation/widgets/common/image_outline.dart'; import '/presentation/screens/hero/single_hero.dart'; -import '/presentation/widgets/search_widget.dart'; -import '/presentation/widgets/image_outline.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/global_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; @@ -40,6 +41,7 @@ class _HeroesState extends State { final constraintsValues = getPreset( MediaQuery.of(context).size, ); + return Scaffold( body: Column( children: [ @@ -50,8 +52,8 @@ class _HeroesState extends State { : Container(), ), Expanded( - child: Consumer( - builder: (context, globalState, child) { + child: Consumer2( + builder: (context, globalState, favoriteState, child) { final filteredHeroes = heroesFromSearch(widget.heroes, globalState.currentQuery); return GridView.builder( @@ -64,47 +66,61 @@ class _HeroesState extends State { itemBuilder: (context, index) { final hero = filteredHeroes[index]; - return Card( - margin: const EdgeInsets.symmetric( - horizontal: 13, vertical: 8), - child: Center( - child: ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 8), - horizontalTitleGap: 8, - leading: ImageOutliner( - imageName: hero.image, - imagePath: heroImage(hero.image), - ), - title: Text( - hero.name, - style: constraintsValues[heroTitleStyle], - ), - subtitle: Text( - hero.inGameDesc, - overflow: TextOverflow.ellipsis, - style: constraintsValues[heroSubtitleStyle], - maxLines: constraintsValues[heroSubtitleRows], - ), - onTap: () { - widget.analyticsHelper.logEvent( - name: widgetEngagement, - parameters: { - 'screen': kHeroPagesClass, - 'widget': listTile, - 'value': hero.id, - }, - ); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SingleHero( - heroId: hero.id, - analyticsHelper: widget.analyticsHelper, - ), + return InkWell( + borderRadius: BorderRadius.circular(20), + onLongPress: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, hero), + onTap: () { + if (!favoriteState.multiSelect) { + widget.analyticsHelper.logEvent( + name: widgetEngagement, + parameters: { + 'screen': kHeroPagesClass, + 'widget': listTile, + 'value': hero.id, + }, + ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SingleHero( + heroId: hero.id, + analyticsHelper: widget.analyticsHelper, ), - ); - }, + ), + ); + } else { + favoriteState.toggleFavoriteFunc( + context, favoriteState, hero); + } + }, + child: Card( + margin: const EdgeInsets.symmetric( + horizontal: 13, vertical: 8), + child: Center( + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(horizontal: 8), + horizontalTitleGap: 8, + leading: ImageOutliner( + imageName: hero.image, + imagePath: heroImage(hero.image), + ), + title: Text( + hero.name, + style: constraintsValues[heroTitleStyle], + ), + subtitle: Text( + hero.inGameDesc, + overflow: TextOverflow.ellipsis, + style: constraintsValues[heroSubtitleStyle], + maxLines: constraintsValues[heroSubtitleRows], + ), + trailing: + favoriteState.isFavorite(hero.type, hero.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ), ), ), ); diff --git a/lib/presentation/screens/hero/single_hero.dart b/lib/presentation/screens/hero/single_hero.dart index 600014b..f293bde 100644 --- a/lib/presentation/screens/hero/single_hero.dart +++ b/lib/presentation/screens/hero/single_hero.dart @@ -2,13 +2,15 @@ import 'dart:convert'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import '/models/towers/common/upgrade_info_class.dart'; import '/models/towers/hero/hero.dart'; -import '/presentation/widgets/hero_stats.dart'; -import '/presentation/widgets/hero_level.dart'; +import '/presentation/widgets/heroes/hero_stats.dart'; +import '/presentation/widgets/heroes/hero_level.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; import '/utilities/utils.dart'; @@ -98,9 +100,25 @@ class _SingleHeroState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(!loading ? singleHero.name : ''), - ), + appBar: !loading + ? AppBar( + title: Text(singleHero.name), + actions: [ + Consumer( + builder: (context, favoriteState, child) { + return IconButton( + onPressed: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, singleHero), + icon: favoriteState.isFavorite( + singleHero.type, singleHero.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ); + }, + ), + ], + ) + : AppBar(), body: !loading ? SingleChildScrollView( child: Padding( diff --git a/lib/presentation/screens/maps/maps.dart b/lib/presentation/screens/maps/maps.dart index ba07c3a..c558a9b 100644 --- a/lib/presentation/screens/maps/maps.dart +++ b/lib/presentation/screens/maps/maps.dart @@ -1,11 +1,12 @@ import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; import '/models/base/base_map.dart'; +import '/presentation/widgets/misc/search_widget.dart'; import '/presentation/screens/maps/single_map.dart'; -import '/presentation/widgets/search_widget.dart'; import '/presentation/widgets/maps/map_card.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/global_state.dart'; import '/utilities/constants.dart'; import '/utilities/utils.dart'; @@ -59,8 +60,8 @@ class _MapsState extends State { }, ), Expanded( - child: Consumer( - builder: (context, globalState, child) { + child: Consumer2( + builder: (context, globalState, favoriteState, child) { final filteredMaps = filterAndSearchMaps(widget.maps, globalState.currentQuery, globalState.currentOption); return GridView.builder( @@ -71,29 +72,36 @@ class _MapsState extends State { ), shrinkWrap: true, itemBuilder: (context, index) { + BaseMap map = filteredMaps[index]; return Padding( padding: const EdgeInsets.all(5.0), child: GestureDetector( + onLongPress: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, map), onTap: () { - widget.analyticsHelper.logEvent( - name: widgetEngagement, - parameters: { - 'screen': kMapPagesClass, - 'widget': listTile, - 'value': filteredMaps[index].id, - }, - ); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SingleMap( - analyticsHelper: widget.analyticsHelper, - mapId: filteredMaps[index].id, + if (!favoriteState.multiSelect) { + widget.analyticsHelper.logEvent( + name: widgetEngagement, + parameters: { + 'screen': kMapPagesClass, + 'widget': map.id, + }, + ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SingleMap( + analyticsHelper: widget.analyticsHelper, + mapId: map.id, + ), ), - ), - ); + ); + } else { + favoriteState.toggleFavoriteFunc( + context, favoriteState, map); + } }, - child: MapCard(singleMap: filteredMaps[index]), + child: MapCard(singleMap: map), ), ); }, diff --git a/lib/presentation/screens/maps/single_map.dart b/lib/presentation/screens/maps/single_map.dart index 9dbcab8..2048ec5 100644 --- a/lib/presentation/screens/maps/single_map.dart +++ b/lib/presentation/screens/maps/single_map.dart @@ -1,9 +1,11 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; import '/models/maps/map.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; @@ -48,9 +50,24 @@ class _SingleMapState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(!loading ? map.name : ''), - ), + appBar: !loading + ? AppBar( + title: Text(map.name), + actions: [ + Consumer( + builder: (context, favoriteState, child) { + return IconButton( + onPressed: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, map), + icon: favoriteState.isFavorite(map.type, map.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ); + }, + ), + ], + ) + : AppBar(), body: !loading ? SingleChildScrollView( child: Padding( diff --git a/lib/presentation/screens/misc/favorite_screen.dart b/lib/presentation/screens/misc/favorite_screen.dart new file mode 100644 index 0000000..f48ab0e --- /dev/null +++ b/lib/presentation/screens/misc/favorite_screen.dart @@ -0,0 +1,55 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import '/analytics/analytics.dart'; +import '/presentation/widgets/misc/orderable_grid.dart'; +import '/utilities/favorite_state.dart'; + +class FavoriteScreen extends StatefulWidget { + const FavoriteScreen({super.key, required this.analyticsHelper}); + + final AnalyticsHelper analyticsHelper; + + @override + State createState() => _FavoriteScreenState(); +} + +class _FavoriteScreenState extends State { + @override + Widget build(BuildContext context) { + FavoriteState favoriteState = + Provider.of(context, listen: false); + List categories = + List.from(favoriteState.favoriteBox.keys.toList()); + final generatedChildren = List.generate( + categories.length, + (index) => OrderableGrid( + gridKey: GlobalKey(), + favoriteItems: favoriteState.getListOfType(categories[index]), + typeName: categories[index], + analyticsHelper: widget.analyticsHelper, + ), + ); + return Scaffold( + appBar: AppBar( + title: const Text('Favorites'), + actions: [ + Consumer( + builder: (context, favoriteState, child) { + return IconButton( + onPressed: () { + favoriteState.toggleMultiSelect(); + }, + icon: Icon( + !favoriteState.multiSelect ? Icons.delete : Icons.close), + ); + }, + ), + ], + ), + body: ListView( + shrinkWrap: true, + children: generatedChildren, + ), + ); + } +} diff --git a/lib/presentation/screens/tower/single_tower.dart b/lib/presentation/screens/tower/single_tower.dart index 82db7a9..9b0d951 100644 --- a/lib/presentation/screens/tower/single_tower.dart +++ b/lib/presentation/screens/tower/single_tower.dart @@ -1,10 +1,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; import '/models/towers/tower/tower.dart'; -import '/presentation/widgets/path.dart'; +import '/presentation/widgets/towers/path.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; import '/utilities/utils.dart'; @@ -67,9 +69,24 @@ class _SingleTowerState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(!loading ? tower.name : ""), - ), + appBar: !loading + ? AppBar( + title: Text(tower.name), + actions: [ + Consumer( + builder: (context, favoriteState, child) { + return IconButton( + onPressed: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, tower), + icon: favoriteState.isFavorite(tower.type, tower.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ); + }, + ), + ], + ) + : AppBar(), body: !loading ? SingleChildScrollView( child: Padding( diff --git a/lib/presentation/screens/tower/towers.dart b/lib/presentation/screens/tower/towers.dart index f6715b1..744c480 100644 --- a/lib/presentation/screens/tower/towers.dart +++ b/lib/presentation/screens/tower/towers.dart @@ -2,10 +2,11 @@ import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; import '/models/base/base_tower.dart'; import '/presentation/screens/tower/single_tower.dart'; -import '/presentation/widgets/search_widget.dart'; -import '/presentation/widgets/image_outline.dart'; +import '/presentation/widgets/misc/search_widget.dart'; +import '/presentation/widgets/common/image_outline.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/global_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; @@ -29,7 +30,6 @@ class _TowersState extends State { @override void initState() { super.initState(); - widget.analyticsHelper.logScreenView( screenClass: kMainPagesClass, screenName: kTowers, @@ -41,6 +41,7 @@ class _TowersState extends State { final constraintsValues = getPreset( MediaQuery.of(context).size, ); + return Scaffold( body: Column( children: [ @@ -51,8 +52,8 @@ class _TowersState extends State { : Container(), ), Expanded( - child: Consumer( - builder: (context, globalState, child) { + child: Consumer2( + builder: (context, globalState, favoriteState, child) { final filteredTowers = filterAndSearchTowers(widget.towers, globalState.currentQuery, globalState.currentOption); @@ -66,49 +67,62 @@ class _TowersState extends State { itemBuilder: (context, index) { final tower = filteredTowers[index]; - return Card( - margin: const EdgeInsets.symmetric( - horizontal: 13, vertical: 8), - child: Center( - child: ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 8), - horizontalTitleGap: 8, - minVerticalPadding: -4, - leading: ImageOutliner( - imageName: tower.image, - imagePath: towerImage(tower.image), - width: constraintsValues[towerImageWidth], - ), - title: Text( - tower.name, - style: constraintsValues[towerTitleStyle], - ), - subtitle: Text( - tower.inGameDesc, - overflow: TextOverflow.ellipsis, - maxLines: constraintsValues[towerSubtitleRows], - style: constraintsValues[towerSubtitleStyle], - ), - onTap: () { - widget.analyticsHelper.logEvent( - name: widgetEngagement, - parameters: { - 'screen': kTowerPagesClass, - 'widget': listTile, - 'value': tower.id, - }, - ); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SingleTower( - towerId: tower.id, - analyticsHelper: widget.analyticsHelper, - ), + return InkWell( + borderRadius: BorderRadius.circular(20), + onLongPress: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, tower), + onTap: () { + if (!favoriteState.multiSelect) { + widget.analyticsHelper.logEvent( + name: widgetEngagement, + parameters: { + 'screen': kTowerPagesClass, + 'widget': listTile, + 'value': tower.id, + }, + ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SingleTower( + towerId: tower.id, + analyticsHelper: widget.analyticsHelper, ), - ); - }, + ), + ); + } else { + favoriteState.toggleFavoriteFunc( + context, favoriteState, tower); + } + }, + child: Card( + margin: const EdgeInsets.symmetric( + horizontal: 13, vertical: 8), + child: Center( + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(horizontal: 8), + horizontalTitleGap: 8, + leading: ImageOutliner( + imageName: tower.image, + imagePath: towerImage(tower.image), + width: constraintsValues[towerImageWidth], + ), + title: Text( + tower.name, + style: constraintsValues[towerTitleStyle], + ), + subtitle: Text( + tower.inGameDesc, + overflow: TextOverflow.ellipsis, + maxLines: constraintsValues[towerSubtitleRows], + style: constraintsValues[towerSubtitleStyle], + ), + trailing: + favoriteState.isFavorite(tower.type, tower.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ), ), ), ); diff --git a/lib/presentation/widgets/bloon_aid_widget.dart b/lib/presentation/widgets/bloons/bloon_aid_widget.dart similarity index 100% rename from lib/presentation/widgets/bloon_aid_widget.dart rename to lib/presentation/widgets/bloons/bloon_aid_widget.dart diff --git a/lib/presentation/widgets/bloons/bloons_grid.dart b/lib/presentation/widgets/bloons/bloons_grid.dart new file mode 100644 index 0000000..4f80d0c --- /dev/null +++ b/lib/presentation/widgets/bloons/bloons_grid.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '/models/base_model.dart'; +import '/presentation/widgets/common/image_outline.dart'; +import '/presentation/screens/bloon/single_bloon.dart'; +import '/analytics/analytics_constants.dart'; +import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; +import '/utilities/images_url.dart'; +import '/utilities/constants.dart'; + +class BloonsGrid extends StatelessWidget { + const BloonsGrid({ + super.key, + required this.analyticsHelper, + required this.bloons, + required this.constraintsValues, + }); + + final AnalyticsHelper analyticsHelper; + final List bloons; + final Map constraintsValues; + + @override + Widget build(BuildContext context) { + return GridView.builder( + itemCount: bloons.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: constraintsValues[bloonCrossCount], + childAspectRatio: constraintsValues[bloonAspectRatio], + ), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + primary: false, + itemBuilder: (context, index) { + final bloon = bloons[index]; + return Consumer( + builder: (context, favoriteState, child) { + return InkWell( + borderRadius: BorderRadius.circular(20), + onLongPress: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, bloon), + onTap: () { + if (!favoriteState.multiSelect) { + analyticsHelper.logEvent( + name: widgetEngagement, + parameters: { + 'screen': kBloonPagesClass, + 'widget': listTile, + 'value': bloon.id, + }, + ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SingleBloon( + analyticsHelper: analyticsHelper, + bloonId: bloon.id, + ), + ), + ); + } else { + favoriteState.toggleFavoriteFunc( + context, favoriteState, bloon); + } + }, + child: Card( + margin: const EdgeInsets.symmetric( + vertical: 3, + horizontal: 7, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImageOutliner( + imageName: bloon.image, + imagePath: bloonImage(bloon.image), + width: constraintsValues[bloonImageWidth], + ), + Center( + child: Text( + bloon.name, + style: constraintsValues[bloonTitleStyle], + ), + ), + Icon( + favoriteState.isFavorite(bloon.type, bloon.id) + ? Icons.star + : Icons.star_border_outlined, + ), + ], + ), + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/presentation/widgets/bloons/bosses_grid.dart b/lib/presentation/widgets/bloons/bosses_grid.dart new file mode 100644 index 0000000..25b9528 --- /dev/null +++ b/lib/presentation/widgets/bloons/bosses_grid.dart @@ -0,0 +1,89 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import '/analytics/analytics_constants.dart'; +import '/analytics/analytics.dart'; +import '/models/base_model.dart'; +import '/presentation/widgets/common/image_outline.dart'; +import '/presentation/screens/bloon/boss_bloon.dart'; +import '/utilities/favorite_state.dart'; +import '/utilities/images_url.dart'; +import '/utilities/constants.dart'; + +class BossesGrid extends StatelessWidget { + const BossesGrid({ + super.key, + required this.analyticsHelper, + required this.bossesList, + required this.constraintsValues, + }); + final AnalyticsHelper analyticsHelper; + final List bossesList; + final Map constraintsValues; + + @override + Widget build(BuildContext context) { + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: constraintsValues[bossCrossCount], + childAspectRatio: constraintsValues[bossAspectRatio], + ), + physics: const NeverScrollableScrollPhysics(), + primary: false, + itemCount: bossesList.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final boss = bossesList[index]; + return Consumer( + builder: (context, favoriteState, child) { + return InkWell( + onLongPress: () => favoriteState.toggleFavoriteFunc( + context, favoriteState, boss), + onTap: () { + if (!favoriteState.multiSelect) { + analyticsHelper.logEvent( + name: widgetEngagement, + parameters: { + 'screen': kBossPagesClass, + 'widget': listTile, + 'value': boss.id, + }, + ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BossBloon( + analyticsHelper: analyticsHelper, + bossId: boss.id, + ), + ), + ); + } else { + favoriteState.toggleFavoriteFunc( + context, favoriteState, boss); + } + }, + child: Card( + child: Center( + child: ListTile( + titleAlignment: ListTileTitleAlignment.center, + leading: ImageOutliner( + imageName: boss.image, + imagePath: bossImage(boss.image), + ), + title: Text( + boss.name, + style: constraintsValues[bossTitleStyle], + ), + trailing: favoriteState.isFavorite(boss.type, boss.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ), + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/presentation/widgets/image_outline.dart b/lib/presentation/widgets/common/image_outline.dart similarity index 100% rename from lib/presentation/widgets/image_outline.dart rename to lib/presentation/widgets/common/image_outline.dart diff --git a/lib/presentation/widgets/loader.dart b/lib/presentation/widgets/common/loader.dart similarity index 100% rename from lib/presentation/widgets/loader.dart rename to lib/presentation/widgets/common/loader.dart diff --git a/lib/presentation/widgets/hero_level.dart b/lib/presentation/widgets/heroes/hero_level.dart similarity index 100% rename from lib/presentation/widgets/hero_level.dart rename to lib/presentation/widgets/heroes/hero_level.dart diff --git a/lib/presentation/widgets/hero_stats.dart b/lib/presentation/widgets/heroes/hero_stats.dart similarity index 100% rename from lib/presentation/widgets/hero_stats.dart rename to lib/presentation/widgets/heroes/hero_stats.dart diff --git a/lib/presentation/widgets/maps/map_card.dart b/lib/presentation/widgets/maps/map_card.dart index 5e23d64..49e578d 100644 --- a/lib/presentation/widgets/maps/map_card.dart +++ b/lib/presentation/widgets/maps/map_card.dart @@ -1,6 +1,8 @@ import 'package:auto_size_text/auto_size_text.dart'; -import 'package:btd6wiki/models/base/base_map.dart'; +import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; +import '/models/base/base_map.dart'; +import '/utilities/favorite_state.dart'; import '/utilities/images_url.dart'; import '/utilities/constants.dart'; import '/utilities/strings.dart'; @@ -12,40 +14,40 @@ class MapCard extends StatelessWidget { @override Widget build(BuildContext context) { - return Card( - elevation: 5, - shadowColor: Colors.black87, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: Image( - semanticLabel: singleMap.name, - image: AssetImage(mapImage(singleMap.image)), - fit: BoxFit.cover, - errorBuilder: (BuildContext context, Object exception, - StackTrace? stackTrace) { - return const Icon(Icons.error); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AutoSizeText( + return Consumer( + builder: (context, favoriteState, child) { + return Card( + elevation: 5, + shadowColor: Colors.black87, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Image( + semanticLabel: singleMap.name, + image: AssetImage(mapImage(singleMap.image)), + fit: BoxFit.cover, + errorBuilder: (BuildContext context, Object exception, + StackTrace? stackTrace) { + return const Icon(Icons.error); + }, + ), + ), + ListTile( + title: AutoSizeText( capitalizeEveryWord(singleMap.name), maxLines: 1, style: bolderNormalStyle, ), - const SizedBox(height: 5), - Text(singleMap.difficulty, style: subtitleStyle), - ], - ), + subtitle: Text(singleMap.difficulty, style: subtitleStyle), + trailing: favoriteState.isFavorite(singleMap.type, singleMap.id) + ? const Icon(Icons.star) + : const Icon(Icons.star_border_outlined), + ), + ], ), - ], - ), + ); + }, ); } } diff --git a/lib/presentation/widgets/about_us.dart b/lib/presentation/widgets/misc/about_us.dart similarity index 98% rename from lib/presentation/widgets/about_us.dart rename to lib/presentation/widgets/misc/about_us.dart index c0f9a27..b999b99 100644 --- a/lib/presentation/widgets/about_us.dart +++ b/lib/presentation/widgets/misc/about_us.dart @@ -1,6 +1,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:flutter/material.dart'; -import '/presentation/widgets/developer_info.dart'; +import 'developer_info.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; import '/utilities/constants.dart'; diff --git a/lib/presentation/widgets/developer_info.dart b/lib/presentation/widgets/misc/developer_info.dart similarity index 100% rename from lib/presentation/widgets/developer_info.dart rename to lib/presentation/widgets/misc/developer_info.dart diff --git a/lib/presentation/widgets/drawer_content.dart b/lib/presentation/widgets/misc/drawer_content.dart similarity index 97% rename from lib/presentation/widgets/drawer_content.dart rename to lib/presentation/widgets/misc/drawer_content.dart index c3932c0..1fcfcb1 100644 --- a/lib/presentation/widgets/drawer_content.dart +++ b/lib/presentation/widgets/misc/drawer_content.dart @@ -1,7 +1,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; -import '/presentation/widgets/about_us.dart'; +import 'about_us.dart'; import '/analytics/analytics_constants.dart'; import '/analytics/analytics.dart'; import '/utilities/global_state.dart'; @@ -27,7 +27,7 @@ class _DrawerContentState extends State { ExpansionTileController(); final ExpansionTileController _mapsExpansionTileController = ExpansionTileController(); - final ExpansionTileController _bloonsExpansionTileControleer = + final ExpansionTileController _bloonsExpansionTileController = ExpansionTileController(); @override @@ -90,7 +90,7 @@ class _DrawerContentState extends State { () { if (value) { _mapsExpansionTileController.collapse(); - _bloonsExpansionTileControleer.collapse(); + _bloonsExpansionTileController.collapse(); } }, ); @@ -153,7 +153,7 @@ class _DrawerContentState extends State { }, ), ExpansionTile( - controller: _bloonsExpansionTileControleer, + controller: _bloonsExpansionTileController, title: Text(capTitles[kBloonsIndex], style: titleStyle.copyWith(color: Colors.teal)), onExpansionChanged: (bool value) { @@ -227,7 +227,7 @@ class _DrawerContentState extends State { setState( () { if (value) { - _bloonsExpansionTileControleer.collapse(); + _bloonsExpansionTileController.collapse(); _towersExpansionTileController.collapse(); } }, diff --git a/lib/presentation/widgets/misc/orderable_grid.dart b/lib/presentation/widgets/misc/orderable_grid.dart new file mode 100644 index 0000000..a11bfed --- /dev/null +++ b/lib/presentation/widgets/misc/orderable_grid.dart @@ -0,0 +1,87 @@ +import 'package:flutter_reorderable_grid_view/entities/order_update_entity.dart'; +import 'package:flutter_reorderable_grid_view/widgets/widgets.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import '/analytics/analytics.dart'; +import '/utilities/favorite_state.dart'; +import '/utilities/constants.dart'; +import '/utilities/strings.dart'; +import '/utilities/utils.dart'; + +class OrderableGrid extends StatefulWidget { + const OrderableGrid({ + super.key, + required this.gridKey, + required this.typeName, + required this.favoriteItems, + required this.analyticsHelper, + }); + + final GlobalKey gridKey; + final String typeName; + final List favoriteItems; + final AnalyticsHelper analyticsHelper; + + @override + State createState() => _OrderableGridState(); +} + +class _OrderableGridState extends State { + final _scrollController = ScrollController(); + @override + Widget build(BuildContext context) { + final constraintsValues = getPreset( + MediaQuery.of(context).size, + ); + return Consumer( + builder: (context, favoriteState, child) { + List generatedChildren = generateGridChildren( + widget.favoriteItems, + widget.analyticsHelper, + widget.typeName, + constraintsValues); + if (generatedChildren.isNotEmpty) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Text( + capitalizeEveryWord(widget.typeName), + style: bigTitleStyle, + ), + ReorderableBuilder( + enableLongPress: false, + scrollController: _scrollController, + onReorder: (List orderUpdateEntities) { + for (final orderUpdateEntity in orderUpdateEntities) { + final favItem = widget.favoriteItems + .removeAt(orderUpdateEntity.oldIndex); + widget.favoriteItems + .insert(orderUpdateEntity.newIndex, favItem); + } + favoriteState.updateIndexes( + widget.typeName, widget.favoriteItems); + }, + builder: (children) { + return GridView( + key: widget.gridKey, + controller: _scrollController, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: constraintsValues[favItemCrossCount], + childAspectRatio: constraintsValues[favItemAspectRatio], + ), + children: children, + ); + }, + children: generatedChildren, + ), + ], + ), + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/presentation/widgets/search_widget.dart b/lib/presentation/widgets/misc/search_widget.dart similarity index 100% rename from lib/presentation/widgets/search_widget.dart rename to lib/presentation/widgets/misc/search_widget.dart diff --git a/lib/presentation/widgets/path.dart b/lib/presentation/widgets/towers/path.dart similarity index 100% rename from lib/presentation/widgets/path.dart rename to lib/presentation/widgets/towers/path.dart diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index ce8ce9d..70e75b2 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -5,6 +5,7 @@ const String kHeroes = 'heroes'; const String kBloons = 'bloons'; const String kBlimps = 'blimps'; const String kBosses = 'bosses'; +const String kMinions = 'minions'; const String kMaps = 'maps'; const int kTowersIndex = 0; @@ -12,6 +13,10 @@ const int kHeroesIndex = 1; const int kBloonsIndex = 2; const int kMapsIndex = 3; +const String kFavorite = 'favorite'; + +const Duration snackBarDuration = Duration(seconds: 2); + const List capTitles = [ 'Towers', 'Heroes', @@ -200,3 +205,9 @@ const String mapCrossCount = 'mapCrossAxisCount'; const String mapAspectRatio = 'mapChildAspectRatio'; const String mapTitleStyle = 'mapTitleStyle'; const String mapSubtitleStyle = 'mapSubtitleStyle'; + +const String favItemCrossCount = 'favItemCrossCount'; +const String favItemAspectRatio = 'favItemAspectRatio'; +const String favItemSubtitleStyle = 'favItemSubtitleStyle'; +const String favItemImageFlex = 'favItemImageFlex'; +const String favItemTextFlex = 'favItemTextFlex'; diff --git a/lib/utilities/favorite_state.dart b/lib/utilities/favorite_state.dart new file mode 100644 index 0000000..c6e3803 --- /dev/null +++ b/lib/utilities/favorite_state.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import '/hive/favorite_model.dart'; +import '/utilities/constants.dart'; + +class FavoriteState extends ChangeNotifier { + late Box> _favoriteBox; + + bool _multiSelect = false; + + FavoriteState() { + _favoriteBox = Hive.box>(kFavorite); + } + + Box> get favoriteBox => _favoriteBox; + bool get multiSelect => _multiSelect; + + List getListOfType(String type) { + if (_favoriteBox.containsKey(type)) { + return List.from(_favoriteBox.get(type)!); + } + + return []; + } + + void toggleMultiSelect() { + _multiSelect = !_multiSelect; + notifyListeners(); + } + + void toggleFavoriteFunc( + BuildContext context, FavoriteState favoriteState, var item) { + String msg = favoriteState.toggleFavorite(item); + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Center(child: Text(msg)), + duration: snackBarDuration, + dismissDirection: DismissDirection.horizontal, + ), + ); + notifyListeners(); + } + + String toggleFavorite(var item) { + bool addedToFavorites = false; + FavoriteModel favItem = _createFavoriteItem(item); + + if (!_favoriteBox.containsKey(favItem.type)) { + _favoriteBox.put(favItem.type, [favItem]); + addedToFavorites = true; + } else { + List typeList = + List.from(_favoriteBox.get(favItem.type)!); + if (isFavorite(favItem.type, favItem.id)) { + typeList.removeWhere((element) => element.id == favItem.id); + addedToFavorites = false; + } else { + typeList.add(favItem); + addedToFavorites = true; + } + _favoriteBox.put(favItem.type, typeList); + } + notifyListeners(); + return addedToFavorites ? 'Added to favorites!' : 'Removed from favorites.'; + } + + bool isFavorite(String type, String id) { + if (!_favoriteBox.containsKey(type)) return false; + List typeList = + List.from(_favoriteBox.get(type)!); + for (FavoriteModel item in typeList) { + if (item.id == id) return true; + } + + return false; + } + + int _getLastIndexOfType(String type) { + if (!_favoriteBox.containsKey(type)) { + return 0; + } else { + var favorites = _favoriteBox.get(type)!; + return favorites.length; + } + } + + FavoriteModel _createFavoriteItem(var item) { + return FavoriteModel( + item.id, + item.name, + item.image, + item.type, + ); + } + + void updateIndexes(String type, List items) { + _favoriteBox.put(type, items); + } +} diff --git a/lib/utilities/layout_presets.dart b/lib/utilities/layout_presets.dart index ba39f4f..f66948f 100644 --- a/lib/utilities/layout_presets.dart +++ b/lib/utilities/layout_presets.dart @@ -18,9 +18,9 @@ Map presetUS = { skinAspectRatio: 1.0, // bloons bloonCrossCount: 2, - bloonAspectRatio: 2.85, + bloonAspectRatio: 3.0, bloonTitleStyle: smallTitleStyle.copyWith(fontSize: 12.0), - bloonImageWidth: 28.0, + bloonImageWidth: 26.0, // bosses bossCrossCount: 1, bossAspectRatio: 4.3, @@ -28,6 +28,12 @@ Map presetUS = { // maps mapCrossCount: 1, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 2, + favItemAspectRatio: 0.6, + favItemImageFlex: 5, + favItemTextFlex: 2, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 14.0), }; Map presetXS = { // towers @@ -37,7 +43,6 @@ Map presetXS = { towerSubtitleStyle: subtitleStyle.copyWith(fontSize: 13.0), towerSubtitleRows: 2, towerImageWidth: 55.0, - // heroes heroCrossCount: 1, heroAspectRatio: 3.5, @@ -48,9 +53,9 @@ Map presetXS = { skinAspectRatio: 1.0, // bloons bloonCrossCount: 2, - bloonAspectRatio: 2.7, - bloonTitleStyle: smallTitleStyle.copyWith(fontSize: 15.0), - bloonImageWidth: 28.0, + bloonAspectRatio: 3.0, + bloonTitleStyle: smallTitleStyle.copyWith(fontSize: 13.0), + bloonImageWidth: 26.0, // bosses bossCrossCount: 1, bossAspectRatio: 4.3, @@ -58,6 +63,12 @@ Map presetXS = { // maps mapCrossCount: 1, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 3, + favItemAspectRatio: 0.60, + favItemImageFlex: 9, + favItemTextFlex: 4, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 14.0), }; Map presetSM = { // towers @@ -67,7 +78,6 @@ Map presetSM = { towerSubtitleStyle: subtitleStyle.copyWith(fontSize: 14.0), towerSubtitleRows: 2, towerImageWidth: 65.0, - // heroes heroCrossCount: 1, heroAspectRatio: 3.5, @@ -88,6 +98,12 @@ Map presetSM = { // maps mapCrossCount: 1, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 3, + favItemAspectRatio: 0.65, + favItemImageFlex: 7, + favItemTextFlex: 3, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 16.5), }; Map presetMD = { // towers @@ -97,7 +113,6 @@ Map presetMD = { towerSubtitleStyle: subtitleStyle.copyWith(fontSize: 14.5), towerSubtitleRows: 3, towerImageWidth: 70.0, - // heroes heroCrossCount: 1, heroAspectRatio: 3.5, @@ -118,6 +133,12 @@ Map presetMD = { // maps mapCrossCount: 2, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 3, + favItemAspectRatio: 0.65, + favItemImageFlex: 7, + favItemTextFlex: 3, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 16.5), }; Map presetLG = { // towers @@ -127,7 +148,6 @@ Map presetLG = { towerSubtitleStyle: subtitleStyle.copyWith(fontSize: 15), towerSubtitleRows: 2, towerImageWidth: 90.0, - // heroes heroCrossCount: 1, heroAspectRatio: 4.3, @@ -148,6 +168,12 @@ Map presetLG = { // maps mapCrossCount: 2, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 4, + favItemAspectRatio: 0.62, + favItemImageFlex: 7, + favItemTextFlex: 3, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 15.5), }; Map presetXL = { // towers @@ -157,7 +183,6 @@ Map presetXL = { towerSubtitleStyle: subtitleStyle.copyWith(fontSize: 14.0), towerSubtitleRows: 2, towerImageWidth: 50.0, - // heroes heroCrossCount: 2, heroAspectRatio: 3.0, @@ -178,6 +203,12 @@ Map presetXL = { // maps mapCrossCount: 2, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 5, + favItemAspectRatio: 0.62, + favItemImageFlex: 7, + favItemTextFlex: 3, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 15.5), }; Map presetXXL = { // towers @@ -187,7 +218,6 @@ Map presetXXL = { towerSubtitleStyle: subtitleStyle.copyWith(fontSize: 14.0), towerSubtitleRows: 2, towerImageWidth: 65.0, - // heroes heroCrossCount: 2, heroAspectRatio: 3.5, @@ -208,6 +238,12 @@ Map presetXXL = { // maps mapCrossCount: 2, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 6, + favItemAspectRatio: 0.60, + favItemImageFlex: 7, + favItemTextFlex: 4, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 15.0), }; Map presetXXXL = { // towers @@ -217,7 +253,6 @@ Map presetXXXL = { towerSubtitleStyle: subtitleStyle.copyWith(fontSize: 14.5), towerSubtitleRows: 3, towerImageWidth: 90.0, - // heroes heroCrossCount: 3, heroAspectRatio: 2.5, @@ -238,4 +273,10 @@ Map presetXXXL = { // maps mapCrossCount: 3, mapAspectRatio: 1.3, + // favorites + favItemCrossCount: 6, + favItemAspectRatio: 0.60, + favItemImageFlex: 7, + favItemTextFlex: 3, + favItemSubtitleStyle: subtitleStyle.copyWith(fontSize: 15.0), }; diff --git a/lib/utilities/utils.dart b/lib/utilities/utils.dart index e6e4d2f..d607778 100644 --- a/lib/utilities/utils.dart +++ b/lib/utilities/utils.dart @@ -1,5 +1,9 @@ +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:flutter/material.dart'; +import '/hive/favorite_model.dart'; +import '/analytics/analytics_constants.dart'; +import '/analytics/analytics.dart'; import '/models/bloons/common/relative_class.dart'; import '/models/towers/common/stats_class.dart'; import '/models/towers/common/cost_class.dart'; @@ -7,8 +11,92 @@ import '/models/base/base_tower.dart'; import '/models/base/base_hero.dart'; import '/models/base/base_map.dart'; import '/models/base_model.dart'; -import 'constants.dart'; +import '/presentation/screens/tower/single_tower.dart'; +import '/presentation/screens/bloon/single_bloon.dart'; +import '/presentation/screens/bloon/boss_bloon.dart'; +import '/presentation/screens/hero/single_hero.dart'; +import '/presentation/screens/maps/single_map.dart'; +import '/utilities/favorite_state.dart'; import 'layout_presets.dart'; +import 'images_url.dart'; +import 'constants.dart'; + +int desiredCategoryOrder(dynamic key1, dynamic key2) { + // Define the desired order as a list of category keys + final desiredOrder = [ + 'towers', + 'heroes', + 'bloons', + 'blimps', + 'bosses', + 'maps', + ]; + + // Get the category names from the keys + final category1 = key1.split(':')[0]; + final category2 = key2.split(':')[0]; + + // Find the indices of the categories in the desired order + final index1 = desiredOrder.indexOf(category1); + final index2 = desiredOrder.indexOf(category2); + + // Compare the indices to determine the order + if (index1 == -1 || index2 == -1) { + // Handle unexpected categories (not in desiredOrder) + return 0; // Or throw an error if preferred + } else if (index1 < index2) { + return -1; // Category1 comes before Category2 + } else if (index1 > index2) { + return 1; // Category1 comes after Category2 + } else { + return 0; // Categories have the same desired order (shouldn't happen) + } +} + +void navigateToPage(BuildContext context, var item, + AnalyticsHelper analyticsHelper, String originScreen, String originWidget) { + analyticsHelper.logEvent( + name: widgetEngagement, + parameters: { + 'screen': originScreen, + 'widget': originWidget, + 'value': item.id, + }, + ); + + Map destinations = { + kTowers: SingleTower( + towerId: item.id, + analyticsHelper: analyticsHelper, + ), + kHeroes: SingleHero( + heroId: item.id, + analyticsHelper: analyticsHelper, + ), + kBloons: SingleBloon( + bloonId: item.id, + analyticsHelper: analyticsHelper, + ), + kBlimps: SingleBloon( + bloonId: item.id, + analyticsHelper: analyticsHelper, + ), + kBosses: BossBloon( + bossId: item.id, + analyticsHelper: analyticsHelper, + ), + kMaps: SingleMap( + mapId: item.id, + analyticsHelper: analyticsHelper, + ), + }; + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => destinations[item.type]!, + ), + ); +} String formatBigNumber(int number) { if (number < 1000) { @@ -22,6 +110,74 @@ String formatBigNumber(int number) { } } +List generateGridChildren( + List favoriteItems, + AnalyticsHelper analyticsHelper, + String typeName, + Map constraintsValues) { + return List.generate( + favoriteItems.length, + (index) { + FavoriteModel favItem = favoriteItems.elementAt(index); + return Consumer( + key: Key(favItem.id), + builder: (context, favoriteState, child) { + return InkWell( + onLongPress: () { + favoriteState.toggleFavoriteFunc(context, favoriteState, favItem); + favoriteItems.removeWhere((item) => item.id == favItem.id); + }, + onTap: () { + if (!favoriteState.multiSelect) { + navigateToPage( + context, + favItem, + analyticsHelper, + kFavoritesClass, + card, + ); + } else { + favoriteState.toggleFavoriteFunc( + context, favoriteState, favItem); + favoriteItems.removeWhere((item) => item.id == favItem.id); + } + }, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: constraintsValues[favItemImageFlex], + child: Image( + image: + AssetImage(assetImagePath(typeName, favItem.image)), + ), + ), + Flexible( + flex: constraintsValues[favItemTextFlex], + child: Center( + child: Text( + favItem.name, + textAlign: TextAlign.center, + style: constraintsValues[favItemSubtitleStyle], + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ); +} + String getPathKeyFromIndex(int index) { switch (index) { case 0: @@ -49,6 +205,24 @@ String extraStatsToString(Stats stats) { return "Status Effects: ${stats.statuseffects}\nIncome Boosts: ${stats.incomeboosts}\nTower Boosts: ${stats.towerboosts}"; } +String assetImagePath(String type, String imageName) { + if (type == kTowers) { + return towerImage(imageName); + } else if (type == kHeroes) { + return heroImage(imageName); + } else if (type == kBloons || type == kBlimps) { + return bloonImage(imageName); + } else if (type == kBosses) { + return bossImage(imageName); + } else if (type == kMinions) { + return minionImage(imageName); + } else if (type == kMaps) { + return mapImage(imageName); + } else { + throw Exception('Unsupported image type - $type'); + } +} + List filterAndSearchBloons( List bloons, String query, String option) { query = query.toLowerCase(); @@ -162,7 +336,7 @@ Future openMail(String mailString) async { } Map getPreset(Size size) { - if (size.width < 310) { + if (size.width < 321) { return presetUS; } else if (size.width < 360) { return presetXS; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f1bd702..8b0aac0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,14 @@ import Foundation import firebase_analytics import firebase_core +import path_provider_foundation import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 10051fe..bf26608 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,22 +1,38 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7 + sha256: "1a52f1afae8ab7ac4741425114713bdbba802f1ce1e0648e167ffcc6e05e96cf" url: "https://pub.dev" source: hosted - version: "1.3.16" + version: "1.3.21" adaptive_theme: dependency: "direct main" description: name: adaptive_theme - sha256: "38b433c0761d9f15017a15c9ff3e15e8905d3e9bad54a489c7939da6bdf782dd" + sha256: f4ee609b464e5efc68131d9d15ba9aa1de4e3b5ede64be17781c6e19a52d637d url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.6.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" android_intent_plus: dependency: transitive description: @@ -29,10 +45,10 @@ packages: dependency: transitive description: name: archive - sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.9" + version: "3.4.10" args: dependency: transitive description: @@ -65,6 +81,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + 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: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 + url: "https://pub.dev" + source: hosted + version: "8.9.0" carousel_slider: dependency: "direct main" description: @@ -93,10 +173,10 @@ packages: dependency: transitive description: name: cli_util - sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.4.1" clock: dependency: transitive description: @@ -105,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -137,6 +225,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + url: "https://pub.dev" + source: hosted + version: "2.3.4" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -165,34 +269,34 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: "5e92d510eacd66c354718fd9cc8f66ffdfa025640b645c4742297fb973770508" + sha256: edb9f9eaecf0e6431e5c12b7fabdb68be3e85ce51f941ccbfa6cb71327e8b535 url: "https://pub.dev" source: hosted - version: "10.7.4" + version: "10.8.5" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: "72977325a72af5ebb8e53b5c5533cb2e33eec481cd46210cfe5427f5efba55d8" + sha256: de4a54353cf58412c6da6b660a0dbad8efacb33b345c0286bc3a2edb869124d8 url: "https://pub.dev" source: hosted - version: "3.8.4" + version: "3.9.5" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "8b9710be7e292e2a5ad34fff449d4b668c5808fb339649e69181727a4534f579" + sha256: "77e4c02ffd0204ccc7856221193265c807b7e056fa62855f973a7f77435b5d41" url: "https://pub.dev" source: hosted - version: "0.5.5+11" + version: "0.5.5+17" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb" + sha256: "7e049e32a9d347616edb39542cf92cd53fdb4a99fb6af0a0bff327c14cd76445" url: "https://pub.dev" source: hosted - version: "2.24.2" + version: "2.25.4" firebase_core_platform_interface: dependency: transitive description: @@ -205,10 +309,18 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0 + sha256: "57e61d6010e253b36d38191cefd6199d7849152cdcd234b61ca290cdb278a0ba" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -230,6 +342,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + flutter_reorderable_grid_view: + dependency: "direct main" + description: + name: flutter_reorderable_grid_view + sha256: ac92a49a8411adfda40f75eff44d0958ee879b0f9d6776bb6154c74aa166aaf1 + url: "https://pub.dev" + source: hosted + version: "4.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -244,18 +364,74 @@ packages: dependency: "direct main" description: name: font_awesome_flutter - sha256: "52671aea66da73b58d42ec6d0912b727a42248dd9a7c76d6c20f275783c48c08" + sha256: "275ff26905134bcb59417cf60ad979136f1f8257f2f449914b2c3e05bbb4cd6f" + url: "https://pub.dev" + source: hosted + version: "10.7.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" url: "https://pub.dev" source: hosted - version: "10.6.0" + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" http: dependency: "direct main" description: name: http - sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -268,10 +444,18 @@ packages: dependency: transitive description: name: image - sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" + sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a" url: "https://pub.dev" source: hosted - version: "4.1.3" + version: "4.1.6" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" js: dependency: transitive description: @@ -296,6 +480,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -320,6 +512,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" model_viewer_plus: dependency: "direct main" description: @@ -336,6 +536,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -344,6 +552,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -356,10 +588,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -380,26 +612,34 @@ packages: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + url: "https://pub.dev" + source: hosted + version: "3.7.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "1.5.1" provider: dependency: "direct main" description: @@ -408,6 +648,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" shared_preferences: dependency: transitive description: @@ -428,10 +684,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: @@ -444,10 +700,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: @@ -464,6 +720,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -477,6 +749,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" source_span: dependency: transitive description: @@ -501,6 +789,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -525,6 +821,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" typed_data: dependency: transitive description: @@ -537,34 +841,34 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.2.4" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -577,26 +881,26 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" vector_math: dependency: transitive description: @@ -605,6 +909,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" web: dependency: transitive description: @@ -613,54 +925,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" webview_flutter: dependency: transitive description: name: webview_flutter - sha256: "42393b4492e629aa3a88618530a4a00de8bb46e50e7b3993fedbfdc5352f0dbf" + sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413 url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: "8326ee235f87605a2bfc444a4abc897f4abc78d83f054ba7d3d1074ce82b4fbf" + sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0" url: "https://pub.dev" source: hosted - version: "3.12.1" + version: "3.15.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "68e86162aa8fc646ae859e1585995c096c95fc2476881fa0c4a8d10f56013a5a" + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.10.0" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: accdaaa49a2aca2dc3c3230907988954cdd23fed0a19525d6c9789d380f4dc76 + sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86" url: "https://pub.dev" source: hosted - version: "3.9.4" + version: "3.12.0" win32: dependency: transitive description: name: win32 - sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.2.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: @@ -678,5 +998,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.2.3 <4.0.0" + flutter: ">=3.16.6" diff --git a/pubspec.yaml b/pubspec.yaml index 5e985bd..f0cec73 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,10 +17,10 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.17+19 +version: 1.0.17+20 environment: - sdk: ">=2.18.5 <3.0.0" + sdk: ">=2.18.5 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -46,12 +46,17 @@ dependencies: carousel_slider: ^4.2.1 smooth_page_indicator: ^1.1.0 provider: ^6.1.1 + hive: ^2.2.3 + hive_flutter: ^1.1.0 + flutter_reorderable_grid_view: ^4.0.0 dev_dependencies: flutter_launcher_icons: ^0.13.1 flutter_test: sdk: flutter flutter_lints: ^3.0.1 + hive_generator: ^2.0.0 + build_runner: ^2.1.0 flutter_icons: android: true