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

feat: Add initial brand support #431

Merged
merged 12 commits into from
May 7, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'brand_bloc.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'package:bloc/bloc.dart';

import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';

part 'brand_event.dart';
part 'brand_state.dart';

// [BrandBloc] is a Bloc responsible for managing the brand state.
coire1 marked this conversation as resolved.
Show resolved Hide resolved
// This Bloc listens for [BrandEvent]s and updates the [BrandState]
// accordingly.
// The [BrandState] can be passed to the [MaterialApp] as a theme
// allowing to change the theme at runtime.
//
// The [BrandChanged] accepts a [BrandKey] and uses that is used for
// the selection of the related [ThemeData] defined in the [brands]
// map.
// To trigger the theme change is just necessary to dispatch the
// [BrandChanged] with the appropriate key:
//
// ```dart
// context.read<BrandBloc>().add(
// const BrandChanged(BrandKey.catalyst),
// );
// ```
//
// Usage example:
//
// ```dart
// BlocProvider(
// create: (context) => BrandBloc(),
// child: BlocBuilder<BrandBloc, BrandState>(
// builder: (context, state) {
// return MaterialApp(
// builder: (context, state) => Scaffold(
// body: Row(
// children: [
// MaterialButton(
// color: Theme.of(context).primaryColor,
// onPressed: () {
// context.read<BrandBloc>().add(
// const BrandChanged(BrandKey.catalyst),
// );
// },
// child: const Text('Switch to Catalyst Theme'),
// ),
// ],
// ),
// ),
// theme: state.themeData,
// );
// },
// ),
// );
// ```


final class BrandBloc extends Bloc<BrandEvent, BrandState> {
BrandBloc() : super(BrandState()) {
on<BrandChanged>(_onBrandChanged);
}

void _onBrandChanged(
BrandChanged event,
Emitter<BrandState> emit,
) {
emit(BrandState(themeData: brands[event.brand]));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
part of 'brand_bloc.dart';

abstract final class BrandEvent extends Equatable {
coire1 marked this conversation as resolved.
Show resolved Hide resolved
const BrandEvent();

@override
List<Object> get props => [];
}

final class BrandChanged extends BrandEvent {
coire1 marked this conversation as resolved.
Show resolved Hide resolved
final BrandKey brand;

const BrandChanged(this.brand);

@override
List<Object> get props => [brand];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
part of 'brand_bloc.dart';

final class BrandState extends Equatable {
final ThemeData? themeData;

BrandState({ThemeData? themeData})
: themeData = themeData ?? brands[BrandKey.catalyst];

@override
List<Object> get props => [themeData ?? ''];
}
5 changes: 5 additions & 0 deletions catalyst_voices/packages/catalyst_voices_blocs/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ environment:
dependencies:
bloc: ^8.1.2
bloc_concurrency: ^0.2.2
catalyst_voices_assets:
path: ../catalyst_voices_assets
catalyst_voices_models:
path: ../catalyst_voices_models
catalyst_voices_repositories:
Expand All @@ -31,4 +33,7 @@ dev_dependencies:
bloc_test: ^9.1.4
catalyst_analysis:
path: ../../../catalyst_voices_packages/catalyst_analysis
flutter_bloc: ^8.1.5
flutter_test:
sdk: flutter
test: ^1.24.9
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_blocs/src/brand/brand_bloc.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
const catalystKey = Key('C');
const dummyKey = Key('D');

Widget buildApp() => BlocProvider(
create: (context) => BrandBloc(),
child: BlocBuilder<BrandBloc, BrandState>(
builder: (context, state) {
return MaterialApp(
builder: (context, state) => Scaffold(
body: Row(
children: [
MaterialButton(
key: catalystKey,
color: Theme.of(context).primaryColor,
onPressed: () {
context.read<BrandBloc>().add(
const BrandChanged(BrandKey.catalyst),
);
},
child: const Text('Catalyst'),
),
MaterialButton(
key: dummyKey,
color: Theme.of(context).primaryColor,
child: const Text('Dummy'),
onPressed: () {
context.read<BrandBloc>().add(
const BrandChanged(BrandKey.dummy),
);
},
),
],
),
),
theme: state.themeData,
);
},
),
);

group('Test brands', () {

// Colors used in the Brand themes as primary. They are used for
// the color of the widgets we are testing and they are the colors
// we will check against to ensure correct rendering.
const dummyColor = Color(0xFFFF5722);
const catalystColor = VoicesColors.blue;

testWidgets('Default Catalyst theme is applied', (tester) async {
await tester.pumpWidget(
buildApp(),
);

final catalystButton = find.byKey(catalystKey);
final dummyButton = find.byKey(dummyKey);

expect(catalystButton, findsOneWidget);
expect(dummyButton, findsOneWidget);
expect(
tester.widget<MaterialButton>(catalystButton).color,
catalystColor,
);
expect(
tester.widget<MaterialButton>(dummyButton).color,
catalystColor,
);
});
testWidgets('Dummy Theme is applied after switch', (tester) async {
await tester.pumpWidget(
buildApp(),
);

final catalystButton = find.byKey(catalystKey);
final dummyButton = find.byKey(dummyKey);

expect(catalystButton, findsOneWidget);
expect(dummyButton, findsOneWidget);

await tester.tap(dummyButton);
// We need to wait for the animation to complete
await tester.pumpAndSettle();
expect(
tester.widget<MaterialButton>(catalystButton).color,
dummyColor,
);
expect(
tester.widget<MaterialButton>(catalystButton).color,
dummyColor,
);
});

testWidgets('Catalyst Theme is applied after switch', (tester) async {
await tester.pumpWidget(
buildApp(),
);

final catalystButton = find.byKey(catalystKey);
final dummyButton = find.byKey(dummyKey);

expect(catalystButton, findsOneWidget);
expect(dummyButton, findsOneWidget);

// We first switch do DummyBrand, we wait for the animation completion
// and then we switch back to the CatalystBrand to check the correct
// color is applied.
await tester.tap(dummyButton);
await tester.pumpAndSettle();
await tester.tap(catalystButton);
await tester.pumpAndSettle();
expect(
tester.widget<MaterialButton>(catalystButton).color,
catalystColor,
);
expect(
tester.widget<MaterialButton>(catalystButton).color,
catalystColor,
);
});

});

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
library catalyst_voices_models;

export 'src/authentication_status.dart';
export 'src/brands.dart';
export 'src/catalyst_voices_models.dart';
export 'src/session_data.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_assets/generated/colors.gen.dart';

import 'package:flutter/material.dart';

// [brands] is a Map that stores the ThemeData for all the
// available brands in the Catalyst Voices app.
// This map is used by the [BrandBloc] to properly
// switch the active theme based on the BlocState.
coire1 marked this conversation as resolved.
Show resolved Hide resolved

final Map<BrandKey, ThemeData> brands = {
coire1 marked this conversation as resolved.
Show resolved Hide resolved
BrandKey.catalyst: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: VoicesColors.blue,
primary: VoicesColors.blue,
),
),
BrandKey.dummy: ThemeData.from(
coire1 marked this conversation as resolved.
Show resolved Hide resolved
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFFFF5722),
primary: const Color(0xFFFF5722),
),
),
};

enum BrandKey {
catalyst,
dummy,
}
4 changes: 4 additions & 0 deletions catalyst_voices/packages/catalyst_voices_models/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ environment:
sdk: ">=3.2.6 <4.0.0"

dependencies:
catalyst_voices_assets:
path: ../catalyst_voices_assets
equatable: ^2.0.5
flutter:
sdk: flutter
meta: ^1.10.0

dev_dependencies:
Expand Down
Loading