completed ui for current user apps

This commit is contained in:
Andrew 2022-09-09 02:31:00 +07:00
parent 24f458b06f
commit 41b7d22fd9
14 changed files with 718 additions and 37 deletions

View file

@ -1,11 +1,56 @@
import 'dart:convert';
import 'dart:io';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../models/scoop_app_model.dart';
import '../utils/scoop_utils.dart';
part 'scoop_list_event.dart'; part 'scoop_list_event.dart';
part 'scoop_list_state.dart'; part 'scoop_list_state.dart';
class ScoopListBloc extends Bloc<ScoopListEvent, ScoopListState> { class ScoopListBloc extends Bloc<ScoopListEvent, ScoopListState> {
ScoopListBloc() : super(ScoopListInitial()) { ScoopListBloc() : super(ScoopListInitial()) {
on<ScoopListEvent>((event, emit) {}); on<ScoopLocate>(
(event, emit) {
if (scoopInstalled()) {
add(ScoopListRequested());
} else {
emit(ScoopNotFound());
}
},
transformer: droppable(),
);
on<ScoopUpdateRequested>(
(event, emit) async {
try {
final process =
await Process.start('scoop', ["update"], runInShell: true);
await process.stdout.transform(utf8.decoder).forEach((e) {
emit(ScoopUpdateInProgress(e));
});
int exitCode = await process.exitCode;
if (exitCode != 0) {
throw Exception(
"Command `scoop update` failed with code $exitCode");
}
add(ScoopListRequested());
} catch (e) {
emit(ScoopUpdateError(e.toString()));
}
},
transformer: droppable(),
);
on<ScoopListRequested>(
(event, emit) async {
emit(ScoopListLoading());
final apps = await getInstalledScoopApps();
emit(ScoopLocalAppList(apps));
},
transformer: droppable(),
);
} }
} }

View file

@ -1,4 +1,24 @@
part of 'scoop_list_bloc.dart'; part of 'scoop_list_bloc.dart';
@immutable @immutable
abstract class ScoopListEvent {} abstract class ScoopListEvent extends Equatable {
const ScoopListEvent();
@override
List<Object> get props => [];
}
class ScoopLocate extends ScoopListEvent {}
class ScoopUpdateRequested extends ScoopListEvent {}
class ScoopListRequested extends ScoopListEvent {}
class ScoopSearchRequested extends ScoopListEvent {
final String query;
const ScoopSearchRequested(this.query);
@override
List<Object> get props => [query];
}

View file

@ -1,6 +1,42 @@
part of 'scoop_list_bloc.dart'; part of 'scoop_list_bloc.dart';
@immutable @immutable
abstract class ScoopListState {} abstract class ScoopListState extends Equatable {
const ScoopListState();
@override
List<Object> get props => [];
}
class ScoopListInitial extends ScoopListState {} class ScoopListInitial extends ScoopListState {}
class ScoopNotFound extends ScoopListState {}
class ScoopListLoading extends ScoopListState {}
class ScoopLocalAppList extends ScoopListState {
final List<ScoopAppModel> apps;
const ScoopLocalAppList(this.apps);
@override
List<Object> get props => [apps];
}
class ScoopUpdateInProgress extends ScoopListState {
final String message;
const ScoopUpdateInProgress(this.message);
@override
List<Object> get props => [message];
}
class ScoopUpdateError extends ScoopListState {
final String message;
const ScoopUpdateError(this.message);
@override
List<Object> get props => [message];
}

View file

