Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Latest launch #14

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
722013e
feat: added launch repository and launch model
ninogjoni Nov 16, 2021
4fb6fbd
feat: add launch example and launches empty cubit and view
ninogjoni Nov 16, 2021
bc0c092
feat: add _getOne method in client
ninogjoni Nov 16, 2021
fdcf413
feat: add launchRepository in test
ninogjoni Nov 16, 2021
4a767b9
fix: update launch model
ninogjoni Nov 16, 2021
8e7df5c
feat: implemented launches cubit and state
ninogjoni Nov 16, 2021
5605391
feat: add launch page
ninogjoni Nov 16, 2021
d76e6e8
feat: add launch text
ninogjoni Nov 16, 2021
4c3ac2e
feat: add launch widget in home and launch image
ninogjoni Nov 16, 2021
6c33f52
feat: add links and patch in launch model
ninogjoni Nov 16, 2021
1d66dda
feat: add links in launch
ninogjoni Nov 16, 2021
23847f2
feat: add launch intl
ninogjoni Nov 16, 2021
41bb55b
feat: add latest launch page
ninogjoni Nov 16, 2021
9b1fd14
feat: add test for launches
ninogjoni Nov 16, 2021
483fde1
fix: replace positioned with container
ninogjoni Nov 16, 2021
4a2063c
chore: implement suggestions
ninogjoni Dec 1, 2021
a089826
chore: implement minor suggestions
ninogjoni Jan 28, 2022
f9c8361
chore: remove fieldRenames
ninogjoni Jan 28, 2022
00ec01c
chore: comments and docs
ninogjoni Jan 28, 2022
d9119b2
chore: remove unnecessary imports
ninogjoni Jan 28, 2022
7345a2a
fix: test crew member stringify
ninogjoni Jan 28, 2022
eda3bf3
chore: correct test and rename right key
ninogjoni Jan 28, 2022
d1ab807
feat: add launch tests
ninogjoni Jan 29, 2022
3897c05
add: latest launch test
ninogjoni Mar 16, 2022
20b55f0
chore: 100% test coverage
ninogjoni Apr 7, 2022
7768873
chore: implement suggested changes
ninogjoni Apr 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/images/img_spacex_launch.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:crew_member_repository/crew_member_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:launch_repository/launch_repository.dart';
import 'package:rocket_repository/rocket_repository.dart';
import 'package:spacex_demo/home/home.dart';
import 'package:spacex_demo/l10n/l10n.dart';
Expand All @@ -11,19 +12,23 @@ class App extends StatelessWidget {
Key? key,
required RocketRepository rocketRepository,
required CrewMemberRepository crewMemberRepository,
required LaunchRepository launchRepository,
}) : _rocketRepository = rocketRepository,
_crewMemberRepository = crewMemberRepository,
_launchRepository = launchRepository,
super(key: key);

final RocketRepository _rocketRepository;
final CrewMemberRepository _crewMemberRepository;
final LaunchRepository _launchRepository;

@override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: [
RepositoryProvider.value(value: _rocketRepository),
RepositoryProvider.value(value: _crewMemberRepository),
RepositoryProvider.value(value: _launchRepository)
],
child: const AppView(),
);
Expand Down
17 changes: 17 additions & 0 deletions lib/home/widgets/home_page_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:spacex_demo/crew/crew.dart';
import 'package:spacex_demo/home/home.dart';
import 'package:spacex_demo/l10n/l10n.dart';
import 'package:spacex_demo/launches/view/launches_page.dart';
ninogjoni marked this conversation as resolved.
Show resolved Hide resolved
import 'package:spacex_demo/rockets/rockets.dart';

