diff --git a/pokedex/lib/app/di/injection.config.dart b/pokedex/lib/app/di/injection.config.dart index c401065..d61636d 100644 --- a/pokedex/lib/app/di/injection.config.dart +++ b/pokedex/lib/app/di/injection.config.dart @@ -12,6 +12,12 @@ import 'package:dio/dio.dart' as _i361; import 'package:get_it/get_it.dart' as _i174; import 'package:injectable/injectable.dart' as _i526; import 'package:pokedex/common/network/di/network_module.dart' as _i813; +import 'package:pokedex/features/pokemon_list/data/pokemon_list_repository.dart' + as _i1009; +import 'package:pokedex/features/pokemon_list/domain/boundary/pokemon_list_repository.dart' + as _i977; +import 'package:pokedex/features/pokemon_list/presentation/bloc/pokemon_list_bloc.dart' + as _i285; // initializes the registration of main-scope dependencies inside of GetIt _i174.GetIt init( @@ -31,6 +37,10 @@ _i174.GetIt init( ); gh.singleton<_i361.Dio>( () => networkModule.provideClient(gh(instanceName: 'baseUrl'))); + gh.factory<_i977.PokemonListRepository>( + () => _i1009.PokemonListRepositoryImpl(dio: gh<_i361.Dio>())); + gh.factory<_i285.PokemonListBloc>( + () => _i285.PokemonListBloc(gh<_i977.PokemonListRepository>())); return getIt; } diff --git a/pokedex/lib/common/network/di/network_module.dart b/pokedex/lib/common/network/di/network_module.dart index 3b864e7..e994c09 100644 --- a/pokedex/lib/common/network/di/network_module.dart +++ b/pokedex/lib/common/network/di/network_module.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:injectable/injectable.dart'; @module @@ -13,5 +14,9 @@ abstract class NetworkModule { ) => Dio( BaseOptions(baseUrl: baseUrl), - ); + )..interceptors.add( + LogInterceptor( + logPrint: (element) => debugPrint(element.toString()), + ), + ); } diff --git a/pokedex/lib/features/pokemon_list/data/pokemon_list_repository.dart b/pokedex/lib/features/pokemon_list/data/pokemon_list_repository.dart index ede8fb2..e0ae921 100644 --- a/pokedex/lib/features/pokemon_list/data/pokemon_list_repository.dart +++ b/pokedex/lib/features/pokemon_list/data/pokemon_list_repository.dart @@ -1,10 +1,12 @@ import 'package:dio/dio.dart'; +import 'package:injectable/injectable.dart'; import '../../../common/api/model/named_api_response.dart'; import '../domain/boundary/pokemon_list_repository.dart'; import '../domain/model/pokemon.dart'; import '../domain/model/pokemon_page_data.dart'; +@Injectable(as: PokemonListRepository) class PokemonListRepositoryImpl implements PokemonListRepository { PokemonListRepositoryImpl({ required this.dio, diff --git a/pokedex/lib/features/pokemon_list/presentation/pokemon_list_page.dart b/pokedex/lib/features/pokemon_list/presentation/pokemon_list_page.dart index 0eab7e5..7255693 100644 --- a/pokedex/lib/features/pokemon_list/presentation/pokemon_list_page.dart +++ b/pokedex/lib/features/pokemon_list/presentation/pokemon_list_page.dart @@ -1,12 +1,40 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:pokedex/features/pokemon_list/presentation/bloc/pokemon_list_bloc.dart'; +import 'package:pokedex/features/pokemon_list/presentation/widgets/pokemon_success.dart'; class PokemonListPage extends StatelessWidget { const PokemonListPage({super.key}); @override Widget build(BuildContext context) { - return const Scaffold( - body: Text('Pokemon List'), + return BlocProvider( + create: (_) => GetIt.instance.get() + ..add( + const PokemonListEventRequest(), + ), + child: Scaffold( + body: SafeArea( + child: BlocBuilder( + builder: (context, state) { + final pageState = state.pageStatus; + if (state.firstPage) { + if (pageState == PageStatus.loading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (pageState == PageStatus.failure) { + return const Text('Error'); + } else if (pageState == PageStatus.initial) { + return const SizedBox.shrink(); + } + } + return const PokemonSuccess(); + }, + ), + ), + ), ); } } diff --git a/pokedex/lib/features/pokemon_list/presentation/widgets/pokemon_success.dart b/pokedex/lib/features/pokemon_list/presentation/widgets/pokemon_success.dart new file mode 100644 index 0000000..2172f67 --- /dev/null +++ b/pokedex/lib/features/pokemon_list/presentation/widgets/pokemon_success.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pokedex/design/base/spacing.dart'; +import 'package:pokedex/features/pokemon_list/presentation/bloc/pokemon_list_bloc.dart'; +import 'package:pokedex/features/pokemon_list/presentation/widgets/pokemon_card.dart'; + +class PokemonSuccess extends StatefulWidget { + const PokemonSuccess({super.key}); + + @override + State createState() => _PokemonSuccessState(); +} + +class _PokemonSuccessState extends State { + final _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScrollListener); + } + + @override + void dispose() { + _scrollController + ..removeListener(_onScrollListener) + ..dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + final state = context.watch().state; + final pageStatus = state.pageStatus; + + return CustomScrollView( + controller: _scrollController, + slivers: [ + const SliverAppBar( + snap: true, + floating: true, + pinned: true, + scrolledUnderElevation: 0, + expandedHeight: 120.0, + flexibleSpace: FlexibleSpaceBar( + centerTitle: false, + titlePadding: EdgeInsets.symmetric(horizontal: Spacing.kL), + title: Text('Hello World'), + ), + ), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: Spacing.kL), + sliver: SliverList.separated( + separatorBuilder: (context, index) => const SizedBox( + height: Spacing.kM, + ), + itemBuilder: (context, index) { + final pokemon = state.result[index]; + return PokemonCard(); + }, + itemCount: state.result.length, + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (_, __) { + final Widget tailWidget; + if (!state.firstPage && pageStatus == PageStatus.loading) { + tailWidget = const Padding( + padding: EdgeInsets.all(Spacing.kM), + child: Center( + child: CircularProgressIndicator(), + )); + } else if (!state.firstPage && pageStatus == PageStatus.failure) { + tailWidget = Padding( + padding: const EdgeInsets.symmetric( + vertical: Spacing.kM, horizontal: Spacing.kL), + child: Column( + children: [ + Text('Error title'), + ], + ), + ); + } else { + tailWidget = const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.symmetric(vertical: Spacing.kXL), + child: tailWidget, + ); + }, + childCount: 1, + ), + ) + ], + ); + } + + void _onScrollListener() { + if (_isBottomReached) { + // request other + } + } + + bool get _isBottomReached { + if (!_scrollController.hasClients) { + return false; + } + final maxScroll = _scrollController.position.maxScrollExtent; + final currentScroll = _scrollController.offset; + return currentScroll >= (maxScroll * 0.9); + } +}