Skip to content

Commit

Permalink
feat(*): saving more data types and allow disable window configs
Browse files Browse the repository at this point in the history
* Allow saving the following data types into database:
  * DateTime: Before this commit, only part of `DateTime` support.
  * Offset.
  * Size.
* Allow disable `window_manager` features usage by adding cmdline arg
  `--no-window-configs`.
  * This features is useful when want to disable those related fetures
    such as in a tiling window manager environment or window became.
    missing due to `setPosition` to a out-of-bound position after
    monitor layout changed, which will add in near future.
  * When applying this cmdline arg, window title, window postion and
    window size use default values and not remembered.
  * This disabling feature (as well as window fetures) only available
    on desktop platforms.
  • Loading branch information
realth000 committed Oct 4, 2024
1 parent c6e68e1 commit 153197d
Show file tree
Hide file tree
Showing 21 changed files with 275 additions and 66 deletions.
38 changes: 35 additions & 3 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import 'package:tsdm_client/shared/providers/storage_provider/storage_provider.d
import 'package:tsdm_client/shared/repositories/forum_home_repository/forum_home_repository.dart';
import 'package:tsdm_client/shared/repositories/fragments_repository/fragments_repository.dart';
import 'package:tsdm_client/themes/app_themes.dart';
import 'package:window_manager/window_manager.dart';

