Skip to content

Commit

Permalink
Integrate lw_file_system in packs
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeDoctorDE committed Jun 9, 2024
1 parent 7d20bc0 commit 91afa44
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 34 deletions.
15 changes: 15 additions & 0 deletions app/lib/api/storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'dart:io';

import 'package:path_provider/path_provider.dart';

const quokkaSubDirectory = '/Linwood/Quokka';

Future<String> getQuokkaDirectory() async {
String? path;
if (Platform.isAndroid) {
path ??= (await getExternalStorageDirectory())?.path;
}
path ??= (await getApplicationDocumentsDirectory()).path;
path += quokkaSubDirectory;
return path;
}
4 changes: 3 additions & 1 deletion app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,7 @@
"configuration": "Configuration",
"background": "Background",
"search": "Search",
"onlyFavorites": "Only favorites"
"onlyFavorites": "Only favorites",
"loading": "Loading",
"install": "Install"
}
10 changes: 8 additions & 2 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:quokka/pages/home/page.dart';
import 'package:quokka/pages/settings/general.dart';
import 'package:quokka/pages/settings/personalization.dart';
import 'package:quokka/services/network.dart';
import 'package:quokka/services/packs.dart';
import 'package:quokka/theme.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
Expand Down Expand Up @@ -47,8 +48,13 @@ Future<void> main(List<String> args) async {
BlocProvider.value(
value: WindowCubit(fullScreen: await isFullScreen())),
],
child: RepositoryProvider(
create: (context) => NetworkingService(settingsCubit),
child: MultiRepositoryProvider(
providers: [
RepositoryProvider(
create: (context) => NetworkingService(settingsCubit),
),
RepositoryProvider(create: (context) => PacksService()),
],
child: QuokkaApp(),
),
),
Expand Down
16 changes: 8 additions & 8 deletions app/lib/models/definitions/meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ part 'meta.mapper.dart';

@MappableClass()
class PackMetadata with PackMetadataMappable {
final String? name;
final String? description;
final String? author;
final String? version;
final String name;
final String description;
final String author;
final String version;

PackMetadata({
this.name,
this.description,
this.author,
this.version,
this.name = '',
this.description = '',
this.author = '',
this.version = '',
});
}
2 changes: 2 additions & 0 deletions app/lib/models/definitions/pack.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,6 @@ class PackData {

String getTranslationOrKey(String path, String key) =>
getTranslation(path, key) ?? key;

Uint8List export() => Uint8List.fromList(ZipEncoder().encode(archive) ?? []);
}
175 changes: 165 additions & 10 deletions app/lib/pages/home/packs.dart
Original file line number Diff line number Diff line change
@@ -1,7 +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:material_leap/material_leap.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart';
import 'package:quokka/models/definitions/pack.dart';
import 'package:quokka/services/packs.dart';
import 'package:quokka/widgets/search.dart';

class PacksDialog extends StatefulWidget {
Expand All @@ -14,12 +17,26 @@ class PacksDialog extends StatefulWidget {
class _PacksDialogState extends State<PacksDialog>
with TickerProviderStateMixin {
bool _gridView = false;
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
);
late final TabController _tabController;
Future<Map<String, PackData>>? _packsFuture;
(PackData, String, bool)? _selectedPack;

@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_controller.addStatusListener((status) {
if (status == AnimationStatus.dismissed) {
setState(() {
_selectedPack = null;
});
}
});
_packsFuture = context.read<PacksService>().getPacks();
}

@override
Expand All @@ -28,8 +45,19 @@ class _PacksDialogState extends State<PacksDialog>
super.dispose();
}

void _selectPack(PackData pack, String id, bool installed) {
_controller.forward();
setState(() {
_selectedPack = (pack, id, installed);
});
}

void _deselectPack() => _controller.reverse();

