Skip to content

Commit

Permalink
Replace RouteAware with FocusDetector
Browse files Browse the repository at this point in the history
  • Loading branch information
veloce committed Feb 7, 2024
1 parent d940420 commit 7229f88
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 151 deletions.
176 changes: 176 additions & 0 deletions lib/src/utils/focus_detector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import 'package:flutter/widgets.dart';
import 'package:visibility_detector/visibility_detector.dart';

// code taken and adapted from
// https://github.com/EdsonBueno/focus_detector

/// Fires callbacks every time the widget appears or disappears from the screen.
class FocusDetector extends StatefulWidget {
const FocusDetector({
required this.child,
this.onFocusGained,
this.onFocusRegained,
this.onFocusLost,
this.onVisibilityGained,
this.onVisibilityRegained,
this.onVisibilityLost,
this.onForegroundGained,
this.onForegroundLost,
super.key,
});

/// Called when the widget becomes visible or enters foreground while visible.
final VoidCallback? onFocusGained;

/// Called when the widget gains focus again (same as onFocusGained but does
/// not fires the first time).
final VoidCallback? onFocusRegained;

/// Called when the widget becomes invisible or enters background while visible.
final VoidCallback? onFocusLost;

/// Called when the widget becomes visible.
final VoidCallback? onVisibilityGained;

/// Called when the widget become visible again (same as onVisibilityGained but
/// does not fires the first time).
final VoidCallback? onVisibilityRegained;

/// Called when the widget becomes invisible.
final VoidCallback? onVisibilityLost;

/// Called when the app entered the foreground while the widget is visible.
final VoidCallback? onForegroundGained;

/// Called when the app is sent to background while the widget was visible.
final VoidCallback? onForegroundLost;

/// The widget below this widget in the tree.
final Widget child;

@override
_FocusDetectorState createState() => _FocusDetectorState();
}

class _FocusDetectorState extends State<FocusDetector>
with WidgetsBindingObserver {
final _visibilityDetectorKey = UniqueKey();

/// Counter to keep track of the visibility changes.
int _visibilityCounter = 0;

/// Whether this widget is currently visible within the app.
bool _isWidgetVisible = false;

/// Whether the app is in the foreground.
bool _isAppInForeground = true;

@override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_notifyPlaneTransition(state);
}

/// Notifies app's transitions to/from the foreground.
void _notifyPlaneTransition(AppLifecycleState state) {
if (!_isWidgetVisible) {
return;
}

final isAppResumed = state == AppLifecycleState.resumed;
final wasResumed = _isAppInForeground;
if (isAppResumed && !wasResumed) {
_isAppInForeground = true;
_notifyFocusGain();
_notifyForegroundGain();
return;
}

final isAppPaused = state == AppLifecycleState.paused;
if (isAppPaused && wasResumed) {
_isAppInForeground = false;
_notifyFocusLoss();
_notifyForegroundLoss();
}
}

@override
Widget build(BuildContext context) => VisibilityDetector(
key: _visibilityDetectorKey,
onVisibilityChanged: (visibilityInfo) {
final visibleFraction = visibilityInfo.visibleFraction;
_notifyVisibilityStatusChange(visibleFraction);
},
child: widget.child,
);

/// Notifies changes in the widget's visibility.
void _notifyVisibilityStatusChange(double newVisibleFraction) {
if (!_isAppInForeground) {
return;
}

final wasFullyVisible = _isWidgetVisible;
final isFullyVisible = newVisibleFraction == 1;
if (!wasFullyVisible && isFullyVisible) {
_isWidgetVisible = true;
_notifyFocusGain();
_notifyVisibilityGain();
_visibilityCounter++;
}

final isFullyInvisible = newVisibleFraction == 0;
if (wasFullyVisible && isFullyInvisible) {
_isWidgetVisible = false;
_notifyFocusLoss();
_notifyVisibilityLoss();
}
}

void _notifyFocusGain() {
widget.onFocusGained?.call();
if (_visibilityCounter > 0) {
widget.onFocusRegained?.call();
}
}

void _notifyFocusLoss() {
widget.onFocusLost?.call();
}

void _notifyVisibilityGain() {
widget.onVisibilityGained?.call();
if (_visibilityCounter > 0) {
widget.onVisibilityRegained?.call();
}
}

void _notifyVisibilityLoss() {
widget.onVisibilityLost?.call();
}

void _notifyForegroundGain() {
final onForegroundGained = widget.onForegroundGained;
if (onForegroundGained != null) {
onForegroundGained();
}
}

