Skip to content

Commit 00d0491

Browse files
committed
feat: add CompactAutoResetController for auto-hide UI controls
1 parent 941f8bc commit 00d0491

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter/foundation.dart';
4+
import 'package:logging/logging.dart';
5+
6+
final _logger = Logger('CompactAutoResetController');
7+
8+
/// Manages a state that automatically reverts from an expanded state to a
9+
/// compact state after a specified duration of inactivity.
10+
class CompactAutoResetController extends ChangeNotifier {
11+
static const int defaultAutoResetSeconds = 5;
12+
13+
CompactAutoResetController({
14+
this.autoResetSeconds = defaultAutoResetSeconds,
15+
bool initialCompact = false,
16+
bool initiallyActive = false,
17+
}) : assert(autoResetSeconds > 0, 'autoResetSeconds must be > 0'),
18+
_compact = initialCompact,
19+
_active = initiallyActive {
20+
if (_active && !_compact) {
21+
_startTimer(reason: 'init');
22+
}
23+
}
24+
25+
/// The duration in seconds to wait before automatically switching to compact mode.
26+
final int autoResetSeconds;
27+
28+
/// Internal timer used to track the auto-reset duration.
29+
Timer? _timer;
30+
31+
/// Tracks whether the auto-reset functionality is currently enabled.
32+
bool _active;
33+
34+
/// Tracks the current visual state (compact vs expanded).
35+
bool _compact;
36+
37+
/// Whether the controller allows auto-reset behavior.
38+
bool get active => _active;
39+
40+
/// Whether the UI is currently in compact mode.
41+
bool get compact => _compact;
42+
43+
/// Returns true if the countdown timer is currently running.
44+
@visibleForTesting
45+
bool get isRunning => _timer?.isActive ?? false;
46+
47+
/// Toggles the active state of the controller.
48+
///
49+
/// When set to `true`, the timer starts, and the state expands.
50+
/// When set to `false`, the timer stops, and the state remains expanded.
51+
void setActive(bool value, {String reason = 'setActive'}) {
52+
if (_active == value) return;
53+
54+
_active = value;
55+
_logger.info('$reason: active changed to $_active');
56+
57+
// Always expand (show controls) when activity state changes.
58+
_compact = false;
59+
60+
if (_active) {
61+
_startTimer(reason: 'became active');
62+
} else {
63+
_cancelTimer(reason: 'became inactive');
64+
}
65+
66+
notifyListeners();
67+
}
68+
69+
/// Convenience method to enable the controller.
70+
void activate() => setActive(true, reason: 'activate()');
71+
72+
/// Convenience method to disable the controller.
73+
void deactivate() => setActive(false, reason: 'deactivate()');
74+
75+
/// Toggles the compact state.
76+
///
77+
/// If currently compact, it expands and starts the timer.
78+
/// If currently expanded, it compacts and stops the timer.
79+
void toggle() {
80+
if (_compact) {
81+
setCompact(false, reason: 'toggle(expand)');
82+
} else {
83+
setCompact(true, reason: 'toggle(hide)');
84+
}
85+
}
86+
87+
/// Manually sets the compact state.
88+
void setCompact(bool value, {String reason = 'setCompact'}) {
89+
if (_compact == value) return;
90+
91+
_compact = value;
92+
_logger.info('$reason: compact changed to $_compact');
93+
94+
if (!_active) {
95+
notifyListeners();
96+
return;
97+
}
98+
99+
if (_compact) {
100+
_cancelTimer(reason: 'compacted');
101+
} else {
102+
_startTimer(reason: 'expanded');
103+
}
104+
105+
notifyListeners();
106+
}
107+
108+
/// Resets the auto-hide timer if the controller is active and currently expanded.
109+
///
110+
/// This is used to keep the controls visible while the user is interacting with them.
111+
void poke() {
112+
if (!_active || _compact) return;
113+
114+
_logger.finer('poke: restarting timer');
115+
_startTimer(reason: 'poke');
116+
}
117+
118+
void _startTimer({required String reason}) {
119+
_cancelTimer(reason: 'restart');
120+
_timer = Timer(Duration(seconds: autoResetSeconds), _handleTimeout);
121+
}
122+
123+
/// Handles the timeout event and switches to compact mode.
124+
void _handleTimeout() {
125+
if (!_active) return;
126+
_logger.info('timeout: auto-compacting');
127+
_compact = true;
128+
notifyListeners();
129+
}
130+
131+
/// Cancels the current timer if it exists.
132+
void _cancelTimer({required String reason}) {
133+
_timer?.cancel();
134+
_timer = null;
135+
}
136+
137+
/// Disposes the controller by canceling the timer and resetting state.
138+
@override
139+
void dispose() {
140+
_cancelTimer(reason: 'dispose');
141+
super.dispose();
142+
}
143+
}

lib/features/call/utils/utils.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export 'audio_constraints_builder.dart';
22
export 'call_error_reporter.dart';
3+
export 'compact_auto_reset_controller.dart';
34
export 'contact_name_resolver.dart';
45
export 'ice_filter.dart';
56
export 'peer_connection_policy_applier.dart';

0 commit comments

Comments
 (0)