mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
added start/stop button
This commit is contained in:
parent
378ab45f45
commit
c532801358
5 changed files with 223 additions and 303 deletions
|
@ -1,201 +1,76 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/data/models/volume_action.dart';
|
|
||||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
import 'package:lightmeter/screens/timer/event_timer.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
|
import 'package:lightmeter/screens/timer/state_timer.dart';
|
||||||
as communication_events;
|
|
||||||
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
|
|
||||||
as communication_states;
|
|
||||||
import 'package:lightmeter/screens/metering/event_metering.dart';
|
|
||||||
import 'package:lightmeter/screens/metering/state_metering.dart';
|
|
||||||
import 'package:lightmeter/screens/metering/utils/notifier_volume_keys.dart';
|
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
|
||||||
|
|
||||||
class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
|
class TimerBloc extends Bloc<TimerEvent, TimerState> {
|
||||||
final MeteringInteractor _meteringInteractor;
|
final MeteringInteractor _meteringInteractor;
|
||||||
final VolumeKeysNotifier _volumeKeysNotifier;
|
late Timer? _timer;
|
||||||
final MeteringCommunicationBloc _communicationBloc;
|
final int timerLength;
|
||||||
late final StreamSubscription<communication_states.ScreenState> _communicationSubscription;
|
|
||||||
|
|
||||||
MeteringBloc(
|
TimerBloc(this._meteringInteractor, this.timerLength)
|
||||||
this._meteringInteractor,
|
: super(
|
||||||
this._volumeKeysNotifier,
|
TimerStoppedState(
|
||||||
this._communicationBloc,
|
duration: Duration(seconds: timerLength),
|
||||||
) : super(
|
timeLeft: Duration(seconds: timerLength),
|
||||||
MeteringDataState(
|
|
||||||
ev100: null,
|
|
||||||
iso: _meteringInteractor.iso,
|
|
||||||
nd: _meteringInteractor.ndFilter,
|
|
||||||
isMetering: false,
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
_volumeKeysNotifier.addListener(onVolumeKey);
|
on<StartTimerEvent>(_onStartTimer);
|
||||||
_communicationSubscription = _communicationBloc.stream
|
on<SetTimeLeftEvent>(_onSetTimeLeft);
|
||||||
.where((state) => state is communication_states.ScreenState)
|
on<StopTimerEvent>(_onStopTimer);
|
||||||
.map((state) => state as communication_states.ScreenState)
|
on<ResetTimerEvent>(_onResetTimer);
|
||||||
.listen(onCommunicationState);
|
|
||||||
|
|
||||||
on<EquipmentProfileChangedEvent>(_onEquipmentProfileChanged);
|
|
||||||
on<IsoChangedEvent>(_onIsoChanged);
|
|
||||||
on<NdChangedEvent>(_onNdChanged);
|
|
||||||
on<MeasureEvent>(_onMeasure, transformer: droppable());
|
|
||||||
on<MeasuredEvent>(_onMeasured);
|
|
||||||
on<MeasureErrorEvent>(_onMeasureError);
|
|
||||||
on<SettingsOpenedEvent>(_onSettingsOpened);
|
|
||||||
on<SettingsClosedEvent>(_onSettingsClosed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onTransition(Transition<MeteringEvent, MeteringState> transition) {
|
|
||||||
super.onTransition(transition);
|
|
||||||
if (transition.nextState is MeteringDataState) {
|
|
||||||
final nextState = transition.nextState as MeteringDataState;
|
|
||||||
if (transition.currentState is LoadingState ||
|
|
||||||
transition.currentState is MeteringDataState &&
|
|
||||||
(transition.currentState as MeteringDataState).ev != nextState.ev) {
|
|
||||||
if (nextState.hasError) {
|
|
||||||
_meteringInteractor.errorVibration();
|
|
||||||
} else {
|
|
||||||
_meteringInteractor.responseVibration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
_volumeKeysNotifier.removeListener(onVolumeKey);
|
_timer?.cancel();
|
||||||
await _communicationSubscription.cancel();
|
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
Future<void> _onStartTimer(StartTimerEvent _, Emitter emit) async {
|
||||||
void onCommunicationState(communication_states.ScreenState communicationState) {
|
|
||||||
if (communicationState is communication_states.MeasuredState) {
|
|
||||||
_handleEv100(
|
|
||||||
communicationState.ev100,
|
|
||||||
isMetering: communicationState is communication_states.MeteringInProgressState,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onEquipmentProfileChanged(EquipmentProfileChangedEvent event, Emitter emit) {
|
|
||||||
bool willUpdateMeasurements = false;
|
|
||||||
|
|
||||||
/// Update selected ISO value and discard selected film, if selected equipment profile
|
|
||||||
/// doesn't contain currently selected value
|
|
||||||
IsoValue iso = state.iso;
|
|
||||||
if (!event.equipmentProfileData.isoValues.any((v) => state.iso.value == v.value)) {
|
|
||||||
_meteringInteractor.iso = event.equipmentProfileData.isoValues.first;
|
|
||||||
iso = event.equipmentProfileData.isoValues.first;
|
|
||||||
willUpdateMeasurements = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The same for ND filter
|
|
||||||
NdValue nd = state.nd;
|
|
||||||
if (!event.equipmentProfileData.ndValues.any((v) => state.nd.value == v.value)) {
|
|
||||||
_meteringInteractor.ndFilter = event.equipmentProfileData.ndValues.first;
|
|
||||||
nd = event.equipmentProfileData.ndValues.first;
|
|
||||||
willUpdateMeasurements = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (willUpdateMeasurements) {
|
|
||||||
emit(
|
|
||||||
MeteringDataState(
|
|
||||||
ev100: state.ev100,
|
|
||||||
iso: iso,
|
|
||||||
nd: nd,
|
|
||||||
isMetering: state.isMetering,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onIsoChanged(IsoChangedEvent event, Emitter emit) {
|
|
||||||
if (state.iso != event.isoValue) {
|
|
||||||
_meteringInteractor.iso = event.isoValue;
|
|
||||||
emit(
|
|
||||||
MeteringDataState(
|
|
||||||
ev100: state.ev100,
|
|
||||||
iso: event.isoValue,
|
|
||||||
nd: state.nd,
|
|
||||||
isMetering: state.isMetering,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onNdChanged(NdChangedEvent event, Emitter emit) {
|
|
||||||
if (state.nd != event.ndValue) {
|
|
||||||
_meteringInteractor.ndFilter = event.ndValue;
|
|
||||||
emit(
|
|
||||||
MeteringDataState(
|
|
||||||
ev100: state.ev100,
|
|
||||||
iso: state.iso,
|
|
||||||
nd: event.ndValue,
|
|
||||||
isMetering: state.isMetering,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onMeasure(MeasureEvent _, Emitter emit) {
|
|
||||||
_meteringInteractor.quickVibration();
|
|
||||||
_communicationBloc.add(const communication_events.MeasureEvent());
|
|
||||||
emit(
|
emit(
|
||||||
LoadingState(
|
TimerResumedState(
|
||||||
iso: state.iso,
|
duration: state.duration,
|
||||||
nd: state.nd,
|
timeLeft: state.timeLeft,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||||
|
add(SetTimeLeftEvent(state.timeLeft - const Duration(seconds: 1)));
|
||||||
|
if (state.timeLeft.inMilliseconds == 0) {
|
||||||
|
add(const StopTimerEvent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSetTimeLeft(SetTimeLeftEvent event, Emitter emit) async {
|
||||||
|
emit(
|
||||||
|
TimerResumedState(
|
||||||
|
duration: state.duration,
|
||||||
|
timeLeft: event.timeLeft,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleEv100(double? ev100, {required bool isMetering}) {
|
Future<void> _onStopTimer(StopTimerEvent _, Emitter emit) async {
|
||||||
if (ev100 == null || ev100.isNaN || ev100.isInfinite) {
|
_timer?.cancel();
|
||||||
add(MeasureErrorEvent(isMetering: isMetering));
|
|
||||||
} else {
|
|
||||||
add(MeasuredEvent(ev100, isMetering: isMetering));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onMeasured(MeasuredEvent event, Emitter emit) {
|
|
||||||
emit(
|
emit(
|
||||||
MeteringDataState(
|
TimerStoppedState(
|
||||||
ev100: event.ev100,
|
duration: state.duration,
|
||||||
iso: state.iso,
|
timeLeft: state.timeLeft,
|
||||||
nd: state.nd,
|
|
||||||
isMetering: event.isMetering,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onMeasureError(MeasureErrorEvent event, Emitter emit) {
|
Future<void> _onResetTimer(ResetTimerEvent _, Emitter emit) async {
|
||||||
|
_timer?.cancel();
|
||||||
emit(
|
emit(
|
||||||
MeteringDataState(
|
TimerStoppedState(
|
||||||
ev100: null,
|
duration: state.duration,
|
||||||
iso: state.iso,
|
timeLeft: state.duration,
|
||||||
nd: state.nd,
|
|
||||||
isMetering: event.isMetering,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
void onVolumeKey() {
|
|
||||||
if (_meteringInteractor.volumeAction == VolumeAction.shutter) {
|
|
||||||
add(const MeasureEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onSettingsOpened(SettingsOpenedEvent _, Emitter __) {
|
|
||||||
_communicationBloc.add(const communication_events.SettingsOpenedEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onSettingsClosed(SettingsClosedEvent _, Emitter __) {
|
|
||||||
_communicationBloc.add(const communication_events.SettingsClosedEvent());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,21 @@
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
sealed class TimerEvent {
|
||||||
|
const TimerEvent();
|
||||||
sealed class MeteringEvent {
|
|
||||||
const MeteringEvent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class EquipmentProfileChangedEvent extends MeteringEvent {
|
class StartTimerEvent extends TimerEvent {
|
||||||
final EquipmentProfile equipmentProfileData;
|
const StartTimerEvent();
|
||||||
|
|
||||||
const EquipmentProfileChangedEvent(this.equipmentProfileData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class IsoChangedEvent extends MeteringEvent {
|
class StopTimerEvent extends TimerEvent {
|
||||||
final IsoValue isoValue;
|
const StopTimerEvent();
|
||||||
|
|
||||||
const IsoChangedEvent(this.isoValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NdChangedEvent extends MeteringEvent {
|
class SetTimeLeftEvent extends TimerEvent {
|
||||||
final NdValue ndValue;
|
final Duration timeLeft;
|
||||||
|
|
||||||
const NdChangedEvent(this.ndValue);
|
const SetTimeLeftEvent(this.timeLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MeasureEvent extends MeteringEvent {
|
class ResetTimerEvent extends TimerEvent {
|
||||||
const MeasureEvent();
|
const ResetTimerEvent();
|
||||||
}
|
|
||||||
|
|
||||||
class MeasuredEvent extends MeteringEvent {
|
|
||||||
final double ev100;
|
|
||||||
final bool isMetering;
|
|
||||||
|
|
||||||
const MeasuredEvent(this.ev100, {required this.isMetering});
|
|
||||||
}
|
|
||||||
|
|
||||||
class MeasureErrorEvent extends MeteringEvent {
|
|
||||||
final bool isMetering;
|
|
||||||
|
|
||||||
const MeasureErrorEvent({required this.isMetering});
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsOpenedEvent extends MeteringEvent {
|
|
||||||
const SettingsOpenedEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsClosedEvent extends MeteringEvent {
|
|
||||||
const SettingsClosedEvent();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||||
import 'package:lightmeter/providers/services_provider.dart';
|
import 'package:lightmeter/providers/services_provider.dart';
|
||||||
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
import 'package:lightmeter/screens/timer/bloc_timer.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
|
||||||
import 'package:lightmeter/screens/metering/screen_metering.dart';
|
|
||||||
import 'package:lightmeter/screens/metering/utils/notifier_volume_keys.dart';
|
|
||||||
import 'package:lightmeter/screens/timer/screen_timer.dart';
|
import 'package:lightmeter/screens/timer/screen_timer.dart';
|
||||||
|
|
||||||
class TimerFlow extends StatelessWidget {
|
class TimerFlow extends StatelessWidget {
|
||||||
|
@ -22,17 +19,11 @@ class TimerFlow extends StatelessWidget {
|
||||||
ServicesProvider.of(context).lightSensorService,
|
ServicesProvider.of(context).lightSensorService,
|
||||||
ServicesProvider.of(context).volumeEventsService,
|
ServicesProvider.of(context).volumeEventsService,
|
||||||
)..initialize(),
|
)..initialize(),
|
||||||
child: MultiBlocProvider(
|
child: BlocProvider(
|
||||||
providers: [
|
create: (context) => TimerBloc(
|
||||||
BlocProvider(create: (_) => MeteringCommunicationBloc()),
|
MeteringInteractorProvider.of(context),
|
||||||
BlocProvider(
|
124,
|
||||||
create: (context) => MeteringBloc(
|
),
|
||||||
MeteringInteractorProvider.of(context),
|
|
||||||
VolumeKeysNotifier(ServicesProvider.of(context).volumeEventsService),
|
|
||||||
context.read<MeteringCommunicationBloc>(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const TimerScreen(),
|
child: const TimerScreen(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,81 +1,145 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
import 'package:lightmeter/screens/timer/bloc_timer.dart';
|
||||||
|
import 'package:lightmeter/screens/timer/event_timer.dart';
|
||||||
|
import 'package:lightmeter/screens/timer/state_timer.dart';
|
||||||
import 'package:material_color_utilities/material_color_utilities.dart';
|
import 'package:material_color_utilities/material_color_utilities.dart';
|
||||||
|
|
||||||
class TimerScreen extends StatelessWidget {
|
class TimerScreen extends StatefulWidget {
|
||||||
const TimerScreen({super.key});
|
const TimerScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TimerScreen> createState() => _TimerScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TimerScreenState extends State<TimerScreen> with TickerProviderStateMixin {
|
||||||
|
late AnimationController timelineController;
|
||||||
|
late Animation<double> timelineAnimation;
|
||||||
|
late AnimationController startStopIconController;
|
||||||
|
late Animation<double> startStopIconAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
timelineController = AnimationController(vsync: this, duration: Dimens.durationS);
|
||||||
|
timelineAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(timelineController);
|
||||||
|
|
||||||
|
startStopIconController = AnimationController(vsync: this, duration: Dimens.durationS);
|
||||||
|
startStopIconAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(startStopIconController);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
context.read<TimerBloc>().add(const StartTimerEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
timelineController.dispose();
|
||||||
|
startStopIconController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return BlocListener<TimerBloc, TimerState>(
|
||||||
appBar: AppBar(
|
listenWhen: (previous, current) =>
|
||||||
automaticallyImplyLeading: false,
|
previous is TimerStoppedState && current is TimerResumedState ||
|
||||||
centerTitle: true,
|
previous is TimerResumedState && current is TimerStoppedState,
|
||||||
elevation: 0,
|
listener: (context, state) => _updateAnimations(state),
|
||||||
title: Text(
|
child: Scaffold(
|
||||||
'Test',
|
appBar: AppBar(
|
||||||
style: TextStyle(
|
automaticallyImplyLeading: false,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
centerTitle: true,
|
||||||
fontSize: Dimens.grid24,
|
elevation: 0,
|
||||||
|
title: Text(
|
||||||
|
'Test',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
fontSize: Dimens.grid24,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
actions: [if (Navigator.of(context).canPop()) const CloseButton()],
|
||||||
),
|
),
|
||||||
actions: [const CloseButton()],
|
body: SafeArea(
|
||||||
),
|
child: Center(
|
||||||
body: SafeArea(
|
child: Padding(
|
||||||
child: Center(
|
padding: const EdgeInsets.all(Dimens.paddingL),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox.fromSize(
|
const Spacer(),
|
||||||
size: Size.square(MediaQuery.sizeOf(context).width - Dimens.paddingL * 4),
|
// SizedBox.fromSize(
|
||||||
child: _Timer(
|
// size: Size.square(MediaQuery.sizeOf(context).width - Dimens.paddingL * 4),
|
||||||
remainingSeconds: 5,
|
// child: BlocBuilder<TimerBloc, TimerState>(
|
||||||
timerLength: 124,
|
// builder: (context, state) {
|
||||||
),
|
// return _Timer(
|
||||||
|
// timeLeft: state.timeLeft,
|
||||||
|
// duration: state.duration,
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
BlocBuilder<TimerBloc, TimerState>(
|
||||||
|
buildWhen: (previous, current) => previous.timeLeft != current.timeLeft,
|
||||||
|
builder: (_, state) => _Timer(
|
||||||
|
timeLeft: state.timeLeft,
|
||||||
|
duration: state.duration,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
BlocBuilder<TimerBloc, TimerState>(
|
||||||
|
builder: (_, state) => FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.read<TimerBloc>().add(
|
||||||
|
state is TimerStoppedState ? const StartTimerEvent() : const StopTimerEvent(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: AnimatedIcon(
|
||||||
|
icon: AnimatedIcons.play_pause,
|
||||||
|
progress: startStopIconAnimation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateAnimations(TimerState state) {
|
||||||
|
switch (state) {
|
||||||
|
case TimerResumedState():
|
||||||
|
startStopIconController.forward();
|
||||||
|
case TimerStoppedState():
|
||||||
|
startStopIconController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Timer extends StatelessWidget {
|
class _Timer extends StatelessWidget {
|
||||||
final int remainingSeconds;
|
final Duration timeLeft;
|
||||||
final int timerLength;
|
final Duration duration;
|
||||||
|
|
||||||
const _Timer({
|
const _Timer({
|
||||||
required this.remainingSeconds,
|
required this.timeLeft,
|
||||||
required this.timerLength,
|
required this.duration,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomPaint(
|
return Text(
|
||||||
painter: _TimelinePainter(
|
parseSeconds(),
|
||||||
backgroundColor: ElevationOverlay.applySurfaceTint(
|
style: Theme.of(context).textTheme.headlineLarge,
|
||||||
Theme.of(context).cardTheme.color!,
|
|
||||||
Theme.of(context).cardTheme.surfaceTintColor,
|
|
||||||
Theme.of(context).cardTheme.elevation!,
|
|
||||||
),
|
|
||||||
progressColor: Theme.of(context).colorScheme.primary,
|
|
||||||
progress: remainingSeconds / timerLength,
|
|
||||||
),
|
|
||||||
willChange: true,
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
parseSeconds(),
|
|
||||||
style: Theme.of(context).textTheme.headlineLarge,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,15 +155,15 @@ class _Timer extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
int remainingSeconds = this.remainingSeconds;
|
int remainingSeconds = timeLeft.inSeconds;
|
||||||
// longer than 1 hours
|
// longer than 1 hours
|
||||||
if (timerLength >= 3600) {
|
if (duration.inSeconds >= 3600) {
|
||||||
final hours = remainingSeconds ~/ 3600;
|
final hours = remainingSeconds ~/ 3600;
|
||||||
buffer.writeAll([addZeroIfNeeded(hours), ':']);
|
buffer.writeAll([addZeroIfNeeded(hours), ':']);
|
||||||
remainingSeconds -= hours * 3600;
|
remainingSeconds -= hours * 3600;
|
||||||
}
|
}
|
||||||
// longer than 1 minute
|
// longer than 1 minute
|
||||||
if (timerLength >= 60 || timerLength == 0) {
|
if (duration.inSeconds >= 60 || duration.inSeconds == 0) {
|
||||||
final minutes = remainingSeconds ~/ 60;
|
final minutes = remainingSeconds ~/ 60;
|
||||||
buffer.writeAll([addZeroIfNeeded(minutes), ':']);
|
buffer.writeAll([addZeroIfNeeded(minutes), ':']);
|
||||||
remainingSeconds -= minutes * 60;
|
remainingSeconds -= minutes * 60;
|
||||||
|
@ -110,6 +174,33 @@ class _Timer extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TimerTimeline extends StatelessWidget {
|
||||||
|
final double progress;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const _TimerTimeline({
|
||||||
|
required this.progress,
|
||||||
|
required this.child,
|
||||||
|
}) : assert(progress >= 0 && progress <= 1);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: _TimelinePainter(
|
||||||
|
backgroundColor: ElevationOverlay.applySurfaceTint(
|
||||||
|
Theme.of(context).cardTheme.color!,
|
||||||
|
Theme.of(context).cardTheme.surfaceTintColor,
|
||||||
|
Theme.of(context).cardTheme.elevation!,
|
||||||
|
),
|
||||||
|
progressColor: Theme.of(context).colorScheme.primary,
|
||||||
|
progress: progress,
|
||||||
|
),
|
||||||
|
willChange: true,
|
||||||
|
child: Center(child: child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _TimelinePainter extends CustomPainter {
|
class _TimelinePainter extends CustomPainter {
|
||||||
final Color progressColor;
|
final Color progressColor;
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
|
|
|
@ -1,36 +1,26 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
abstract class MeteringState {
|
sealed class TimerState {
|
||||||
final double? ev100;
|
final Duration duration;
|
||||||
final IsoValue iso;
|
final Duration timeLeft;
|
||||||
final NdValue nd;
|
|
||||||
final bool isMetering;
|
|
||||||
|
|
||||||
const MeteringState({
|
const TimerState({
|
||||||
this.ev100,
|
required this.duration,
|
||||||
required this.iso,
|
required this.timeLeft,
|
||||||
required this.nd,
|
|
||||||
required this.isMetering,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoadingState extends MeteringState {
|
class TimerStoppedState extends TimerState {
|
||||||
const LoadingState({
|
const TimerStoppedState({
|
||||||
required super.iso,
|
required super.duration,
|
||||||
required super.nd,
|
required super.timeLeft,
|
||||||
}) : super(isMetering: true);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MeteringDataState extends MeteringState {
|
class TimerResumedState extends TimerState {
|
||||||
const MeteringDataState({
|
const TimerResumedState({
|
||||||
required super.ev100,
|
required super.duration,
|
||||||
required super.iso,
|
required super.timeLeft,
|
||||||
required super.nd,
|
|
||||||
required super.isMetering,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
double? get ev => ev100 != null ? ev100! + log2(iso.value / 100) - nd.stopReduction : null;
|
|
||||||
bool get hasError => ev == null;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue