Add Mods menu
Dieser Commit ist enthalten in:
Ursprung
c95fda17ca
Commit
89d1e1d5b5
@ -27,6 +27,8 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'package:steamwar_multitool/src/provider/provider.dart';
|
import 'package:steamwar_multitool/src/provider/provider.dart';
|
||||||
import 'package:steamwar_multitool/src/screens/home/lists/events_list.dart';
|
import 'package:steamwar_multitool/src/screens/home/lists/events_list.dart';
|
||||||
|
|
||||||
|
import 'lists/mod_list.dart';
|
||||||
|
|
||||||
class HomeScreen extends HookConsumerWidget {
|
class HomeScreen extends HookConsumerWidget {
|
||||||
const HomeScreen({Key? key}) : super(key: key);
|
const HomeScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@ -79,7 +81,6 @@ class HomeScreen extends HookConsumerWidget {
|
|||||||
label: Text("Mods"),
|
label: Text("Mods"),
|
||||||
selectedIcon: Icon(Icons.developer_mode),
|
selectedIcon: Icon(Icons.developer_mode),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
const NavigationDrawerDestination(
|
const NavigationDrawerDestination(
|
||||||
icon: Icon(Icons.settings_outlined),
|
icon: Icon(Icons.settings_outlined),
|
||||||
label: Text("Settings"),
|
label: Text("Settings"),
|
||||||
@ -129,17 +130,12 @@ class HomeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: IndexedStack(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
index: navRailIndex.value,
|
||||||
child: IndexedStack(
|
children: const [
|
||||||
index: navRailIndex.value,
|
EventListComponent(),
|
||||||
children: const [
|
ModListComponent(),
|
||||||
//ServerListComponent(),
|
],
|
||||||
EventListComponent(),
|
|
||||||
Placeholder(),
|
|
||||||
//ModListComponent(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -38,53 +38,56 @@ class EventListComponent extends HookConsumerWidget {
|
|||||||
final pastEvents =
|
final pastEvents =
|
||||||
data.where((element) => !element.isUpcoming && !element.isCurrent);
|
data.where((element) => !element.isUpcoming && !element.isCurrent);
|
||||||
final currentEvent = data.indexWhere((element) => element.isCurrent);
|
final currentEvent = data.indexWhere((element) => element.isCurrent);
|
||||||
return ListView(
|
return Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
Row(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
Row(
|
||||||
onPressed: () {
|
children: [
|
||||||
showModalBottomSheet(
|
FilledButton.icon(
|
||||||
constraints: BoxConstraints(maxWidth: 500),
|
onPressed: () {
|
||||||
context: context,
|
showModalBottomSheet(
|
||||||
builder: (context) => const CreateEventDialog());
|
constraints: BoxConstraints(maxWidth: 500),
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const CreateEventDialog());
|
||||||
|
},
|
||||||
|
label: const Text("Create Event"),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
if (currentEvent != -1)
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
data[currentEvent].name,
|
||||||
|
),
|
||||||
|
subtitle: const Text(
|
||||||
|
"Current Event",
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.priority_high),
|
||||||
|
onTap: () {
|
||||||
|
context.go("/event/${data[currentEvent].id}");
|
||||||
},
|
},
|
||||||
label: const Text("Create Event"),
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
),
|
),
|
||||||
],
|
if (upcomingEvents.isNotEmpty)
|
||||||
),
|
Text(
|
||||||
const SizedBox(
|
"Upcoming Events",
|
||||||
height: 16,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
),
|
|
||||||
if (currentEvent != -1)
|
|
||||||
ListTile(
|
|
||||||
title: Text(
|
|
||||||
data[currentEvent].name,
|
|
||||||
),
|
),
|
||||||
subtitle: const Text(
|
...upcomingEvents.map(
|
||||||
"Current Event",
|
(e) => EventTile(e),
|
||||||
|
),
|
||||||
|
if (pastEvents.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
"Past Events",
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
),
|
),
|
||||||
leading: const Icon(Icons.priority_high),
|
...pastEvents.map((e) => EventTile(e)),
|
||||||
onTap: () {
|
],
|
||||||
context.go("/event/${data[currentEvent].id}");
|
),
|
||||||
},
|
|
||||||
),
|
|
||||||
if (upcomingEvents.isNotEmpty)
|
|
||||||
Text(
|
|
||||||
"Upcoming Events",
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
),
|
|
||||||
...upcomingEvents.map(
|
|
||||||
(e) => EventTile(e),
|
|
||||||
),
|
|
||||||
if (pastEvents.isNotEmpty)
|
|
||||||
Text(
|
|
||||||
"Past Events",
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
),
|
|
||||||
...pastEvents.map((e) => EventTile(e)),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}, error: (err, stack) {
|
}, error: (err, stack) {
|
||||||
return ErrorComponent(err, stack);
|
return ErrorComponent(err, stack);
|
||||||
|
@ -18,36 +18,296 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:steamwar_multitool/src/components/error.dart';
|
import 'package:steamwar_multitool/src/components/error.dart';
|
||||||
import 'package:steamwar_multitool/src/provider/mods.dart';
|
import 'package:steamwar_multitool/src/provider/mods.dart';
|
||||||
|
import 'package:steamwar_multitool/src/types/mods.dart';
|
||||||
|
import 'package:url_launcher/link.dart';
|
||||||
|
|
||||||
class ModListComponent extends HookConsumerWidget {
|
class ModListComponent extends HookConsumerWidget {
|
||||||
const ModListComponent({Key? key}) : super(key: key);
|
const ModListComponent({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final list = ref.watch(uncheckedMods);
|
final tabController = useTabController(initialLength: 2);
|
||||||
|
|
||||||
return list.when(
|
return Column(
|
||||||
|
children: [
|
||||||
|
TabBar(
|
||||||
|
tabs: [
|
||||||
|
const Tab(
|
||||||
|
text: "Unrated",
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.search),
|
||||||
|
Text("Search"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
controller: tabController,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: tabController,
|
||||||
|
children: const [
|
||||||
|
UnratedModsList(),
|
||||||
|
SearchModsList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnratedModsList extends HookConsumerWidget {
|
||||||
|
const UnratedModsList({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final mods = ref.watch(uncheckedMods);
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
return mods.when(
|
||||||
data: (data) {
|
data: (data) {
|
||||||
return ListView(
|
return ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
for (final mod in data)
|
||||||
child: Text("WIP",
|
ExpansionTile(
|
||||||
style: Theme.of(context).textTheme.headline5)),
|
title: Text(mod.name),
|
||||||
...data
|
subtitle:
|
||||||
.map(
|
Text("Modloader: ${mod.platform.name.capitalize()}"),
|
||||||
(e) => ListTile(
|
initiallyExpanded: i++ == 0,
|
||||||
title: Text(e.name),
|
children: [
|
||||||
subtitle: Text(e.platform.name),
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Link(
|
||||||
|
target: LinkTarget.blank,
|
||||||
|
uri: Uri.tryParse(
|
||||||
|
"https://modrinth.com/mods?q=${mod.name}"),
|
||||||
|
builder: (context, followLink) {
|
||||||
|
return FilledButton.tonalIcon(
|
||||||
|
onPressed: followLink,
|
||||||
|
label: const Text("Modrinth"),
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Link(
|
||||||
|
target: LinkTarget.blank,
|
||||||
|
uri: Uri.tryParse(
|
||||||
|
"https://beta.curseforge.com/minecraft/search?search=${mod.name}"),
|
||||||
|
builder: (context, followLink) {
|
||||||
|
return FilledButton.tonalIcon(
|
||||||
|
onPressed: followLink,
|
||||||
|
label: const Text("Curseforge"),
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Link(
|
||||||
|
target: LinkTarget.blank,
|
||||||
|
uri: Uri.tryParse(
|
||||||
|
"https://github.com/search?q=${mod.name}"),
|
||||||
|
builder: (context, followLink) {
|
||||||
|
return FilledButton.tonalIcon(
|
||||||
|
onPressed: followLink,
|
||||||
|
label: const Text("Github"),
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
const Divider(),
|
||||||
.toList()
|
SegmentedButton(
|
||||||
|
segments: [
|
||||||
|
for (final type in ModType.values)
|
||||||
|
ButtonSegment(
|
||||||
|
value: type,
|
||||||
|
label: Text(type.name.capitalize()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
selected: {mod.type},
|
||||||
|
multiSelectionEnabled: false,
|
||||||
|
emptySelectionAllowed: false,
|
||||||
|
onSelectionChanged: (value) async {
|
||||||
|
final repo = await ref.read(modRepository.future);
|
||||||
|
await repo.updateMod(
|
||||||
|
mod.platform, mod.name, value.first);
|
||||||
|
ref.invalidate(uncheckedMods);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
error: (err, stack) => ErrorComponent(err, stack),
|
error: (err, stack) => ErrorComponent(err, stack),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()));
|
loading: () => const LinearProgressIndicator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchModsList extends HookConsumerWidget {
|
||||||
|
const SearchModsList({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final modsList = ref.watch(mods);
|
||||||
|
final nameFilter = useState("");
|
||||||
|
final platformFilter = useState(Platform.values.toSet());
|
||||||
|
final typeFilter = useState(ModType.values.toSet());
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
labelText: "Name",
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
nameFilter.value = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
SegmentedButton(
|
||||||
|
segments: [
|
||||||
|
for (final platform in Platform.values)
|
||||||
|
ButtonSegment(
|
||||||
|
value: platform,
|
||||||
|
label: Text(platform.name.capitalize()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
selected: platformFilter.value,
|
||||||
|
multiSelectionEnabled: true,
|
||||||
|
emptySelectionAllowed: false,
|
||||||
|
onSelectionChanged: (value) {
|
||||||
|
platformFilter.value = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
SegmentedButton(
|
||||||
|
segments: [
|
||||||
|
for (final type in ModType.values)
|
||||||
|
ButtonSegment(
|
||||||
|
value: type,
|
||||||
|
label: Text(type.name.capitalize()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
selected: typeFilter.value,
|
||||||
|
multiSelectionEnabled: true,
|
||||||
|
emptySelectionAllowed: false,
|
||||||
|
onSelectionChanged: (value) {
|
||||||
|
typeFilter.value = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
...modsList.when(
|
||||||
|
data: (data) {
|
||||||
|
final filtered = data
|
||||||
|
.where((mod) =>
|
||||||
|
(nameFilter.value.isEmpty ||
|
||||||
|
mod.name
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(nameFilter.value.toLowerCase())) &&
|
||||||
|
platformFilter.value.contains(mod.platform) &&
|
||||||
|
typeFilter.value.contains(mod.type))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return filtered.map((mod) {
|
||||||
|
return ExpansionTile(
|
||||||
|
title: Text(mod.name),
|
||||||
|
subtitle:
|
||||||
|
Text("Modloader: ${mod.platform.name.capitalize()}"),
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Link(
|
||||||
|
target: LinkTarget.blank,
|
||||||
|
uri: Uri.tryParse(
|
||||||
|
"https://modrinth.com/mods?q=${mod.name}"),
|
||||||
|
builder: (context, followLink) {
|
||||||
|
return FilledButton.tonalIcon(
|
||||||
|
onPressed: followLink,
|
||||||
|
label: const Text("Modrinth"),
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Link(
|
||||||
|
target: LinkTarget.blank,
|
||||||
|
uri: Uri.tryParse(
|
||||||
|
"https://beta.curseforge.com/minecraft/search?search=${mod.name}"),
|
||||||
|
builder: (context, followLink) {
|
||||||
|
return FilledButton.tonalIcon(
|
||||||
|
onPressed: followLink,
|
||||||
|
label: const Text("Curseforge"),
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Link(
|
||||||
|
target: LinkTarget.blank,
|
||||||
|
uri: Uri.tryParse(
|
||||||
|
"https://github.com/search?q=${mod.name}"),
|
||||||
|
builder: (context, followLink) {
|
||||||
|
return FilledButton.tonalIcon(
|
||||||
|
onPressed: followLink,
|
||||||
|
label: const Text("Github"),
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
SegmentedButton(
|
||||||
|
segments: [
|
||||||
|
for (final type in ModType.values)
|
||||||
|
ButtonSegment(
|
||||||
|
value: type,
|
||||||
|
label: Text(type.name.capitalize()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
selected: {mod.type},
|
||||||
|
multiSelectionEnabled: false,
|
||||||
|
emptySelectionAllowed: false,
|
||||||
|
onSelectionChanged: (value) async {
|
||||||
|
final repo = await ref.read(modRepository.future);
|
||||||
|
await repo.updateMod(
|
||||||
|
mod.platform, mod.name, value.first);
|
||||||
|
ref.invalidate(mods);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err, stack) => [ErrorComponent(err, stack)],
|
||||||
|
loading: () => const [LinearProgressIndicator()])
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StringExtension on String {
|
||||||
|
String capitalize() {
|
||||||
|
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
pubspec.lock
28
pubspec.lock
@ -292,10 +292,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: feab99a20fd248c658c923ba98f4449ca6e575c3dee9fdf07146f4f33482c6bc
|
sha256: "50bc08b72ede07daaf897deb4c00ca8fd8976c8821a4c84d7aeef0e92d1c5620"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.5"
|
version: "6.5.6"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -540,10 +540,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: cf2a42fb20148502022861f71698db12d937c7459345a1bdaa88fc91a91b3603
|
sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.1"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -705,18 +705,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: dd729390aa936bf1bdf5cd1bc7468ff340263f80a2c4f569416507667de8e3c8
|
sha256: a52628068d282d01a07cd86e6ba99e497aa45ce8c91159015b2416907d78e411
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.26"
|
version: "6.0.27"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92"
|
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.3"
|
version: "6.1.4"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -729,10 +729,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: "0ef2b4f97942a16523e51256b799e9aa1843da6c60c55eefbfa9dbc2dcb8331a"
|
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.4"
|
version: "3.0.5"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -777,18 +777,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
|
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.4.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
|
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.4"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren