Skip to content

Commit

Permalink
Add Steam global and user config display
Browse files Browse the repository at this point in the history
  • Loading branch information
ashuntu committed Aug 22, 2024
1 parent c5621bc commit 0cf8994
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 24 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.vscode

target
*.snap

Expand Down
5 changes: 5 additions & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ name: game_center

packages:
- packages/**

scripts:
generate: >
melos exec -c 1 --fail-fast --depends-on=build_runner -- \
dart run build_runner build --delete-conflicting-outputs
20 changes: 5 additions & 15 deletions packages/game_center/lib/app/app_pages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,34 @@ final pages = <AppPage>[
leading: Icon(LauncherPage.icon(selected)),
title: Text(LauncherPage.label(context)),
),
pageBuilder: (_) => const YaruDetailPage(
body: LauncherPage(),
),
pageBuilder: (_) => LauncherPage()
),
(
titleBuilder: (context, selected) => YaruMasterTile(
leading: Icon(SteamPage.icon(selected)),
title: Text(SteamPage.label(context)),
),
pageBuilder: (_) => const YaruDetailPage(
body: SteamPage(),
),
pageBuilder: (_) => SteamPage(),
),
(
titleBuilder: (context, selected) => YaruMasterTile(
leading: Icon(SystemPage.icon(selected)),
title: Text(SystemPage.label(context)),
),
pageBuilder: (_) => const YaruDetailPage(
body: SystemPage(),
),
pageBuilder: (_) => SystemPage()
),
(
titleBuilder: (context, selected) => YaruMasterTile(
leading: Icon(SettingsPage.icon(selected)),
title: Text(SettingsPage.label(context)),
),
pageBuilder: (_) => const YaruDetailPage(
body: SettingsPage(),
),
pageBuilder: (_) => SettingsPage()
),
(
titleBuilder: (context, selected) => YaruMasterTile(
leading: Icon(AboutPage.icon(selected)),
title: Text(AboutPage.label(context)),
),
pageBuilder: (_) => const YaruDetailPage(
body: AboutPage(),
),
pageBuilder: (_) => AboutPage()
),
];
6 changes: 5 additions & 1 deletion packages/game_center/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
"launcherPageLabel": "Apps",

"steamPageLabel": "Steam",
"steamGlobalConfigTitle": "Steam Global Config",
"steamUserConfigTitle": "Steam User Configs",

"settingsPageLabel": "Settings",

"systemPageLabel": "System"
"systemPageLabel": "System",

"loadingLabel": "Loading..."
}
23 changes: 23 additions & 0 deletions packages/game_center/lib/layout.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter/widgets.dart';

const kPagePadding = 24.0;

class AppScrollView extends StatelessWidget {
AppScrollView({required this.children});

final List<Widget> children;

@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverPadding(
padding: EdgeInsets.all(kPagePadding),
sliver: SliverList.list(
children: children,
),
)
],
);
}
}
7 changes: 6 additions & 1 deletion packages/game_center/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:game_center/app/app.dart';
import 'package:ubuntu_localizations/ubuntu_localizations.dart';
import 'package:yaru/yaru.dart';
Expand All @@ -7,5 +8,9 @@ Future<void> main() async {
await initDefaultLocale();
await YaruWindowTitleBar.ensureInitialized();

runApp(const App());
runApp(
const ProviderScope(
child: App(),
),
);
}
106 changes: 106 additions & 0 deletions packages/game_center/lib/steam/steam_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'dart:io';

import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:vdf/vdf.dart';
import 'package:watcher/watcher.dart';
import 'package:path/path.dart' as path;

part 'steam_model.g.dart';

const steamDataDir = '.steam/steam';

/// Steam's global settings
String steamGlobalConfig(String installLocation) {
return '$installLocation/$steamDataDir/config/config.vdf';
}

/// Steam user settings *(not to be confused with the Linux user)*.
///
/// Most users of Steam will only have 1 account per Linux user. However, it is
/// possible to log in to multiple Steam accounts from a single Linux user.
String steamUserConfig(String installLocation, String userID) {
return '$installLocation/$steamDataDir/userdata/$userID/config/localconfig.vdf';
}

typedef Config = ({
Map<String, dynamic> globalConfig,
Map<String, Map<String, dynamic>> userConfigs,
});

@riverpod
class SteamModel extends _$SteamModel {
late String installLocation;
late Map<String, dynamic> globalConfig;
late Map<String, Map<String, dynamic>> userConfigs;

@override
Future<Config> build({String? install}) async {
// default install location
if (install == null) {
final home = Platform.environment['HOME'];
if (home == null) {
throw StateError('\$HOME not found.');
}
install = '$home/snap/steam/common';
}

installLocation = install;

// global config
final fileSystem = FileWatcher(steamGlobalConfig(installLocation));
fileSystem.events.listen((event) async {
switch (event.type) {
case ChangeType.MODIFY:
await updateGlobalConfig();
}
});
final file = File(steamGlobalConfig(installLocation));
globalConfig = vdfDecode(await file.readAsString());

// user configs
final users = await listUserDirs();
userConfigs = new Map();
for (final steamID in users) {
final fileSystem = FileWatcher(steamUserConfig(installLocation, steamID));
fileSystem.events.listen((event) async {
switch (event.type) {
case ChangeType.MODIFY:
await updateUserConfig(steamID);
}
});
final file = File(steamUserConfig(installLocation, steamID));
userConfigs[steamID] = vdfDecode(await file.readAsString());
}

return (
globalConfig: globalConfig,
userConfigs: userConfigs,
);
}

Future<List<String>> listUserDirs() async {
var userdata =
await Directory('$installLocation/$steamDataDir/userdata').list();
var directories =
userdata.where((x) => x is Directory).map((x) => x as Directory);
return directories.map((dir) => path.basename(dir.path)).toList();
}

Future<void> updateGlobalConfig() async {
final file = File(steamGlobalConfig(installLocation));
globalConfig = vdfDecode(await file.readAsString());
state = AsyncData((
globalConfig: globalConfig,
userConfigs: userConfigs,
));
}

Future<void> updateUserConfig(String steamID) async {
final file = File(steamUserConfig(installLocation, steamID));
userConfigs[steamID] = vdfDecode(await file.readAsString());
state = AsyncData((
globalConfig: globalConfig,
userConfigs: userConfigs,
));
}
}
121 changes: 116 additions & 5 deletions packages/game_center/lib/steam/steam_page.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:game_center/layout.dart';
import 'package:game_center/steam/steam_model.dart';
import 'package:vdf/vdf.dart';
import 'package:yaru/yaru.dart';

class SteamPage extends StatelessWidget {
class SteamPage extends ConsumerWidget {
const SteamPage({super.key});

static IconData icon(bool selected) =>
Expand All @@ -11,11 +15,118 @@ class SteamPage extends StatelessWidget {
AppLocalizations.of(context).steamPageLabel;

@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final steam = ref.watch(steamModelProvider());

return Center(
child: Text(l10n.steamPageLabel),
return steam.when(
data: (data) => AppScrollView(
children: [
Text(
l10n.steamGlobalConfigTitle,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: kPagePadding),
_SteamGlobalConfigText(),
const SizedBox(height: kPagePadding),
Text(
l10n.steamUserConfigTitle,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: kPagePadding),
_SteamUserConfigs(),
],
),
loading: () => Center(
child: Text(l10n.loadingLabel),
),
error: (error, stackTrace) => Center(
child: Text(error.toString()),
),
);
}
}

class _SteamGlobalConfigText extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final steam = ref.watch(steamModelProvider());
final controller = new TextEditingController();

steam.whenData((data) async {
controller.text = vdf.encode(data.globalConfig).replaceAll('\t', ' ');
});

return steam.when(
data: (data) => TextField(
controller: controller,
readOnly: true,
minLines: 16,
maxLines: 16,
),
error: (error, trace) => Center(
child: Text(error.toString()),
),
loading: () => Center(
child: Text(l10n.loadingLabel),
),
);
}
}

class _SteamUserConfigs extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final steam = ref.watch(steamModelProvider());

return steam.when(
data: (data) => Column(
children: [
for (final userID in data.userConfigs.keys)
_SteamUserConfigText(userID: userID)
],
),
error: (error, trace) => Center(
child: Text(error.toString()),
),
loading: () => Center(
child: Text(l10n.loadingLabel),
),
);
}
}

class _SteamUserConfigText extends ConsumerWidget {
_SteamUserConfigText({required this.userID});

final String userID;

@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final steam = ref.watch(steamModelProvider());
final controller = new TextEditingController();

steam.whenData((data) async {
controller.text =
vdf.encode(data.userConfigs[userID]!).replaceAll('\t', ' ');
});

return steam.when(
data: (data) => TextField(
controller: controller,
readOnly: true,
minLines: 16,
maxLines: 16,
),
error: (error, trace) => Center(
child: Text(error.toString()),
),
loading: () => Center(
child: Text(l10n.loadingLabel),
),
);
}
}
11 changes: 9 additions & 2 deletions packages/game_center/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,33 @@ publish_to: 'none'
version: 1.0.0+1

environment:
sdk: '>=3.2.3 <4.0.0'
flutter: '>=3.22.3'
sdk: '>=3.5.0 <4.0.0'
flutter: '>=3.24.0'

dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter_riverpod: ^2.5.1
intl: ^0.19.0
path: ^1.9.0
riverpod_annotation: ^2.3.5
ubuntu_lints: ^0.4.0
ubuntu_localizations: ^0.4.0
ubuntu_logger: ^0.1.1
ubuntu_service: ^0.3.2
vdf: ^1.1.0
watcher: ^1.1.0
yaru: ^4.1.0
yaru_window: ^0.2.1

dev_dependencies:
build_runner: ^2.4.12
flutter_test:
sdk: flutter
mockito: ^5.4.4
riverpod_generator: ^2.4.2
yaru_test: ^0.1.6

flutter:
Expand Down

0 comments on commit 0cf8994

Please sign in to comment.