Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: tray and window behaviours #1298

Merged
merged 7 commits into from
Aug 30, 2024
Merged

refactor: tray and window behaviours #1298

merged 7 commits into from
Aug 30, 2024

Conversation

XavierChanth
Copy link
Member

- What I did

  1. Decoupled the tray feature from the rest of the app - tray should know about other features, but other features should not have to know about the tray. We want the tray to pull updates from the app state, the app state shouldn't have to push updates to the tray.

  2. Removed the app icon from the dock, it's just kind of there and doesn't work to create a new window when you click it. The app should be interacted with from the tray, not the dock.

Note for future me: no. 2 should be fine on windows and macOS, but Linux needs extra dependencies depending on the DE / tray bar being used. We also potentially have to deal with X vs Wayland. This will need to be well tested before releasing. But Anything that is X + gtk should be fine if the additional dependencies for the tray are installed. So I think we can easily detect and install the missing dependency on demand for mainstream package managers (apt & dnf). Besides, anyone using Linux outside of the supported group I just mentioned is probably going to be happy with the CLI version of NoPorts.

- How I did it

- How to verify it

- Description for the changelog
refactor: tray and window behaviours

Using a bunch of state listeners to observe when tray should reload,
rather than each piece of state telling the tray when to update (which
is an anti-pattern, confusing, and bug prone).
@XavierChanth XavierChanth self-assigned this Aug 30, 2024
@@ -79,7 +79,7 @@ class App extends StatelessWidget {

/// A cubit which manages the system tray entries
BlocProvider<TrayCubit>(
create: (_) => TrayCubit()..initialize(),
create: (_) => TrayCubit(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets called in a more appropriate place now.

@@ -32,7 +31,6 @@ class FavoriteBloc extends LoggingBloc<FavoriteEvent, FavoritesState> {
return;
}
emit(FavoritesLoaded(favs.values));
App.navState.currentContext?.read<TrayCubit>().reloadFavorites();
Copy link
Member Author

@XavierChanth XavierChanth Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decoupling - remove all "pushed" updates to the tray.

@@ -38,12 +37,10 @@ class ProfileBloc extends LoggingBloc<ProfileEvent, ProfileState> {

if (profile == null) {
emit(ProfileFailedLoad(uuid));
App.navState.currentContext?.read<TrayCubit>().reloadFavorites();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of the same

@@ -87,34 +87,50 @@ class TrayCubit extends LoggingCubit<TrayState> {
emit(const TrayLoaded());
}

Future<void> reloadFavorites() async {
Future<void> reload() async {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No functional changes to the reload tray method, but I did document it for clarity

Comment on lines +129 to +131
/// PERF: We should conditionally call setContextMenu if there was a state
/// change which resulted in an actual change to the favorites list.
/// Currently we just force call updates which is really inefficient
Copy link
Member Author

@XavierChanth XavierChanth Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lists that we iterate through are not expected to be long, but in the future, if someone were to have 1000s (probably an even bigger magnitude) of profiles loaded in the app we will have to determine if the tray reloading needs to be optimized - reducing useless reloads may help

Comment on lines +22 to +27
/// Must strongly type [context] here or Dart will infer the wrong type for
/// the [.read()] extension which causes an error
void reloadTray(BuildContext context, _) {
context.read<TrayCubit>().reload();
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the listener which triggers tray reloads whenever a piece of state changes.

Comment on lines +44 to +74
listeners: [
/// Reload the tray whenever one of the following states changes
/// Note: this doesn't always result in a change to the tray, but we
/// still have to check
BlocListener<FavoriteBloc, FavoritesState>(
listener: reloadTray,
),
BlocListener<ProfileListBloc, ProfileListState>(
listener: reloadTray,
),
BlocListener<ProfilesRunningCubit, ProfilesRunningState>(
listener: reloadTray,
),

/// Yeah I really hate this... an indefinite list of listeners
/// but it's the only way to decouple the profiles from having to know
/// about the tray
///
/// The tray should know about profiles, profiles should not know
/// about tray. Even if it is slightly more costly in performance, the
/// calls where we take the performance hit are:
/// 1. In an asynchronous background task (who cares)
/// 2. Worth it, compared to the potential maintenance costs
...profiles.map((uuid) => BlocProvider<ProfileBloc>(
key: Key("TrayManager-$uuid"),
create: (context) => profileCacheCubit.getProfileBloc(uuid),
child: BlocListener<ProfileBloc, ProfileState>(
listener: reloadTray,
),
)),
],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these are the new listeners which will trigger the tray to reload whenever any of these change - could be an edited profile, favorited / unfavorited, start / stop, etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike that we need to iterate through each profile and add it as a listener, we could optimize by filtering to only favorited profiles, but that introduces edge cases not worth dealing with right now.

@@ -6,5 +8,11 @@ import 'app.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
windowManager.ensureInitialized();
runApp(const App());
try {
await windowManager.setSkipTaskbar(true); // Don't show the app icon in dock
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disable dock icon - the dock icon wasn't functional, and made it look like the app is hung, but really the app is only functional from the tray.

@XavierChanth XavierChanth merged commit 46ae69d into trunk Aug 30, 2024
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants