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

Linux review #193

Merged
merged 13 commits into from
Dec 12, 2023
2 changes: 1 addition & 1 deletion lib/api/api_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class DevHttpOverrides extends HttpOverrides {
/// * <https://github.com/bluecherrydvr/unity/discussions/42>
/// * [compute], used to compute data in another thread
static void configureCertificates() {
ServersProvider.instance = ServersProvider();
ServersProvider.instance = ServersProvider.dump();
HttpOverrides.global = DevHttpOverrides();
}

Expand Down
8 changes: 4 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Future<void> main(List<String> args) async {
} else {
try {
// this is just a mock. HomeProvider depends on this, so we mock the instance
ServersProvider.instance = ServersProvider();
ServersProvider.instance = ServersProvider.dump();
await SettingsProvider.ensureInitialized();
await DesktopViewProvider.ensureInitialized();

Expand Down Expand Up @@ -149,8 +149,8 @@ Future<void> main(List<String> args) async {
// wait time at the splash screen
// settings provider needs to be initalized alone
await SettingsProvider.ensureInitialized();
await DownloadsManager.ensureInitialized();
await Future.wait([
DownloadsManager.ensureInitialized(),
MobileViewProvider.ensureInitialized(),
DesktopViewProvider.ensureInitialized(),
ServersProvider.ensureInitialized(),
Expand Down Expand Up @@ -265,8 +265,8 @@ class _UnityAppState extends State<UnityApp> with WidgetsBindingObserver {
],
supportedLocales: AppLocalizations.supportedLocales,
themeMode: settings.themeMode,
theme: createTheme(themeMode: ThemeMode.light),
darkTheme: createTheme(themeMode: ThemeMode.dark),
theme: createTheme(brightness: Brightness.light),
darkTheme: createTheme(brightness: Brightness.dark),
initialRoute: '/',
routes: {
'/': (context) => const Home(),
Expand Down
56 changes: 56 additions & 0 deletions lib/providers/app_provider_interface.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This file is a part of Bluecherry Client (https://github.com/bluecherrydvr/unity).
*
* Copyright 2022 Bluecherry, LLC
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'package:flutter/widgets.dart';
import 'package:safe_local_storage/safe_local_storage.dart';

abstract class UnityProvider extends ChangeNotifier {
Future<void> initialize();
Future<void> reloadInterface() => initialize();

@protected
Future<void> initializeStorage(SafeLocalStorage storage, String key) async {
try {
final hive = await storage.read() as Map;
if (!hive.containsKey(key)) {
await save();
} else {
await restore();
}
} catch (e) {
await save();
}
}

@mustCallSuper
@protected
Future<void> save({bool notifyListeners = true}) async {
if (notifyListeners) {
this.notifyListeners();
}
}

@mustCallSuper
@protected
Future<void> restore({bool notifyListeners = true}) async {
if (notifyListeners) {
this.notifyListeners();
}
}
}
86 changes: 45 additions & 41 deletions lib/providers/desktop_view_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:bluecherry_client/models/device.dart';
import 'package:bluecherry_client/models/layout.dart';
import 'package:bluecherry_client/providers/app_provider_interface.dart';
import 'package:bluecherry_client/utils/constants.dart';
import 'package:bluecherry_client/utils/storage.dart';
import 'package:bluecherry_client/utils/video_player.dart';
import 'package:flutter/foundation.dart';

class DesktopViewProvider extends ChangeNotifier {
/// `late` initialized [DesktopViewProvider] instance.
static late final DesktopViewProvider instance;
class DesktopViewProvider extends UnityProvider {
DesktopViewProvider._();

/// Initializes the [DesktopViewProvider] instance & fetches state from `async`
/// `package:hive` method-calls. Called before [runApp].
static late final DesktopViewProvider instance;
static Future<DesktopViewProvider> ensureInitialized() async {
instance = DesktopViewProvider();
instance = DesktopViewProvider._();
await instance.initialize();
return instance;
}
Expand All @@ -55,40 +56,44 @@ class DesktopViewProvider extends ChangeNotifier {
/// Gets the current selected layout
Layout get currentLayout => layouts[currentLayoutIndex];

/// Called by [ensureInitialized].
@override
Future<void> initialize() async {
final hive = await desktopView.read() as Map;
if (!hive.containsKey(kHiveDesktopLayouts)) {
await _save();
} else {
await _restore();
// Create video player instances for the device tiles already present in the view (restored from cache).
for (final device in currentLayout.devices) {
UnityPlayers.players[device.uuid] = UnityPlayers.forDevice(device);
}
await initializeStorage(desktopView, kHiveDesktopLayouts);
for (final device in currentLayout.devices) {
final completer = Completer();
UnityPlayers.players[device.uuid] ??= UnityPlayers.forDevice(
device,
() async {
if (Platform.isLinux) {
await Future.delayed(const Duration(milliseconds: 250));
}
completer.complete();
},
);
await completer.future;
}
}

/// Saves current layout/order of [Device]s to cache using `package:hive`.
/// Pass [notifyListeners] as `false` to prevent redundant redraws.
Future<void> _save({bool notifyListeners = true}) async {
@override
Future<void> save({bool notifyListeners = true}) async {
try {
await desktopView.write({
kHiveDesktopLayouts:
jsonEncode(layouts.map((layout) => layout.toMap()).toList()),
kHiveDesktopCurrentLayout: _currentLayout,
});
} catch (e) {
debugPrint(e.toString());
} catch (error, stackTrace) {
debugPrint('Failed to save desktop view:\n $error\n$stackTrace');
}

if (notifyListeners) {
this.notifyListeners();
}
super.save(notifyListeners: notifyListeners);
}

/// Restores current layout/order of [Device]s from `package:hive` cache.
Future<void> _restore({bool notifyListeners = true}) async {
@override
Future<void> restore({bool notifyListeners = true}) async {
final data = await desktopView.read() as Map;

layouts = ((await compute(
Expand All @@ -102,13 +107,11 @@ class DesktopViewProvider extends ChangeNotifier {
}).toList();
_currentLayout = data[kHiveDesktopCurrentLayout] ?? 0;

if (notifyListeners) {
this.notifyListeners();
}
super.restore(notifyListeners: notifyListeners);
}

/// Adds [device] to the current layout
Future<void> add(Device device, [Layout? layout]) {
Future<void> add(Device device, [Layout? layout]) async {
assert(
!currentLayout.devices.contains(device),
'The device is already in the layout',
Expand All @@ -124,7 +127,7 @@ class DesktopViewProvider extends ChangeNotifier {
var previousDevice = layout.devices.firstOrNull;
if (previousDevice != null) {
layout.devices.clear();
_releaseDevice(device);
await _releaseDevice(device);
}
}

Expand All @@ -133,17 +136,17 @@ class DesktopViewProvider extends ChangeNotifier {
debugPrint('Added $device');

notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}
return Future.value();
}

/// Releases a device if no layout is using it
void _releaseDevice(Device device) {
Future<void> _releaseDevice(Device device) async {
if (!UnityPlayers.players.containsKey(device.uuid)) return;
if (!layouts
.any((layout) => layout.devices.any((d) => d.uuid == device.uuid))) {
UnityPlayers.releaseDevice(device.uuid);
await UnityPlayers.releaseDevice(device.uuid);
}
}

Expand All @@ -156,7 +159,7 @@ class DesktopViewProvider extends ChangeNotifier {
_releaseDevice(device);
}
notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

/// Removes all the [devices] provided
Expand All @@ -174,7 +177,7 @@ class DesktopViewProvider extends ChangeNotifier {
}

notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

Future<void> removeDevicesFromCurrentLayout(Iterable<Device> devices) {
Expand All @@ -187,7 +190,7 @@ class DesktopViewProvider extends ChangeNotifier {
}

notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

/// Moves a device tile from [initial] position to [end] position inside a [tab].
Expand All @@ -198,7 +201,7 @@ class DesktopViewProvider extends ChangeNotifier {
currentLayout.devices.insert(end, currentLayout.devices.removeAt(initial));
// Prevent redundant latency.
notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

/// Adds a new layout
Expand All @@ -210,7 +213,7 @@ class DesktopViewProvider extends ChangeNotifier {
debugPrint('$layout already exists');
}
notifyListeners();
await _save(notifyListeners: false);
await save(notifyListeners: false);

return layouts.indexOf(layout);
}
Expand All @@ -233,7 +236,7 @@ class DesktopViewProvider extends ChangeNotifier {
}
}
notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

/// Replaces [oldLayout] with [newLayout]
Expand All @@ -250,19 +253,20 @@ class DesktopViewProvider extends ChangeNotifier {
}

notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

/// Updates the current layout index
Future<void> updateCurrentLayout(int layoutIndex) {
_currentLayout = layoutIndex;

for (final device in currentLayout.devices) {
// creates the device that don't exist
UnityPlayers.players[device.uuid] ??= UnityPlayers.forDevice(device);
}

notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

/// Reorders the layouts
Expand All @@ -278,7 +282,7 @@ class DesktopViewProvider extends ChangeNotifier {

layouts.insert(newIndex, layouts.removeAt(oldIndex));
notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}

/// Updates a device in all the layouts.
Expand All @@ -301,6 +305,6 @@ class DesktopViewProvider extends ChangeNotifier {
}

notifyListeners();
return _save(notifyListeners: false);
return save(notifyListeners: false);
}
}
Loading