/// Main app for tsdm_client.
class App extends StatelessWidget {
class App extends StatefulWidget {
/// Constructor.
const App(this.color, this.themeModeIndex, {super.key});

Expand All @@ -34,6 +35,23 @@ class App extends StatelessWidget {
/// Initial theme mode index.
final int themeModeIndex;

@override
State<App> createState() => _AppState();
}

class _AppState extends State<App> with WindowListener {
@override
void initState() {
super.initState();
windowManager.addListener(this);
}

@override
void dispose() {
windowManager.removeListener(this);
super.dispose();
}

@override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
Expand Down Expand Up @@ -87,8 +105,8 @@ class App extends StatelessWidget {
],
child: BlocProvider(
create: (context) => ThemeCubit(
accentColor: color >= 0 ? Color(color) : null,
themeModeIndex: themeModeIndex,
accentColor: widget.color >= 0 ? Color(widget.color) : null,
themeModeIndex: widget.themeModeIndex,
),
child: BlocBuilder<ThemeCubit, ThemeState>(
buildWhen: (prev, curr) => prev != curr,
Expand All @@ -114,4 +132,18 @@ class App extends StatelessWidget {
),
);
}

@override
Future<void> onWindowMove() async {
super.onWindowMove();
final x = await windowManager.getPosition();
debugPrint('>>> window moved to $x');
}

@override
Future<void> onWindowResize() async {
super.onWindowResize();
final x = await windowManager.getSize();
debugPrint('>>> window size is $x');
}
}
35 changes: 35 additions & 0 deletions lib/cmd.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:args/args.dart';
import 'package:tsdm_client/instance.dart';

/// All flags in cmdline.
abstract class Flags {
/// Disable window related configs, including window size, window title and
/// window position.
///
/// Set to false if needed, as in recovery mode.
static const noWindowConfigs = 'no-window-configs';
}

/// Cmdline arguments parsed result.
class CmdArgs {
/// Constructor.
const CmdArgs({
required this.noWindowConfigs,
});

/// Disable window related configs.
///
/// Set to `true` will disable window_manager related features.
/// Also the **ONLY** way to disable it.
final bool noWindowConfigs;
}

/// Parse cmdline [args] into global variable [cmdArgs].
void parseCmdArgs(List<String> args) {
final parser = ArgParser()..addFlag(Flags.noWindowConfigs, negatable: false);

final argsResult = parser.parse(args);
cmdArgs = CmdArgs(
noWindowConfigs: argsResult.flag(Flags.noWindowConfigs),
);
}
46 changes: 12 additions & 34 deletions lib/features/settings/bloc/settings_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ import 'package:tsdm_client/utils/logger.dart';

part 'settings_bloc.mapper.dart';
part 'settings_event.dart';

part 'settings_state.dart';

const _scrollDebounceDuration = Duration(milliseconds: 300);

/// Emitter.
typedef _Emitter = Emitter<SettingsState>;

Expand All @@ -42,39 +39,21 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> with LoggerMixin {
_settingsMapSub = _settingsRepository.settings
.listen((settings) => add(SettingsMapChanged(settings)));

on<SettingsMapChanged>(_onSettingsMapChanged);
on<SettingsScrollOffsetChanged>(
_onSettingsScrollOffsetChanged,
transformer: debounce(_scrollDebounceDuration),
);
on<SettingsEvent>(
(event, emit) async => switch (event) {
final SettingsMapChanged e => _onSettingsMapChanged(e, emit),
final SettingsScrollOffsetChanged e =>
_onSettingsScrollOffsetChanged(e, emit),
final SettingsValueChanged<int> e => _onSettingsValueChanged<int>(
e,
emit,
),
final SettingsValueChanged<double> e => _onSettingsValueChanged<double>(
e,
emit,
),
final SettingsValueChanged<bool> e => _onSettingsValueChanged<bool>(
e,
emit,
),
final SettingsValueChanged<String> e => _onSettingsValueChanged<String>(
e,
emit,
),
final SettingsValueChanged<DateTime> e =>
_onSettingsValueChanged<DateTime>(
e,
emit,
),
final SettingsValueChanged<dynamic> _ =>
throw Exception('Unsupported settings change event type'),
final SettingsValueChanged<int> e => _onValueChanged<int>(e),
final SettingsValueChanged<double> e => _onValueChanged<double>(e),
final SettingsValueChanged<bool> e => _onValueChanged<bool>(e),
final SettingsValueChanged<String> e => _onValueChanged<String>(e),
final SettingsValueChanged<DateTime> e => _onValueChanged<DateTime>(e),
final SettingsValueChanged<Offset> e => _onValueChanged<Offset>(e),
final SettingsValueChanged<Size> e => _onValueChanged<Size>(e),
final SettingsValueChanged<dynamic> e =>
throw Exception('Unsupported settings change event '
'type(${e.runtimeType}): $e'),
},
);
}
Expand All @@ -98,11 +77,10 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> with LoggerMixin {
_fragmentsRepository.settingsPageScrollOffset = event.offset;
}

Future<void> _onSettingsValueChanged<T>(
Future<void> _onValueChanged<T>(
SettingsValueChanged<T> event,
_Emitter emit,
) async {
debug('settings value changed: ${event.settings.name}: ${event.value}');
debug('settings value changed: ${event.settings.name}<$T>: ${event.value}');
await _settingsRepository.setValue<T>(event.settings, event.value);
}

Expand Down
10 changes: 10 additions & 0 deletions lib/features/settings/repositories/settings_repository.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io' if (dart.libaray.js) 'package:web/web.dart';
import 'dart:ui';

import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
Expand Down Expand Up @@ -28,6 +29,9 @@ extension _ExtractExt on List<SettingsEntity> {
double => v.doubleValue,
String => v.stringValue,
bool => v.boolValue,
DateTime => v.dateTimeValue,
Offset => v.offsetValue,
Size => v.sizeValue,
_ => null,
} ??
settings.defaultValue) as T;
Expand Down Expand Up @@ -127,6 +131,9 @@ final class SettingsRepository with LoggerMixin {
double => _storage.getDouble(name),
String => _storage.getString(name),
bool => _storage.getBool(name),
DateTime => _storage.getDateTime(name),
Offset => _storage.getOffset(name),
Size => _storage.getSize(name),
_ => () {
error('failed to getValue for key $key: unsupported type $T');
return null;
Expand Down Expand Up @@ -156,6 +163,9 @@ final class SettingsRepository with LoggerMixin {
double => _storage.saveDouble(name, value as double),
String => _storage.saveString(name, value as String),
bool => _storage.saveBool(name, value: value as bool),
DateTime => _storage.saveDateTime(name, value as DateTime),
Offset => _storage.saveOffset(name, value as Offset),
Size => _storage.saveSize(name, value as Size),
final t => () {
error('failed to save settings for key $key:'
' unsupported type in storage: $t');
Expand Down
38 changes: 17 additions & 21 deletions lib/features/settings/view/settings_page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
Expand All @@ -23,11 +24,11 @@ import 'package:tsdm_client/shared/providers/checkin_provider/models/check_in_fe
import 'package:tsdm_client/utils/platform.dart';
import 'package:tsdm_client/utils/show_bottom_sheet.dart';
import 'package:tsdm_client/utils/show_toast.dart';
import 'package:tsdm_client/utils/window_configs.dart';
import 'package:tsdm_client/widgets/color_palette.dart';
import 'package:tsdm_client/widgets/section_list_tile.dart';
import 'package:tsdm_client/widgets/section_switch_list_tile.dart';
import 'package:tsdm_client/widgets/section_title_text.dart';
import 'package:window_manager/window_manager.dart';

/// Settings page of the app.
class SettingsPage extends StatefulWidget {
Expand Down Expand Up @@ -165,10 +166,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (localeGroup.$2) {
// Use system language.
LocaleSettings.useDeviceLocale();
if (isDesktop) {
await windowManager
.setTitle(LocaleSettings.currentLocale.translations.appName);
}
await desktopUpdateWindowTitle();
if (!context.mounted) {
return;
}
Expand All @@ -178,10 +176,7 @@ class _SettingsPageState extends State<SettingsPage> {
return;
}
LocaleSettings.setLocale(localeGroup.$1!);
if (isDesktop) {
await windowManager
.setTitle(LocaleSettings.currentLocale.translations.appName);
}
await desktopUpdateWindowTitle();
if (!context.mounted) {
return;
}
Expand Down Expand Up @@ -450,18 +445,19 @@ class _SettingsPageState extends State<SettingsPage> {
final tr = context.t.settingsPage.advancedSection;
return [
SectionTitleText(tr.title),
SectionListTile(
leading: const Icon(Icons.developer_mode_outlined),
title: const Text('DEBUG SHOWCASE'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => const DebugShowcasePage(),
),
);
},
),
if (!kReleaseMode)
SectionListTile(
leading: const Icon(Icons.developer_mode_outlined),
title: const Text('DEBUG SHOWCASE'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => const DebugShowcasePage(),
),
);
},
),
SectionSwitchListTile(
secondary: Icon(MdiIcons.networkOutline),
title: Text(tr.useProxy),
Expand Down
8 changes: 8 additions & 0 deletions lib/instance.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import 'package:get_it/get_it.dart';
import 'package:talker_flutter/talker_flutter.dart';
import 'package:tsdm_client/cmd.dart';

/// Global service locator instance.
final getIt = GetIt.instance;

/// Global logger instance.
final talker = TalkerFlutter.init();

/// Global cmdline args.
///
/// Only used in desktop platforms.
///
/// Init in [parseCmdArgs]
late final CmdArgs cmdArgs;
10 changes: 7 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import 'package:flutter/material.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:system_theme/system_theme.dart';
import 'package:tsdm_client/app.dart';
import 'package:tsdm_client/cmd.dart';
import 'package:tsdm_client/constants/layout.dart';
import 'package:tsdm_client/features/settings/repositories/settings_repository.dart';
import 'package:tsdm_client/i18n/strings.g.dart';
import 'package:tsdm_client/instance.dart';
import 'package:tsdm_client/shared/models/models.dart';
import 'package:tsdm_client/shared/providers/providers.dart';
import 'package:tsdm_client/utils/platform.dart';
import 'package:tsdm_client/utils/window_configs.dart';
import 'package:window_manager/window_manager.dart';

Future<void> main() async {
Future<void> main(List<String> args) async {
parseCmdArgs(args);

talker.debug('start app...');
WidgetsFlutterBinding.ensureInitialized();
await initProviders();
Expand All @@ -30,8 +34,8 @@ Future<void> main() async {

if (isDesktop) {
await windowManager.ensureInitialized();
await windowManager
.setTitle(LocaleSettings.currentLocale.translations.appName);
await desktopUpdateWindowTitle();
await windowManager.center();
}

// System color.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'dart:convert';
import 'dart:ui';

import 'package:drift/drift.dart';

part 'offset.dart';
part 'size.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
part of 'convertable.dart';

/// Converter between [Offset] and [String].
class OffsetConverter extends TypeConverter<Offset, String> {
/// Internal constructor.
const OffsetConverter();

static const _keyDx = 'dx';
static const _keyDy = 'dy';

@override
Offset fromSql(String fromDb) {
final jsonMap = jsonDecode(fromDb) as Map<String, double>;
return Offset(jsonMap[_keyDx]!, jsonMap[_keyDy]!);
}

@override
String toSql(Offset value) => jsonEncode(<String, double>{
_keyDx: value.dx,
_keyDy: value.dy,
});
}
22 changes: 22 additions & 0 deletions lib/shared/providers/storage_provider/models/convertable/size.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
part of 'convertable.dart';

/// Converter between [Size] and [String].
class SizeConverter extends TypeConverter<Size, String> {
/// Constructor.
const SizeConverter();

static const _keyWidth = 'width';
static const _keyHeight = 'height';

@override
Size fromSql(String fromDb) {
final jsonMap = jsonDecode(fromDb) as Map<String, double>;
return Size(jsonMap[_keyWidth]!, jsonMap[_keyHeight]!);
}

@override
String toSql(Size value) => jsonEncode(<String, double>{
_keyWidth: value.width,
_keyHeight: value.height,
});
}
Loading

0 comments on commit 153197d

Please sign in to comment.