void _notifyForegroundLoss() {
final onForegroundLost = widget.onForegroundLost;
if (onForegroundLost != null) {
onForegroundLost();
}
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
48 changes: 15 additions & 33 deletions lib/src/view/relation/relation_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/src/model/relation/relation_ctrl.dart';
import 'package:lichess_mobile/src/model/user/user.dart';
import 'package:lichess_mobile/src/navigation.dart';
import 'package:lichess_mobile/src/utils/focus_detector.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/view/relation/following_screen.dart';
Expand All @@ -22,13 +22,22 @@ class RelationScreen extends ConsumerStatefulWidget {
ConsumerState<RelationScreen> createState() => _RelationScreenState();
}

class _RelationScreenState extends ConsumerState<RelationScreen>
with RouteAware {
class _RelationScreenState extends ConsumerState<RelationScreen> {
@override
Widget build(BuildContext context) {
return PlatformWidget(
androidBuilder: _androidBuilder,
iosBuilder: _iosBuilder,
return FocusDetector(
onFocusRegained: () {
ref.read(relationCtrlProvider.notifier).startWatchingFriends();
},
onFocusLost: () {
if (context.mounted) {
ref.read(relationCtrlProvider.notifier).stopWatchingFriends();
}
},
child: PlatformWidget(
androidBuilder: _androidBuilder,
iosBuilder: _iosBuilder,
),
);
}

Expand All @@ -47,33 +56,6 @@ class _RelationScreenState extends ConsumerState<RelationScreen>
child: _Body(),
);
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
final route = ModalRoute.of(context);
if (route != null && route is PageRoute) {
homeRouteObserver.subscribe(this, route);
}
}

@override
void dispose() {
homeRouteObserver.unsubscribe(this);
super.dispose();
}

@override
void didPushNext() {
ref.read(relationCtrlProvider.notifier).stopWatchingFriends();
super.didPushNext();
}

@override
void didPopNext() {
ref.read(relationCtrlProvider.notifier).startWatchingFriends();
super.didPopNext();
}
}

class _Body extends StatelessWidget {
Expand Down
77 changes: 16 additions & 61 deletions lib/src/view/watch/live_tv_channels_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,34 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/src/constants.dart';
import 'package:lichess_mobile/src/model/tv/live_tv_channels.dart';
import 'package:lichess_mobile/src/model/tv/tv_channel.dart';
import 'package:lichess_mobile/src/navigation.dart';
import 'package:lichess_mobile/src/styles/lichess_colors.dart';
import 'package:lichess_mobile/src/styles/styles.dart';
import 'package:lichess_mobile/src/utils/chessground_compat.dart';
import 'package:lichess_mobile/src/utils/focus_detector.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/view/watch/tv_screen.dart';
import 'package:lichess_mobile/src/widgets/board_preview.dart';
import 'package:lichess_mobile/src/widgets/platform.dart';
import 'package:lichess_mobile/src/widgets/user_full_name.dart';

class LiveTvChannelsScreen extends ConsumerStatefulWidget {
class LiveTvChannelsScreen extends ConsumerWidget {
const LiveTvChannelsScreen({super.key});

@override
ConsumerState<LiveTvChannelsScreen> createState() => _TvChannelsScreenState();
}

class _TvChannelsScreenState extends ConsumerState<LiveTvChannelsScreen>
with RouteAware, WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
return PlatformWidget(
androidBuilder: _androidBuilder,
iosBuilder: _iosBuilder,
Widget build(BuildContext context, WidgetRef ref) {
return FocusDetector(
onFocusRegained: () {
ref.read(liveTvChannelsProvider.notifier).startWatching();
},
onFocusLost: () {
if (context.mounted) {
ref.read(liveTvChannelsProvider.notifier).stopWatching();
}
},
child: PlatformWidget(
androidBuilder: _androidBuilder,
iosBuilder: _iosBuilder,
),
);
}

Expand All @@ -52,55 +56,6 @@ class _TvChannelsScreenState extends ConsumerState<LiveTvChannelsScreen>
child: _Body(),
);
}

@override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
ref.read(liveTvChannelsProvider.notifier).startWatching();
} else {
ref.read(liveTvChannelsProvider.notifier).stopWatching();
}
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
final route = ModalRoute.of(context);
if (route != null && route is PageRoute) {
rootNavPageRouteObserver.subscribe(this, route);
}
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
rootNavPageRouteObserver.unsubscribe(this);
super.dispose();
}

@override
void didPushNext() {
ref.read(liveTvChannelsProvider.notifier).stopWatching();
super.didPushNext();
}

@override
void didPopNext() {
ref.read(liveTvChannelsProvider.notifier).startWatching();
super.didPopNext();
}

@override
void didPop() {
ref.read(liveTvChannelsProvider.notifier).stopWatching();
super.didPop();
}
}

class _Body extends ConsumerWidget {
Expand Down
Loading

0 comments on commit 7229f88

Please sign in to comment.