@override
Widget build(BuildContext context) {
final currentSize = MediaQuery.sizeOf(context).width;
final isMobile = currentSize < LeapBreakpoints.medium;
return ResponsiveAlertDialog(
title: Text(AppLocalizations.of(context).packs),
constraints: const BoxConstraints(
Expand All @@ -52,17 +80,69 @@ class _PacksDialogState extends State<PacksDialog>
),
],
),
const SizedBox(height: 8),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
Center(
child: Text(AppLocalizations.of(context).comingSoon),
),
Center(
child: Text(AppLocalizations.of(context).comingSoon),
),
],
child: FutureBuilder<Map<String, PackData>>(
future: _packsFuture,
builder: (context, snapshot) {
final packs = snapshot.data?.entries.toList() ?? [];
final view = TabBarView(
controller: _tabController,
children: [
ListView.builder(
itemCount: packs.length,
itemBuilder: (context, index) {
final key = packs[index].key;
final pack = packs[index].value;
final metadata = pack.getMetadata();
return ListTile(
title: Text(metadata?.name ??
AppLocalizations.of(context).unnamed),
selected: _selectedPack?.$1 == pack,
onTap: () => _selectPack(pack, key, true),
);
},
),
Center(
child: Text(AppLocalizations.of(context).comingSoon),
),
],
);
if (isMobile) {
return view;
}
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: view,
),
SizeTransition(
sizeFactor: CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
),
axis: Axis.horizontal,
child: SizedBox(
width: 300,
child: Card(
child: _PacksDetailsView(
pack: _selectedPack?.$1,
onClose: _deselectPack,
onInstall: _selectedPack?.$3 ?? false
? null
: _deselectPack,
onRemove: (_selectedPack?.$3 ?? false) &&
(_selectedPack?.$2.isNotEmpty ?? true)
? _deselectPack
: null,
),
),
),
),
],
);
},
),
),
],
Expand All @@ -87,3 +167,78 @@ class _PacksDialogState extends State<PacksDialog>
);
}
}

class _PacksDetailsView extends StatelessWidget {
final PackData? pack;
final VoidCallback? onClose, onInstall, onRemove;

const _PacksDetailsView({
this.pack,
this.onClose,
this.onInstall,
this.onRemove,
});

@override
Widget build(BuildContext context) {
final metadata = pack?.getMetadata();
if (metadata == null) {
return const SizedBox();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Header(
title: Text(metadata.name),
actions: [
if (onClose != null) ...[
IconButton.outlined(
icon: const Icon(PhosphorIconsLight.x),
onPressed: onClose,
),
],
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
shrinkWrap: true,
children: [
Text(metadata.description),
],
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (onInstall != null) ...[
SizedBox(
height: 42,
child: FilledButton.tonalIcon(
onPressed: onInstall,
label: Text(AppLocalizations.of(context).install),
icon: const Icon(PhosphorIconsLight.download),
),
),
],
if (onRemove != null) ...[
SizedBox(
height: 42,
child: FilledButton.tonalIcon(
onPressed: onRemove,
label: Text(AppLocalizations.of(context).remove),
icon: const Icon(PhosphorIconsLight.trash),
),
),
],
],
),
),
],
);
}
}
9 changes: 5 additions & 4 deletions app/lib/pages/home/page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_leap/material_leap.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart';
import 'package:quokka/cubits/settings.dart';
Expand Down Expand Up @@ -50,30 +51,30 @@ class HomePage extends StatelessWidget {
overflowSpacing: 8,
children: [
(
'Play',
AppLocalizations.of(context).play,
PhosphorIconsLight.play,
() => showDialog(
context: context,
builder: (context) => const PlayDialog()),
),
(
'Connect',
AppLocalizations.of(context).connect,
PhosphorIconsLight.plugsConnected,
() => showDialog(
context: context,
builder: (context) =>
const ConnectDialog()),
),
(
'Packs',
AppLocalizations.of(context).packs,
PhosphorIconsLight.package,
() => showDialog(
context: context,
builder: (context) => const PacksDialog(),
),
),
(
'Options',
AppLocalizations.of(context).settings,
PhosphorIconsLight.gear,
() => openSettings(context),
),
Expand Down
34 changes: 30 additions & 4 deletions app/lib/services/packs.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import 'dart:io';

import 'package:collection/collection.dart';
import 'package:lw_file_system/lw_file_system.dart';
import 'package:path_provider/path_provider.dart';
import 'package:quokka/api/storage.dart';
import 'package:quokka/models/definitions/pack.dart';

class PacksService {
final PackData? _corePack;
final TypedKeyFileSystem<PackData> _fileSystem;

PacksService({
PackData? corePack,
}) : _corePack = corePack;
}) : _corePack = corePack,
_fileSystem = TypedKeyFileSystem.build(
FileSystemConfig(
passwordStorage: SecureStoragePasswordStorage(),
storeName: 'packs',
getDirectory: (storage) async =>
'${await getQuokkaDirectory()}/Packs',
database: 'quokka.db',
),
onDecode: (data) => PackData.fromData(data),
onEncode: (data) => data.export(),
);

Future<PackData?> fetchCorePack() async =>
_corePack ?? await PackData.getCorePack();

Future<List<PackData>> getPacks() async => [
await fetchCorePack(),
].whereNotNull().toList();
Future<Map<String, PackData>> getPacks() async {
final corePack = await fetchCorePack();
await _fileSystem.initialize();
return {
...Map.fromEntries((await _fileSystem.getFiles())
.map((file) => file.data == null
? null
: MapEntry(file.fileNameWithoutExtension, file.data!))
.whereNotNull()),
if (corePack != null) '': corePack,
};
}
}
Loading

0 comments on commit 91afa44

Please sign in to comment.