Erweiterungen
This commit is contained in:
1 parent
dd88480f29
commit
3c2e9ceecb
14 files changed
+179
-26
No files matched your search
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stadtbote/content_views/tour_list_page.dart';
|
||||||
|
import 'package:stadtbote/content_views/utils/content_view_widget.dart';
|
||||||
|
import 'package:stadtbote/content_views/utils/header_info.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:stadtbote/tools/message_service.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
import '../model/job.dart';
|
||||||
|
import '../tools/constants.dart';
|
||||||
|
import '../tools/util.dart';
|
||||||
|
|
||||||
|
class ApproachAreaPage extends ContentViewWidget {
|
||||||
|
const ApproachAreaPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ApproachAreaPage> createState() => _ApproachAreaPageState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<HeaderInfo> getHeaderInfo(BuildContext context) async {
|
||||||
|
return HeaderInfo(
|
||||||
|
titleWidget: const Text(
|
||||||
|
'Angenommene Aufträge',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 24.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightButtonEnabled: true,
|
||||||
|
rightButtonVisible: true,
|
||||||
|
rightButtonImage: Util.fetchFutureIcon(const Icon(Icons.info_outline, size: 20.0, color: Colors.white)),
|
||||||
|
rightButtonOnPressed: () async {
|
||||||
|
await Util.startBrowser('https://app.assecutor.de/help/sb/app/context/001/index.html');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ApproachAreaPageState extends State<ApproachAreaPage> {
|
||||||
|
late List<Job> _jobs;
|
||||||
|
|
||||||
|
late bool _initCompleted = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
Future.delayed(const Duration(seconds: 0), () async {
|
||||||
|
_jobs = await MyApp.isarInstance.jobs.filter().stateEqualTo(1).findAll();
|
||||||
|
|
||||||
|
_initCompleted = true;
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!_initCompleted) {
|
||||||
|
return Util.getWaitWidget(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const Text("Hello World!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clicked(Job job) {
|
||||||
|
MessageService().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: TourListPage(job: job));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stadtbote/content_views/accepted_jobs_page.dart';
|
import 'package:stadtbote/content_views/accepted_jobs_page.dart';
|
||||||
|
import 'package:stadtbote/content_views/approach_area_page.dart';
|
||||||
import 'package:stadtbote/content_views/assigned_jobs_page.dart';
|
import 'package:stadtbote/content_views/assigned_jobs_page.dart';
|
||||||
import 'package:stadtbote/content_views/finished_jobs_page.dart';
|
import 'package:stadtbote/content_views/finished_jobs_page.dart';
|
||||||
import 'package:stadtbote/content_views/messages_page.dart';
|
import 'package:stadtbote/content_views/messages_page.dart';
|
||||||
@@ -132,8 +133,9 @@ class _MainPageState extends State<MainPage> {
|
|||||||
child: getDefaultButton(
|
child: getDefaultButton(
|
||||||
'Zugewiesene Aufträge',
|
'Zugewiesene Aufträge',
|
||||||
image: Image.asset('assets/zugewiesene_auftraege.png', width: 30, height: 30),
|
image: Image.asset('assets/zugewiesene_auftraege.png', width: 30, height: 30),
|
||||||
() => {MessageService()
|
() => {
|
||||||
.publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: const AssignedJobsPage())}),
|
MessageService().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: const AssignedJobsPage())
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -196,18 +198,15 @@ class _MainPageState extends State<MainPage> {
|
|||||||
child: getDefaultButton(
|
child: getDefaultButton(
|
||||||
'Nachrichten',
|
'Nachrichten',
|
||||||
image: Image.asset('assets/nachrichten.png', width: 30, height: 30),
|
image: Image.asset('assets/nachrichten.png', width: 30, height: 30),
|
||||||
() => {
|
() =>
|
||||||
MessageService().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: const MessagesPage())
|
{MessageService().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: const MessagesPage())}));
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
getDefaultButton(
|
getDefaultButton(
|
||||||
'Anfahrtsbereich',
|
'Anfahrtsbereich',
|
||||||
image: Image.asset('assets/anfahrtsbereich.png', width: 30, height: 30),
|
image: Image.asset('assets/anfahrtsbereich.png', width: 30, height: 30),
|
||||||
() => {
|
() => {MessageService().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: const ApproachAreaPage())}),
|
||||||
//BroadcastReceiver().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: const FinishedJobsPage())
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
getDefaultButton(
|
getDefaultButton(
|
||||||
'Abmelden',
|
'Abmelden',
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class MessagesPage extends ContentViewWidget {
|
|||||||
rightButtonVisible: true,
|
rightButtonVisible: true,
|
||||||
rightButtonImage: Util.fetchFutureIcon(const Icon(Icons.info_outline, size: 20.0, color: Colors.white)),
|
rightButtonImage: Util.fetchFutureIcon(const Icon(Icons.info_outline, size: 20.0, color: Colors.white)),
|
||||||
rightButtonOnPressed: () async {
|
rightButtonOnPressed: () async {
|
||||||
showInfosPopup(context, await MyApp.isarInstance.status.where().findFirst());
|
await Util.startBrowser('https://app.assecutor.de/help/sb/app/context/003/index.html');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
import 'dart:convert' show utf8;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@@ -46,11 +46,11 @@ class TaskListPage extends ContentViewWidget {
|
|||||||
fontSize: 24.0,
|
fontSize: 24.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
rightButtonEnabled: false,
|
rightButtonEnabled: true,
|
||||||
rightButtonVisible: false,
|
rightButtonVisible: true,
|
||||||
rightButtonImage: null,
|
rightButtonImage: Util.fetchFutureIcon(const Icon(Icons.info_outline, size: 20.0, color: Colors.white)),
|
||||||
rightButtonOnPressed: () async {
|
rightButtonOnPressed: () async {
|
||||||
//BroadcastReceiver().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: EmployeeEditProfileWidget());
|
await Util.startBrowser('https://app.assecutor.de/help/sb/app/context/002/index.html');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ class _TaskListPageState extends State<TaskListPage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
taskInfo.task.topic,
|
utf8.decode(taskInfo.task.topic.codeUnits),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ class TourListPage extends ContentViewWidget {
|
|||||||
fontSize: 24.0,
|
fontSize: 24.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
rightButtonEnabled: false,
|
rightButtonEnabled: true,
|
||||||
rightButtonVisible: false,
|
rightButtonVisible: true,
|
||||||
rightButtonImage: null,
|
rightButtonImage: Util.fetchFutureIcon(const Icon(Icons.info_outline, size: 20.0, color: Colors.white)),
|
||||||
rightButtonOnPressed: () async {
|
rightButtonOnPressed: () async {
|
||||||
//BroadcastReceiver().publish<ContentViewWidget>(Constants.messageTopicPushContentView, arguments: EmployeeEditProfileWidget());
|
await Util.startBrowser('https://app.assecutor.de/help/sb/app/context/004/index.html');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ _sendAnswer(Job job, int item) {
|
|||||||
message.reaction.track.pos = FinTaskPos();
|
message.reaction.track.pos = FinTaskPos();
|
||||||
message.reaction.track.pos.lat = currentPosition != null ? currentPosition.latitude : 0.0;
|
message.reaction.track.pos.lat = currentPosition != null ? currentPosition.latitude : 0.0;
|
||||||
message.reaction.track.pos.long = currentPosition != null ? currentPosition.longitude : 0.0;
|
message.reaction.track.pos.long = currentPosition != null ? currentPosition.longitude : 0.0;
|
||||||
message.reaction.track.pos.acc = currentPosition != null ? currentPosition.accuracy : 0.0;
|
message.reaction.track.pos.acc = currentPosition != null ? currentPosition.accuracy.toInt() : 0;
|
||||||
|
|
||||||
MqttCore.instance.publishPayloadMessage("job", message.toJson());
|
MqttCore.instance.publishPayloadMessage("job", message.toJson());
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class FinTaskPos {
|
|||||||
|
|
||||||
late double long;
|
late double long;
|
||||||
|
|
||||||
late double acc;
|
late int acc;
|
||||||
|
|
||||||
FinTaskPos();
|
FinTaskPos();
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
|
|||||||
FinTaskPos _$FinTaskPosFromJson(Map<String, dynamic> json) => FinTaskPos()
|
FinTaskPos _$FinTaskPosFromJson(Map<String, dynamic> json) => FinTaskPos()
|
||||||
..lat = (json['lat'] as num).toDouble()
|
..lat = (json['lat'] as num).toDouble()
|
||||||
..long = (json['long'] as num).toDouble()
|
..long = (json['long'] as num).toDouble()
|
||||||
..acc = (json['acc'] as num).toDouble();
|
..acc = json['acc'] as int;
|
||||||
|
|
||||||
Map<String, dynamic> _$FinTaskPosToJson(FinTaskPos instance) =>
|
Map<String, dynamic> _$FinTaskPosToJson(FinTaskPos instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:stadtbote/protocoll_messages/fin_task_message.dart';
|
||||||
|
|
||||||
|
part 'track_message.g.dart';
|
||||||
|
|
||||||
|
/*
|
||||||
|
var payloadObject = new JObject();
|
||||||
|
|
||||||
|
var posObject = new JObject();
|
||||||
|
payloadObject.Add(new JProperty("pos", posObject));
|
||||||
|
posObject.Add(new JProperty("time", DateTime.Now.ToString("o")));
|
||||||
|
posObject.Add(new JProperty("lat", location.Latitude));
|
||||||
|
posObject.Add(new JProperty("long", location.Longitude));
|
||||||
|
if (location.Accuracy != null)
|
||||||
|
{
|
||||||
|
posObject.Add(new JProperty("acc", (int)location.Accuracy));
|
||||||
|
}
|
||||||
|
|
||||||
|
SendJsonMessageAsync("track", payloadObject);
|
||||||
|
*/
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class TrackMessage {
|
||||||
|
late FinTaskPos pos;
|
||||||
|
|
||||||
|
TrackMessage();
|
||||||
|
|
||||||
|
factory TrackMessage.fromJson(Map<String, dynamic> json) => _$TrackMessageFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$TrackMessageToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'track_message.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
TrackMessage _$TrackMessageFromJson(Map<String, dynamic> json) => TrackMessage()
|
||||||
|
..pos = FinTaskPos.fromJson(json['pos'] as Map<String, dynamic>);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$TrackMessageToJson(TrackMessage instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'pos': instance.pos,
|
||||||
|
};
|
||||||
@@ -5,15 +5,18 @@ import 'dart:math';
|
|||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:mqtt_client/mqtt_client.dart';
|
import 'package:mqtt_client/mqtt_client.dart';
|
||||||
import 'package:mqtt_client/mqtt_server_client.dart';
|
import 'package:mqtt_client/mqtt_server_client.dart';
|
||||||
|
import 'package:stadtbote/model/job_tour.dart';
|
||||||
import 'package:stadtbote/protocoll_messages/bootstrap_request.dart';
|
import 'package:stadtbote/protocoll_messages/bootstrap_request.dart';
|
||||||
import 'package:stadtbote/protocoll_messages/bootstrap_response_payload.dart';
|
import 'package:stadtbote/protocoll_messages/bootstrap_response_payload.dart';
|
||||||
import 'package:stadtbote/protocoll_messages/device_object.dart';
|
import 'package:stadtbote/protocoll_messages/device_object.dart';
|
||||||
import 'package:stadtbote/model/job.dart';
|
import 'package:stadtbote/model/job.dart';
|
||||||
import 'package:stadtbote/model/message.dart';
|
import 'package:stadtbote/model/message.dart';
|
||||||
|
import 'package:stadtbote/protocoll_messages/fin_task_message.dart';
|
||||||
import 'package:stadtbote/protocoll_messages/job_del_message.dart';
|
import 'package:stadtbote/protocoll_messages/job_del_message.dart';
|
||||||
import 'package:stadtbote/protocoll_messages/payload_container.dart';
|
import 'package:stadtbote/protocoll_messages/payload_container.dart';
|
||||||
import 'package:stadtbote/protocoll_messages/start_session_message.dart';
|
import 'package:stadtbote/protocoll_messages/start_session_message.dart';
|
||||||
import 'package:stadtbote/protocoll_messages/sync_jobs_message.dart';
|
import 'package:stadtbote/protocoll_messages/sync_jobs_message.dart';
|
||||||
|
import 'package:stadtbote/protocoll_messages/track_message.dart';
|
||||||
import 'package:stadtbote/tools/connection_status.dart';
|
import 'package:stadtbote/tools/connection_status.dart';
|
||||||
import 'package:stadtbote/tools/message_service.dart';
|
import 'package:stadtbote/tools/message_service.dart';
|
||||||
import 'package:stadtbote/tools/messages/job_deleted_message.dart';
|
import 'package:stadtbote/tools/messages/job_deleted_message.dart';
|
||||||
@@ -49,6 +52,8 @@ class MqttCore {
|
|||||||
|
|
||||||
late int _reconnectDelay = 0;
|
late int _reconnectDelay = 0;
|
||||||
|
|
||||||
|
late Timer? _trackTimer;
|
||||||
|
|
||||||
MqttCore() {
|
MqttCore() {
|
||||||
_processBootstrap();
|
_processBootstrap();
|
||||||
}
|
}
|
||||||
@@ -168,7 +173,7 @@ class MqttCore {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'job_del':
|
case 'job_del':
|
||||||
//_jobDeleteReceived(payloadData);
|
_jobDeleteReceived(payloadData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sync_request':
|
case 'sync_request':
|
||||||
@@ -209,6 +214,8 @@ class MqttCore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
MessageService().publish<StatusInfoUpdate>(Constants.messageTopicStatusInfoUpdate, arguments: StatusInfoUpdate(status: statusMessage));
|
MessageService().publish<StatusInfoUpdate>(Constants.messageTopicStatusInfoUpdate, arguments: StatusInfoUpdate(status: statusMessage));
|
||||||
|
|
||||||
|
_startTrackTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _rankingMessageReceived(payloadData) async {
|
void _rankingMessageReceived(payloadData) async {
|
||||||
@@ -288,6 +295,8 @@ class MqttCore {
|
|||||||
void _clientDisconnected() {
|
void _clientDisconnected() {
|
||||||
connectionState = ConnectionStateEnum.notConnected;
|
connectionState = ConnectionStateEnum.notConnected;
|
||||||
|
|
||||||
|
_cancelTrackTimer();
|
||||||
|
|
||||||
Util.logInfo("_clientDisconnected called");
|
Util.logInfo("_clientDisconnected called");
|
||||||
|
|
||||||
MessageService().publish<ConnectionStatus>(Constants.messageTopicConnectionStatusChanged, arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected));
|
MessageService().publish<ConnectionStatus>(Constants.messageTopicConnectionStatusChanged, arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected));
|
||||||
@@ -374,4 +383,25 @@ class MqttCore {
|
|||||||
|
|
||||||
MqttCore.instance.publishPayloadMessage("server", answer.toJson());
|
MqttCore.instance.publishPayloadMessage("server", answer.toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _startTrackTimer() {
|
||||||
|
_trackTimer = Timer(const Duration(seconds: 60), () {
|
||||||
|
var position = MyApp.currentPosition;
|
||||||
|
if (position != null) {
|
||||||
|
var trackMessage = TrackMessage();
|
||||||
|
trackMessage.pos = FinTaskPos();
|
||||||
|
trackMessage.pos.lat = position.latitude;
|
||||||
|
trackMessage.pos.long = position.longitude;
|
||||||
|
trackMessage.pos.acc = position.accuracy.toInt();
|
||||||
|
|
||||||
|
publishPayloadMessage("track", trackMessage.toJson());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cancelTrackTimer() {
|
||||||
|
if (_trackTimer != null) {
|
||||||
|
_trackTimer!.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:stadtbote/tools/constants.dart';
|
import 'package:stadtbote/tools/constants.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../Tools/colored_safe_area.dart';
|
import '../Tools/colored_safe_area.dart';
|
||||||
@@ -174,6 +175,12 @@ class Util {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> startBrowser(String websiteUrl) async {
|
||||||
|
if (!await launchUrl(Uri.parse(websiteUrl))) {
|
||||||
|
throw Exception('Could not launch $websiteUrl');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget getDefaultButton(String text, Function() onPressed, {Image? image, double? buttonHeight}) {
|
Widget getDefaultButton(String text, Function() onPressed, {Image? image, double? buttonHeight}) {
|
||||||
|
|||||||
+3
-3
@@ -1254,13 +1254,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
|
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
version: "6.2.4"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ dependencies:
|
|||||||
flutter_chat_ui: ^1.6.10
|
flutter_chat_ui: ^1.6.10
|
||||||
geolocator: ^10.1.0
|
geolocator: ^10.1.0
|
||||||
expandable: ^5.0.1
|
expandable: ^5.0.1
|
||||||
|
url_launcher: ^6.2.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in new issue
Block a user