@ -1,6 +1,7 @@
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:ladle/bloc/scoop_list_bloc.dart'; import 'package:ladle/bloc/scoop_list_bloc.dart';
import 'package:system_theme/system_theme.dart'; import 'package:system_theme/system_theme.dart';
@ -12,8 +13,10 @@ void main() async {
await SystemTheme.accentColor.load(); await SystemTheme.accentColor.load();
Intl.defaultLocale = 'en_US';
doWhenWindowReady(() { doWhenWindowReady(() {
const initialSize = Size(600, 450); const initialSize = Size(800, 600);
appWindow.minSize = initialSize; appWindow.minSize = initialSize;
appWindow.size = initialSize; appWindow.size = initialSize;
appWindow.alignment = Alignment.center; appWindow.alignment = Alignment.center;
@ -24,7 +27,7 @@ void main() async {
runApp(MultiBlocProvider( runApp(MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (context) => ScoopListBloc(), create: (context) => ScoopListBloc()..add(ScoopLocate()),
), ),
], ],
child: const LadleApp(), child: const LadleApp(),

View file

@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';
class ScoopAppModel extends Equatable {
final String name;
final String description;
final String bucket;
final DateTime updatedAt;
const ScoopAppModel({
required this.name,
required this.description,
required this.bucket,
required this.updatedAt,
});
@override
List<Object?> get props => [name, description, bucket, updatedAt];
}

View file

@ -1,4 +1,14 @@
import 'dart:convert';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:ladle/models/scoop_app_model.dart';
import 'package:ladle/utils/set_extension.dart';
import 'package:styled_widget/styled_widget.dart';
import '../bloc/scoop_list_bloc.dart';
class AppListFragment extends StatefulWidget { class AppListFragment extends StatefulWidget {
const AppListFragment({Key? key}) : super(key: key); const AppListFragment({Key? key}) : super(key: key);
@ -8,32 +18,272 @@ class AppListFragment extends StatefulWidget {
} }
class _AppListFragmentState extends State<AppListFragment> { class _AppListFragmentState extends State<AppListFragment> {
final scrollController = ScrollController();
final openedApps = <ScoopAppModel>{};
int currentIndex = 0; int currentIndex = 0;
int tabs = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( final state = context.read<ScoopListBloc>().state;
height: 600, if (state is! ScoopLocalAppList) {
return const Center(child: Text("No apps installed"));
}
return ScaffoldPage(
padding: EdgeInsets.zero,
header: AutoSuggestBox(
items: state.apps.map((e) => e.name).toList(),
leadingIcon: const Icon(FluentIcons.search).padding(all: 16),
placeholder: "Search for apps",
onSelected: (value) {
final app = state.apps.firstWhere((e) => e.name == value);
setState(() {
openedApps.add(app);
});
},
),
content: Flex(
direction: Axis.horizontal,
children: [
Expanded(
flex: 1,
child: ListView.builder(
key: PageStorageKey(state.apps.length),
controller: scrollController,
itemCount: state.apps.length,
itemBuilder: (context, index) =>
_createAppWidget(state.apps[index]),
).backgroundColor(Colors.black.withAlpha(50)),
),
Expanded(
flex: 3,
child: TabView( child: TabView(
wheelScroll: true,
currentIndex: currentIndex, currentIndex: currentIndex,
onChanged: (index) => setState(() => currentIndex = index), onChanged: (index) => setState(() => currentIndex = index),
onNewPressed: () { tabs: openedApps
setState(() => tabs++); .map((e) => Tab(
}, text: Text(e.name),
tabs: List.generate(tabs, (index) {
return Tab(
text: Text('Tab $index'),
closeIcon: FluentIcons.chrome_close, closeIcon: FluentIcons.chrome_close,
onClosed: () => setState(
() {
final appIdx = openedApps.indexOf(e);
if (appIdx < currentIndex) {
currentIndex--;
} else {
currentIndex = 0;
}
openedApps.remove(e);
},
),
))
.toList(growable: false),
bodies: openedApps.map(_createAppPage).toList(growable: false),
),
),
],
),
); );
}
Widget _createAppWidget(ScoopAppModel appModel) {
return GestureDetector(
onTap: () => setState(() {
if (openedApps.contains(appModel)) {
currentIndex = openedApps.indexOf(appModel);
} else {
openedApps.add(appModel);
currentIndex = openedApps.length - 1;
}
}), }),
bodies: List.generate( behavior: HitTestBehavior.opaque,
tabs, child: Container(
(index) => Container( padding: const EdgeInsets.all(16),
color: index.isEven ? Colors.red : Colors.yellow, child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
appModel.name,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
), ),
overflow: TextOverflow.ellipsis,
),
),
Text(
DateFormat("dd.MM.yyyy hh:mm")
.format(appModel.updatedAt),
style: const TextStyle(
fontSize: 10,
),
),
],
),
Text(
appModel.description,
style: const TextStyle(
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
FluentIcons.repo_solid,
size: 12,
),
const SizedBox(width: 5),
Text(
appModel.bucket,
style: const TextStyle(
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
),
],
),
],
),
),
],
), ),
), ),
); );
} }
Widget _createAppPage(ScoopAppModel appModel) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(appModel.name).fontSize(32).bold().paddingDirectional(bottom: 16),
Row(
children: [
Chip(
image: const Icon(FluentIcons.repo_solid),
text: Text(appModel.bucket),
).padding(right: 8),
Chip.selected(
image: const Icon(FluentIcons.calendar),
text: Text(
DateFormat("dd.MM.yyyy hh:mm").format(appModel.updatedAt)),
).padding(right: 8),
OutlinedButton(
child: const Text("Update"),
onPressed: () => _runScoopRoutine(
title: "Updating ${appModel.name}",
params: ["update", appModel.name],
),
).padding(right: 8),
OutlinedButton(
child: const Text("Reinstall"),
onPressed: () => _runScoopRoutine(
title: "Reinstalling ${appModel.name}",
executable: "powershell",
params: [
"-c",
"scoop",
"uninstall",
appModel.name,
"&&",
"scoop",
"install",
appModel.name
],
).then((_) =>
context.read<ScoopListBloc>().add(ScoopListRequested())),
).padding(right: 8),
OutlinedButton(
child: const Text("Uninstall"),
onPressed: () => _runScoopRoutine(
title: "Uninstalling ${appModel.name}",
params: ["uninstall", appModel.name],
).then((_) =>
context.read<ScoopListBloc>().add(ScoopListRequested())),
),
],
).paddingDirectional(bottom: 16),
Text(
appModel.description,
style: FluentTheme.of(context).typography.body,
),
],
).padding(all: 16);
}
Future<void> _runScoopRoutine({
String executable = "scoop",
required String title,
required List<String> params,
}) async {
final textController = TextEditingController();
final textScroller = ScrollController();
final process = await Process.start(executable, params, runInShell: true);
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
textController.text = "Waiting for process to start...\n";
process.stdout.transform(utf8.decoder).forEach((element) {
element = element.trim();
if (element.isEmpty) return;
textController.text +=
"${DateFormat("hh:mm:ss").format(DateTime.now())} $element\n";
textScroller.jumpTo(textScroller.position.maxScrollExtent);
});
process.stderr.transform(utf8.decoder).forEach((element) {
element = element.trim();
if (element.isEmpty) return;
textController.text +=
"${DateFormat("hh:mm:ss").format(DateTime.now())} stderr: $element\n";
textScroller.jumpTo(textScroller.position.maxScrollExtent);
});
return FutureBuilder<int>(
future: process.exitCode,
builder: (context, snapshot) {
return ContentDialog(
constraints: const BoxConstraints(minWidth: 1000),
title: Text(title),
content: TextBox(
enabled: false,
maxLines: 5,
minLines: 5,
controller: textController,
scrollController: textScroller,
scrollPhysics: snapshot.hasData
? null
: const NeverScrollableScrollPhysics(),
),
actions: snapshot.hasData
? [
Button(
child: const Text("Close"),
onPressed: () => Navigator.of(context).pop(),
),
]
: [
Button(
child: const Text("Cancel"),
onPressed: () {
process.kill();
Navigator.pop(context);
},
)
],
);
},
);
},
);
process.kill();
}
} }

