mirror of
https://github.com/flutter/holobooth.git
synced 2025-07-04 13:46:14 +08:00
feat: props selection (#245)
* feat: hat selection * feat: reutilize code * feat: selected glasses * chore: coverage * chore: on tab * feat: glasses selected * feat: clothes selection * feat: other selection * chore: rename * chore: rename * chore: rename * chore: rename * chore: refactor enums * chore: feedback * chore: comment * chore: semantics * chore: keys * chore: refactor * chore: rename * chore: nit * Update lib/in_experience_selection/widgets/props_selection_tab_bar_view.dart Co-authored-by: Alejandro Santiago <dev@alestiago.com> * Update lib/in_experience_selection/options/glasses.dart Co-authored-by: Alejandro Santiago <dev@alestiago.com> Co-authored-by: Alejandro Santiago <dev@alestiago.com>
This commit is contained in:
BIN
assets/props/clothes.png
Normal file
BIN
assets/props/clothes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 301 B |
BIN
assets/props/glasses_icon.png
Normal file
BIN
assets/props/glasses_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 236 B |
BIN
assets/props/hats_icon.png
Normal file
BIN
assets/props/hats_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 510 B |
BIN
assets/props/others_icon.png
Normal file
BIN
assets/props/others_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 181 B |
@ -170,11 +170,27 @@ class $AssetsImagesGen {
|
||||
class $AssetsPropsGen {
|
||||
const $AssetsPropsGen();
|
||||
|
||||
/// File path: assets/props/clothes.png
|
||||
AssetGenImage get clothes => const AssetGenImage('assets/props/clothes.png');
|
||||
|
||||
/// File path: assets/props/glasses_icon.png
|
||||
AssetGenImage get glassesIcon =>
|
||||
const AssetGenImage('assets/props/glasses_icon.png');
|
||||
|
||||
/// File path: assets/props/hats_icon.png
|
||||
AssetGenImage get hatsIcon =>
|
||||
const AssetGenImage('assets/props/hats_icon.png');
|
||||
|
||||
/// File path: assets/props/others_icon.png
|
||||
AssetGenImage get othersIcon =>
|
||||
const AssetGenImage('assets/props/others_icon.png');
|
||||
|
||||
/// File path: assets/props/prop1.png
|
||||
AssetGenImage get prop1 => const AssetGenImage('assets/props/prop1.png');
|
||||
|
||||
/// List of all assets
|
||||
List<AssetGenImage> get values => [prop1];
|
||||
List<AssetGenImage> get values =>
|
||||
[clothes, glassesIcon, hatsIcon, othersIcon, prop1];
|
||||
}
|
||||
|
||||
class Assets {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
part 'character_selection_event.dart';
|
||||
part 'character_selection_state.dart';
|
||||
|
||||
class CharacterSelectionBloc extends Bloc<CharacterSelectionEvent, Character> {
|
||||
CharacterSelectionBloc() : super(Character.dash) {
|
||||
|
@ -1,6 +0,0 @@
|
||||
part of 'character_selection_bloc.dart';
|
||||
|
||||
enum Character {
|
||||
dash,
|
||||
sparky,
|
||||
}
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/assets/assets.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
class CharacterSelector extends StatefulWidget {
|
||||
const CharacterSelector({super.key, required this.viewportFraction});
|
||||
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
part '../../in_experience_selection/bloc/in_experience_selection_event.dart';
|
||||
@ -12,19 +11,22 @@ class InExperienceSelectionBloc
|
||||
extends Bloc<InExperienceSelectionEvent, InExperienceSelectionState> {
|
||||
InExperienceSelectionBloc({required Character characterPreSelected})
|
||||
: super(InExperienceSelectionState(character: characterPreSelected)) {
|
||||
on<InExperienceSelectionHatSelected>(_hatSelected);
|
||||
on<InExperienceSelectionHatToggled>(_hatToggled);
|
||||
on<InExperienceSelectionBackgroundSelected>(_backgroundSelected);
|
||||
on<InExperienceSelectionCharacterSelected>(_characterSelected);
|
||||
on<InExperienceSelectionGlassesToggled>(_glassesToggled);
|
||||
on<InExperienceSelectionClothesToggled>(_clothesToggled);
|
||||
on<InExperienceSelectionHandleheldLeftToggled>(_handleheldLeftToggled);
|
||||
}
|
||||
|
||||
FutureOr<void> _hatSelected(
|
||||
InExperienceSelectionHatSelected event,
|
||||
FutureOr<void> _hatToggled(
|
||||
InExperienceSelectionHatToggled event,
|
||||
Emitter<InExperienceSelectionState> emit,
|
||||
) {
|
||||
if (event.hat == state.selectedHat) {
|
||||
emit(state.copyWith(selectedHat: Hats.none));
|
||||
if (event.hat == state.hat) {
|
||||
emit(state.copyWith(hat: Hats.none));
|
||||
} else {
|
||||
emit(state.copyWith(selectedHat: event.hat));
|
||||
emit(state.copyWith(hat: event.hat));
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,4 +43,37 @@ class InExperienceSelectionBloc
|
||||
) {
|
||||
emit(state.copyWith(character: event.character));
|
||||
}
|
||||
|
||||
FutureOr<void> _glassesToggled(
|
||||
InExperienceSelectionGlassesToggled event,
|
||||
Emitter<InExperienceSelectionState> emit,
|
||||
) {
|
||||
if (event.glasses == state.glasses) {
|
||||
emit(state.copyWith(glasses: Glasses.none));
|
||||
} else {
|
||||
emit(state.copyWith(glasses: event.glasses));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _clothesToggled(
|
||||
InExperienceSelectionClothesToggled event,
|
||||
Emitter<InExperienceSelectionState> emit,
|
||||
) {
|
||||
if (event.clothes == state.clothes) {
|
||||
emit(state.copyWith(clothes: Clothes.none));
|
||||
} else {
|
||||
emit(state.copyWith(clothes: event.clothes));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _handleheldLeftToggled(
|
||||
InExperienceSelectionHandleheldLeftToggled event,
|
||||
Emitter<InExperienceSelectionState> emit,
|
||||
) {
|
||||
if (event.handheldlLeft == state.handheldlLeft) {
|
||||
emit(state.copyWith(handheldlLeft: HandheldlLeft.none));
|
||||
} else {
|
||||
emit(state.copyWith(handheldlLeft: event.handheldlLeft));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ abstract class InExperienceSelectionEvent extends Equatable {
|
||||
const InExperienceSelectionEvent();
|
||||
}
|
||||
|
||||
class InExperienceSelectionHatSelected extends InExperienceSelectionEvent {
|
||||
const InExperienceSelectionHatSelected(this.hat);
|
||||
class InExperienceSelectionHatToggled extends InExperienceSelectionEvent {
|
||||
const InExperienceSelectionHatToggled(this.hat);
|
||||
|
||||
final Hats hat;
|
||||
|
||||
@ -32,3 +32,31 @@ class InExperienceSelectionCharacterSelected
|
||||
@override
|
||||
List<Object> get props => [character];
|
||||
}
|
||||
|
||||
class InExperienceSelectionGlassesToggled extends InExperienceSelectionEvent {
|
||||
const InExperienceSelectionGlassesToggled(this.glasses);
|
||||
|
||||
final Glasses glasses;
|
||||
|
||||
@override
|
||||
List<Object> get props => [glasses];
|
||||
}
|
||||
|
||||
class InExperienceSelectionClothesToggled extends InExperienceSelectionEvent {
|
||||
const InExperienceSelectionClothesToggled(this.clothes);
|
||||
|
||||
final Clothes clothes;
|
||||
|
||||
@override
|
||||
List<Object> get props => [clothes];
|
||||
}
|
||||
|
||||
class InExperienceSelectionHandleheldLeftToggled
|
||||
extends InExperienceSelectionEvent {
|
||||
const InExperienceSelectionHandleheldLeftToggled(this.handheldlLeft);
|
||||
|
||||
final HandheldlLeft handheldlLeft;
|
||||
|
||||
@override
|
||||
List<Object> get props => [handheldlLeft];
|
||||
}
|
||||
|
@ -2,31 +2,46 @@ part of 'in_experience_selection_bloc.dart';
|
||||
|
||||
class InExperienceSelectionState extends Equatable {
|
||||
const InExperienceSelectionState({
|
||||
this.selectedHat = Hats.none,
|
||||
this.hat = Hats.none,
|
||||
this.background = Background.space,
|
||||
this.character = Character.dash,
|
||||
this.glasses = Glasses.none,
|
||||
this.clothes = Clothes.none,
|
||||
this.handheldlLeft = HandheldlLeft.none,
|
||||
});
|
||||
|
||||
final Hats selectedHat;
|
||||
final Hats hat;
|
||||
final Background background;
|
||||
final Character character;
|
||||
final Glasses glasses;
|
||||
final Clothes clothes;
|
||||
final HandheldlLeft handheldlLeft;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
selectedHat,
|
||||
hat,
|
||||
background,
|
||||
character,
|
||||
glasses,
|
||||
clothes,
|
||||
handheldlLeft,
|
||||
];
|
||||
|
||||
InExperienceSelectionState copyWith({
|
||||
Hats? selectedHat,
|
||||
Hats? hat,
|
||||
Background? background,
|
||||
Character? character,
|
||||
Glasses? glasses,
|
||||
Clothes? clothes,
|
||||
HandheldlLeft? handheldlLeft,
|
||||
}) {
|
||||
return InExperienceSelectionState(
|
||||
selectedHat: selectedHat ?? this.selectedHat,
|
||||
hat: hat ?? this.hat,
|
||||
background: background ?? this.background,
|
||||
character: character ?? this.character,
|
||||
glasses: glasses ?? this.glasses,
|
||||
clothes: clothes ?? this.clothes,
|
||||
handheldlLeft: handheldlLeft ?? this.handheldlLeft,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:io_photobooth/assets/assets.dart';
|
||||
|
||||
enum Background { space, beach, underwater }
|
||||
enum Background {
|
||||
space(1),
|
||||
beach(2),
|
||||
underwater(3);
|
||||
|
||||
extension BackgroundX on Background {
|
||||
double toDouble() {
|
||||
switch (this) {
|
||||
case Background.space:
|
||||
return 1;
|
||||
case Background.beach:
|
||||
return 2;
|
||||
case Background.underwater:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
const Background(this.riveIndex);
|
||||
final double riveIndex;
|
||||
|
||||
ImageProvider toImageProvider() {
|
||||
switch (this) {
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:io_photobooth/assets/assets.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:photobooth_ui/photobooth_ui.dart';
|
||||
|
||||
extension CharacterX on Character {
|
||||
enum Character {
|
||||
dash,
|
||||
sparky;
|
||||
|
||||
Image toImage() {
|
||||
switch (this) {
|
||||
case Character.dash:
|
||||
|
5
lib/in_experience_selection/options/clothes.dart
Normal file
5
lib/in_experience_selection/options/clothes.dart
Normal file
@ -0,0 +1,5 @@
|
||||
enum Clothes {
|
||||
none,
|
||||
clothes1,
|
||||
clothes2,
|
||||
}
|
8
lib/in_experience_selection/options/glasses.dart
Normal file
8
lib/in_experience_selection/options/glasses.dart
Normal file
@ -0,0 +1,8 @@
|
||||
enum Glasses {
|
||||
none(0),
|
||||
glasses1(2);
|
||||
|
||||
const Glasses(this.riveIndex);
|
||||
|
||||
final double riveIndex;
|
||||
}
|
12
lib/in_experience_selection/options/handhelf_left.dart
Normal file
12
lib/in_experience_selection/options/handhelf_left.dart
Normal file
@ -0,0 +1,12 @@
|
||||
enum HandheldlLeft {
|
||||
none,
|
||||
handheldLeft1,
|
||||
handheldLeft2,
|
||||
handheldLeft3,
|
||||
handheldLeft4,
|
||||
handheldLeft5,
|
||||
handheldLeft6,
|
||||
handheldLeft7,
|
||||
handheldLeft8,
|
||||
handheldLeft9,
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
export 'background.dart';
|
||||
export 'character.dart';
|
||||
export 'clothes.dart';
|
||||
export 'glasses.dart';
|
||||
export 'handhelf_left.dart';
|
||||
export 'hats.dart';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:io_photobooth/l10n/l10n.dart';
|
||||
import 'package:photobooth_ui/photobooth_ui.dart';
|
||||
|
@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
class ClothesSelectionTabBarView extends StatelessWidget {
|
||||
const ClothesSelectionTabBarView({super.key});
|
||||
|
||||
@visibleForTesting
|
||||
static Key clothesSelectionKey(Clothes item) {
|
||||
return Key('clothes_selection_${item.name}');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedClothes =
|
||||
context.select((InExperienceSelectionBloc bloc) => bloc.state.clothes);
|
||||
const items = Clothes.values;
|
||||
return PropsGridView(
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return PropSelectionElement(
|
||||
key: clothesSelectionKey(item),
|
||||
onTap: () {
|
||||
context
|
||||
.read<InExperienceSelectionBloc>()
|
||||
.add(InExperienceSelectionClothesToggled(item));
|
||||
},
|
||||
name: item.name,
|
||||
isSelected: item == selectedClothes,
|
||||
);
|
||||
},
|
||||
itemCount: items.length,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
class GlassesSelectionTabBarView extends StatelessWidget {
|
||||
const GlassesSelectionTabBarView({super.key});
|
||||
|
||||
@visibleForTesting
|
||||
static Key glassesSelectionKey(Glasses item) {
|
||||
return Key('glasses_selection_${item.name}');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedGlasses =
|
||||
context.select((InExperienceSelectionBloc bloc) => bloc.state.glasses);
|
||||
const items = Glasses.values;
|
||||
return PropsGridView(
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return PropSelectionElement(
|
||||
key: glassesSelectionKey(item),
|
||||
onTap: () {
|
||||
context
|
||||
.read<InExperienceSelectionBloc>()
|
||||
.add(InExperienceSelectionGlassesToggled(item));
|
||||
},
|
||||
name: item.name,
|
||||
isSelected: item == selectedGlasses,
|
||||
);
|
||||
},
|
||||
itemCount: items.length,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
class HatsSelectionTabBarView extends StatelessWidget {
|
||||
const HatsSelectionTabBarView({super.key});
|
||||
|
||||
@visibleForTesting
|
||||
static Key hatSelectionKey(Hats item) {
|
||||
return Key('hat_selection_${item.name}');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedHat =
|
||||
context.select((InExperienceSelectionBloc bloc) => bloc.state.hat);
|
||||
return PropsGridView(
|
||||
itemBuilder: (context, index) {
|
||||
final item = Hats.values[index];
|
||||
return PropSelectionElement(
|
||||
key: hatSelectionKey(item),
|
||||
onTap: () {
|
||||
context
|
||||
.read<InExperienceSelectionBloc>()
|
||||
.add(InExperienceSelectionHatToggled(item));
|
||||
},
|
||||
name: item.name,
|
||||
isSelected: item == selectedHat,
|
||||
);
|
||||
},
|
||||
itemCount: Hats.values.length,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
class MiscellaneousSelectionTabBarView extends StatelessWidget {
|
||||
const MiscellaneousSelectionTabBarView({super.key});
|
||||
|
||||
@visibleForTesting
|
||||
static Key miscellaneousSelectionKey(HandheldlLeft item) {
|
||||
return Key('miscellaneous_selection_${item.name}');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedHandheldlLeft = context
|
||||
.select((InExperienceSelectionBloc bloc) => bloc.state.handheldlLeft);
|
||||
const items = HandheldlLeft.values;
|
||||
return PropsGridView(
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return PropSelectionElement(
|
||||
key: miscellaneousSelectionKey(item),
|
||||
onTap: () {
|
||||
context
|
||||
.read<InExperienceSelectionBloc>()
|
||||
.add(InExperienceSelectionHandleheldLeftToggled(item));
|
||||
},
|
||||
name: item.name,
|
||||
isSelected: item == selectedHandheldlLeft,
|
||||
);
|
||||
},
|
||||
itemCount: items.length,
|
||||
);
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ class _PrimarySelectionViewState extends State<PrimarySelectionView>
|
||||
children: const [
|
||||
CharacterSelectionTabBarView(),
|
||||
BackgroundSelectionTabBarView(),
|
||||
SizedBox(),
|
||||
PropsSelectionTabBarView(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
22
lib/in_experience_selection/widgets/props_grid_view.dart
Normal file
22
lib/in_experience_selection/widgets/props_grid_view.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PropsGridView extends StatelessWidget {
|
||||
const PropsGridView({
|
||||
super.key,
|
||||
required this.itemBuilder,
|
||||
required this.itemCount,
|
||||
});
|
||||
|
||||
final Widget? Function(BuildContext context, int index) itemBuilder;
|
||||
final int itemCount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: itemBuilder,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photobooth_ui/photobooth_ui.dart';
|
||||
|
||||
class PropSelectionElement extends StatelessWidget {
|
||||
const PropSelectionElement({
|
||||
required this.name,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
focusable: true,
|
||||
button: true,
|
||||
label: name,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 75,
|
||||
width: 100,
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? PhotoboothColors.white
|
||||
: PhotoboothColors.transparent,
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
const Color(0xFF2C2C2C).withOpacity(0.3),
|
||||
const Color(0xFF868686).withOpacity(0.4),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: PhotoboothColors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:io_photobooth/assets/assets.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
class PropsSelectionTabBarView extends StatefulWidget {
|
||||
const PropsSelectionTabBarView({
|
||||
super.key,
|
||||
this.initialIndex = 0,
|
||||
});
|
||||
|
||||
@visibleForTesting
|
||||
static const glassesTabKey = ValueKey('glasses_tab');
|
||||
|
||||
@visibleForTesting
|
||||
static const clothesTabKey = ValueKey('clothes_tab');
|
||||
|
||||
@visibleForTesting
|
||||
static const othersTabKey = ValueKey('others_tab');
|
||||
|
||||
final int initialIndex;
|
||||
|
||||
@override
|
||||
State<PropsSelectionTabBarView> createState() =>
|
||||
_PropsSelectionTabBarViewState();
|
||||
}
|
||||
|
||||
class _PropsSelectionTabBarViewState extends State<PropsSelectionTabBarView>
|
||||
with TickerProviderStateMixin {
|
||||
late final TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(
|
||||
length: 4,
|
||||
vsync: this,
|
||||
initialIndex: widget.initialIndex,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
_PropSelectionTab(
|
||||
assetGenImage: Assets.props.hatsIcon,
|
||||
),
|
||||
_PropSelectionTab(
|
||||
key: PropsSelectionTabBarView.glassesTabKey,
|
||||
assetGenImage: Assets.props.glassesIcon,
|
||||
),
|
||||
_PropSelectionTab(
|
||||
key: PropsSelectionTabBarView.clothesTabKey,
|
||||
assetGenImage: Assets.props.clothes,
|
||||
),
|
||||
_PropSelectionTab(
|
||||
key: PropsSelectionTabBarView.othersTabKey,
|
||||
assetGenImage: Assets.props.othersIcon,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: const [
|
||||
HatsSelectionTabBarView(),
|
||||
GlassesSelectionTabBarView(),
|
||||
ClothesSelectionTabBarView(),
|
||||
MiscellaneousSelectionTabBarView(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PropSelectionTab extends StatefulWidget {
|
||||
const _PropSelectionTab({
|
||||
super.key,
|
||||
required this.assetGenImage,
|
||||
});
|
||||
|
||||
final AssetGenImage assetGenImage;
|
||||
|
||||
@override
|
||||
State<_PropSelectionTab> createState() => _PropSelectionTabState();
|
||||
}
|
||||
|
||||
class _PropSelectionTabState extends State<_PropSelectionTab>
|
||||
with AutomaticKeepAliveClientMixin<_PropSelectionTab> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
// Setting default icon size to avoid tap issues on testing.
|
||||
// As the child will be an image, if there is no default size, on tap will
|
||||
// throw a warning because the child will have no size
|
||||
final iconSize = IconTheme.of(context).size;
|
||||
return Tab(
|
||||
child: widget.assetGenImage.image(
|
||||
color: IconTheme.of(context).color,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
@ -1,4 +1,11 @@
|
||||
export 'background_selection_tab_bar_view.dart';
|
||||
export 'character_selection_tab_bar_view.dart';
|
||||
export 'clothes_selection_tab_bar_view.dart';
|
||||
export 'glasses_selection_tab_bar_view.dart';
|
||||
export 'hats_selection_tab_bar_view.dart';
|
||||
export 'miscellaneous_selection_tab_bar_view.dart';
|
||||
export 'primary_selection_tab_view.dart';
|
||||
export 'props_grid_view.dart';
|
||||
export 'props_selection_element.dart';
|
||||
export 'props_selection_tab_bar_view.dart';
|
||||
export 'selection_layer.dart';
|
||||
|
@ -2,7 +2,6 @@ import 'package:avatar_detector_repository/avatar_detector_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:io_photobooth/avatar_detector/avatar_detector.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:io_photobooth/photo_booth/photo_booth.dart';
|
||||
import 'package:io_photobooth/share/share.dart';
|
||||
|
@ -13,8 +13,14 @@ class PhotoboothCharacter extends StatelessWidget {
|
||||
(AvatarDetectorBloc bloc) => bloc.state.avatar,
|
||||
);
|
||||
|
||||
final hatSelected = context
|
||||
.select((InExperienceSelectionBloc bloc) => bloc.state.selectedHat);
|
||||
final hat =
|
||||
context.select((InExperienceSelectionBloc bloc) => bloc.state.hat);
|
||||
final glasses =
|
||||
context.select((InExperienceSelectionBloc bloc) => bloc.state.glasses);
|
||||
final clothes =
|
||||
context.select((InExperienceSelectionBloc bloc) => bloc.state.clothes);
|
||||
final handheldlLeft = context
|
||||
.select((InExperienceSelectionBloc bloc) => bloc.state.handheldlLeft);
|
||||
|
||||
// TODO(alestiago): Check out if normalizations is sufficient for all
|
||||
// characters and devices.
|
||||
@ -31,7 +37,10 @@ class PhotoboothCharacter extends StatelessWidget {
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: DashAnimation(
|
||||
avatar: avatar,
|
||||
hatSelected: hatSelected,
|
||||
hat: hat,
|
||||
glasses: glasses,
|
||||
clothes: clothes,
|
||||
handheldlLeft: handheldlLeft,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class BackgroundAnimationState extends State<BackgroundAnimation>
|
||||
void _onRiveInit(Artboard artboard) {
|
||||
backgroundController = BackgroundAnimationStateMachineController(artboard);
|
||||
backgroundController!.background
|
||||
.change(widget.backgroundSelected.toDouble());
|
||||
.change(widget.backgroundSelected.riveIndex);
|
||||
artboard.addController(backgroundController!);
|
||||
_animationController.addListener(_controlBackground);
|
||||
}
|
||||
@ -92,7 +92,7 @@ class BackgroundAnimationState extends State<BackgroundAnimation>
|
||||
final oldBackground = oldWidget.backgroundSelected;
|
||||
if (newBackground != oldBackground) {
|
||||
backgroundController.background
|
||||
.change(widget.backgroundSelected.toDouble());
|
||||
.change(widget.backgroundSelected.riveIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,17 @@ class DashAnimation extends StatefulWidget {
|
||||
const DashAnimation({
|
||||
super.key,
|
||||
required this.avatar,
|
||||
required this.hatSelected,
|
||||
required this.hat,
|
||||
required this.glasses,
|
||||
required this.clothes,
|
||||
required this.handheldlLeft,
|
||||
});
|
||||
|
||||
final Avatar avatar;
|
||||
final Hats hatSelected;
|
||||
final Hats hat;
|
||||
final Glasses glasses;
|
||||
final Clothes clothes;
|
||||
final HandheldlLeft handheldlLeft;
|
||||
|
||||
@override
|
||||
State<DashAnimation> createState() => DashAnimationState();
|
||||
@ -69,28 +75,39 @@ class DashAnimationState extends State<DashAnimation>
|
||||
..end = newOffset;
|
||||
_animationController.forward(from: 0);
|
||||
}
|
||||
|
||||
if (oldWidget.avatar.mouthDistance != widget.avatar.mouthDistance) {
|
||||
dashController.mouthDistance.change(
|
||||
widget.avatar.mouthDistance * 100,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldWidget.avatar.rightEyeIsClosed != widget.avatar.rightEyeIsClosed) {
|
||||
dashController.rightEyeIsClosed.change(
|
||||
widget.avatar.rightEyeIsClosed ? 99 : 0,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldWidget.avatar.leftEyeIsClosed != widget.avatar.leftEyeIsClosed) {
|
||||
dashController.leftEyeIsClosed.change(
|
||||
widget.avatar.leftEyeIsClosed ? 99 : 0,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldWidget.hatSelected != widget.hatSelected) {
|
||||
dashController.hatSelected.change(
|
||||
widget.hatSelected.index.toDouble(),
|
||||
if (oldWidget.hat != widget.hat) {
|
||||
dashController.hats.change(
|
||||
widget.hat.index.toDouble(),
|
||||
);
|
||||
}
|
||||
if (oldWidget.glasses != widget.glasses) {
|
||||
dashController.glasses.change(
|
||||
widget.glasses.riveIndex,
|
||||
);
|
||||
}
|
||||
if (oldWidget.clothes != widget.clothes) {
|
||||
dashController.clothes.change(
|
||||
widget.clothes.index.toDouble(),
|
||||
);
|
||||
}
|
||||
if (oldWidget.handheldlLeft != widget.handheldlLeft) {
|
||||
dashController.handheldlLeft.change(
|
||||
widget.handheldlLeft.index.toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -162,12 +179,36 @@ class DashStateMachineController extends StateMachineController {
|
||||
}
|
||||
|
||||
const hatsInputName = 'Hats';
|
||||
final hatSelected = findInput<double>(hatsInputName);
|
||||
if (hatSelected is SMINumber) {
|
||||
this.hatSelected = hatSelected;
|
||||
final hats = findInput<double>(hatsInputName);
|
||||
if (hats is SMINumber) {
|
||||
this.hats = hats;
|
||||
} else {
|
||||
throw StateError('Could not find input "$hatsInputName"');
|
||||
}
|
||||
|
||||
const glassesInputName = 'Glasses';
|
||||
final glasses = findInput<double>(glassesInputName);
|
||||
if (glasses is SMINumber) {
|
||||
this.glasses = glasses;
|
||||
} else {
|
||||
throw StateError('Could not find input "$glassesInputName"');
|
||||
}
|
||||
|
||||
const clothesInputName = 'Clothes';
|
||||
final clothes = findInput<double>(clothesInputName);
|
||||
if (clothes is SMINumber) {
|
||||
this.clothes = clothes;
|
||||
} else {
|
||||
throw StateError('Could not find input "$clothesInputName"');
|
||||
}
|
||||
|
||||
const handheldLeftInputName = 'HandheldLeft';
|
||||
final handheldlLeft = findInput<double>(handheldLeftInputName);
|
||||
if (handheldlLeft is SMINumber) {
|
||||
this.handheldlLeft = handheldlLeft;
|
||||
} else {
|
||||
throw StateError('Could not find input "$handheldLeftInputName"');
|
||||
}
|
||||
}
|
||||
|
||||
late final SMINumber x;
|
||||
@ -175,6 +216,9 @@ class DashStateMachineController extends StateMachineController {
|
||||
late final SMINumber mouthDistance;
|
||||
late final SMINumber leftEyeIsClosed;
|
||||
late final SMINumber rightEyeIsClosed;
|
||||
late final SMINumber hatSelected;
|
||||
late final SMINumber hats;
|
||||
late final SMINumber glasses;
|
||||
late final SMINumber clothes;
|
||||
late final SMINumber handheldlLeft;
|
||||
}
|
||||
// coverage:ignore-end
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:io_photobooth/l10n/l10n.dart';
|
||||
import 'package:io_photobooth/photo_booth/photo_booth.dart';
|
||||
import 'package:io_photobooth/share/share.dart';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
void main() {
|
||||
group('CharacterSelectionBloc', () {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
void main() {
|
||||
group('CharacterSelectionSelected', () {
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:io_photobooth/photo_booth/photo_booth.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
|
||||
void main() {
|
||||
@ -12,23 +11,23 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
group('InExperienceSelectionHatSelected', () {
|
||||
group('InExperienceSelectionHatToggled', () {
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state with hat selected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
act: (bloc) => bloc.add(InExperienceSelectionHatSelected(Hats.helmet)),
|
||||
act: (bloc) => bloc.add(InExperienceSelectionHatToggled(Hats.helmet)),
|
||||
expect: () => const <InExperienceSelectionState>[
|
||||
InExperienceSelectionState(selectedHat: Hats.helmet)
|
||||
InExperienceSelectionState(hat: Hats.helmet)
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state with hat unselected.',
|
||||
'emits state without hat when hat was previously selected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
seed: () => InExperienceSelectionState(selectedHat: Hats.helmet),
|
||||
act: (bloc) => bloc.add(InExperienceSelectionHatSelected(Hats.helmet)),
|
||||
seed: () => InExperienceSelectionState(hat: Hats.helmet),
|
||||
act: (bloc) => bloc.add(InExperienceSelectionHatToggled(Hats.helmet)),
|
||||
expect: () =>
|
||||
const <InExperienceSelectionState>[InExperienceSelectionState()],
|
||||
);
|
||||
@ -79,5 +78,88 @@ void main() {
|
||||
expect: () => const <InExperienceSelectionState>[],
|
||||
);
|
||||
});
|
||||
|
||||
group('InExperienceSelectionGlassesToggled', () {
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state with glasses selected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
act: (bloc) =>
|
||||
bloc.add(InExperienceSelectionGlassesToggled(Glasses.glasses1)),
|
||||
expect: () => const <InExperienceSelectionState>[
|
||||
InExperienceSelectionState(glasses: Glasses.glasses1)
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state without glasses when glasses were previously selected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
seed: () => InExperienceSelectionState(glasses: Glasses.glasses1),
|
||||
act: (bloc) =>
|
||||
bloc.add(InExperienceSelectionGlassesToggled(Glasses.glasses1)),
|
||||
expect: () =>
|
||||
const <InExperienceSelectionState>[InExperienceSelectionState()],
|
||||
);
|
||||
});
|
||||
|
||||
group('InExperienceSelectionClothesToggled', () {
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state with clothes selected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
act: (bloc) =>
|
||||
bloc.add(InExperienceSelectionClothesToggled(Clothes.clothes1)),
|
||||
expect: () => const <InExperienceSelectionState>[
|
||||
InExperienceSelectionState(clothes: Clothes.clothes1)
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state without clothes when clothes was previously selected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
seed: () => InExperienceSelectionState(clothes: Clothes.clothes1),
|
||||
act: (bloc) =>
|
||||
bloc.add(InExperienceSelectionClothesToggled(Clothes.clothes1)),
|
||||
expect: () =>
|
||||
const <InExperienceSelectionState>[InExperienceSelectionState()],
|
||||
);
|
||||
});
|
||||
|
||||
group('InExperienceSelectionHandleheldLeftToggled', () {
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state with handleheld left selected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
act: (bloc) => bloc.add(
|
||||
InExperienceSelectionHandleheldLeftToggled(
|
||||
HandheldlLeft.handheldLeft1,
|
||||
),
|
||||
),
|
||||
expect: () => const <InExperienceSelectionState>[
|
||||
InExperienceSelectionState(
|
||||
handheldlLeft: HandheldlLeft.handheldLeft1,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<InExperienceSelectionBloc, InExperienceSelectionState>(
|
||||
'emits state with handleheld left unselected.',
|
||||
build: () =>
|
||||
InExperienceSelectionBloc(characterPreSelected: Character.dash),
|
||||
seed: () => InExperienceSelectionState(
|
||||
handheldlLeft: HandheldlLeft.handheldLeft1,
|
||||
),
|
||||
act: (bloc) => bloc.add(
|
||||
InExperienceSelectionHandleheldLeftToggled(
|
||||
HandheldlLeft.handheldLeft1,
|
||||
),
|
||||
),
|
||||
expect: () => const <InExperienceSelectionState>[
|
||||
InExperienceSelectionState(),
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ import 'package:io_photobooth/in_experience_selection/in_experience_selection.da
|
||||
|
||||
void main() {
|
||||
group('InExperienceSelectionEvent', () {
|
||||
group('InExperienceSelectionHatSelected', () {
|
||||
group('InExperienceSelectionHatToggled', () {
|
||||
test('support value comparison', () {
|
||||
final eventA = InExperienceSelectionHatSelected(Hats.helmet);
|
||||
final eventB = InExperienceSelectionHatSelected(Hats.helmet);
|
||||
final eventC = InExperienceSelectionHatSelected(Hats.none);
|
||||
final eventA = InExperienceSelectionHatToggled(Hats.helmet);
|
||||
final eventB = InExperienceSelectionHatToggled(Hats.helmet);
|
||||
final eventC = InExperienceSelectionHatToggled(Hats.none);
|
||||
expect(eventA, equals(eventB));
|
||||
expect(eventA, isNot(equals(eventC)));
|
||||
});
|
||||
|
@ -2,7 +2,6 @@ import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
|
@ -0,0 +1,169 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
class _MockInExperienceSelectionBloc
|
||||
extends MockBloc<InExperienceSelectionEvent, InExperienceSelectionState>
|
||||
implements InExperienceSelectionBloc {}
|
||||
|
||||
void main() {
|
||||
group('PropsSelectionTabBarView', () {
|
||||
late InExperienceSelectionBloc inExperienceSelectionBloc;
|
||||
|
||||
setUp(() {
|
||||
inExperienceSelectionBloc = _MockInExperienceSelectionBloc();
|
||||
when(() => inExperienceSelectionBloc.state)
|
||||
.thenReturn(InExperienceSelectionState());
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'display HatsSelectionTabBarView by default',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
expect(find.byType(HatsSelectionTabBarView), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'display GlassesSelectionTabBarView by tapping on glasses tab',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
final finder = find.byKey(PropsSelectionTabBarView.glassesTabKey);
|
||||
expect(finder, findsOneWidget);
|
||||
await tester.tap(finder);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(GlassesSelectionTabBarView), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'display ClothesSelectionTabBarView by tapping on clothes tab',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
final finder = find.byKey(PropsSelectionTabBarView.clothesTabKey);
|
||||
expect(finder, findsOneWidget);
|
||||
await tester.tap(finder);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ClothesSelectionTabBarView), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'display OthersSelectionTabBarView by tapping on others tab',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
final finder = find.byKey(PropsSelectionTabBarView.othersTabKey);
|
||||
expect(finder, findsOneWidget);
|
||||
await tester.tap(finder);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(MiscellaneousSelectionTabBarView), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'adds InExperienceSelectionHatToggled tapping on a hat',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
const hat = Hats.helmet;
|
||||
await tester
|
||||
.tap(find.byKey(HatsSelectionTabBarView.hatSelectionKey(hat)));
|
||||
verify(
|
||||
() => inExperienceSelectionBloc
|
||||
.add(InExperienceSelectionHatToggled(hat)),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'adds InExperienceSelectionGlassesToggled tapping on a glasses',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(initialIndex: 1),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
const glasses = Glasses.glasses1;
|
||||
await tester.tap(
|
||||
find.byKey(GlassesSelectionTabBarView.glassesSelectionKey(glasses)),
|
||||
);
|
||||
verify(
|
||||
() => inExperienceSelectionBloc
|
||||
.add(InExperienceSelectionGlassesToggled(glasses)),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'adds InExperienceSelectionClothesToggled tapping on a clothes',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(initialIndex: 2),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
const clothes = Clothes.clothes1;
|
||||
await tester.tap(
|
||||
find.byKey(ClothesSelectionTabBarView.clothesSelectionKey(clothes)),
|
||||
);
|
||||
verify(
|
||||
() => inExperienceSelectionBloc
|
||||
.add(InExperienceSelectionClothesToggled(clothes)),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'adds InExperienceSelectionHandleheldLeftToggled tapping on '
|
||||
'handheld left',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpSubject(
|
||||
PropsSelectionTabBarView(initialIndex: 3),
|
||||
inExperienceSelectionBloc,
|
||||
);
|
||||
const item = HandheldlLeft.handheldLeft1;
|
||||
await tester.tap(
|
||||
find.byKey(
|
||||
MiscellaneousSelectionTabBarView.miscellaneousSelectionKey(item),
|
||||
),
|
||||
);
|
||||
verify(
|
||||
() => inExperienceSelectionBloc
|
||||
.add(InExperienceSelectionHandleheldLeftToggled(item)),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
extension on WidgetTester {
|
||||
Future<void> pumpSubject(
|
||||
PropsSelectionTabBarView subject,
|
||||
InExperienceSelectionBloc inExperienceSelectionBloc,
|
||||
) =>
|
||||
pumpApp(
|
||||
MultiBlocProvider(
|
||||
providers: [BlocProvider.value(value: inExperienceSelectionBloc)],
|
||||
child: Scaffold(
|
||||
body: subject,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:io_photobooth/avatar_detector/avatar_detector.dart';
|
||||
import 'package:io_photobooth/character_selection/character_selection.dart';
|
||||
import 'package:io_photobooth/in_experience_selection/in_experience_selection.dart';
|
||||
import 'package:io_photobooth/photo_booth/photo_booth.dart';
|
||||
import 'package:io_photobooth/share/share.dart';
|
||||
|
@ -16,7 +16,10 @@ void main() {
|
||||
rightEyeIsClosed: false,
|
||||
distance: 0.5,
|
||||
);
|
||||
var hatSelected = Hats.none;
|
||||
var selectedHat = Hats.none;
|
||||
var selectedGlasses = Glasses.none;
|
||||
var selectedClothes = Clothes.none;
|
||||
var selectedHandheldlLeft = HandheldlLeft.none;
|
||||
|
||||
late StateSetter stateSetter;
|
||||
await tester.pumpWidget(
|
||||
@ -26,7 +29,10 @@ void main() {
|
||||
stateSetter = setState;
|
||||
return DashAnimation(
|
||||
avatar: avatar,
|
||||
hatSelected: hatSelected,
|
||||
hat: selectedHat,
|
||||
glasses: selectedGlasses,
|
||||
clothes: selectedClothes,
|
||||
handheldlLeft: selectedHandheldlLeft,
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -37,9 +43,12 @@ void main() {
|
||||
final state =
|
||||
tester.state(find.byType(DashAnimation)) as DashAnimationState;
|
||||
final controller = state.dashController;
|
||||
final x = controller?.x.value;
|
||||
final y = controller?.y.value;
|
||||
final hatSelectedValue = controller?.hatSelected.value;
|
||||
final xValue = controller?.x.value;
|
||||
final yValue = controller?.y.value;
|
||||
final hatsValue = controller?.hats.value;
|
||||
final glassesValue = controller?.glasses.value;
|
||||
final clothesValue = controller?.clothes.value;
|
||||
final handheldlLeftValue = controller?.handheldlLeft.value;
|
||||
|
||||
stateSetter(() {
|
||||
avatar = Avatar(
|
||||
@ -50,7 +59,10 @@ void main() {
|
||||
rightEyeIsClosed: !avatar.rightEyeIsClosed,
|
||||
distance: avatar.distance,
|
||||
);
|
||||
hatSelected = Hats.helmet;
|
||||
selectedHat = Hats.helmet;
|
||||
selectedGlasses = Glasses.glasses1;
|
||||
selectedClothes = Clothes.clothes1;
|
||||
selectedHandheldlLeft = HandheldlLeft.handheldLeft1;
|
||||
});
|
||||
await tester.pump(Duration(milliseconds: 150));
|
||||
await tester.pump(Duration(milliseconds: 150));
|
||||
@ -58,9 +70,15 @@ void main() {
|
||||
expect(controller?.mouthDistance.value, avatar.mouthDistance * 100);
|
||||
expect(controller?.leftEyeIsClosed.value, 99);
|
||||
expect(controller?.rightEyeIsClosed.value, 99);
|
||||
expect(controller?.x.value, isNot(equals(x)));
|
||||
expect(controller?.y.value, isNot(equals(y)));
|
||||
expect(controller?.hatSelected.value, isNot(hatSelectedValue));
|
||||
expect(controller?.x.value, isNot(equals(xValue)));
|
||||
expect(controller?.y.value, isNot(equals(yValue)));
|
||||
expect(controller?.hats.value, isNot(hatsValue));
|
||||
expect(controller?.glasses.value, isNot(glassesValue));
|
||||
expect(controller?.clothes.value, isNot(clothesValue));
|
||||
expect(
|
||||
controller?.handheldlLeft.value,
|
||||
isNot(handheldlLeftValue),
|
||||
);
|
||||
await tester.pump(kThemeAnimationDuration);
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user