✨ Channel creation & alter
This commit is contained in:
parent
0e208cc320
commit
835203706d
@ -144,6 +144,8 @@
|
|||||||
"fieldChatAliasHint": "The unique channel alias within the site, used to represent the channel in URL, leave blank to auto generate. Should be URL-Safe.",
|
"fieldChatAliasHint": "The unique channel alias within the site, used to represent the channel in URL, leave blank to auto generate. Should be URL-Safe.",
|
||||||
"fieldChatName": "Name",
|
"fieldChatName": "Name",
|
||||||
"fieldChatDescription": "Description",
|
"fieldChatDescription": "Description",
|
||||||
|
"fieldChatBelongToRealm": "Belongs to",
|
||||||
|
"fieldChatBelongToRealmUnset": "Unset Channel Belongs to Realm",
|
||||||
"channelEditingNotice": "You are editing channel {}",
|
"channelEditingNotice": "You are editing channel {}",
|
||||||
"channelDeleted": "Chat channel {} has been deleted." ,
|
"channelDeleted": "Chat channel {} has been deleted." ,
|
||||||
"channelDelete": "Delete channel {}",
|
"channelDelete": "Delete channel {}",
|
||||||
|
@ -144,6 +144,8 @@
|
|||||||
"fieldChatAliasHint": "全站范围内唯一的频道别名,用于在 URL 中表示该频道,留空则自动生成。应遵循 URL-Safe 的原则。",
|
"fieldChatAliasHint": "全站范围内唯一的频道别名,用于在 URL 中表示该频道,留空则自动生成。应遵循 URL-Safe 的原则。",
|
||||||
"fieldChatName": "名称",
|
"fieldChatName": "名称",
|
||||||
"fieldChatDescription": "描述",
|
"fieldChatDescription": "描述",
|
||||||
|
"fieldChatBelongToRealm": "所属领域",
|
||||||
|
"fieldChatBelongToRealmUnset": "未设置频道所属领域",
|
||||||
"channelEditingNotice": "您正在编辑频道 {}",
|
"channelEditingNotice": "您正在编辑频道 {}",
|
||||||
"channelDeleted": "聊天频道 {} 已被删除" ,
|
"channelDeleted": "聊天频道 {} 已被删除" ,
|
||||||
"channelDelete": "删除聊天频道 {}",
|
"channelDelete": "删除聊天频道 {}",
|
||||||
|
@ -4,6 +4,10 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/chat.dart';
|
||||||
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
const ChatScreen({super.key});
|
const ChatScreen({super.key});
|
||||||
@ -13,14 +17,36 @@ class ChatScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChatScreenState extends State<ChatScreen> {
|
class _ChatScreenState extends State<ChatScreen> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
List<SnChannel>? _channels;
|
||||||
|
|
||||||
Future<void> _fetchChannels({scope = 'global', direct = false}) async {
|
Future<void> _fetchChannels({scope = 'global', direct = false}) async {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
setState(() => _isBusy = true);
|
||||||
final resp = await sn.client.get(
|
|
||||||
'/cgi/im/channels/$scope/me/available',
|
try {
|
||||||
queryParameters: {
|
final sn = context.read<SnNetworkProvider>();
|
||||||
'direct': direct,
|
final resp = await sn.client.get(
|
||||||
},
|
'/cgi/im/channels/$scope/me/available',
|
||||||
);
|
queryParameters: {
|
||||||
|
'direct': direct,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_channels = List<SnChannel>.from(
|
||||||
|
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchChannels();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,6 +61,28 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
GoRouter.of(context).pushNamed('chatManage');
|
GoRouter.of(context).pushNamed('chatManage');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _channels?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final channel = _channels![idx];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(channel.name),
|
||||||
|
subtitle: Text(channel.description),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
leading: AccountImage(
|
||||||
|
content: null,
|
||||||
|
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@ -9,6 +7,9 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/chat.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
@ -28,15 +29,50 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
final _descriptionController = TextEditingController();
|
final _descriptionController = TextEditingController();
|
||||||
|
|
||||||
|
List<SnRealm>? _realms;
|
||||||
|
SnRealm? _belongToRealm;
|
||||||
|
|
||||||
|
Future<void> _fetchRealms() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/realms/me/available');
|
||||||
|
_realms = List<SnRealm>.from(
|
||||||
|
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (mounted) context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SnChannel? _editingChannel;
|
||||||
|
|
||||||
|
Future<void> _fetchChannel() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get(
|
||||||
|
'/cgi/im/channels/${widget.editingChannelAlias}',
|
||||||
|
);
|
||||||
|
_editingChannel = SnChannel.fromJson(resp.data);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _performAction() async {
|
Future<void> _performAction() async {
|
||||||
final uuid = const Uuid();
|
final uuid = const Uuid();
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
// TODO Add realm support
|
final scope = _belongToRealm != null ? _belongToRealm!.alias : 'global';
|
||||||
// final scope = widget.realm != null ? widget.realm!.alias : 'global';
|
|
||||||
final scope = 'global';
|
|
||||||
final payload = {
|
final payload = {
|
||||||
'alias': _aliasController.text.isNotEmpty
|
'alias': _aliasController.text.isNotEmpty
|
||||||
? _aliasController.text.toLowerCase()
|
? _aliasController.text.toLowerCase()
|
||||||
@ -55,7 +91,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
method: widget.editingChannelAlias != null ? 'PUT' : 'POST',
|
method: widget.editingChannelAlias != null ? 'PUT' : 'POST',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
log(jsonEncode(resp.data));
|
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
if (context.mounted) Navigator.pop(context, resp.data);
|
if (context.mounted) Navigator.pop(context, resp.data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -66,6 +101,13 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.editingChannelAlias != null) _fetchChannel();
|
||||||
|
_fetchRealms();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -86,53 +128,164 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
LoadingIndicator(isActive: _isBusy),
|
LoadingIndicator(isActive: _isBusy),
|
||||||
const Gap(24),
|
if (_editingChannel != null)
|
||||||
TextField(
|
MaterialBanner(
|
||||||
controller: _aliasController,
|
leading: const Icon(Icons.edit),
|
||||||
decoration: InputDecoration(
|
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
|
||||||
border: const UnderlineInputBorder(),
|
dividerColor: Colors.transparent,
|
||||||
labelText: 'fieldChatAlias'.tr(),
|
content: Text(
|
||||||
helperText: 'fieldChatAliasHint'.tr(),
|
'channelEditingNotice'
|
||||||
helperMaxLines: 2,
|
.tr(args: ['#${_editingChannel!.alias}']),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text('cancel').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
DropdownButtonHideUnderline(
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
child: DropdownButton2<SnRealm>(
|
||||||
),
|
isExpanded: true,
|
||||||
const Gap(4),
|
hint: Text(
|
||||||
TextField(
|
'fieldChatBelongToRealm'.tr(),
|
||||||
controller: _nameController,
|
style: TextStyle(
|
||||||
decoration: InputDecoration(
|
color: Theme.of(context).hintColor,
|
||||||
border: const UnderlineInputBorder(),
|
),
|
||||||
labelText: 'fieldChatName'.tr(),
|
),
|
||||||
|
items: [
|
||||||
|
...(_realms?.map(
|
||||||
|
(SnRealm item) => DropdownMenuItem<SnRealm>(
|
||||||
|
value: item,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
AccountImage(
|
||||||
|
content: item.avatar,
|
||||||
|
radius: 16,
|
||||||
|
fallbackWidget: const Icon(
|
||||||
|
Symbols.group,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(item.name).textStyle(Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium!),
|
||||||
|
Text(
|
||||||
|
item.description,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).textStyle(
|
||||||
|
Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
[]),
|
||||||
|
DropdownMenuItem<SnRealm>(
|
||||||
|
value: null,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 16,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).colorScheme.onSurface,
|
||||||
|
child: const Icon(Symbols.clear),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('fieldChatBelongToRealmUnset')
|
||||||
|
.tr()
|
||||||
|
.textStyle(
|
||||||
|
Theme.of(context).textTheme.bodyMedium!,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
value: _belongToRealm,
|
||||||
|
onChanged: (SnRealm? value) {
|
||||||
|
setState(() => _belongToRealm = value);
|
||||||
|
},
|
||||||
|
buttonStyleData: const ButtonStyleData(
|
||||||
|
padding: EdgeInsets.only(right: 16),
|
||||||
|
height: 60,
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
|
height: 60,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
TextField(
|
|
||||||
controller: _descriptionController,
|
|
||||||
maxLines: null,
|
|
||||||
minLines: 3,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: const UnderlineInputBorder(),
|
|
||||||
labelText: 'fieldChatDescription'.tr(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Row(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton.icon(
|
TextField(
|
||||||
onPressed: _isBusy ? null : _performAction,
|
controller: _aliasController,
|
||||||
icon: const Icon(Symbols.save),
|
decoration: InputDecoration(
|
||||||
label: Text('apply').tr(),
|
border: const UnderlineInputBorder(),
|
||||||
|
labelText: 'fieldChatAlias'.tr(),
|
||||||
|
helperText: 'fieldChatAliasHint'.tr(),
|
||||||
|
helperMaxLines: 2,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
TextField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const UnderlineInputBorder(),
|
||||||
|
labelText: 'fieldChatName'.tr(),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
TextField(
|
||||||
|
controller: _descriptionController,
|
||||||
|
maxLines: null,
|
||||||
|
minLines: 3,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const UnderlineInputBorder(),
|
||||||
|
labelText: 'fieldChatDescription'.tr(),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: _isBusy ? null : _performAction,
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
label: Text('apply').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
).padding(horizontal: 24),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
30
lib/types/chat.dart
Normal file
30
lib/types/chat.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
|
part 'chat.freezed.dart';
|
||||||
|
part 'chat.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnChannel with _$SnChannel {
|
||||||
|
const factory SnChannel({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required dynamic deletedAt,
|
||||||
|
required String alias,
|
||||||
|
required String name,
|
||||||
|
required String description,
|
||||||
|
required List<dynamic> members,
|
||||||
|
required dynamic messages,
|
||||||
|
required dynamic calls,
|
||||||
|
required int type,
|
||||||
|
required int accountId,
|
||||||
|
required bool isPublic,
|
||||||
|
required bool isCommunity,
|
||||||
|
required SnRealm? realm,
|
||||||
|
required int? realmId,
|
||||||
|
}) = _SnChannel;
|
||||||
|
|
||||||
|
factory SnChannel.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnChannelFromJson(json);
|
||||||
|
}
|
516
lib/types/chat.freezed.dart
Normal file
516
lib/types/chat.freezed.dart
Normal file
@ -0,0 +1,516 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'chat.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
SnChannel _$SnChannelFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnChannel.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnChannel {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
dynamic get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
String get alias => throw _privateConstructorUsedError;
|
||||||
|
String get name => throw _privateConstructorUsedError;
|
||||||
|
String get description => throw _privateConstructorUsedError;
|
||||||
|
List<dynamic> get members => throw _privateConstructorUsedError;
|
||||||
|
dynamic get messages => throw _privateConstructorUsedError;
|
||||||
|
dynamic get calls => throw _privateConstructorUsedError;
|
||||||
|
int get type => throw _privateConstructorUsedError;
|
||||||
|
int get accountId => throw _privateConstructorUsedError;
|
||||||
|
bool get isPublic => throw _privateConstructorUsedError;
|
||||||
|
bool get isCommunity => throw _privateConstructorUsedError;
|
||||||
|
SnRealm? get realm => throw _privateConstructorUsedError;
|
||||||
|
int? get realmId => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnChannel to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnChannel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnChannelCopyWith<SnChannel> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnChannelCopyWith<$Res> {
|
||||||
|
factory $SnChannelCopyWith(SnChannel value, $Res Function(SnChannel) then) =
|
||||||
|
_$SnChannelCopyWithImpl<$Res, SnChannel>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
String alias,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
List<dynamic> members,
|
||||||
|
dynamic messages,
|
||||||
|
dynamic calls,
|
||||||
|
int type,
|
||||||
|
int accountId,
|
||||||
|
bool isPublic,
|
||||||
|
bool isCommunity,
|
||||||
|
SnRealm? realm,
|
||||||
|
int? realmId});
|
||||||
|
|
||||||
|
$SnRealmCopyWith<$Res>? get realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnChannelCopyWithImpl<$Res, $Val extends SnChannel>
|
||||||
|
implements $SnChannelCopyWith<$Res> {
|
||||||
|
_$SnChannelCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnChannel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? alias = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? members = null,
|
||||||
|
Object? messages = freezed,
|
||||||
|
Object? calls = freezed,
|
||||||
|
Object? type = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? isPublic = null,
|
||||||
|
Object? isCommunity = null,
|
||||||
|
Object? realm = freezed,
|
||||||
|
Object? realmId = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
alias: null == alias
|
||||||
|
? _value.alias
|
||||||
|
: alias // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
members: null == members
|
||||||
|
? _value.members
|
||||||
|
: members // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<dynamic>,
|
||||||
|
messages: freezed == messages
|
||||||
|
? _value.messages
|
||||||
|
: messages // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
calls: freezed == calls
|
||||||
|
? _value.calls
|
||||||
|
: calls // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
type: null == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
isPublic: null == isPublic
|
||||||
|
? _value.isPublic
|
||||||
|
: isPublic // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
isCommunity: null == isCommunity
|
||||||
|
? _value.isCommunity
|
||||||
|
: isCommunity // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
realm: freezed == realm
|
||||||
|
? _value.realm
|
||||||
|
: realm // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnRealm?,
|
||||||
|
realmId: freezed == realmId
|
||||||
|
? _value.realmId
|
||||||
|
: realmId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnChannel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnRealmCopyWith<$Res>? get realm {
|
||||||
|
if (_value.realm == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnRealmCopyWith<$Res>(_value.realm!, (value) {
|
||||||
|
return _then(_value.copyWith(realm: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnChannelImplCopyWith<$Res>
|
||||||
|
implements $SnChannelCopyWith<$Res> {
|
||||||
|
factory _$$SnChannelImplCopyWith(
|
||||||
|
_$SnChannelImpl value, $Res Function(_$SnChannelImpl) then) =
|
||||||
|
__$$SnChannelImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
String alias,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
List<dynamic> members,
|
||||||
|
dynamic messages,
|
||||||
|
dynamic calls,
|
||||||
|
int type,
|
||||||
|
int accountId,
|
||||||
|
bool isPublic,
|
||||||
|
bool isCommunity,
|
||||||
|
SnRealm? realm,
|
||||||
|
int? realmId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnRealmCopyWith<$Res>? get realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnChannelImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnChannelCopyWithImpl<$Res, _$SnChannelImpl>
|
||||||
|
implements _$$SnChannelImplCopyWith<$Res> {
|
||||||
|
__$$SnChannelImplCopyWithImpl(
|
||||||
|
_$SnChannelImpl _value, $Res Function(_$SnChannelImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnChannel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? alias = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? members = null,
|
||||||
|
Object? messages = freezed,
|
||||||
|
Object? calls = freezed,
|
||||||
|
Object? type = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? isPublic = null,
|
||||||
|
Object? isCommunity = null,
|
||||||
|
Object? realm = freezed,
|
||||||
|
Object? realmId = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnChannelImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
alias: null == alias
|
||||||
|
? _value.alias
|
||||||
|
: alias // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
members: null == members
|
||||||
|
? _value._members
|
||||||
|
: members // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<dynamic>,
|
||||||
|
messages: freezed == messages
|
||||||
|
? _value.messages
|
||||||
|
: messages // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
calls: freezed == calls
|
||||||
|
? _value.calls
|
||||||
|
: calls // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
type: null == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
isPublic: null == isPublic
|
||||||
|
? _value.isPublic
|
||||||
|
: isPublic // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
isCommunity: null == isCommunity
|
||||||
|
? _value.isCommunity
|
||||||
|
: isCommunity // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
realm: freezed == realm
|
||||||
|
? _value.realm
|
||||||
|
: realm // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnRealm?,
|
||||||
|
realmId: freezed == realmId
|
||||||
|
? _value.realmId
|
||||||
|
: realmId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnChannelImpl implements _SnChannel {
|
||||||
|
const _$SnChannelImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.alias,
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required final List<dynamic> members,
|
||||||
|
required this.messages,
|
||||||
|
required this.calls,
|
||||||
|
required this.type,
|
||||||
|
required this.accountId,
|
||||||
|
required this.isPublic,
|
||||||
|
required this.isCommunity,
|
||||||
|
required this.realm,
|
||||||
|
required this.realmId})
|
||||||
|
: _members = members;
|
||||||
|
|
||||||
|
factory _$SnChannelImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnChannelImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final dynamic deletedAt;
|
||||||
|
@override
|
||||||
|
final String alias;
|
||||||
|
@override
|
||||||
|
final String name;
|
||||||
|
@override
|
||||||
|
final String description;
|
||||||
|
final List<dynamic> _members;
|
||||||
|
@override
|
||||||
|
List<dynamic> get members {
|
||||||
|
if (_members is EqualUnmodifiableListView) return _members;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_members);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final dynamic messages;
|
||||||
|
@override
|
||||||
|
final dynamic calls;
|
||||||
|
@override
|
||||||
|
final int type;
|
||||||
|
@override
|
||||||
|
final int accountId;
|
||||||
|
@override
|
||||||
|
final bool isPublic;
|
||||||
|
@override
|
||||||
|
final bool isCommunity;
|
||||||
|
@override
|
||||||
|
final SnRealm? realm;
|
||||||
|
@override
|
||||||
|
final int? realmId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnChannel(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, messages: $messages, calls: $calls, type: $type, accountId: $accountId, isPublic: $isPublic, isCommunity: $isCommunity, realm: $realm, realmId: $realmId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnChannelImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other.deletedAt, deletedAt) &&
|
||||||
|
(identical(other.alias, alias) || other.alias == alias) &&
|
||||||
|
(identical(other.name, name) || other.name == name) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
const DeepCollectionEquality().equals(other._members, _members) &&
|
||||||
|
const DeepCollectionEquality().equals(other.messages, messages) &&
|
||||||
|
const DeepCollectionEquality().equals(other.calls, calls) &&
|
||||||
|
(identical(other.type, type) || other.type == type) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId) &&
|
||||||
|
(identical(other.isPublic, isPublic) ||
|
||||||
|
other.isPublic == isPublic) &&
|
||||||
|
(identical(other.isCommunity, isCommunity) ||
|
||||||
|
other.isCommunity == isCommunity) &&
|
||||||
|
(identical(other.realm, realm) || other.realm == realm) &&
|
||||||
|
(identical(other.realmId, realmId) || other.realmId == realmId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
const DeepCollectionEquality().hash(deletedAt),
|
||||||
|
alias,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
const DeepCollectionEquality().hash(_members),
|
||||||
|
const DeepCollectionEquality().hash(messages),
|
||||||
|
const DeepCollectionEquality().hash(calls),
|
||||||
|
type,
|
||||||
|
accountId,
|
||||||
|
isPublic,
|
||||||
|
isCommunity,
|
||||||
|
realm,
|
||||||
|
realmId);
|
||||||
|
|
||||||
|
/// Create a copy of SnChannel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnChannelImplCopyWith<_$SnChannelImpl> get copyWith =>
|
||||||
|
__$$SnChannelImplCopyWithImpl<_$SnChannelImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnChannelImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnChannel implements SnChannel {
|
||||||
|
const factory _SnChannel(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final dynamic deletedAt,
|
||||||
|
required final String alias,
|
||||||
|
required final String name,
|
||||||
|
required final String description,
|
||||||
|
required final List<dynamic> members,
|
||||||
|
required final dynamic messages,
|
||||||
|
required final dynamic calls,
|
||||||
|
required final int type,
|
||||||
|
required final int accountId,
|
||||||
|
required final bool isPublic,
|
||||||
|
required final bool isCommunity,
|
||||||
|
required final SnRealm? realm,
|
||||||
|
required final int? realmId}) = _$SnChannelImpl;
|
||||||
|
|
||||||
|
factory _SnChannel.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnChannelImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
dynamic get deletedAt;
|
||||||
|
@override
|
||||||
|
String get alias;
|
||||||
|
@override
|
||||||
|
String get name;
|
||||||
|
@override
|
||||||
|
String get description;
|
||||||
|
@override
|
||||||
|
List<dynamic> get members;
|
||||||
|
@override
|
||||||
|
dynamic get messages;
|
||||||
|
@override
|
||||||
|
dynamic get calls;
|
||||||
|
@override
|
||||||
|
int get type;
|
||||||
|
@override
|
||||||
|
int get accountId;
|
||||||
|
@override
|
||||||
|
bool get isPublic;
|
||||||
|
@override
|
||||||
|
bool get isCommunity;
|
||||||
|
@override
|
||||||
|
SnRealm? get realm;
|
||||||
|
@override
|
||||||
|
int? get realmId;
|
||||||
|
|
||||||
|
/// Create a copy of SnChannel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnChannelImplCopyWith<_$SnChannelImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
49
lib/types/chat.g.dart
Normal file
49
lib/types/chat.g.dart
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'chat.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SnChannelImpl _$$SnChannelImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnChannelImpl(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'],
|
||||||
|
alias: json['alias'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
description: json['description'] as String,
|
||||||
|
members: json['members'] as List<dynamic>,
|
||||||
|
messages: json['messages'],
|
||||||
|
calls: json['calls'],
|
||||||
|
type: (json['type'] as num).toInt(),
|
||||||
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
|
isPublic: json['is_public'] as bool,
|
||||||
|
isCommunity: json['is_community'] as bool,
|
||||||
|
realm: json['realm'] == null
|
||||||
|
? null
|
||||||
|
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
|
||||||
|
realmId: (json['realm_id'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnChannelImplToJson(_$SnChannelImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt,
|
||||||
|
'alias': instance.alias,
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
'members': instance.members,
|
||||||
|
'messages': instance.messages,
|
||||||
|
'calls': instance.calls,
|
||||||
|
'type': instance.type,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'is_public': instance.isPublic,
|
||||||
|
'is_community': instance.isCommunity,
|
||||||
|
'realm': instance.realm?.toJson(),
|
||||||
|
'realm_id': instance.realmId,
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user