View file

@ -1,6 +1,10 @@
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher.dart';
import '../bloc/scoop_list_bloc.dart';
import '../widgets/windows_buttons_widget.dart'; import '../widgets/windows_buttons_widget.dart';
import 'app_list_fragment.dart'; import 'app_list_fragment.dart';
@ -16,23 +20,89 @@ class _HomePageState extends State<HomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<ScoopListBloc, ScoopListState>(
builder: (context, state) {
if (state is ScoopNotFound) {
return _appWithoutScoopInstalled();
}
return NavigationView( return NavigationView(
appBar: _buildAppbar(), appBar: _buildAppbar(state),
pane: _buildNavigationPane(), pane: _buildNavigationPane(),
content: _buildContent(), content: _buildContent(state),
);
},
); );
} }
NavigationAppBar _buildAppbar() { Widget _appWithoutScoopInstalled() {
return NavigationAppBar( return NavigationView(
appBar: NavigationAppBar(
title: MoveWindow( title: MoveWindow(
child: const Align( child: const Align(
alignment: Alignment.centerLeft, alignment: Alignment.center,
child: Text("Ladle"), child: Text("Ladle"),
), ),
), ),
actions: MoveWindow( actions: MoveWindow(
child: Row(children: const [Spacer(), WindowButtons()]), child: Row(children: const [
Spacer(),
WindowButtons(),
]),
),
automaticallyImplyLeading: false,
),
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Scoop not found!\nYou can find installation instructions here:",
textAlign: TextAlign.center,
),
Button(
onPressed: () {
launchUrl(Uri.parse("https://scoop.sh/"));
},
child: const Text("Scoop website"),
).padding(all: 10),
],
),
);
}
NavigationAppBar _buildAppbar(ScoopListState state) {
return NavigationAppBar(
title: MoveWindow(
child: const Align(
alignment: Alignment.center,
child: Text("Ladle"),
),
),
actions: MoveWindow(
child: Row(children: [
const Spacer(),
WindowButton(
iconBuilder: (buttonContext) => Center(
child: state is ScoopListLoading
? const ProgressBar()
: const Icon(
FluentIcons.refresh,
size: 12,
),
),
onPressed: state is ScoopListLoading
? null
: () =>
context.read<ScoopListBloc>().add(ScoopUpdateRequested()),
),
WindowButton(
iconBuilder: (buttonContext) => const Icon(
FluentIcons.settings,
size: 12,
),
onPressed: () => Navigator.pushNamed(context, "/settings"),
),
const WindowButtons(),
]),
), ),
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
); );
@ -41,17 +111,20 @@ class _HomePageState extends State<HomePage> {
NavigationPane _buildNavigationPane() { NavigationPane _buildNavigationPane() {
return NavigationPane( return NavigationPane(
selected: _fragmentIndex, selected: _fragmentIndex,
displayMode: PaneDisplayMode.top, displayMode: PaneDisplayMode.compact,
onChanged: (i) => setState(() => _fragmentIndex = i), onChanged: (i) => setState(() => _fragmentIndex = i),
menuButton: const SizedBox(),
items: [ items: [
PaneItem( PaneItem(
title: const Text("Home"), title: const Text("Home"),
icon: const Icon(FluentIcons.home), icon: const Icon(FluentIcons.home),
), ),
PaneItem( PaneItem(
title: const Text("Updates"), title: const Text("Apps"),
icon: const Icon(FluentIcons.update_restore), icon: const Icon(FluentIcons.apps_content),
), ),
],
footerItems: [
PaneItemSeparator(), PaneItemSeparator(),
PaneItem( PaneItem(
title: const Text("Settings"), title: const Text("Settings"),
@ -61,13 +134,34 @@ class _HomePageState extends State<HomePage> {
); );
} }
NavigationBody _buildContent() { Widget _buildContent(ScoopListState state) {
if (state is ScoopListLoading) {
return const Center(child: ProgressBar());
} else if (state is ScoopUpdateInProgress) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text("Updating Scoop").fontSize(24),
Text(state.message).textColor(Colors.orange).italic(),
const ProgressBar(),
],
);
} else if (state is ScoopUpdateError) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text("Scoop update error").fontSize(24),
Text(state.message).textColor(Colors.red).italic(),
],
);
}
return NavigationBody( return NavigationBody(
index: _fragmentIndex, index: _fragmentIndex,
children: const [ children: const [
AppListFragment(), AppListFragment(),
Center(child: Text("Scoops")), Center(child: Text("Updates")),
Center(child: Text("Settings")),
], ],
); );
} }

