mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-21 15:00:40 +00:00
separated reusable AnimatedCircluarButton
This commit is contained in:
parent
43ab97f87f
commit
8765998680
9 changed files with 145 additions and 136 deletions
|
@ -7,7 +7,7 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
|||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/widget_bottom_controls.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart';
|
||||
|
@ -329,7 +329,7 @@ Future<void> _expectMeteringStateAndMeasure(
|
|||
|
||||
void expectMeasureButton(double ev) {
|
||||
find.descendant(
|
||||
of: find.byType(MeteringMeasureButton),
|
||||
of: find.byType(MeteringBottomControls),
|
||||
matching: find.text('${ev.toStringAsFixed(1)}\n${S.current.ev}'),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:lightmeter/data/models/ev_source_type.dart';
|
|||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart';
|
||||
|
@ -21,6 +20,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
|
||||
import '../integration_test/utils/widget_tester_actions.dart';
|
||||
import 'mocks/iap_products_mock.dart';
|
||||
import 'utils/finder_actions.dart';
|
||||
|
||||
@isTest
|
||||
void testPurchases(String description) {
|
||||
|
@ -84,7 +84,7 @@ void _expectProMeteringScreen({required bool enabled}) {
|
|||
expect(find.byType(NdValuePicker), findsOneWidget);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(MeteringMeasureButton),
|
||||
of: find.measureButton(),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Text && widget.data!.contains('\u2081\u2080\u2080')),
|
||||
),
|
||||
enabled ? findsOneWidget : findsNothing,
|
||||
|
|
10
integration_test/utils/finder_actions.dart
Normal file
10
integration_test/utils/finder_actions.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/widget_bottom_controls.dart';
|
||||
import 'package:lightmeter/screens/shared/animated_circular_button/widget_button_circular_animated.dart';
|
||||
|
||||
extension CommonFindersExtension on CommonFinders {
|
||||
Finder measureButton() => find.descendant(
|
||||
of: find.byType(MeteringBottomControls),
|
||||
matching: find.byType(AnimatedCircluarButton),
|
||||
);
|
||||
}
|
|
@ -5,7 +5,6 @@ import 'package:lightmeter/application_wrapper.dart';
|
|||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/screen_metering.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
@ -13,6 +12,7 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
|||
|
||||
import '../mocks/iap_products_mock.dart';
|
||||
import '../mocks/paid_features_mock.dart';
|
||||
import 'finder_actions.dart';
|
||||
import 'platform_channel_mock.dart';
|
||||
|
||||
const mockPhotoEv100 = 8.3;
|
||||
|
@ -46,16 +46,16 @@ extension WidgetTesterCommonActions on WidgetTester {
|
|||
}
|
||||
|
||||
Future<void> takePhoto() async {
|
||||
await tap(find.byType(MeteringMeasureButton));
|
||||
await tap(find.measureButton());
|
||||
await pump(const Duration(seconds: 2)); // wait for circular progress indicator
|
||||
await pump(const Duration(seconds: 1)); // wait for circular progress indicator
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> toggleIncidentMetering(double ev) async {
|
||||
await tap(find.byType(MeteringMeasureButton));
|
||||
await tap(find.measureButton());
|
||||
await sendMockIncidentEv(ev);
|
||||
await tap(find.byType(MeteringMeasureButton));
|
||||
await tap(find.measureButton());
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
|
||||
const String _subscript100 = '\u2081\u2080\u2080';
|
||||
|
||||
class MeteringMeasureButton extends StatefulWidget {
|
||||
final double? ev;
|
||||
final double? ev100;
|
||||
final bool isMetering;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const MeteringMeasureButton({
|
||||
required this.ev,
|
||||
required this.ev100,
|
||||
required this.isMetering,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MeteringMeasureButton> createState() => _MeteringMeasureButtonState();
|
||||
}
|
||||
|
||||
class _MeteringMeasureButtonState extends State<MeteringMeasureButton> {
|
||||
bool _isPressed = false;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MeteringMeasureButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.isMetering != widget.isMetering) {
|
||||
_isPressed = widget.isMetering;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onTapDown: (_) {
|
||||
setState(() {
|
||||
_isPressed = true;
|
||||
});
|
||||
},
|
||||
onTapUp: (_) {
|
||||
setState(() {
|
||||
_isPressed = false;
|
||||
});
|
||||
},
|
||||
onTapCancel: () {
|
||||
setState(() {
|
||||
_isPressed = false;
|
||||
});
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: AnimatedScale(
|
||||
duration: Dimens.durationS,
|
||||
scale: _isPressed ? 0.9 : 1.0,
|
||||
child: FilledCircle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
size: Dimens.grid72 - Dimens.grid8,
|
||||
child: Center(
|
||||
child: widget.ev != null ? _EvValueText(ev: widget.ev!, ev100: widget.ev100!) : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: CircularProgressIndicator(
|
||||
/// This key is needed to make indicator start from the same point every time
|
||||
key: ValueKey(widget.isMetering),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
value: widget.isMetering ? null : 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EvValueText extends StatelessWidget {
|
||||
final double ev;
|
||||
final double ev100;
|
||||
|
||||
const _EvValueText({
|
||||
required this.ev,
|
||||
required this.ev100,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Text(
|
||||
_text(context),
|
||||
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.surface),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
|
||||
String _text(BuildContext context) {
|
||||
final bool showEv100 = context.isPro && UserPreferencesProvider.showEv100Of(context);
|
||||
final StringBuffer buffer = StringBuffer()
|
||||
..writeAll([
|
||||
(showEv100 ? ev100 : ev).toStringAsFixed(1),
|
||||
'\n',
|
||||
S.of(context).ev,
|
||||
if (showEv100) _subscript100,
|
||||
]);
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
|
@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/shared/animated_circular_button/widget_button_circular_animated.dart';
|
||||
import 'package:lightmeter/screens/shared/bottom_controls_bar/widget_bottom_controls_bar.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
|
||||
class MeteringBottomControls extends StatelessWidget {
|
||||
final double? ev;
|
||||
|
@ -39,11 +40,11 @@ class MeteringBottomControls extends StatelessWidget {
|
|||
: S.of(context).tooltipUseLightSensor,
|
||||
)
|
||||
: null,
|
||||
center: MeteringMeasureButton(
|
||||
ev: ev,
|
||||
ev100: ev100,
|
||||
isMetering: isMetering,
|
||||
onTap: onMeasure,
|
||||
center: AnimatedCircluarButton(
|
||||
progress: isMetering ? null : 1.0,
|
||||
isPressed: isMetering,
|
||||
onPressed: onMeasure,
|
||||
child: ev != null ? _EvValueText(ev: ev!, ev100: ev100!) : null,
|
||||
),
|
||||
right: IconButton(
|
||||
onPressed: onSettings,
|
||||
|
@ -53,3 +54,36 @@ class MeteringBottomControls extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EvValueText extends StatelessWidget {
|
||||
static const String _subscript100 = '\u2081\u2080\u2080';
|
||||
final double ev;
|
||||
final double ev100;
|
||||
|
||||
const _EvValueText({
|
||||
required this.ev,
|
||||
required this.ev100,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Text(
|
||||
_text(context),
|
||||
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.surface),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
|
||||
String _text(BuildContext context) {
|
||||
final bool showEv100 = context.isPro && UserPreferencesProvider.showEv100Of(context);
|
||||
final StringBuffer buffer = StringBuffer()
|
||||
..writeAll([
|
||||
(showEv100 ? ev100 : ev).toStringAsFixed(1),
|
||||
'\n',
|
||||
S.of(context).ev,
|
||||
if (showEv100) _subscript100,
|
||||
]);
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart';
|
||||
|
||||
class AnimatedCircluarButton extends StatefulWidget {
|
||||
final double? progress;
|
||||
final bool isPressed;
|
||||
final VoidCallback onPressed;
|
||||
final Widget? child;
|
||||
|
||||
const AnimatedCircluarButton({
|
||||
this.progress = 1.0,
|
||||
required this.isPressed,
|
||||
required this.onPressed,
|
||||
this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedCircluarButton> createState() => _AnimatedCircluarButtonState();
|
||||
}
|
||||
|
||||
class _AnimatedCircluarButtonState extends State<AnimatedCircluarButton> {
|
||||
bool _isPressed = false;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AnimatedCircluarButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.isPressed != widget.isPressed) {
|
||||
_isPressed = widget.isPressed;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onPressed,
|
||||
onTapDown: (_) {
|
||||
setState(() {
|
||||
_isPressed = true;
|
||||
});
|
||||
},
|
||||
onTapUp: (_) {
|
||||
setState(() {
|
||||
_isPressed = false;
|
||||
});
|
||||
},
|
||||
onTapCancel: () {
|
||||
setState(() {
|
||||
_isPressed = false;
|
||||
});
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: AnimatedScale(
|
||||
duration: Dimens.durationS,
|
||||
scale: _isPressed ? 0.9 : 1.0,
|
||||
child: FilledCircle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
size: Dimens.grid72 - Dimens.grid8,
|
||||
child: Center(
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: CircularProgressIndicator(
|
||||
/// This key is needed to make indicator start from the same point every time
|
||||
key: ValueKey(widget.progress),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
value: widget.progress,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/shared/animated_circular_button/widget_button_circular_animated.dart';
|
||||
import 'package:lightmeter/screens/shared/bottom_controls_bar/widget_bottom_controls_bar.dart';
|
||||
import 'package:lightmeter/screens/timer/bloc_timer.dart';
|
||||
import 'package:lightmeter/screens/timer/components/metering_config/widget_metering_config_timer.dart';
|
||||
|
@ -96,8 +97,8 @@ class _TimerScreenState extends State<TimerScreen> with TickerProviderStateMixin
|
|||
icon: const Icon(Icons.restore),
|
||||
),
|
||||
center: BlocBuilder<TimerBloc, TimerState>(
|
||||
builder: (_, state) => FloatingActionButton(
|
||||
shape: state is TimerResumedState ? null : const CircleBorder(),
|
||||
builder: (_, state) => AnimatedCircluarButton(
|
||||
isPressed: state is TimerResumedState,
|
||||
onPressed: () {
|
||||
if (timelineAnimation.value == 0) {
|
||||
return;
|
||||
|
@ -108,6 +109,7 @@ class _TimerScreenState extends State<TimerScreen> with TickerProviderStateMixin
|
|||
child: AnimatedIcon(
|
||||
icon: AnimatedIcons.play_pause,
|
||||
progress: startStopIconAnimation,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -8,11 +8,11 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
|||
import 'package:lightmeter/data/models/theme_type.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
|
||||
import 'package:lightmeter/screens/metering/flow_metering.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../integration_test/utils/finder_actions.dart';
|
||||
import '../../../integration_test/utils/platform_channel_mock.dart';
|
||||
import '../../application_mock.dart';
|
||||
|
||||
|
@ -67,7 +67,7 @@ void main() {
|
|||
Future<void> takePhoto(WidgetTester tester, Key scenarioWidgetKey) async {
|
||||
final button = find.descendant(
|
||||
of: find.byKey(scenarioWidgetKey),
|
||||
matching: find.byType(MeteringMeasureButton),
|
||||
matching: find.measureButton(),
|
||||
);
|
||||
await tester.tap(button);
|
||||
await tester.pump(const Duration(seconds: 2)); // wait for circular progress indicator
|
||||
|
@ -78,7 +78,7 @@ void main() {
|
|||
Future<void> toggleIncidentMetering(WidgetTester tester, Key scenarioWidgetKey, double ev) async {
|
||||
final button = find.descendant(
|
||||
of: find.byKey(scenarioWidgetKey),
|
||||
matching: find.byType(MeteringMeasureButton),
|
||||
matching: find.measureButton(),
|
||||
);
|
||||
await tester.tap(button);
|
||||
await sendMockIncidentEv(ev);
|
||||
|
|
Loading…
Reference in a new issue