class HomePageContent extends StatelessWidget {
Expand All @@ -15,6 +16,22 @@ class HomePageContent extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
child: SpaceXCategoryCard(
key: const Key(
'homePageContent_latestLaunch_spaceXCategoryCard',
),
onTap: () => Navigator.of(context).push(LaunchesPage.route()),
title: Text(l10n.latestLaunchSpaceXTileTitle),
imageUrl: 'assets/images/img_spacex_launch.jpeg',
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
Expand Down
51 changes: 36 additions & 15 deletions lib/l10n/arb/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@
"@homeAppBarTitle": {
"description": "Text shown in the AppBar of the Home Page"
},
"rocketSpaceXTileTitle": "Rockets",
"@rocketSpaceXTileTitle": {
"description": "Text included as title on the rockets tile on the home page"
},
"crewSpaceXTileTitle": "Crew",
"@crewSpaceXTileTitle": {
"description": "Text included as title on the crew tile on the home page"
},
"rocketsAppBarTitle": "Rockets",
"rocketSpaceXTileTitle": "Rockets",
"@rocketSpaceXTileTitle": {
"description": "Text included as title on the rockets tile on the home page"
},
"crewSpaceXTileTitle": "Crew",
"@crewSpaceXTileTitle": {
"description": "Text included as title on the crew tile on the home page"
},
"latestLaunchSpaceXTileTitle": "Latest Launch",
"@latestLaunchSpaceXTileTitle": {
"description": "Text included as title on the latest launch tile on the home page"
},
"latestLaunchAppBarTitle": "Latest Launch",
"@latestLaunchAppBarTitle": {
"description": "Text shown in the AppBar of the latest Launch Page"
},
"rocketsAppBarTitle": "Rockets",
"@rocketsAppBarTitle": {
"description": "Text shown in the AppBar of the Rockets Page"
},
Expand All @@ -29,32 +37,45 @@
}
}
},
"latestLaunchSubtitle": "Launched: {date}",
"@latestLaunchSubtitle": {
"description": "Subtitle text shown on the Launches Page that indicates the latest launch.",
"placeholders": {
"date": {
"example": "31-12-2021"
}
}
},
"openWebcastButtonText": "Open Webcast",
"@openWebcastButtonText": {
"description": "Button text shown on the Rocket Details Page that opens the corresponding Webcast page."
},
"openWikipediaButtonText": "Open Wikipedia",
"@openWikipediaButtonText": {
"description": "Button text shown on the Rocket Details Page that opens the corresponding Wikipedia page."
},
"crewAppBarTitle": "Crew",
"crewAppBarTitle": "Crew",
"@crewAppBarTitle": {
"description": "Text shown in the AppBar of the Crew Page"
},
"crewFetchErrorMessage": "Something went wrong while fetching crew members. Please try again later.",
"@crewFetchErrorMessage": {
"description": "Error text shown on the Home Page when an error occurred while fetching crew members."
},
"crewMemberDetailsAgency": "Agency",
"crewMemberDetailsAgency": "Agency",
"@crewMemberDetailsAgency": {
"description": "Prefix word placed in the 1st subtitle of the crew member details page"
},
"crewMemberDetailsParticipatedLaunches": "Has participated in",
"crewMemberDetailsParticipatedLaunches": "Has participated in",
"@crewMemberDetailsParticipatedLaunches": {
"description": "Prefix text placed in the 2nd subtitle of the crew member details page"
},
"crewMemberDetailsLaunch": "launch",
"crewMemberDetailsLaunch": "launch",
"@crewMemberDetailsLaunch": {
"description": "Singular suffix word placed in the 2nd subtitle of the crew member details page"
},
"crewMemberDetailsLaunches": "launches",
"crewMemberDetailsLaunches": "launches",
"@crewMemberDetailsLaunches": {
"description": "Plural suffix word placed in the 2nd subtitle of the crew member details page"
}
}
}
41 changes: 41 additions & 0 deletions lib/launches/cubit/launches_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:launch_repository/launch_repository.dart';
import 'package:spacex_api/spacex_api.dart';

part 'launches_state.dart';

class LaunchesCubit extends Cubit<LaunchesState> {
LaunchesCubit({
required LaunchRepository launchRepository,
}) : _launchRepository = launchRepository,
super(const LaunchesState());

final LaunchRepository _launchRepository;

Future<void> fetchLatestLaunch() async {
emit(
LaunchesState(
ninogjoni marked this conversation as resolved.
Show resolved Hide resolved
status: LaunchesStatus.loading,
latestLaunch: state.latestLaunch,
),
);

try {
final latestLaunch = await _launchRepository.fetchLatestLaunch();
emit(
LaunchesState(
status: LaunchesStatus.success,
latestLaunch: latestLaunch,
),
);
} on Exception {
emit(
LaunchesState(
status: LaunchesStatus.failure,
latestLaunch: state.latestLaunch,
),
);
}
}
}
16 changes: 16 additions & 0 deletions lib/launches/cubit/launches_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
part of 'launches_cubit.dart';

enum LaunchesStatus { initial, loading, success, failure }

class LaunchesState extends Equatable {
const LaunchesState({
this.status = LaunchesStatus.initial,
this.latestLaunch,
});

final LaunchesStatus status;
final Launch? latestLaunch;

@override
List<Object?> get props => [status, latestLaunch];
}
2 changes: 2 additions & 0 deletions lib/launches/launches.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'cubit/launches_cubit.dart';
export 'view/launches_page.dart';
178 changes: 178 additions & 0 deletions lib/launches/view/launches_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:launch_repository/launch_repository.dart';
import 'package:spacex_demo/l10n/l10n.dart';
import 'package:spacex_demo/launches/cubit/launches_cubit.dart';
ninogjoni marked this conversation as resolved.
Show resolved Hide resolved
import 'package:url_launcher/url_launcher.dart';

class LaunchesPage extends StatelessWidget {
const LaunchesPage({Key? key}) : super(key: key);

static Route<LaunchesPage> route() {
return MaterialPageRoute(
builder: (context) => const LaunchesPage(),
);
}

@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => LaunchesCubit(
launchRepository: context.read<LaunchRepository>(),
)..fetchLatestLaunch(),
child: const LaunchesView(),
);
}
}

class LaunchesView extends StatelessWidget {
const LaunchesView({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final l10n = context.l10n;

return Scaffold(
appBar: AppBar(
title: Text(l10n.latestLaunchAppBarTitle),
),
body: const Center(
child: _LaunchesContent(),
),
);
}
}

class _LaunchesContent extends StatelessWidget {
const _LaunchesContent({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final status = context.select((LaunchesCubit cubit) => cubit.state.status);

switch (status) {
case LaunchesStatus.initial:
return const SizedBox(
key: Key('launchesView_initial_sizedBox'),
);
case LaunchesStatus.loading:
return const Center(
key: Key('launchesView_loading_indicator'),
child: CircularProgressIndicator.adaptive(),
);
case LaunchesStatus.failure:
return Center(
key: const Key('launchesView_failure_text'),
child: Text(l10n.rocketsFetchErrorMessage),
);
case LaunchesStatus.success:
return const _LatestLaunch(
ninogjoni marked this conversation as resolved.
Show resolved Hide resolved
key: Key('launchesView_success_rocketList'),
);
}
}
}

class _LatestLaunch extends StatelessWidget {
const _LatestLaunch({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final l10n = context.l10n;

final latestLaunch =
context.select((LaunchesCubit cubit) => cubit.state.latestLaunch!);

return Container(
ninogjoni marked this conversation as resolved.
Show resolved Hide resolved
padding: const EdgeInsets.only(top: 10),
child: Column(
children: [
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
CircleAvatar(
backgroundImage: NetworkImage(latestLaunch.links.patch.small),
radius: 50,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
ninogjoni marked this conversation as resolved.
Show resolved Hide resolved
children: [
Text(latestLaunch.name),
],
),
Row(
children: [
Text('${latestLaunch.flightNumber}'),
],
),
],
),
)
],
),
subtitle: latestLaunch.dateUtc == null
? null
: Text(
l10n.latestLaunchSubtitle(
DateFormat('dd-MM-yyyy hh:mm')
.format(latestLaunch.dateUtc!),
),
),
),
const SizedBox(
height: 35,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
alignment: Alignment.bottomCenter,
child: SizedBox(
height: 64,
child: ElevatedButton(
key: const Key(
'launchesPage_openWebcast_elevatedButton',
),
onPressed: () async {
final url = latestLaunch.links.webcast;

if (await canLaunch(url)) {
await launch(url);
}
},
child: Text(l10n.openWebcastButtonText),
),
),
),
Container(
ninogjoni marked this conversation as resolved.
Show resolved Hide resolved
alignment: Alignment.bottomCenter,
child: SizedBox(
height: 64,
child: ElevatedButton(
key: const Key(
'launchesPage_openWikipedia_elevatedButton',
),
onPressed: () async {
final url = latestLaunch.links.wikipedia;

if (await canLaunch(url)) {
await launch(url);
}
},
child: Text(l10n.openWikipediaButtonText),
),
),
),
],
),
],
),
);
}
}
Loading