100
lib/utils/scoop_utils.dart Normal file
View file

@ -0,0 +1,100 @@
import 'dart:convert';
import 'dart:io';
import 'package:yaml/yaml.dart';
import '../models/scoop_app_model.dart';
bool scoopInstalled() {
final home = Platform.environment["UserProfile"];
return Directory("$home/scoop").existsSync();
}
Future<List<String>> getScoopBuckets() async {
final home = Platform.environment["UserProfile"];
final scoopBucketsDir = Directory("$home/scoop/buckets");
final buckets = <String>[];
if (await scoopBucketsDir.exists()) {
await scoopBucketsDir.list().forEach((element) {
if (element is Directory) {
buckets.add(element.path.split("\\").last);
}
});
}
return buckets;
}
Future<List<ScoopAppModel>> searchScoopApps(String query) async {
final home = Platform.environment["UserProfile"];
final buckets = await getScoopBuckets();
final apps = <ScoopAppModel>[];
for (final bucket in buckets) {
final bucketDir = Directory("$home/scoop/buckets/$bucket/bucket");
final files = bucketDir.listSync().whereType<File>().where(
(element) =>
element.path.endsWith(".json") ||
element.path.endsWith(".yml") ||
element.path.endsWith(".yaml"),
);
for (final file in files) {
final content = await file.readAsString();
final data =
file.path.endsWith(".json") ? jsonDecode(content) : loadYaml(content);
String appName =
file.path.split("\\").last.replaceAll(RegExp(r"\.(json|ya?ml)"), "");
String appDescription = data["description"] ?? "No description";
DateTime appUpdatedAt = await file.lastModified();
apps.add(ScoopAppModel(
name: appName,
description: appDescription,
bucket: bucket,
updatedAt: appUpdatedAt,
));
}
}
return apps;
}
Future<List<ScoopAppModel>> getInstalledScoopApps() async {
final home = Platform.environment["UserProfile"];
final scoopAppsDir = Directory("$home/scoop/apps");
final apps = <ScoopAppModel>[];
if (await scoopAppsDir.exists()) {
final elementsInDir = scoopAppsDir.listSync().whereType<Directory>();
for (final element in elementsInDir) {
String appName = element.path.split("\\").last;
String appDescription = "No description";
String appBucket = "UNKNOWN";
DateTime appUpdatedAt = DateTime.fromMicrosecondsSinceEpoch(0);
final manifestFile = File("${element.path}/current/manifest.json");
if (await manifestFile.exists()) {
final manifestData = jsonDecode(await manifestFile.readAsString());
appDescription = manifestData["description"] ?? appDescription;
appUpdatedAt = await manifestFile.lastModified();
}
final installFile = File("${element.path}/current/install.json");
if (await installFile.exists()) {
final installData = jsonDecode(await installFile.readAsString());
appBucket = installData["bucket"] ?? appBucket;
}
apps.add(ScoopAppModel(
name: appName,
description: appDescription,
bucket: appBucket,
updatedAt: appUpdatedAt,
));
}
}
return apps;
}

