add some props an features
This commit is contained in:
@@ -17,6 +17,8 @@ class AnwendungController extends GetxController {
|
|||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
||||||
final druckbettController = TextEditingController();
|
final druckbettController = TextEditingController();
|
||||||
final druckController = TextEditingController();
|
final druckController = TextEditingController();
|
||||||
|
final empfohleneDrucktemperaturController = TextEditingController();
|
||||||
|
final empfohlenerHeizbettbereichController = TextEditingController();
|
||||||
final bauraumController = TextEditingController();
|
final bauraumController = TextEditingController();
|
||||||
final sonstigesController = TextEditingController();
|
final sonstigesController = TextEditingController();
|
||||||
final bauraumGeschlossen = false.obs;
|
final bauraumGeschlossen = false.obs;
|
||||||
@@ -48,6 +50,10 @@ class AnwendungController extends GetxController {
|
|||||||
anwendungen.assignAll([profile]);
|
anwendungen.assignAll([profile]);
|
||||||
druckbettController.text = profile.druckbettTemperatur.toString();
|
druckbettController.text = profile.druckbettTemperatur.toString();
|
||||||
druckController.text = profile.druckTemperatur.toString();
|
druckController.text = profile.druckTemperatur.toString();
|
||||||
|
empfohleneDrucktemperaturController.text =
|
||||||
|
profile.empfohleneDrucktemperatur;
|
||||||
|
empfohlenerHeizbettbereichController.text =
|
||||||
|
profile.empfohlenerHeizbettbereich;
|
||||||
bauraumController.text = profile.bauraumTemperatur?.toString() ?? '';
|
bauraumController.text = profile.bauraumTemperatur?.toString() ?? '';
|
||||||
sonstigesController.text = profile.sonstiges;
|
sonstigesController.text = profile.sonstiges;
|
||||||
bauraumGeschlossen.value = profile.bauraumGeschlossen;
|
bauraumGeschlossen.value = profile.bauraumGeschlossen;
|
||||||
@@ -67,6 +73,10 @@ class AnwendungController extends GetxController {
|
|||||||
erstelltAm: existing?.erstelltAm ?? DateTime.now(),
|
erstelltAm: existing?.erstelltAm ?? DateTime.now(),
|
||||||
druckbettTemperatur: InputValidators.number(druckbettController.text)!,
|
druckbettTemperatur: InputValidators.number(druckbettController.text)!,
|
||||||
druckTemperatur: InputValidators.number(druckController.text)!,
|
druckTemperatur: InputValidators.number(druckController.text)!,
|
||||||
|
empfohleneDrucktemperatur: empfohleneDrucktemperaturController.text
|
||||||
|
.trim(),
|
||||||
|
empfohlenerHeizbettbereich: empfohlenerHeizbettbereichController.text
|
||||||
|
.trim(),
|
||||||
bauraumGeschlossen: bauraumGeschlossen.value,
|
bauraumGeschlossen: bauraumGeschlossen.value,
|
||||||
bauraumTemperatur: InputValidators.number(bauraumController.text),
|
bauraumTemperatur: InputValidators.number(bauraumController.text),
|
||||||
sonstiges: sonstigesController.text.trim(),
|
sonstiges: sonstigesController.text.trim(),
|
||||||
@@ -94,6 +104,8 @@ class AnwendungController extends GetxController {
|
|||||||
void onClose() {
|
void onClose() {
|
||||||
druckbettController.dispose();
|
druckbettController.dispose();
|
||||||
druckController.dispose();
|
druckController.dispose();
|
||||||
|
empfohleneDrucktemperaturController.dispose();
|
||||||
|
empfohlenerHeizbettbereichController.dispose();
|
||||||
bauraumController.dispose();
|
bauraumController.dispose();
|
||||||
sonstigesController.dispose();
|
sonstigesController.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ class AnwendungModel {
|
|||||||
final DateTime erstelltAm;
|
final DateTime erstelltAm;
|
||||||
final double druckbettTemperatur;
|
final double druckbettTemperatur;
|
||||||
final double druckTemperatur;
|
final double druckTemperatur;
|
||||||
|
final String empfohleneDrucktemperatur;
|
||||||
|
final String empfohlenerHeizbettbereich;
|
||||||
final bool bauraumGeschlossen;
|
final bool bauraumGeschlossen;
|
||||||
final double? bauraumTemperatur;
|
final double? bauraumTemperatur;
|
||||||
final String sonstiges;
|
final String sonstiges;
|
||||||
@@ -15,6 +17,8 @@ class AnwendungModel {
|
|||||||
required this.druckbettTemperatur,
|
required this.druckbettTemperatur,
|
||||||
required this.druckTemperatur,
|
required this.druckTemperatur,
|
||||||
required this.bauraumGeschlossen,
|
required this.bauraumGeschlossen,
|
||||||
|
this.empfohleneDrucktemperatur = '',
|
||||||
|
this.empfohlenerHeizbettbereich = '',
|
||||||
this.bauraumTemperatur,
|
this.bauraumTemperatur,
|
||||||
this.sonstiges = '',
|
this.sonstiges = '',
|
||||||
});
|
});
|
||||||
@@ -25,6 +29,8 @@ class AnwendungModel {
|
|||||||
'erstelltAm': erstelltAm.toIso8601String(),
|
'erstelltAm': erstelltAm.toIso8601String(),
|
||||||
'druckbettTemperatur': druckbettTemperatur,
|
'druckbettTemperatur': druckbettTemperatur,
|
||||||
'druckTemperatur': druckTemperatur,
|
'druckTemperatur': druckTemperatur,
|
||||||
|
'empfohleneDrucktemperatur': empfohleneDrucktemperatur,
|
||||||
|
'empfohlenerHeizbettbereich': empfohlenerHeizbettbereich,
|
||||||
'bauraumGeschlossen': bauraumGeschlossen,
|
'bauraumGeschlossen': bauraumGeschlossen,
|
||||||
'bauraumTemperatur': bauraumTemperatur,
|
'bauraumTemperatur': bauraumTemperatur,
|
||||||
'sonstiges': sonstiges,
|
'sonstiges': sonstiges,
|
||||||
@@ -36,6 +42,10 @@ class AnwendungModel {
|
|||||||
erstelltAm: DateTime.parse(json['erstelltAm'] as String),
|
erstelltAm: DateTime.parse(json['erstelltAm'] as String),
|
||||||
druckbettTemperatur: (json['druckbettTemperatur'] as num).toDouble(),
|
druckbettTemperatur: (json['druckbettTemperatur'] as num).toDouble(),
|
||||||
druckTemperatur: (json['druckTemperatur'] as num).toDouble(),
|
druckTemperatur: (json['druckTemperatur'] as num).toDouble(),
|
||||||
|
empfohleneDrucktemperatur:
|
||||||
|
json['empfohleneDrucktemperatur'] as String? ?? '',
|
||||||
|
empfohlenerHeizbettbereich:
|
||||||
|
json['empfohlenerHeizbettbereich'] as String? ?? '',
|
||||||
bauraumGeschlossen: json['bauraumGeschlossen'] as bool,
|
bauraumGeschlossen: json['bauraumGeschlossen'] as bool,
|
||||||
bauraumTemperatur: (json['bauraumTemperatur'] as num?)?.toDouble(),
|
bauraumTemperatur: (json['bauraumTemperatur'] as num?)?.toDouble(),
|
||||||
sonstiges: json['sonstiges'] as String? ?? '',
|
sonstiges: json['sonstiges'] as String? ?? '',
|
||||||
|
|||||||
@@ -22,11 +22,30 @@ class AnwendungForm extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
EntryFieldGrid(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: controller.empfohleneDrucktemperaturController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Empfohlene Drucktemperatur von–bis',
|
||||||
|
hintText: 'z. B. 220–260 °C',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: controller.empfohlenerHeizbettbereichController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Empfohlener Heizbettbereich von–bis',
|
||||||
|
hintText: 'z. B. 0–60 °C',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
EntryFieldGrid(
|
EntryFieldGrid(
|
||||||
children: [
|
children: [
|
||||||
EntryNumberField(
|
EntryNumberField(
|
||||||
controller: controller.druckController,
|
controller: controller.druckController,
|
||||||
label: 'Drucktemperatur',
|
label: 'Nozzletemperatur',
|
||||||
unit: '°C',
|
unit: '°C',
|
||||||
validator: InputValidators.positive,
|
validator: InputValidators.positive,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -13,21 +13,60 @@ class BauteilFilamentAuswahl extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Obx(() {
|
Widget build(BuildContext context) => Obx(() {
|
||||||
final ids = {
|
final ids = controller.selectedFilamente.keys.toList();
|
||||||
...controller.filamente.map((item) => item.filamentId),
|
final availableFilamente = controller.filamente
|
||||||
...controller.selectedFilamente.keys,
|
.where(
|
||||||
};
|
(item) => !controller.selectedFilamente.containsKey(item.filamentId),
|
||||||
if (ids.isEmpty) {
|
)
|
||||||
|
.toList();
|
||||||
|
if (controller.filamente.isEmpty && ids.isEmpty) {
|
||||||
return const Text(
|
return const Text(
|
||||||
'Erfasse zuerst ein Filament in der Einkaufsverwaltung.',
|
'Erfasse zuerst ein Filament in der Einkaufsverwaltung.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
children: ids.map((id) {
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
final selected = controller.selectedFilamente.containsKey(id);
|
children: [
|
||||||
|
if (availableFilamente.isNotEmpty) ...[
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
key: ValueKey(
|
||||||
|
availableFilamente
|
||||||
|
.map((item) => item.filamentId)
|
||||||
|
.toList()
|
||||||
|
.toString(),
|
||||||
|
),
|
||||||
|
isExpanded: true,
|
||||||
|
decoration: const InputDecoration(labelText: 'Filament hinzufügen'),
|
||||||
|
hint: const Text('Filament auswählen'),
|
||||||
|
items: availableFilamente
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem(
|
||||||
|
value: item.filamentId,
|
||||||
|
child: Text(
|
||||||
|
controller.filamentLabel(item.filamentId),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (id) {
|
||||||
|
if (id != null) controller.toggleFilament(id, true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
] else if (controller.filamente.isNotEmpty) ...[
|
||||||
|
const Text('Alle Filamente wurden hinzugefügt.'),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
if (ids.isEmpty)
|
||||||
|
const Text(
|
||||||
|
'Noch keine Filamente hinzugefügt.',
|
||||||
|
style: TextStyle(color: AppTheme.muted),
|
||||||
|
),
|
||||||
|
...ids.map((id) {
|
||||||
final profiles = controller.profilesFor(id);
|
final profiles = controller.profilesFor(id);
|
||||||
final available = controller.verfuegbarGramm(id);
|
final available = controller.verfuegbarGramm(id);
|
||||||
final checkbox = CheckboxListTile(
|
final filament = ListTile(
|
||||||
key: ValueKey('filament-$id'),
|
key: ValueKey('filament-$id'),
|
||||||
title: Text(controller.filamentLabel(id)),
|
title: Text(controller.filamentLabel(id)),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
@@ -35,12 +74,13 @@ class BauteilFilamentAuswahl extends StatelessWidget {
|
|||||||
? 'Startgewicht unbekannt'
|
? 'Startgewicht unbekannt'
|
||||||
: '${DisplayFormats.number(available)} g verfügbar',
|
: '${DisplayFormats.number(available)} g verfügbar',
|
||||||
),
|
),
|
||||||
value: selected,
|
trailing: IconButton(
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
tooltip: 'Filament entfernen',
|
||||||
onChanged: (value) => controller.toggleFilament(id, value ?? false),
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => controller.toggleFilament(id, false),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
final consumption = selected
|
final consumption = Padding(
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: EntryNumberField(
|
child: EntryNumberField(
|
||||||
controller: controller.verbrauchController(id),
|
controller: controller.verbrauchController(id),
|
||||||
@@ -48,8 +88,7 @@ class BauteilFilamentAuswahl extends StatelessWidget {
|
|||||||
unit: 'g',
|
unit: 'g',
|
||||||
validator: InputValidators.positive,
|
validator: InputValidators.positive,
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
: const SizedBox.shrink();
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: Material(
|
child: Material(
|
||||||
@@ -60,16 +99,16 @@ class BauteilFilamentAuswahl extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) =>
|
builder: (context, constraints) =>
|
||||||
constraints.maxWidth >= 500 && selected
|
constraints.maxWidth >= 500
|
||||||
? Row(
|
? Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: checkbox),
|
Expanded(child: filament),
|
||||||
SizedBox(width: 190, child: consumption),
|
SizedBox(width: 190, child: consumption),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: Column(children: [checkbox, if (selected) consumption]),
|
: Column(children: [filament, consumption]),
|
||||||
),
|
),
|
||||||
if (selected && profiles.isNotEmpty)
|
if (profiles.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
@@ -109,7 +148,7 @@ class BauteilFilamentAuswahl extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (selected && profiles.isEmpty)
|
if (profiles.isEmpty)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
|
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -121,7 +160,8 @@ class BauteilFilamentAuswahl extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.2+3
|
version: 1.0.4+5
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.13.0
|
sdk: ^3.13.0
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ void main() {
|
|||||||
final controller = Get.put(AnwendungController(filamentId: 'a'));
|
final controller = Get.put(AnwendungController(filamentId: 'a'));
|
||||||
await show(tester, const AnwendungView(), narrow: true);
|
await show(tester, const AnwendungView(), narrow: true);
|
||||||
controller.druckController.text = '220,5';
|
controller.druckController.text = '220,5';
|
||||||
|
controller.empfohleneDrucktemperaturController.text = '220–260 °C';
|
||||||
|
controller.empfohlenerHeizbettbereichController.text = '0–60 °C';
|
||||||
controller.druckbettController.text = '65';
|
controller.druckbettController.text = '65';
|
||||||
controller.bauraumController.text = '40';
|
controller.bauraumController.text = '40';
|
||||||
controller.bauraumGeschlossen.value = true;
|
controller.bauraumGeschlossen.value = true;
|
||||||
@@ -120,6 +122,8 @@ void main() {
|
|||||||
expect(saved.anwendungId, matches(uuidPattern));
|
expect(saved.anwendungId, matches(uuidPattern));
|
||||||
expect(saved.filamentId, 'a');
|
expect(saved.filamentId, 'a');
|
||||||
expect(saved.druckTemperatur, 220.5);
|
expect(saved.druckTemperatur, 220.5);
|
||||||
|
expect(saved.empfohleneDrucktemperatur, '220–260 °C');
|
||||||
|
expect(saved.empfohlenerHeizbettbereich, '0–60 °C');
|
||||||
expect(saved.druckbettTemperatur, 65);
|
expect(saved.druckbettTemperatur, 65);
|
||||||
expect(saved.bauraumGeschlossen, isTrue);
|
expect(saved.bauraumGeschlossen, isTrue);
|
||||||
expect(saved.bauraumTemperatur, 40);
|
expect(saved.bauraumTemperatur, 40);
|
||||||
@@ -131,6 +135,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(reloaded.anwendungen.single.anwendungId, saved.anwendungId);
|
expect(reloaded.anwendungen.single.anwendungId, saved.anwendungId);
|
||||||
expect(reloaded.druckController.text, '220.5');
|
expect(reloaded.druckController.text, '220.5');
|
||||||
|
expect(reloaded.empfohleneDrucktemperaturController.text, '220–260 °C');
|
||||||
|
expect(reloaded.empfohlenerHeizbettbereichController.text, '0–60 °C');
|
||||||
expect(reloaded.druckbettController.text, '65.0');
|
expect(reloaded.druckbettController.text, '65.0');
|
||||||
expect(reloaded.bauraumController.text, '40.0');
|
expect(reloaded.bauraumController.text, '40.0');
|
||||||
expect(reloaded.bauraumGeschlossen.value, isTrue);
|
expect(reloaded.bauraumGeschlossen.value, isTrue);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:ertl_filament_verwaltung/services/bauteil_photo_service.dart';
|
|||||||
import 'package:ertl_filament_verwaltung/services/get_storage_services.dart';
|
import 'package:ertl_filament_verwaltung/services/get_storage_services.dart';
|
||||||
import 'package:ertl_filament_verwaltung/theme/app_theme.dart';
|
import 'package:ertl_filament_verwaltung/theme/app_theme.dart';
|
||||||
import 'package:ertl_filament_verwaltung/widgets/bauteil_card.dart';
|
import 'package:ertl_filament_verwaltung/widgets/bauteil_card.dart';
|
||||||
|
import 'package:ertl_filament_verwaltung/widgets/bauteil_filament_auswahl.dart';
|
||||||
import 'package:ertl_filament_verwaltung/widgets/bauteil_photo.dart';
|
import 'package:ertl_filament_verwaltung/widgets/bauteil_photo.dart';
|
||||||
|
|
||||||
import 'helpers/memory_storage_service.dart';
|
import 'helpers/memory_storage_service.dart';
|
||||||
@@ -123,6 +124,53 @@ void main() {
|
|||||||
home.filamente.firstWhere((item) => item.filamentId == id),
|
home.filamente.firstWhere((item) => item.filamentId == id),
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
|
testWidgets('Dropdown adds only selected filaments and supports removal', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
GetMaterialApp(
|
||||||
|
theme: AppTheme.light,
|
||||||
|
home: Scaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: BauteilFilamentAuswahl(controller: controller),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(find.byKey(const ValueKey('filament-a')), findsNothing);
|
||||||
|
expect(find.byKey(const ValueKey('filament-b')), findsNothing);
|
||||||
|
|
||||||
|
Future<void> add(String id) async {
|
||||||
|
await tester.tap(find.byType(DropdownButtonFormField<String>));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text(controller.filamentLabel(id)).last);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
await add('a');
|
||||||
|
controller.verbrauchController('a').text = '25';
|
||||||
|
expect(find.byKey(const ValueKey('filament-a')), findsOneWidget);
|
||||||
|
expect(find.byKey(const ValueKey('filament-b')), findsNothing);
|
||||||
|
expect(controller.selectedFilamente.keys, ['a']);
|
||||||
|
|
||||||
|
await add('b');
|
||||||
|
expect(find.byType(DropdownButtonFormField<String>), findsNothing);
|
||||||
|
expect(find.byKey(const ValueKey('filament-b')), findsOneWidget);
|
||||||
|
await tester.tap(find.byTooltip('Filament entfernen').first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const ValueKey('filament-a')), findsNothing);
|
||||||
|
expect(controller.selectedFilamente.keys, ['b']);
|
||||||
|
|
||||||
|
await add('a');
|
||||||
|
expect(controller.verbrauchController('a').text, '25');
|
||||||
|
controller.resetForm();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const ValueKey('filament-a')), findsNothing);
|
||||||
|
expect(find.byKey(const ValueKey('filament-b')), findsNothing);
|
||||||
|
expect(find.byType(DropdownButtonFormField<String>), findsOneWidget);
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Legacy records retain unknown usage and old weight units are supported',
|
'Legacy records retain unknown usage and old weight units are supported',
|
||||||
() {
|
() {
|
||||||
|
|||||||
Reference in New Issue
Block a user