Allow sending errors to Sentry
This commit is contained in:
parent
baf0dfa99d
commit
567f5070e9
@ -12,6 +12,7 @@ import 'package:okane/ui/pages/budgets/budget_details.dart';
|
|||||||
import 'package:okane/ui/pages/transaction_details.dart';
|
import 'package:okane/ui/pages/transaction_details.dart';
|
||||||
import 'package:okane/ui/state/core.dart';
|
import 'package:okane/ui/state/core.dart';
|
||||||
import 'package:okane/ui/state/settings.dart';
|
import 'package:okane/ui/state/settings.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
import 'ui/pages/loans/loan_details.dart';
|
import 'ui/pages/loans/loan_details.dart';
|
||||||
|
|
||||||
@ -21,11 +22,20 @@ Future<void> main() async {
|
|||||||
|
|
||||||
final settings = SettingsCubit();
|
final settings = SettingsCubit();
|
||||||
await settings.loadSettings();
|
await settings.loadSettings();
|
||||||
|
|
||||||
GetIt.I.registerSingleton<SettingsCubit>(settings);
|
GetIt.I.registerSingleton<SettingsCubit>(settings);
|
||||||
GetIt.I.registerSingleton<CoreCubit>(CoreCubit());
|
GetIt.I.registerSingleton<CoreCubit>(CoreCubit());
|
||||||
GetIt.I.registerSingleton<OkaneDatabase>(OkaneDatabase());
|
GetIt.I.registerSingleton<OkaneDatabase>(OkaneDatabase());
|
||||||
|
|
||||||
runApp(const MyApp());
|
final appRunner = () => runApp(const MyApp());
|
||||||
|
if (settings.sentryDsn != null) {
|
||||||
|
print("Setting up Sentry!");
|
||||||
|
await SentryFlutter.init((options) {
|
||||||
|
options.dsn = settings.sentryDsn!;
|
||||||
|
}, appRunner: appRunner);
|
||||||
|
} else {
|
||||||
|
appRunner();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:okane/i18n/strings.g.dart';
|
import 'package:okane/i18n/strings.g.dart';
|
||||||
|
import 'package:okane/ui/pages/account/breakdown_card.dart';
|
||||||
import 'package:okane/ui/state/settings.dart';
|
import 'package:okane/ui/state/settings.dart';
|
||||||
import 'package:okane/ui/utils.dart';
|
import 'package:okane/ui/utils.dart';
|
||||||
|
|
||||||
@ -67,6 +68,74 @@ class SettingsPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
BlocBuilder<SettingsCubit, SettingsWrapper>(
|
||||||
|
builder:
|
||||||
|
(context, state) => ListTile(
|
||||||
|
title: Text("Sentry"),
|
||||||
|
subtitle: Text(
|
||||||
|
"When enabled sends errors to the configured Sentry DSN.",
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: Icon(Icons.clear),
|
||||||
|
onPressed:
|
||||||
|
state.settings.sentryDsn == null
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final result = await confirm(
|
||||||
|
context,
|
||||||
|
"Clear Sentry DSN",
|
||||||
|
"Are you sure you want to clear the Sentry DSN?",
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GetIt.I.get<SettingsCubit>().setSettings(
|
||||||
|
state.settings.copyWith(sentryDsn: null),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
final controller = TextEditingController(
|
||||||
|
text: state.settings.sentryDsn ?? '',
|
||||||
|
);
|
||||||
|
final result = await showDialogOrModal<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: controller,
|
||||||
|
decoration: InputDecoration(hintText: "Sentry DSN"),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
OutlinedButton(
|
||||||
|
child: Text("Apply"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(controller.text);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GetIt.I.get<SettingsCubit>().setSettings(
|
||||||
|
state.settings.copyWith(sentryDsn: result),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ enum ColorSchemeSettings { light, dark, system }
|
|||||||
abstract class Settings with _$Settings {
|
abstract class Settings with _$Settings {
|
||||||
const factory Settings({
|
const factory Settings({
|
||||||
@Default(ColorSchemeSettings.system) ColorSchemeSettings colorScheme,
|
@Default(ColorSchemeSettings.system) ColorSchemeSettings colorScheme,
|
||||||
|
@Default(null) String? sentryDsn,
|
||||||
}) = _Settings;
|
}) = _Settings;
|
||||||
|
|
||||||
factory Settings.fromJson(Map<String, Object?> json) =>
|
factory Settings.fromJson(Map<String, Object?> json) =>
|
||||||
@ -45,4 +46,6 @@ class SettingsCubit extends Cubit<SettingsWrapper> {
|
|||||||
|
|
||||||
await _prefs.setString("settings", jsonEncode(settings.toJson()));
|
await _prefs.setString("settings", jsonEncode(settings.toJson()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? get sentryDsn => state.settings.sentryDsn;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$Settings {
|
mixin _$Settings {
|
||||||
ColorSchemeSettings get colorScheme => throw _privateConstructorUsedError;
|
ColorSchemeSettings get colorScheme => throw _privateConstructorUsedError;
|
||||||
|
String? get sentryDsn => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -34,7 +35,7 @@ abstract class $SettingsCopyWith<$Res> {
|
|||||||
factory $SettingsCopyWith(Settings value, $Res Function(Settings) then) =
|
factory $SettingsCopyWith(Settings value, $Res Function(Settings) then) =
|
||||||
_$SettingsCopyWithImpl<$Res, Settings>;
|
_$SettingsCopyWithImpl<$Res, Settings>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({ColorSchemeSettings colorScheme});
|
$Res call({ColorSchemeSettings colorScheme, String? sentryDsn});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -49,7 +50,7 @@ class _$SettingsCopyWithImpl<$Res, $Val extends Settings>
|
|||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({Object? colorScheme = null}) {
|
$Res call({Object? colorScheme = null, Object? sentryDsn = freezed}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
colorScheme:
|
colorScheme:
|
||||||
@ -57,6 +58,11 @@ class _$SettingsCopyWithImpl<$Res, $Val extends Settings>
|
|||||||
? _value.colorScheme
|
? _value.colorScheme
|
||||||
: colorScheme // ignore: cast_nullable_to_non_nullable
|
: colorScheme // ignore: cast_nullable_to_non_nullable
|
||||||
as ColorSchemeSettings,
|
as ColorSchemeSettings,
|
||||||
|
sentryDsn:
|
||||||
|
freezed == sentryDsn
|
||||||
|
? _value.sentryDsn
|
||||||
|
: sentryDsn // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@ -72,7 +78,7 @@ abstract class _$$SettingsImplCopyWith<$Res>
|
|||||||
) = __$$SettingsImplCopyWithImpl<$Res>;
|
) = __$$SettingsImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({ColorSchemeSettings colorScheme});
|
$Res call({ColorSchemeSettings colorScheme, String? sentryDsn});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -86,7 +92,7 @@ class __$$SettingsImplCopyWithImpl<$Res>
|
|||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({Object? colorScheme = null}) {
|
$Res call({Object? colorScheme = null, Object? sentryDsn = freezed}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$SettingsImpl(
|
_$SettingsImpl(
|
||||||
colorScheme:
|
colorScheme:
|
||||||
@ -94,6 +100,11 @@ class __$$SettingsImplCopyWithImpl<$Res>
|
|||||||
? _value.colorScheme
|
? _value.colorScheme
|
||||||
: colorScheme // ignore: cast_nullable_to_non_nullable
|
: colorScheme // ignore: cast_nullable_to_non_nullable
|
||||||
as ColorSchemeSettings,
|
as ColorSchemeSettings,
|
||||||
|
sentryDsn:
|
||||||
|
freezed == sentryDsn
|
||||||
|
? _value.sentryDsn
|
||||||
|
: sentryDsn // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -102,7 +113,10 @@ class __$$SettingsImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$SettingsImpl implements _Settings {
|
class _$SettingsImpl implements _Settings {
|
||||||
const _$SettingsImpl({this.colorScheme = ColorSchemeSettings.system});
|
const _$SettingsImpl({
|
||||||
|
this.colorScheme = ColorSchemeSettings.system,
|
||||||
|
this.sentryDsn = null,
|
||||||
|
});
|
||||||
|
|
||||||
factory _$SettingsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$SettingsImplFromJson(json);
|
_$$SettingsImplFromJson(json);
|
||||||
@ -110,10 +124,13 @@ class _$SettingsImpl implements _Settings {
|
|||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final ColorSchemeSettings colorScheme;
|
final ColorSchemeSettings colorScheme;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String? sentryDsn;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Settings(colorScheme: $colorScheme)';
|
return 'Settings(colorScheme: $colorScheme, sentryDsn: $sentryDsn)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -122,12 +139,14 @@ class _$SettingsImpl implements _Settings {
|
|||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$SettingsImpl &&
|
other is _$SettingsImpl &&
|
||||||
(identical(other.colorScheme, colorScheme) ||
|
(identical(other.colorScheme, colorScheme) ||
|
||||||
other.colorScheme == colorScheme));
|
other.colorScheme == colorScheme) &&
|
||||||
|
(identical(other.sentryDsn, sentryDsn) ||
|
||||||
|
other.sentryDsn == sentryDsn));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, colorScheme);
|
int get hashCode => Object.hash(runtimeType, colorScheme, sentryDsn);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@ -142,8 +161,10 @@ class _$SettingsImpl implements _Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Settings implements Settings {
|
abstract class _Settings implements Settings {
|
||||||
const factory _Settings({final ColorSchemeSettings colorScheme}) =
|
const factory _Settings({
|
||||||
_$SettingsImpl;
|
final ColorSchemeSettings colorScheme,
|
||||||
|
final String? sentryDsn,
|
||||||
|
}) = _$SettingsImpl;
|
||||||
|
|
||||||
factory _Settings.fromJson(Map<String, dynamic> json) =
|
factory _Settings.fromJson(Map<String, dynamic> json) =
|
||||||
_$SettingsImpl.fromJson;
|
_$SettingsImpl.fromJson;
|
||||||
@ -151,6 +172,8 @@ abstract class _Settings implements Settings {
|
|||||||
@override
|
@override
|
||||||
ColorSchemeSettings get colorScheme;
|
ColorSchemeSettings get colorScheme;
|
||||||
@override
|
@override
|
||||||
|
String? get sentryDsn;
|
||||||
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$SettingsImplCopyWith<_$SettingsImpl> get copyWith =>
|
_$$SettingsImplCopyWith<_$SettingsImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
@ -14,11 +14,13 @@ _$SettingsImpl _$$SettingsImplFromJson(Map<String, dynamic> json) =>
|
|||||||
json['colorScheme'],
|
json['colorScheme'],
|
||||||
) ??
|
) ??
|
||||||
ColorSchemeSettings.system,
|
ColorSchemeSettings.system,
|
||||||
|
sentryDsn: json['sentryDsn'] as String? ?? null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SettingsImplToJson(_$SettingsImpl instance) =>
|
Map<String, dynamic> _$$SettingsImplToJson(_$SettingsImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'colorScheme': _$ColorSchemeSettingsEnumMap[instance.colorScheme]!,
|
'colorScheme': _$ColorSchemeSettingsEnumMap[instance.colorScheme]!,
|
||||||
|
'sentryDsn': instance.sentryDsn,
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$ColorSchemeSettingsEnumMap = {
|
const _$ColorSchemeSettingsEnumMap = {
|
||||||
|
@ -6,9 +6,13 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <sentry_flutter/sentry_flutter_plugin.h>
|
||||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
|
||||||
|
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||||
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
sentry_flutter
|
||||||
sqlite3_flutter_libs
|
sqlite3_flutter_libs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
56
pubspec.lock
56
pubspec.lock
@ -405,6 +405,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -565,6 +573,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
package_info_plus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus
|
||||||
|
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.3.0"
|
||||||
|
package_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_platform_interface
|
||||||
|
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
path:
|
path:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -685,6 +709,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.9"
|
version: "1.2.9"
|
||||||
|
sentry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sentry
|
||||||
|
sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.14.2"
|
||||||
|
sentry_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sentry_flutter
|
||||||
|
sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.14.2"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -810,6 +850,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -898,6 +946,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -29,6 +29,9 @@ dependencies:
|
|||||||
drift: ^2.26.1
|
drift: ^2.26.1
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.2.4
|
||||||
|
|
||||||
|
# For optional error tracking
|
||||||
|
sentry_flutter: ^8.14.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
Loading…
Reference in New Issue
Block a user