Skip to content

Commit f6af1d4

Browse files
committed
WIP Add launch details screen + logic
1 parent 6330f94 commit f6af1d4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+5481
-724
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ localize:
2424

2525
# Analyze the project
2626
check:
27-
dart analyze .
27+
dart analyze . && flutter analyze
2828
# flutter pub run dart_code_metrics:metrics analyze lib
2929

3030
# Run with flavors
@@ -62,3 +62,6 @@ force_upgrade:
6262
# Run integration test
6363
integration_test:
6464
flutter test integration_test --flavor dev
65+
66+
# upgrade_deps:
67+
# flutter pub upgrade --major-versions

app-release.apk

-19.8 MB
Binary file not shown.

integration_test/launch_test.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter_bloc_app_template/features/launches/widget/launch_item.dart';
3+
import 'package:flutter_bloc_app_template/main.dart' as app;
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:integration_test/integration_test.dart';
6+
7+
void main() {
8+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
9+
10+
setUp(() {});
11+
12+
testWidgets('launch test', (
13+
WidgetTester tester,
14+
) async {
15+
app.main([]);
16+
await tester.pumpAndSettle();
17+
18+
final firstItem = find.byKey(const Key('FalconSat1'));
19+
20+
expect(firstItem, findsOneWidget);
21+
await tester.ensureVisible(firstItem);
22+
await tester.tap(find.byType(LaunchItem).first);
23+
24+
await tester.pumpAndSettle();
25+
expect(find.text('FalconSat'), findsOneWidget);
26+
});
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter_bloc_app_template/features/launches/widget/launch_item.dart';
3+
import 'package:flutter_bloc_app_template/main.dart' as app;
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:integration_test/integration_test.dart';
6+
7+
void main() {
8+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
9+
10+
setUp(() {});
11+
12+
testWidgets('launches test', (
13+
WidgetTester tester,
14+
) async {
15+
app.main([]);
16+
await tester.pumpAndSettle();
17+
18+
final firstItem = find.byKey(const Key('FalconSat1'));
19+
20+
expect(firstItem, findsOneWidget);
21+
await tester.ensureVisible(firstItem);
22+
await tester.tap(find.byType(LaunchItem).first);
23+
24+
await tester.pumpAndSettle();
25+
expect(find.text('FalconSat'), findsOneWidget);
26+
});
27+
}

lib/app/app.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import 'package:flutter_bloc_app_template/bloc/init/init_bloc.dart';
66
import 'package:flutter_bloc_app_template/di/di_container.dart';
77
import 'package:flutter_bloc_app_template/features/launches/bloc/launches_bloc.dart';
88
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';
119
import 'package:flutter_bloc_app_template/theme/util.dart';
1210

1311
class MyApp extends StatelessWidget {

lib/bloc/theme/theme_cubit.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'package:bloc/bloc.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc_app_template/bloc/theme/app_theme.dart';
44
import 'package:flutter_bloc_app_template/index.dart';
5-
import 'package:flutter_bloc_app_template/repository/theme_repository.dart';
65

76
Map<AppTheme, ThemeData> getThemeData(MaterialTheme theme) {
87
final themeData = <AppTheme, ThemeData>{

lib/data/network/api_result.freezed.dart

Lines changed: 194 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
3+
/// A custom [JsonConverter] that handles converting between a `DateTime?`
4+
/// and either a JSON string (ISO 8601) or an integer (Unix timestamp in
5+
/// milliseconds).
6+
///
7+
/// - When deserializing (`fromJson`), it supports both:
8+
/// - ISO 8601 formatted [String]s, e.g., `"2023-07-30T12:34:56Z"`
9+
/// - [int] values representing Unix timestamps in milliseconds since epoch
10+
///
11+
/// - When serializing (`toJson`), it converts the [DateTime] to an ISO 8601
12+
/// string in UTC.
13+
class TimestampSerializer implements JsonConverter<DateTime?, dynamic> {
14+
/// Creates a constant [TimestampSerializer].
15+
const TimestampSerializer();
16+
17+
/// Converts a JSON value into a [DateTime].
18+
///
19+
/// Supports:
20+
/// - [String] input in ISO 8601 format.
21+
/// - [int] input representing milliseconds since epoch.
22+
///
23+
/// Returns `null` if the input is neither a [String] nor an [int],
24+
/// or if parsing fails.
25+
@override
26+
DateTime? fromJson(dynamic json) {
27+
if (json is String) {
28+
return DateTime.tryParse(json);
29+
} else if (json is int) {
30+
return DateTime.fromMillisecondsSinceEpoch(json, isUtc: true);
31+
}
32+
return null;
33+
}
34+
35+
/// Converts a [DateTime] to a JSON value.
36+
///
37+
/// The output is an ISO 8601 [String] in UTC, or `null` if [date] is `null`.
38+
@override
39+
dynamic toJson(DateTime? date) => date?.toUtc().toIso8601String();
40+
}

lib/data/network/data_source/launches_network_data_source.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter_bloc_app_template/data/network/api_result.dart';
2+
import 'package:flutter_bloc_app_template/data/network/model/launch/full/network_launch_full_model.dart';
23
import 'package:flutter_bloc_app_template/data/network/model/launch/network_launch_model.dart';
34
import 'package:flutter_bloc_app_template/data/network/service/launch_service.dart';
45

@@ -11,6 +12,8 @@ abstract class LaunchesDataSource {
1112
int? launchSuccess,
1213
String? order,
1314
});
15+
16+
Future<ApiResult<NetworkLaunchFullModel>> getLaunch(int flightNumber);
1417
}
1518

1619
class LaunchesNetworkDataSource implements LaunchesDataSource {
@@ -41,4 +44,14 @@ class LaunchesNetworkDataSource implements LaunchesDataSource {
4144
return Future.value(ApiResult.error(e.toString()));
4245
}
4346
}
47+
48+
@override
49+
Future<ApiResult<NetworkLaunchFullModel>> getLaunch(int flightNumber) async {
50+
try {
51+
final result = await _service.fetchLaunch(flightNumber);
52+
return ApiResult.success(result);
53+
} catch (e) {
54+
return Future.value(ApiResult.error(e.toString()));
55+
}
56+
}
4457
}

0 commit comments

Comments
 (0)