diff --git a/app/lib/api/settings.dart b/app/lib/api/settings.dart index f38af01..9551f14 100644 --- a/app/lib/api/settings.dart +++ b/app/lib/api/settings.dart @@ -2,10 +2,18 @@ import 'package:flutter/material.dart'; import '../pages/settings/page.dart'; -Future openSettings(BuildContext context) => showDialog( - context: context, - builder: (context) => Dialog( - clipBehavior: Clip.antiAlias, - child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 600, maxWidth: 800), - child: const SettingsPage(isDialog: true)))); +Future openSettings( + BuildContext context, { + SettingsView view = SettingsView.general, +}) => + showDialog( + context: context, + builder: (context) => Dialog( + clipBehavior: Clip.antiAlias, + child: ConstrainedBox( + constraints: + const BoxConstraints(maxHeight: 600, maxWidth: 800), + child: SettingsPage( + isDialog: true, + view: view, + )))); diff --git a/app/lib/cubits/settings.dart b/app/lib/cubits/settings.dart index 2138ef9..9ea3db3 100644 --- a/app/lib/cubits/settings.dart +++ b/app/lib/cubits/settings.dart @@ -2,17 +2,18 @@ import 'package:dart_mappable/dart_mappable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:quokka/widgets/window.dart'; +import 'package:material_leap/material_leap.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; part 'settings.mapper.dart'; @MappableClass() -class QuokkaSettings with QuokkaSettingsMappable { +class QuokkaSettings with QuokkaSettingsMappable implements LeapSettings { final String localeTag; final ThemeMode theme; final String design; + @override final bool nativeTitleBar; const QuokkaSettings({ @@ -48,7 +49,8 @@ class QuokkaSettings with QuokkaSettingsMappable { } } -class SettingsCubit extends Cubit { +class SettingsCubit extends Cubit + with LeapSettingsStreamableMixin { SettingsCubit(SharedPreferences prefs) : super(QuokkaSettings.fromPrefs(prefs)); diff --git a/app/lib/models/deck.dart b/app/lib/models/definitions/deck.dart similarity index 100% rename from app/lib/models/deck.dart rename to app/lib/models/definitions/deck.dart diff --git a/app/lib/models/deck.mapper.dart b/app/lib/models/definitions/deck.mapper.dart similarity index 100% rename from app/lib/models/deck.mapper.dart rename to app/lib/models/definitions/deck.mapper.dart diff --git a/app/lib/models/meta.dart b/app/lib/models/definitions/meta.dart similarity index 100% rename from app/lib/models/meta.dart rename to app/lib/models/definitions/meta.dart diff --git a/app/lib/models/meta.mapper.dart b/app/lib/models/definitions/meta.mapper.dart similarity index 100% rename from app/lib/models/meta.mapper.dart rename to app/lib/models/definitions/meta.mapper.dart diff --git a/app/lib/models/object.dart b/app/lib/models/definitions/object.dart similarity index 100% rename from app/lib/models/object.dart rename to app/lib/models/definitions/object.dart diff --git a/app/lib/models/object.mapper.dart b/app/lib/models/definitions/object.mapper.dart similarity index 100% rename from app/lib/models/object.mapper.dart rename to app/lib/models/definitions/object.mapper.dart diff --git a/app/lib/models/pack.dart b/app/lib/models/definitions/pack.dart similarity index 93% rename from app/lib/models/pack.dart rename to app/lib/models/definitions/pack.dart index 029e124..b37a09b 100644 --- a/app/lib/models/pack.dart +++ b/app/lib/models/definitions/pack.dart @@ -3,9 +3,9 @@ import 'dart:convert'; import 'package:archive/archive.dart'; import 'package:flutter/services.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:quokka/models/deck.dart'; -import 'package:quokka/models/meta.dart'; -import 'package:quokka/models/object.dart'; +import 'package:quokka/models/definitions/deck.dart'; +import 'package:quokka/models/definitions/meta.dart'; +import 'package:quokka/models/definitions/object.dart'; const kPackMetadataPath = 'pack.json'; const kPackDecksPath = 'decks'; diff --git a/app/lib/models/table.dart b/app/lib/models/table.dart index a4e24b0..a7bc3c9 100644 --- a/app/lib/models/table.dart +++ b/app/lib/models/table.dart @@ -13,18 +13,52 @@ class GridLocation with GridLocationMappable { class GameTable with GameTableMappable { final Map cells; final Map boards; + final Map seats; + final Map players; GameTable({ this.cells = const {}, this.boards = const {}, + this.seats = const {}, + this.players = const {}, + }); +} + +@MappableClass() +class GamePlayer with GamePlayerMappable { + final String name; + final List? teams; + + GamePlayer({ + required this.name, + this.teams = const [], + }); +} + +@MappableClass() +class GameSeat with GameSeatMappable { + final int? color; + + GameSeat({ + this.color, }); } @MappableClass() class TableCell with TableCellMappable { final List objects; + final String? team; + final int reveal; + final int? teamReveal; + + TableCell({ + this.objects = const [], + this.team, + this.reveal = -1, + this.teamReveal, + }); - TableCell(this.objects); + int get teamRevealValue => teamReveal ?? reveal; } @MappableClass() diff --git a/app/lib/models/table.mapper.dart b/app/lib/models/table.mapper.dart index 5085bc1..0f33a35 100644 --- a/app/lib/models/table.mapper.dart +++ b/app/lib/models/table.mapper.dart @@ -135,11 +135,19 @@ class GameTableMapper extends ClassMapperBase { static Map _$boards(GameTable v) => v.boards; static const Field> _f$boards = Field('boards', _$boards, opt: true, def: const {}); + static Map _$seats(GameTable v) => v.seats; + static const Field> _f$seats = + Field('seats', _$seats, mode: FieldMode.member); + static Map _$players(GameTable v) => v.players; + static const Field> _f$players = + Field('players', _$players, mode: FieldMode.member); @override final MappableFields fields = const { #cells: _f$cells, #boards: _f$boards, + #seats: _f$seats, + #players: _f$players, }; static GameTable _instantiate(DecodingData data) { @@ -260,15 +268,31 @@ class TableCellMapper extends ClassMapperBase { static List _$objects(TableCell v) => v.objects; static const Field> _f$objects = - Field('objects', _$objects); + Field('objects', _$objects, opt: true, def: const []); + static String? _$team(TableCell v) => v.team; + static const Field _f$team = + Field('team', _$team, opt: true); + static int _$reveal(TableCell v) => v.reveal; + static const Field _f$reveal = + Field('reveal', _$reveal, opt: true, def: -1); + static int? _$teamReveal(TableCell v) => v.teamReveal; + static const Field _f$teamReveal = + Field('teamReveal', _$teamReveal, opt: true); @override final MappableFields fields = const { #objects: _f$objects, + #team: _f$team, + #reveal: _f$reveal, + #teamReveal: _f$teamReveal, }; static TableCell _instantiate(DecodingData data) { - return TableCell(data.dec(_f$objects)); + return TableCell( + objects: data.dec(_f$objects), + team: data.dec(_f$team), + reveal: data.dec(_f$reveal), + teamReveal: data.dec(_f$teamReveal)); } @override @@ -323,7 +347,8 @@ abstract class TableCellCopyWith<$R, $In extends TableCell, $Out> implements ClassCopyWith<$R, $In, $Out> { ListCopyWith<$R, GameObject, GameObjectCopyWith<$R, GameObject, GameObject>> get objects; - $R call({List? objects}); + $R call( + {List? objects, String? team, int? reveal, int? teamReveal}); TableCellCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -340,11 +365,23 @@ class _TableCellCopyWithImpl<$R, $Out> get objects => ListCopyWith($value.objects, (v, t) => v.copyWith.$chain(t), (v) => call(objects: v)); @override - $R call({List? objects}) => - $apply(FieldCopyWithData({if (objects != null) #objects: objects})); + $R call( + {List? objects, + Object? team = $none, + int? reveal, + Object? teamReveal = $none}) => + $apply(FieldCopyWithData({ + if (objects != null) #objects: objects, + if (team != $none) #team: team, + if (reveal != null) #reveal: reveal, + if (teamReveal != $none) #teamReveal: teamReveal + })); @override - TableCell $make(CopyWithData data) => - TableCell(data.get(#objects, or: $value.objects)); + TableCell $make(CopyWithData data) => TableCell( + objects: data.get(#objects, or: $value.objects), + team: data.get(#team, or: $value.team), + reveal: data.get(#reveal, or: $value.reveal), + teamReveal: data.get(#teamReveal, or: $value.teamReveal)); @override TableCellCopyWith<$R2, TableCell, $Out2> $chain<$R2, $Out2>( @@ -683,3 +720,218 @@ class _GameBoardCopyWithImpl<$R, $Out> Then<$Out2, $R2> t) => _GameBoardCopyWithImpl($value, $cast, t); } + +class GamePlayerMapper extends ClassMapperBase { + GamePlayerMapper._(); + + static GamePlayerMapper? _instance; + static GamePlayerMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = GamePlayerMapper._()); + } + return _instance!; + } + + @override + final String id = 'GamePlayer'; + + static String _$name(GamePlayer v) => v.name; + static const Field _f$name = Field('name', _$name); + static List? _$teams(GamePlayer v) => v.teams; + static const Field> _f$teams = + Field('teams', _$teams, opt: true, def: const []); + + @override + final MappableFields fields = const { + #name: _f$name, + #teams: _f$teams, + }; + + static GamePlayer _instantiate(DecodingData data) { + return GamePlayer(name: data.dec(_f$name), teams: data.dec(_f$teams)); + } + + @override + final Function instantiate = _instantiate; + + static GamePlayer fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static GamePlayer fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin GamePlayerMappable { + String toJson() { + return GamePlayerMapper.ensureInitialized() + .encodeJson(this as GamePlayer); + } + + Map toMap() { + return GamePlayerMapper.ensureInitialized() + .encodeMap(this as GamePlayer); + } + + GamePlayerCopyWith get copyWith => + _GamePlayerCopyWithImpl(this as GamePlayer, $identity, $identity); + @override + String toString() { + return GamePlayerMapper.ensureInitialized() + .stringifyValue(this as GamePlayer); + } + + @override + bool operator ==(Object other) { + return GamePlayerMapper.ensureInitialized() + .equalsValue(this as GamePlayer, other); + } + + @override + int get hashCode { + return GamePlayerMapper.ensureInitialized().hashValue(this as GamePlayer); + } +} + +extension GamePlayerValueCopy<$R, $Out> + on ObjectCopyWith<$R, GamePlayer, $Out> { + GamePlayerCopyWith<$R, GamePlayer, $Out> get $asGamePlayer => + $base.as((v, t, t2) => _GamePlayerCopyWithImpl(v, t, t2)); +} + +abstract class GamePlayerCopyWith<$R, $In extends GamePlayer, $Out> + implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? get teams; + $R call({String? name, List? teams}); + GamePlayerCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _GamePlayerCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, GamePlayer, $Out> + implements GamePlayerCopyWith<$R, GamePlayer, $Out> { + _GamePlayerCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + GamePlayerMapper.ensureInitialized(); + @override + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? get teams => + $value.teams != null + ? ListCopyWith($value.teams!, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(teams: v)) + : null; + @override + $R call({String? name, Object? teams = $none}) => $apply(FieldCopyWithData( + {if (name != null) #name: name, if (teams != $none) #teams: teams})); + @override + GamePlayer $make(CopyWithData data) => GamePlayer( + name: data.get(#name, or: $value.name), + teams: data.get(#teams, or: $value.teams)); + + @override + GamePlayerCopyWith<$R2, GamePlayer, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _GamePlayerCopyWithImpl($value, $cast, t); +} + +class GameSeatMapper extends ClassMapperBase { + GameSeatMapper._(); + + static GameSeatMapper? _instance; + static GameSeatMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = GameSeatMapper._()); + } + return _instance!; + } + + @override + final String id = 'GameSeat'; + + static int? _$color(GameSeat v) => v.color; + static const Field _f$color = + Field('color', _$color, opt: true); + + @override + final MappableFields fields = const { + #color: _f$color, + }; + + static GameSeat _instantiate(DecodingData data) { + return GameSeat(color: data.dec(_f$color)); + } + + @override + final Function instantiate = _instantiate; + + static GameSeat fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static GameSeat fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin GameSeatMappable { + String toJson() { + return GameSeatMapper.ensureInitialized() + .encodeJson(this as GameSeat); + } + + Map toMap() { + return GameSeatMapper.ensureInitialized() + .encodeMap(this as GameSeat); + } + + GameSeatCopyWith get copyWith => + _GameSeatCopyWithImpl(this as GameSeat, $identity, $identity); + @override + String toString() { + return GameSeatMapper.ensureInitialized().stringifyValue(this as GameSeat); + } + + @override + bool operator ==(Object other) { + return GameSeatMapper.ensureInitialized() + .equalsValue(this as GameSeat, other); + } + + @override + int get hashCode { + return GameSeatMapper.ensureInitialized().hashValue(this as GameSeat); + } +} + +extension GameSeatValueCopy<$R, $Out> on ObjectCopyWith<$R, GameSeat, $Out> { + GameSeatCopyWith<$R, GameSeat, $Out> get $asGameSeat => + $base.as((v, t, t2) => _GameSeatCopyWithImpl(v, t, t2)); +} + +abstract class GameSeatCopyWith<$R, $In extends GameSeat, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call({int? color}); + GameSeatCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _GameSeatCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, GameSeat, $Out> + implements GameSeatCopyWith<$R, GameSeat, $Out> { + _GameSeatCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + GameSeatMapper.ensureInitialized(); + @override + $R call({Object? color = $none}) => + $apply(FieldCopyWithData({if (color != $none) #color: color})); + @override + GameSeat $make(CopyWithData data) => + GameSeat(color: data.get(#color, or: $value.color)); + + @override + GameSeatCopyWith<$R2, GameSeat, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _GameSeatCopyWithImpl($value, $cast, t); +} diff --git a/app/lib/pages/board/page.dart b/app/lib/pages/board/page.dart index 0f7807a..cefaaf5 100644 --- a/app/lib/pages/board/page.dart +++ b/app/lib/pages/board/page.dart @@ -3,8 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; +import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:quokka/api/settings.dart'; +import 'package:quokka/cubits/settings.dart'; import 'package:quokka/game/world/game.dart'; import 'package:quokka/main.dart'; import 'package:quokka/pages/board/connect.dart'; @@ -12,7 +14,6 @@ import 'package:quokka/pages/board/create.dart'; import 'package:quokka/pages/board/servers.dart'; import 'package:quokka/services/messenger.dart'; import 'package:quokka/services/network.dart'; -import 'package:quokka/widgets/window.dart'; class BoardPage extends StatelessWidget { final GlobalKey _scaffoldKey = GlobalKey(); @@ -24,7 +25,7 @@ class BoardPage extends StatelessWidget { final networkingService = context.read(); return Scaffold( key: _scaffoldKey, - appBar: const WindowTitleBar( + appBar: const WindowTitleBar( onlyShowOnDesktop: true, title: Text(applicationName), ), diff --git a/app/lib/pages/home/page.dart b/app/lib/pages/home/page.dart index 05e35cf..d51ecfb 100644 --- a/app/lib/pages/home/page.dart +++ b/app/lib/pages/home/page.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; +import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:quokka/cubits/settings.dart'; import 'package:quokka/main.dart'; -import 'package:quokka/models/server.dart'; -import 'package:quokka/services/network.dart'; -import 'package:quokka/widgets/window.dart'; +import 'package:quokka/pages/packs/dialog.dart'; import '../../api/settings.dart'; @@ -16,100 +14,94 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: WindowTitleBar( - title: const Text(shortApplicationName), - actions: [ - IconButton( - icon: const PhosphorIcon(PhosphorIconsLight.gear), - onPressed: () => openSettings(context), - ) - ], + appBar: const WindowTitleBar( + title: Text(shortApplicationName), ), - body: SingleChildScrollView( - child: Align( - child: Container( - padding: const EdgeInsets.all(16), - constraints: const BoxConstraints(maxWidth: 600), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - applicationName, - style: Theme.of(context).textTheme.displayMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - Wrap( - alignment: WrapAlignment.center, - children: [ - ( - AppLocalizations.of(context).board, - PhosphorIconsLight.gridNine, - () => context.push('/board'), + body: LayoutBuilder(builder: (context, constraints) { + return SingleChildScrollView( + child: Align( + child: Container( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + padding: + const EdgeInsets.symmetric(vertical: 32.0, horizontal: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + applicationName, + style: Theme.of(context).textTheme.displayMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + Center( + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(32), ), - ] - .map((e) => SizedBox( - height: 200, - width: 200, - child: Card( - clipBehavior: Clip.antiAlias, - margin: const EdgeInsets.all(8), - child: InkWell( - onTap: e.$3, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon(e.$2, size: 48), - const SizedBox(height: 16), - Text( - e.$1, - style: Theme.of(context) - .textTheme - .headlineSmall, - textAlign: TextAlign.center, - ), - ], - ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: OverflowBar( + overflowAlignment: OverflowBarAlignment.center, + children: [ + ( + 'Play', + PhosphorIconsLight.play, + () => context.push('/board'), + ), + ( + 'Connect', + PhosphorIconsLight.plugsConnected, + () => context.push('/board'), + ), + ( + 'Packs', + PhosphorIconsLight.package, + () => showDialog( + context: context, + builder: (context) => const PacksDialog(), ), - )), - )) - .toList()), - StreamBuilder>( - stream: context.read().fetchServers(), - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center( - child: Text(snapshot.error.toString()), - ); - } - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: CircularProgressIndicator(), - ); - } - final servers = snapshot.data ?? []; - return ListView.builder( - itemCount: servers.length, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - final server = servers[index]; - return ListTile( - title: Text(server.property.name), - subtitle: Text(server.address), - ); - }, - ); - }, - ), - ], + ), + ( + 'Options', + PhosphorIconsLight.gear, + () => openSettings(context), + ), + ] + .map((e) => Container( + padding: const EdgeInsets.all(8.0), + constraints: + const BoxConstraints(minWidth: 250), + child: OutlinedButton.icon( + icon: Icon(e.$2), + style: ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + vertical: 16, + horizontal: 32)), + ), + label: Text( + e.$1, + style: Theme.of(context) + .textTheme + .headlineSmall, + textAlign: TextAlign.center, + ), + onPressed: e.$3, + ), + )) + .toList()), + ), + ), + ), + ], + ), ), ), - ), - ), + ); + }), ); } } diff --git a/app/lib/pages/packs/dialog.dart b/app/lib/pages/packs/dialog.dart new file mode 100644 index 0000000..6edbb99 --- /dev/null +++ b/app/lib/pages/packs/dialog.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:material_leap/material_leap.dart'; + +class PacksDialog extends StatelessWidget { + const PacksDialog({super.key}); + + @override + Widget build(BuildContext context) { + return ResponsiveAlertDialog( + title: Text(AppLocalizations.of(context).packs), + content: const Column( + children: [], + ), + ); + } +} diff --git a/app/lib/pages/settings/general.dart b/app/lib/pages/settings/general.dart index f084301..6c3236d 100644 --- a/app/lib/pages/settings/general.dart +++ b/app/lib/pages/settings/general.dart @@ -2,15 +2,15 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:material_leap/material_leap.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:quokka/api/open.dart'; +import 'package:quokka/cubits/settings.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:http/http.dart' as http; -import '../../widgets/window.dart'; - @immutable class Meta { final String stableVersion, @@ -59,7 +59,7 @@ class GeneralSettingsPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: inView ? Colors.transparent : null, - appBar: WindowTitleBar( + appBar: WindowTitleBar( title: Text(AppLocalizations.of(context).general), backgroundColor: inView ? Colors.transparent : null, inView: inView, diff --git a/app/lib/pages/settings/packs.dart b/app/lib/pages/settings/packs.dart deleted file mode 100644 index f5ea51c..0000000 --- a/app/lib/pages/settings/packs.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:quokka/cubits/settings.dart'; -import 'package:quokka/widgets/window.dart'; - -class PacksSettingsPage extends StatelessWidget { - final bool inView; - - const PacksSettingsPage({ - super.key, - this.inView = false, - }); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: inView ? Colors.transparent : null, - appBar: WindowTitleBar( - inView: inView, - backgroundColor: inView ? Colors.transparent : null, - title: Text(AppLocalizations.of(context).packs), - ), - body: BlocBuilder( - builder: (context, state) { - return ListView(children: const []); - }, - ), - ); - } -} diff --git a/app/lib/pages/settings/page.dart b/app/lib/pages/settings/page.dart index 0b57463..410ff3a 100644 --- a/app/lib/pages/settings/page.dart +++ b/app/lib/pages/settings/page.dart @@ -5,7 +5,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; -import 'package:quokka/pages/settings/packs.dart'; import 'package:url_launcher/url_launcher.dart'; import 'general.dart'; @@ -13,8 +12,7 @@ import 'personalization.dart'; enum SettingsView { general, - personalization, - packs; + personalization; bool get isEnabled => true; @@ -22,20 +20,24 @@ enum SettingsView { SettingsView.general => AppLocalizations.of(context).general, SettingsView.personalization => AppLocalizations.of(context).personalization, - SettingsView.packs => AppLocalizations.of(context).packs, }; IconGetter get icon => switch (this) { SettingsView.general => PhosphorIcons.gear, SettingsView.personalization => PhosphorIcons.monitor, - SettingsView.packs => PhosphorIcons.package, }; String get path => '/settings/$name'; } class SettingsPage extends StatefulWidget { final bool isDialog; - const SettingsPage({super.key, this.isDialog = false}); + final SettingsView view; + + const SettingsPage({ + super.key, + this.isDialog = false, + this.view = SettingsView.general, + }); @override State createState() => _SettingsPageState(); @@ -45,6 +47,13 @@ class _SettingsPageState extends State { SettingsView _view = SettingsView.general; final ScrollController _scrollController = ScrollController(); + @override + void initState() { + super.initState(); + + _view = widget.view; + } + @override void dispose() { super.dispose(); @@ -135,7 +144,6 @@ class _SettingsPageState extends State { SettingsView.general => const GeneralSettingsPage(inView: true), SettingsView.personalization => const PersonalizationSettingsPage(inView: true), - SettingsView.packs => const PacksSettingsPage(inView: true), }; return Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox(width: 300, child: navigation), diff --git a/app/lib/pages/settings/personalization.dart b/app/lib/pages/settings/personalization.dart index 0846890..be6f324 100644 --- a/app/lib/pages/settings/personalization.dart +++ b/app/lib/pages/settings/personalization.dart @@ -11,7 +11,6 @@ import 'package:phosphor_flutter/phosphor_flutter.dart'; import '../../cubits/settings.dart'; import '../../main.dart'; import '../../theme.dart'; -import '../../widgets/window.dart'; class PersonalizationSettingsPage extends StatelessWidget { final bool inView; @@ -32,7 +31,7 @@ class PersonalizationSettingsPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: inView ? Colors.transparent : null, - appBar: WindowTitleBar( + appBar: WindowTitleBar( inView: inView, backgroundColor: inView ? Colors.transparent : null, title: Text(AppLocalizations.of(context).personalization), diff --git a/app/lib/services/packs.dart b/app/lib/services/packs.dart index f195a04..2ad8a2f 100644 --- a/app/lib/services/packs.dart +++ b/app/lib/services/packs.dart @@ -1,5 +1,5 @@ import 'package:collection/collection.dart'; -import 'package:quokka/models/pack.dart'; +import 'package:quokka/models/definitions/pack.dart'; class PacksService { final PackData? _corePack; diff --git a/app/lib/widgets/window.dart b/app/lib/widgets/window.dart deleted file mode 100644 index d33c7bd..0000000 --- a/app/lib/widgets/window.dart +++ /dev/null @@ -1,204 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:phosphor_flutter/phosphor_flutter.dart'; -import 'package:window_manager/window_manager.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import '../cubits/settings.dart'; - -final isWindow = - !kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS); - -class WindowTitleBar extends StatelessWidget implements PreferredSizeWidget { - final List actions; - final Widget? title; - final Widget? leading; - final PreferredSizeWidget? bottom; - final bool onlyShowOnDesktop; - final bool inView; - final Color? backgroundColor; - final double height; - final double? leadingWidth; - - const WindowTitleBar({ - super.key, - this.title, - this.leading, - this.bottom, - this.leadingWidth, - this.backgroundColor, - this.actions = const [], - this.onlyShowOnDesktop = false, - this.inView = false, - this.height = 70, - }); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => - previous.nativeTitleBar != current.nativeTitleBar, - builder: (context, settings) { - final isDesktop = isWindow && !kIsWeb; - if (onlyShowOnDesktop && !isDesktop) return const SizedBox.shrink(); - final appBar = AppBar( - title: title, - backgroundColor: backgroundColor, - automaticallyImplyLeading: !inView, - leading: leading, - bottom: bottom, - leadingWidth: leadingWidth, - toolbarHeight: height, - actions: [ - ...actions, - if (isDesktop && !inView) - WindowButtons( - divider: actions.isNotEmpty, - ), - ], - ); - if (isDesktop && !settings.nativeTitleBar) { - return DragToMoveArea( - child: appBar, - ); - } - return appBar; - }); - } - - @override - Size get preferredSize => Size.fromHeight(height); -} - -class WindowButtons extends StatefulWidget { - final bool divider; - - const WindowButtons({super.key, this.divider = true}); - - @override - State createState() => _WindowButtonsState(); -} - -class _WindowButtonsState extends State with WindowListener { - bool maximized = false, alwaysOnTop = false, fullScreen = false; - - @override - void initState() { - if (!kIsWeb && isWindow) { - windowManager.addListener(this); - } - super.initState(); - updateStates(); - } - - @override - void dispose() { - windowManager.removeListener(this); - super.dispose(); - } - - Future updateStates() async { - final nextMaximized = await windowManager.isMaximized(); - final nextAlwaysOnTop = await windowManager.isAlwaysOnTop(); - final nextFullScreen = await windowManager.isFullScreen(); - if (mounted) { - setState(() { - maximized = nextMaximized; - alwaysOnTop = nextAlwaysOnTop; - fullScreen = nextFullScreen; - }); - } - } - - @override - void onWindowUnmaximize() { - setState(() => maximized = false); - } - - @override - void onWindowMaximize() { - setState(() => maximized = true); - } - - @override - void onWindowEnterFullScreen() { - setState(() => fullScreen = true); - } - - @override - void onWindowLeaveFullScreen() { - setState(() => fullScreen = false); - } - - @override - @override - Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => - previous.nativeTitleBar != current.nativeTitleBar, - builder: (context, settings) { - if (!kIsWeb && isWindow && !settings.nativeTitleBar) { - return LayoutBuilder( - builder: (context, constraints) => Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (widget.divider) const VerticalDivider(), - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 42), - child: Builder(builder: (context) { - return Row( - children: [ - if (!fullScreen) ...[ - IconButton( - icon: const PhosphorIcon( - PhosphorIconsLight.minus), - tooltip: - AppLocalizations.of(context).minimize, - splashRadius: 20, - onPressed: () => windowManager.minimize(), - ), - IconButton( - tooltip: maximized - ? AppLocalizations.of(context).restore - : AppLocalizations.of(context).maximize, - icon: PhosphorIcon( - PhosphorIconsLight.square, - size: maximized ? 14 : 20, - color: Theme.of(context).iconTheme.color, - ), - onPressed: () async => - await windowManager.isMaximized() - ? windowManager.unmaximize() - : windowManager.maximize(), - ), - IconButton( - icon: const PhosphorIcon( - PhosphorIconsLight.x), - tooltip: AppLocalizations.of(context).close, - color: Colors.red, - splashRadius: 20, - onPressed: () => windowManager.close(), - ) - ] - ] - .map((e) => - AspectRatio(aspectRatio: 1, child: e)) - .toList(), - ); - })), - ), - ), - ], - ), - ); - } - return Container(); - }); - } -} diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc index 965f5cf..8dd783f 100644 --- a/app/linux/flutter/generated_plugin_registrant.cc +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -15,6 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_color_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index dacf00d..05bb34a 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color + file_selector_linux screen_retriever url_launcher_linux window_manager diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index 9de84af..b4dfc2d 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,9 +7,11 @@ import Foundation import device_info_plus import dynamic_color +import file_selector_macos import package_info_plus import path_provider_foundation import screen_retriever +import share_plus import shared_preferences_foundation import url_launcher_macos import window_manager @@ -17,9 +19,11 @@ import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) diff --git a/app/pubspec.lock b/app/pubspec.lock index 9306d12..1eb4c2c 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -185,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -274,6 +282,70 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector: + dependency: transitive + description: + name: file_selector + sha256: "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + file_selector_android: + dependency: transitive + description: + name: file_selector_android + sha256: "8bcc3af859e9d47fab9c7dc315537406511a894ab578e198bd8f9ed745ea5a01" + url: "https://pub.dev" + source: hosted + version: "0.5.1+2" + file_selector_ios: + dependency: transitive + description: + name: file_selector_ios + sha256: "38ebf91ecbcfa89a9639a0854ccaed8ab370c75678938eebca7d34184296f0bb" + url: "https://pub.dev" + source: hosted + version: "0.5.3" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_web: + dependency: transitive + description: + name: file_selector_web + sha256: "619e431b224711a3869e30dbd7d516f5f5a4f04b265013a50912f39e1abc88c8" + url: "https://pub.dev" + source: hosted + version: "0.9.4+1" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -533,6 +605,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + lw_sysapi: + dependency: "direct main" + description: + path: "packages/lw_sysapi" + ref: "622bb77e9309c72f0dbd5e5b086dd56272193e9c" + resolved-ref: "622bb77e9309c72f0dbd5e5b086dd56272193e9c" + url: "https://github.com/LinwoodDev/dart_pkgs.git" + source: git + version: "0.0.1" matcher: dependency: transitive description: @@ -553,8 +634,8 @@ packages: dependency: "direct main" description: path: "packages/material_leap" - ref: ff93bce26d1f8d5160cbfe66b548cf9286d0576d - resolved-ref: ff93bce26d1f8d5160cbfe66b548cf9286d0576d + ref: "06057f70ab2fd5f6a001821c326d14a1f06eff08" + resolved-ref: "06057f70ab2fd5f6a001821c326d14a1f06eff08" url: "https://github.com/LinwoodDev/dart_pkgs.git" source: git version: "0.0.1" @@ -792,6 +873,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.9" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + url: "https://pub.dev" + source: hosted + version: "4.0.0" shared_preferences: dependency: "direct main" description: @@ -893,6 +990,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -1045,6 +1150,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + uuid: + dependency: transitive + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" vector_graphics: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index cf569dc..b74106c 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -34,8 +34,13 @@ dependencies: material_leap: git: url: https://github.com/LinwoodDev/dart_pkgs.git - ref: ff93bce26d1f8d5160cbfe66b548cf9286d0576d + ref: 06057f70ab2fd5f6a001821c326d14a1f06eff08 path: packages/material_leap + lw_sysapi: + git: + url: https://github.com/LinwoodDev/dart_pkgs.git + ref: 622bb77e9309c72f0dbd5e5b086dd56272193e9c + path: packages/lw_sysapi flex_color_scheme: ^7.2.0 flutter_svg: ^2.0.10+1 window_manager: ^0.3.8 diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc index eb80a8b..ca4abcd 100644 --- a/app/windows/flutter/generated_plugin_registrant.cc +++ b/app/windows/flutter/generated_plugin_registrant.cc @@ -7,15 +7,21 @@ #include "generated_plugin_registrant.h" #include +#include #include +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index 7ae8880..203bf8e 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -4,7 +4,9 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color + file_selector_windows screen_retriever + share_plus url_launcher_windows window_manager )