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/screens/home/lists/events_list.dart';
|
||||
|
||||
import 'lists/mod_list.dart';
|
||||
|
||||
class HomeScreen extends HookConsumerWidget {
|
||||
const HomeScreen({Key? key}) : super(key: key);
|
||||
|
||||
@ -79,7 +81,6 @@ class HomeScreen extends HookConsumerWidget {
|
||||
label: Text("Mods"),
|
||||
selectedIcon: Icon(Icons.developer_mode),
|
||||
),
|
||||
const Spacer(),
|
||||
const NavigationDrawerDestination(
|
||||
icon: Icon(Icons.settings_outlined),
|
||||
label: Text("Settings"),
|
||||
@ -129,17 +130,12 @@ class HomeScreen extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: IndexedStack(
|
||||
index: navRailIndex.value,
|
||||
children: const [
|
||||
//ServerListComponent(),
|
||||
EventListComponent(),
|
||||
Placeholder(),
|
||||
//ModListComponent(),
|
||||
],
|
||||
),
|
||||
body: IndexedStack(
|
||||
index: navRailIndex.value,
|
||||
children: const [
|
||||
EventListComponent(),
|
||||
ModListComponent(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -38,53 +38,56 @@ class EventListComponent extends HookConsumerWidget {
|
||||
final pastEvents =
|
||||
data.where((element) => !element.isUpcoming && !element.isCurrent);
|
||||
final currentEvent = data.indexWhere((element) => element.isCurrent);
|
||||
return ListView(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
context: context,
|
||||
builder: (context) => const CreateEventDialog());
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (currentEvent != -1)
|
||||
ListTile(
|
||||
title: Text(
|
||||
data[currentEvent].name,
|
||||
if (upcomingEvents.isNotEmpty)
|
||||
Text(
|
||||
"Upcoming Events",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
subtitle: const Text(
|
||||
"Current Event",
|
||||
...upcomingEvents.map(
|
||||
(e) => EventTile(e),
|
||||
),
|
||||
if (pastEvents.isNotEmpty)
|
||||
Text(
|
||||
"Past Events",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
leading: const Icon(Icons.priority_high),
|
||||
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)),
|
||||
],
|
||||
...pastEvents.map((e) => EventTile(e)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}, error: (err, stack) {
|
||||
return ErrorComponent(err, stack);
|
||||
|
@ -18,36 +18,296 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/src/components/error.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 {
|
||||
const ModListComponent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
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) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Center(
|
||||
child: Text("WIP",
|
||||
style: Theme.of(context).textTheme.headline5)),
|
||||
...data
|
||||
.map(
|
||||
(e) => ListTile(
|
||||
title: Text(e.name),
|
||||
subtitle: Text(e.platform.name),
|
||||
for (final mod in data)
|
||||
ExpansionTile(
|
||||
title: Text(mod.name),
|
||||
subtitle:
|
||||
Text("Modloader: ${mod.platform.name.capitalize()}"),
|
||||
initiallyExpanded: i++ == 0,
|
||||
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),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
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(uncheckedMods);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
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"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: feab99a20fd248c658c923ba98f4449ca6e575c3dee9fdf07146f4f33482c6bc
|
||||
sha256: "50bc08b72ede07daaf897deb4c00ca8fd8976c8821a4c84d7aeef0e92d1c5620"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.5"
|
||||
version: "6.5.6"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -540,10 +540,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: cf2a42fb20148502022861f71698db12d937c7459345a1bdaa88fc91a91b3603
|
||||
sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -705,18 +705,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: dd729390aa936bf1bdf5cd1bc7468ff340263f80a2c4f569416507667de8e3c8
|
||||
sha256: a52628068d282d01a07cd86e6ba99e497aa45ce8c91159015b2416907d78e411
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.26"
|
||||
version: "6.0.27"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92"
|
||||
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.3"
|
||||
version: "6.1.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -729,10 +729,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "0ef2b4f97942a16523e51256b799e9aa1843da6c60c55eefbfa9dbc2dcb8331a"
|
||||
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
version: "3.0.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -777,18 +777,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
|
||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.4.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
|
||||
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "3.1.4"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren