Skip to content

Commit

Permalink
Merge pull request #44 from roughike/code-cleanup
Browse files Browse the repository at this point in the history
Code cleanup
  • Loading branch information
roughike authored Apr 8, 2018
2 parents de1cf9e + ebad711 commit a54af3d
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 75 deletions.
46 changes: 25 additions & 21 deletions lib/redux/app/app_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import 'package:inkino/redux/app/app_state.dart';
import 'package:inkino/redux/event/event_reducer.dart';
import 'package:inkino/redux/show/show_reducer.dart';
import 'package:inkino/redux/theater/theater_reducer.dart';
import 'package:redux/redux.dart';

AppState appReducer(AppState state, dynamic action) {
return new AppState(
searchQuery: _searchQueryReducer(state.searchQuery, action),
actorsByName: _actorReducer(state.actorsByName, action),
actorsByName: actorReducer(state.actorsByName, action),
theaterState: theaterReducer(state.theaterState, action),
showState: showReducer(state.showState, action),
eventState: eventReducer(state.eventState, action),
Expand All @@ -23,25 +24,28 @@ String _searchQueryReducer(String searchQuery, action) {
return searchQuery;
}

Map<String, Actor> _actorReducer(Map<String, Actor> actorsByName, action) {
if (action is ActorsUpdatedAction) {
var actors = <String, Actor>{}..addAll(actorsByName);
action.actors.forEach((actor) {
actors.putIfAbsent(actor.name, () => new Actor(name: actor.name));
});

return actors;
} else if (action is ReceivedActorAvatarsAction) {
var actorsWithAvatars = <String, Actor>{}..addAll(actorsByName);
action.actors.forEach((actor) {
actorsWithAvatars[actor.name] = new Actor(
name: actor.name,
avatarUrl: actor.avatarUrl,
);
});

return actorsWithAvatars;
}
final actorReducer = combineTypedReducers<Map<String, Actor>>([
new ReducerBinding<Map<String, Actor>, ActorsUpdatedAction>(_actorsUpdated),
new ReducerBinding<Map<String, Actor>, ReceivedActorAvatarsAction>(_receivedAvatars),
]);

Map<String, Actor> _actorsUpdated(Map<String, Actor> actorsByName, action) {
var actors = <String, Actor>{}..addAll(actorsByName);
action.actors.forEach((actor) {
actors.putIfAbsent(actor.name, () => new Actor(name: actor.name));
});

return actors;
}

Map<String, Actor> _receivedAvatars(Map<String, Actor> actorsByName, action) {
var actorsWithAvatars = <String, Actor>{}..addAll(actorsByName);
action.actors.forEach((actor) {
actorsWithAvatars[actor.name] = new Actor(
name: actor.name,
avatarUrl: actor.avatarUrl,
);
});

return actorsByName;
return actorsWithAvatars;
}
8 changes: 4 additions & 4 deletions lib/ui/event_details/event_backdrop_photo.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:inkino/assets.dart';
import 'package:inkino/data/models/event.dart';
import 'package:inkino/utils/widget_utils.dart';
import 'package:meta/meta.dart';

import 'dart:ui' as ui;

class EventBackdropPhoto extends StatelessWidget {
EventBackdropPhoto({
@required this.event,
Expand Down Expand Up @@ -34,6 +34,8 @@ class EventBackdropPhoto extends StatelessWidget {

Widget _buildPlaceholderBackground(BuildContext context) {
return new Container(
width: MediaQuery.of(context).size.width,
height: height,
decoration: new BoxDecoration(
gradient: new LinearGradient(
begin: Alignment.bottomCenter,
Expand All @@ -44,8 +46,6 @@ class EventBackdropPhoto extends StatelessWidget {
],
),
),
width: MediaQuery.of(context).size.width,
height: height,
child: new Center(
child: new Icon(
Icons.theaters,
Expand Down
2 changes: 2 additions & 0 deletions lib/ui/event_details/showtime_information.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:inkino/data/models/show.dart';
import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
import 'package:url_launcher/url_launcher.dart';

@visibleForTesting
Function(String) launchTicketsUrl = (url) async {
if (await canLaunch(url)) {
await launch(url);
Expand Down
6 changes: 1 addition & 5 deletions lib/ui/events/event_grid_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ class EventGridItem extends StatelessWidget {
gradient: new LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
stops: <double>[
0.0,
0.7,
0.7,
],
stops: <double>[0.0, 0.7, 0.7],
colors: <Color>[
Colors.black,
Colors.transparent,
Expand Down
79 changes: 44 additions & 35 deletions lib/ui/events/event_poster.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:inkino/assets.dart';
import 'package:inkino/data/models/event.dart';
import 'package:inkino/utils/widget_utils.dart';
import 'package:meta/meta.dart';
import 'package:url_launcher/url_launcher.dart';

@visibleForTesting
Function(String) launchTrailerVideo = (url) async {
if (await canLaunch(url)) {
await launch(url);
Expand All @@ -23,6 +25,27 @@ class EventPoster extends StatelessWidget {
final Size size;
final bool displayPlayButton;

BoxDecoration _buildDecorations() {
return new BoxDecoration(
boxShadow: <BoxShadow>[
new BoxShadow(
offset: const Offset(1.0, 1.0),
spreadRadius: 1.0,
blurRadius: 2.0,
color: Colors.black38,
),
],
gradient: new LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: <Color>[
const Color(0xFF222222),
const Color(0xFF424242),
],
),
);
}

Widget _buildPlayButton() {
if (displayPlayButton && event.youtubeTrailers.isNotEmpty) {
return new DecoratedBox(
Expand All @@ -39,7 +62,7 @@ class EventPoster extends StatelessWidget {
icon: new Icon(Icons.play_circle_outline),
iconSize: 42.0,
color: Colors.white.withOpacity(0.8),
onPressed: () async {
onPressed: () {
var url = event.youtubeTrailers.first;
launchTrailerVideo(url);
},
Expand All @@ -53,45 +76,31 @@ class EventPoster extends StatelessWidget {

@override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(
boxShadow: <BoxShadow>[
new BoxShadow(
offset: const Offset(1.0, 1.0),
spreadRadius: 1.0,
blurRadius: 2.0,
color: Colors.black38,
),
],
gradient: new LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: <Color>[
const Color(0xFF222222),
const Color(0xFF424242),
],
),
var content = <Widget>[
new Icon(
Icons.local_movies,
color: Colors.white24,
size: 72.0,
),
new FadeInImage.assetNetwork(
placeholder: ImageAssets.transparentImage,
image: event.images.portraitMedium ?? 'http://example.com',
width: size?.width,
height: size?.height,
fadeInDuration: const Duration(milliseconds: 300),
fit: BoxFit.cover,
),
];

addIfNonNull(_buildPlayButton(), content);

return new Container(
decoration: _buildDecorations(),
width: size?.width,
height: size?.height,
child: new Stack(
alignment: Alignment.center,
children: <Widget>[
new Icon(
Icons.local_movies,
color: Colors.white24,
size: 72.0,
),
new FadeInImage.assetNetwork(
placeholder: ImageAssets.transparentImage,
image: event.images.portraitMedium ?? 'http://example.com',
width: size?.width,
height: size?.height,
fadeInDuration: const Duration(milliseconds: 300),
fit: BoxFit.cover,
),
_buildPlayButton(),
],
children: content,
),
);
}
Expand Down
6 changes: 2 additions & 4 deletions lib/utils/event_name_cleaner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ class EventNameCleaner {
/// "Avengers: Infinity War (2D)" -> "Avengers: Infinity War"
/// "Avengers: Infinity War (2D dub)" -> "Avengers: Infinity War"
///
/// This is probably one of the most horrible regexes I've ever had to come up with.
///
/// For more, see test/event_name_cleaner_test.dart.
static final RegExp _pattern = new RegExp(
r"(\s([23]D$|\(([23]D|dub|orig|spanish|swe).*))");
static final RegExp _pattern =
new RegExp(r"(\s([23]D$|\(([23]D|dub|orig|spanish|swe).*))");

static String cleanup(String name) {
var matches = _pattern.allMatches(name);
Expand Down
86 changes: 86 additions & 0 deletions test/redux/app/app_reducer_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'package:inkino/data/models/actor.dart';
import 'package:inkino/redux/app/app_actions.dart';
import 'package:inkino/redux/app/app_reducer.dart';
import 'package:inkino/redux/app/app_state.dart';
import 'package:test/test.dart';

void main() {
group('AppReducer', () {
test(
'when called with ActorsUpdatedAction, should not modify existing actors',
() {
var state = new AppState.initial().copyWith(
actorsByName: <String, Actor>{
'Seth Ladd': new Actor(
name: 'Seth Ladd',
avatarUrl: 'https://seths-avatar-url',
),
'Eric Seidel': new Actor(
name: 'Eric Seidel',
avatarUrl: 'https://erics-avatar-url',
),
},
);

var reducedState = appReducer(
state,
new ActorsUpdatedAction(<Actor>[
new Actor(name: 'Seth Ladd', avatarUrl: null),
new Actor(name: 'Eric Seidel', avatarUrl: null),
new Actor(name: 'Ian Hickson', avatarUrl: null),
]),
);

expect(
reducedState.actorsByName,
<String, Actor>{
'Seth Ladd': new Actor(
name: 'Seth Ladd',
avatarUrl: 'https://seths-avatar-url',
),
'Eric Seidel': new Actor(
name: 'Eric Seidel',
avatarUrl: 'https://erics-avatar-url',
),
'Ian Hickson': new Actor(
name: 'Ian Hickson',
avatarUrl: null,
),
},
);
});

test(
'when called with ReceivedActorAvatarsAction, should add urls for actors',
() {
var state = new AppState.initial().copyWith(
actorsByName: <String, Actor>{
'Seth Ladd': new Actor(name: 'Seth Ladd', avatarUrl: null),
'Eric Seidel': new Actor(name: 'Eric Seidel', avatarUrl: null),
},
);

var reducedState = appReducer(
state,
new ReceivedActorAvatarsAction(<Actor>[
new Actor(name: 'Seth Ladd', avatarUrl: 'https://seths-avatar-url'),
new Actor(name: 'Eric Seidel', avatarUrl: 'https://erics-avatar-url'),
]),
);

expect(
reducedState.actorsByName,
<String, Actor>{
'Seth Ladd': new Actor(
name: 'Seth Ladd',
avatarUrl: 'https://seths-avatar-url',
),
'Eric Seidel': new Actor(
name: 'Eric Seidel',
avatarUrl: 'https://erics-avatar-url',
),
},
);
});
});
}
17 changes: 16 additions & 1 deletion test/test_utils.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart' as http;
import 'package:flutter/foundation.dart';

/// This replaces the built-in HTTP client with a mocked one. The mocked client
/// will always return a transparent image.
///
/// This is a workaround needed for widget tests that use network images,
/// otherwise the test will crash.
///
/// For more context:
///
/// * https://github.com/flutter/flutter/issues/13433
/// * https://github.com/flutter/flutter_markdown/pull/17
void mockAllImageResponses() {
createHttpClient = createMockImageHttpClient;
}

ValueGetter<http.Client> createMockImageHttpClient = () {
return new http.MockClient((http.BaseRequest request) {
Expand Down
4 changes: 2 additions & 2 deletions test/ui/event_details/event_details_page_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:inkino/data/models/actor.dart';
import 'package:inkino/data/models/event.dart';
Expand All @@ -21,7 +20,8 @@ void main() {
String lastLaunchedTrailerUrl;

setUp(() {
createHttpClient = createMockImageHttpClient;
mockAllImageResponses();

showtimeInfo.launchTicketsUrl = (url) => lastLaunchedTicketsUrl = url;
eventPoster.launchTrailerVideo = (url) => lastLaunchedTrailerUrl = url;
});
Expand Down
5 changes: 2 additions & 3 deletions test/ui/events/events_page_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:inkino/data/loading_status.dart';
import 'package:inkino/data/models/actor.dart';
Expand Down Expand Up @@ -45,11 +44,11 @@ void main() {
EventsPageViewModel mockViewModel;

setUp(() {
mockAllImageResponses();

observer = new NavigatorPushObserver();
mockViewModel = new MockEventsPageViewModel();
when(mockViewModel.refreshEvents).thenReturn(() {});

createHttpClient = createMockImageHttpClient;
});

Future<Null> _buildEventsPage(WidgetTester tester) {
Expand Down

0 comments on commit a54af3d

Please sign in to comment.