Skip to content

Commit c1d2cac

Browse files
committed
WIP launches screen
1 parent 7dc7996 commit c1d2cac

File tree

14 files changed

+446
-20
lines changed

14 files changed

+446
-20
lines changed

android/app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
22
package="dev.shtanko.flutter_bloc_app_template">
3+
4+
<uses-permission android:name="android.permission.INTERNET" />
5+
36
<application
47
android:label="flutter_bloc_app_template"
58
android:name="${applicationName}"

lib/app/app.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
44
import 'package:flutter_bloc_app_template/bloc/init/init_bloc.dart';
55
import 'package:flutter_bloc_app_template/di/di_container.dart';
6+
import 'package:flutter_bloc_app_template/features/launches/bloc/launches_bloc.dart';
67
import 'package:flutter_bloc_app_template/generated/l10n.dart';
78
import 'package:flutter_bloc_app_template/index.dart';
9+
import 'package:flutter_bloc_app_template/repository/launches_repository.dart';
10+
import 'package:flutter_bloc_app_template/repository/theme_repository.dart';
811
import 'package:flutter_bloc_app_template/theme/util.dart';
912
import 'package:flutter_localizations/flutter_localizations.dart';
1013

@@ -22,11 +25,15 @@ class MyApp extends StatelessWidget {
2225
RepositoryProvider<NavigationService>(
2326
create: (context) => NavigationService(),
2427
),
28+
RepositoryProvider<LaunchesRepository>(
29+
create: (context) => diContainer.get<LaunchesRepository>(),
30+
),
2531
],
2632
child: MultiBlocProvider(
2733
providers: [
2834
BlocProvider(
29-
create: (context) => ThemeCubit(diContainer.get())..loadTheme(),
35+
create: (context) =>
36+
ThemeCubit(diContainer.get<ThemeRepository>())..loadTheme(),
3037
),
3138
BlocProvider(
3239
create: (context) => EmailListBloc(
@@ -36,6 +43,13 @@ class MyApp extends StatelessWidget {
3643
EmailListFetched(),
3744
),
3845
),
46+
BlocProvider(
47+
create: (context) => LaunchesBloc(
48+
repository: RepositoryProvider.of<LaunchesRepository>(context),
49+
)..add(
50+
LaunchesFetched(),
51+
),
52+
),
3953
BlocProvider<InitBloc>(
4054
create: (_) => InitBloc()
4155
..add(

lib/data/network/api_result.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,20 @@ abstract class ApiResult<T> with _$ApiResult<T> {
1010

1111
const factory ApiResult.loading() = Loading<T>;
1212
}
13+
14+
extension ApiResultWhen<T> on ApiResult<T> {
15+
R when<R>({
16+
required R Function(T data) success,
17+
required R Function(String message) error,
18+
required R Function() loading,
19+
}) {
20+
if (this is Success<T>) {
21+
return success((this as Success<T>).data);
22+
} else if (this is Error<T>) {
23+
return error((this as Error<T>).message);
24+
} else if (this is Loading<T>) {
25+
return loading();
26+
}
27+
throw AssertionError('Unexpected type: $this');
28+
}
29+
}

lib/di/di_initializer.config.dart

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/di/di_network_module.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import 'package:injectable/injectable.dart';
55

66
@module
77
abstract class NetworkModule {
8+
@factoryMethod
9+
Dio provideDio() {
10+
final dio = Dio();
11+
return dio;
12+
}
13+
814
@factoryMethod
915
LaunchService provideLaunchService(Dio dio) {
1016
return LaunchService(dio);

lib/di/di_repository_module.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import 'package:flutter_bloc_app_template/data/network/data_source/launches_network_data_source.dart';
12
import 'package:flutter_bloc_app_template/data/theme_storage.dart';
3+
import 'package:flutter_bloc_app_template/repository/launches_repository.dart';
24
import 'package:flutter_bloc_app_template/repository/theme_repository.dart';
35
import 'package:injectable/injectable.dart';
46

@@ -7,4 +9,8 @@ abstract class RepositoryModule {
79
@factoryMethod
810
ThemeRepository provideAccidentsRepository(ThemeStorage themeStorage) =>
911
ThemeRepositoryImpl(themeStorage);
12+
13+
@factoryMethod
14+
LaunchesRepository provideLaunchesRepository(LaunchesDataSource dataSource) =>
15+
LaunchesRepositoryImpl(dataSource);
1016
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:equatable/equatable.dart';
3+
import 'package:flutter_bloc_app_template/models/launch.dart';
4+
import 'package:flutter_bloc_app_template/repository/launches_repository.dart';
5+
6+
class LaunchesBloc extends Bloc<LaunchesEvent, LaunchesState> {
7+
LaunchesBloc({required this.repository}) : super(LaunchesInitial()) {
8+
on<LaunchesFetched>(_onStarted);
9+
}
10+
11+
final LaunchesRepository repository;
12+
13+
void _onStarted(LaunchesFetched event, Emitter<LaunchesState> emit) async {
14+
emit(LaunchesLoading());
15+
try {
16+
final items = await repository.getLaunches();
17+
18+
if (items.isEmpty) {
19+
emit(LaunchesEmpty());
20+
} else {
21+
emit(LaunchesLoaded(items));
22+
}
23+
} catch (_) {
24+
emit(LaunchesLoadFailure());
25+
}
26+
}
27+
}
28+
29+
abstract class LaunchesState extends Equatable {
30+
@override
31+
List<Object> get props => [];
32+
}
33+
34+
class LaunchesInitial extends LaunchesState {}
35+
36+
class LaunchesLoading extends LaunchesState {}
37+
38+
class LaunchesLoaded extends LaunchesState {
39+
LaunchesLoaded(this.launches);
40+
41+
final List<LaunchResource> launches;
42+
43+
@override
44+
List<Object> get props => [launches];
45+
}
46+
47+
class LaunchesEmpty extends LaunchesState {}
48+
49+
class LaunchesLoadFailure extends LaunchesState {}
50+
51+
class LaunchesEvent extends Equatable {
52+
@override
53+
List<Object> get props => [];
54+
}
55+
56+
class LaunchesFetched extends LaunchesEvent {}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:flutter_bloc_app_template/features/launches/bloc/launches_bloc.dart';
4+
import 'package:flutter_bloc_app_template/generated/l10n.dart';
5+
import 'package:flutter_bloc_app_template/widgets/empty_widget.dart';
6+
7+
class LaunchesScreen extends StatelessWidget {
8+
const LaunchesScreen({super.key});
9+
10+
@override
11+
Widget build(BuildContext context) => Scaffold(
12+
appBar: AppBar(
13+
title: Text(S.of(context).messagesTitle),
14+
),
15+
body: RefreshIndicator(
16+
onRefresh: () async {
17+
await Future<void>.delayed(const Duration(seconds: 1));
18+
},
19+
child: SizedBox(
20+
height: MediaQuery.of(context).size.height,
21+
child: const LaunchesList(),
22+
),
23+
),
24+
);
25+
}
26+
27+
class LaunchesList extends StatelessWidget {
28+
const LaunchesList({super.key});
29+
30+
@override
31+
Widget build(BuildContext context) =>
32+
BlocBuilder<LaunchesBloc, LaunchesState>(
33+
builder: (context, state) {
34+
if (state is LaunchesInitial) {
35+
return const Center(
36+
child: CircularProgressIndicator(),
37+
);
38+
}
39+
40+
if (state is LaunchesLoading) {
41+
return const Center(
42+
child: CircularProgressIndicator(),
43+
);
44+
}
45+
46+
if (state is LaunchesEmpty) {
47+
return Center(
48+
child: Text(S.of(context).emptyList),
49+
);
50+
}
51+
52+
if (state is LaunchesLoaded) {
53+
var launches = state.launches;
54+
55+
return ListView.builder(
56+
physics: const BouncingScrollPhysics(),
57+
padding: EdgeInsets.zero,
58+
shrinkWrap: true,
59+
primary: false,
60+
itemBuilder: (context, index) => ListTile(
61+
title: Text(launches[index].missionName ?? 'Name'),
62+
onTap: () {
63+
// TODO handle tap
64+
},
65+
),
66+
itemCount: launches.length,
67+
);
68+
}
69+
70+
if (state is LaunchesLoadFailure) {
71+
return Text(S.of(context).error); // TODO
72+
}
73+
74+
return EmptyWidget();
75+
},
76+
);
77+
}

0 commit comments

Comments
 (0)