View file

@ -0,0 +1,14 @@
extension SetExtension<T> on Set<T> {
int indexOf(T el) {
if (contains(el)) {
int idx = 0;
for (final e in this) {
if (e == el) {
return idx;
}
idx++;
}
}
return -1;
}
}

View file

@ -16,6 +16,7 @@ class WindowButtons extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
MinimizeWindowButton(), MinimizeWindowButton(),
MaximizeWindowButton(),
CloseWindowButton(), CloseWindowButton(),
], ],
), ),

View file

@ -50,6 +50,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.1.0" version: "8.1.0"
bloc_concurrency:
dependency: "direct main"
description:
name: bloc_concurrency
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -148,7 +155,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
intl: intl:
dependency: transitive dependency: "direct main"
description: description:
name: intl name: intl
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
@ -161,6 +168,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.4" version: "0.6.4"
lint:
dependency: transitive
description:
name: lint
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -257,6 +271,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -264,6 +285,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
styled_widget:
dependency: "direct main"
description:
name: styled_widget
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0+3"
system_theme: system_theme:
dependency: "direct main" dependency: "direct main"
description: description:
@ -292,6 +320,62 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.9" version: "0.4.9"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.5"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.18"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.17"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.13"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -306,6 +390,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
yaml:
dependency: "direct main"
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
sdks: sdks:
dart: ">=2.17.6 <3.0.0" dart: ">=2.17.6 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"

View file

@ -9,13 +9,18 @@ environment:
dependencies: dependencies:
bitsdojo_window: ^0.1.4 bitsdojo_window: ^0.1.4
bloc: ^8.1.0 bloc: ^8.1.0
bloc_concurrency: ^0.2.0
equatable: ^2.0.5 equatable: ^2.0.5
fluent_ui: ^3.12.0 fluent_ui: ^3.12.0
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^8.1.1 flutter_bloc: ^8.1.1
intl: ^0.17.0
meta: ^1.7.0 meta: ^1.7.0
styled_widget: ^0.4.0+3
system_theme: ^2.0.0 system_theme: ^2.0.0
url_launcher: ^6.1.5
yaml: ^3.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -8,10 +8,13 @@
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h> #include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
#include <system_theme/system_theme_plugin.h> #include <system_theme/system_theme_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
BitsdojoWindowPluginRegisterWithRegistrar( BitsdojoWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
SystemThemePluginRegisterWithRegistrar( SystemThemePluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SystemThemePlugin")); registry->GetRegistrarForPlugin("SystemThemePlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View file

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_windows bitsdojo_window_windows
system_theme system_theme
url_launcher_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST