Compare commits
5 Commits
2abc9808e2
...
811fc40d79
Author | SHA1 | Date | |
---|---|---|---|
811fc40d79 | |||
e05209ba3c | |||
623095473e | |||
f47f1b175a | |||
3b1d291037 |
@ -128,6 +128,7 @@
|
|||||||
"one": "{} social point",
|
"one": "{} social point",
|
||||||
"other": "{} social points"
|
"other": "{} social points"
|
||||||
},
|
},
|
||||||
|
"publisherAffiliatedBy": "Affiliated by {}",
|
||||||
"publisherRunBy": "Run by {}",
|
"publisherRunBy": "Run by {}",
|
||||||
"fieldPublisherBelongToRealm": "Belongs to",
|
"fieldPublisherBelongToRealm": "Belongs to",
|
||||||
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
||||||
@ -431,5 +432,9 @@
|
|||||||
"serviceStatus": "Service Status",
|
"serviceStatus": "Service Status",
|
||||||
"termRelated": "Related Terms",
|
"termRelated": "Related Terms",
|
||||||
"appDetails": "App Details",
|
"appDetails": "App Details",
|
||||||
"postRecommendation": "Highlight Posts"
|
"postRecommendation": "Highlight Posts",
|
||||||
|
"publisherBlockHint": "Block {}",
|
||||||
|
"publisherBlockHintDescription": "You are going to block this publisher's maintainer, this will also block publishers that run by the same user.",
|
||||||
|
"userUnblocked": "{} has been unblocked.",
|
||||||
|
"userBlocked": "{} has been blocked."
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,7 @@
|
|||||||
"one": "{} 点社会信用点",
|
"one": "{} 点社会信用点",
|
||||||
"other": "{} 点社会信用点"
|
"other": "{} 点社会信用点"
|
||||||
},
|
},
|
||||||
|
"publisherAffiliatedBy": "隶属于 {}",
|
||||||
"publisherRunBy": "由 {} 管理",
|
"publisherRunBy": "由 {} 管理",
|
||||||
"fieldPublisherBelongToRealm": "所属领域",
|
"fieldPublisherBelongToRealm": "所属领域",
|
||||||
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
||||||
@ -429,5 +430,9 @@
|
|||||||
"serviceStatus": "服务状态",
|
"serviceStatus": "服务状态",
|
||||||
"termRelated": "相关条款",
|
"termRelated": "相关条款",
|
||||||
"appDetails": "应用程序详情",
|
"appDetails": "应用程序详情",
|
||||||
"postRecommendation": "推荐帖子"
|
"postRecommendation": "推荐帖子",
|
||||||
|
"publisherBlockHint": "屏蔽 {}",
|
||||||
|
"publisherBlockHintDescription": "你正要屏蔽此发布者的运营者,该操作也将屏蔽由同一用户运营的发布者。",
|
||||||
|
"userUnblocked": "已解除屏蔽用户 {}",
|
||||||
|
"userBlocked": "已屏蔽用户 {}"
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 70;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/account.dart';
|
||||||
|
|
||||||
class SnRelationshipProvider {
|
class SnRelationshipProvider {
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
@ -9,6 +10,15 @@ class SnRelationshipProvider {
|
|||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<SnRelationship?> getRelationship(int relatedId) async {
|
||||||
|
try {
|
||||||
|
final resp = await _sn.client.get('/cgi/id/users/me/relations/$relatedId');
|
||||||
|
return SnRelationship.fromJson(resp.data);
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateRelationship(
|
Future<void> updateRelationship(
|
||||||
int relatedId,
|
int relatedId,
|
||||||
int status,
|
int status,
|
||||||
|
@ -58,7 +58,6 @@ final _appRoutes = [
|
|||||||
path: '/write/:mode',
|
path: '/write/:mode',
|
||||||
name: 'postEditor',
|
name: 'postEditor',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => AppBackground(
|
||||||
isLessOptimization: true,
|
|
||||||
child: PostEditorScreen(
|
child: PostEditorScreen(
|
||||||
mode: state.pathParameters['mode']!,
|
mode: state.pathParameters['mode']!,
|
||||||
postEditId: int.tryParse(
|
postEditId: int.tryParse(
|
||||||
@ -77,7 +76,6 @@ final _appRoutes = [
|
|||||||
path: '/search',
|
path: '/search',
|
||||||
name: 'postSearch',
|
name: 'postSearch',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => const AppBackground(
|
||||||
isLessOptimization: true,
|
|
||||||
child: PostSearchScreen(),
|
child: PostSearchScreen(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -119,7 +117,6 @@ final _appRoutes = [
|
|||||||
path: '/:scope/:alias',
|
path: '/:scope/:alias',
|
||||||
name: 'chatRoom',
|
name: 'chatRoom',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => AppBackground(
|
||||||
isLessOptimization: true,
|
|
||||||
child: ChatRoomScreen(
|
child: ChatRoomScreen(
|
||||||
scope: state.pathParameters['scope']!,
|
scope: state.pathParameters['scope']!,
|
||||||
alias: state.pathParameters['alias']!,
|
alias: state.pathParameters['alias']!,
|
||||||
@ -159,7 +156,6 @@ final _appRoutes = [
|
|||||||
secondaryAnimation: secondaryAnimation,
|
secondaryAnimation: secondaryAnimation,
|
||||||
fillColor: Colors.transparent,
|
fillColor: Colors.transparent,
|
||||||
child: AppBackground(
|
child: AppBackground(
|
||||||
isLessOptimization: true,
|
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -195,7 +191,6 @@ final _appRoutes = [
|
|||||||
secondaryAnimation: secondaryAnimation,
|
secondaryAnimation: secondaryAnimation,
|
||||||
fillColor: Colors.transparent,
|
fillColor: Colors.transparent,
|
||||||
child: AppBackground(
|
child: AppBackground(
|
||||||
isLessOptimization: true,
|
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -39,7 +39,7 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
void _showAbuseReportDialog() {
|
void _showAbuseReportDialog() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _AbuseReportDialog(),
|
builder: (context) => AbuseReportDialog(),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value == true && mounted) {
|
if (value == true && mounted) {
|
||||||
_fetchReports();
|
_fetchReports();
|
||||||
@ -91,19 +91,29 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AbuseReportDialog extends StatefulWidget {
|
class AbuseReportDialog extends StatefulWidget {
|
||||||
const _AbuseReportDialog({super.key});
|
final String? resourceLocation;
|
||||||
|
|
||||||
|
const AbuseReportDialog({super.key, this.resourceLocation});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_AbuseReportDialog> createState() => _AbuseReportDialogState();
|
State<AbuseReportDialog> createState() => _AbuseReportDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AbuseReportDialogState extends State<_AbuseReportDialog> {
|
class _AbuseReportDialogState extends State<AbuseReportDialog> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
final _resourceController = TextEditingController();
|
final _resourceController = TextEditingController();
|
||||||
final _reasonController = TextEditingController();
|
final _reasonController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.resourceLocation != null) {
|
||||||
|
_resourceController.text = widget.resourceLocation!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
dispose() {
|
dispose() {
|
||||||
_resourceController.dispose();
|
_resourceController.dispose();
|
||||||
@ -144,6 +154,7 @@ class _AbuseReportDialogState extends State<_AbuseReportDialog> {
|
|||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _resourceController,
|
controller: _resourceController,
|
||||||
|
readOnly: widget.resourceLocation != null,
|
||||||
maxLength: null,
|
maxLength: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.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:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@ -118,19 +119,19 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.logout),
|
leading: const Icon(Symbols.logout),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
context
|
final confirm = await context.showConfirmDialog(
|
||||||
.showConfirmDialog(
|
|
||||||
'accountLogoutConfirmTitle'.tr(),
|
'accountLogoutConfirmTitle'.tr(),
|
||||||
'accountLogoutConfirm'.tr(),
|
'accountLogoutConfirm'.tr(),
|
||||||
)
|
);
|
||||||
.then((value) {
|
|
||||||
if(!context.mounted) return;
|
if (!confirm) return;
|
||||||
if (value) ua.logoutUser();
|
if (!context.mounted) return;
|
||||||
final ws = context.read<WebSocketProvider>();
|
ua.logoutUser();
|
||||||
ws.disconnect();
|
final ws = context.read<WebSocketProvider>();
|
||||||
Hive.deleteFromDisk();
|
ws.disconnect();
|
||||||
});
|
await Hive.deleteFromDisk();
|
||||||
|
await Hive.initFlutter();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
@ -31,7 +31,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final resp = await sn.client.get('/cgi/co/publishers');
|
final resp = await sn.client.get('/cgi/co/publishers/me');
|
||||||
final List<SnPublisher> out = List<SnPublisher>.from(
|
final List<SnPublisher> out = List<SnPublisher>.from(
|
||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []);
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []);
|
||||||
|
|
||||||
|
@ -145,6 +145,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
padding: const WidgetStatePropertyAll(
|
padding: const WidgetStatePropertyAll(
|
||||||
EdgeInsets.symmetric(horizontal: 24),
|
EdgeInsets.symmetric(horizontal: 24),
|
||||||
),
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
_searchTerm = value;
|
_searchTerm = value;
|
||||||
},
|
},
|
||||||
|
@ -14,42 +14,54 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/account/account_image.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/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
import '../../providers/relationship.dart';
|
||||||
|
import '../abuse_report.dart';
|
||||||
|
|
||||||
class PostPublisherScreen extends StatefulWidget {
|
class PostPublisherScreen extends StatefulWidget {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
const PostPublisherScreen({super.key, required this.name});
|
const PostPublisherScreen({super.key, required this.name});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PostPublisherScreen> createState() => _PostPublisherScreenState();
|
State<PostPublisherScreen> createState() => _PostPublisherScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostPublisherScreenState extends State<PostPublisherScreen>
|
class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTickerProviderStateMixin {
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
late final ScrollController _scrollController = ScrollController();
|
late final ScrollController _scrollController = ScrollController();
|
||||||
late final TabController _tabController =
|
late final TabController _tabController = TabController(length: 3, vsync: this);
|
||||||
TabController(length: 3, vsync: this);
|
|
||||||
|
|
||||||
SnPublisher? _publisher;
|
SnPublisher? _publisher;
|
||||||
SnAccount? _account;
|
SnAccount? _account;
|
||||||
|
SnRelationship? _accountRelationship;
|
||||||
|
SnRealm? _realm;
|
||||||
|
|
||||||
Future<void> _fetchPublisher() async {
|
Future<void> _fetchPublisher() async {
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/publishers/${widget.name}');
|
final resp = await sn.client.get('/cgi/co/publishers/${widget.name}');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_publisher = SnPublisher.fromJson(resp.data);
|
_publisher = SnPublisher.fromJson(resp.data);
|
||||||
_account = await ud.getAccount(_publisher?.accountId);
|
_account = await ud.getAccount(_publisher?.accountId);
|
||||||
|
_accountRelationship = await rel.getRelationship(_account!.id);
|
||||||
|
if (_publisher?.realmId != null && _publisher!.realmId != 0) {
|
||||||
|
final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}');
|
||||||
|
_realm = SnRealm.fromJson(resp.data);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err).then((_) {
|
context.showErrorDialog(err).then((_) {
|
||||||
if (mounted) Navigator.pop(context);
|
if (mounted) Navigator.pop(context);
|
||||||
});
|
});
|
||||||
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@ -114,14 +126,12 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
double _appBarBlur = 0.0;
|
double _appBarBlur = 0.0;
|
||||||
|
|
||||||
late final _appBarWidth = MediaQuery.of(context).size.width;
|
late final _appBarWidth = MediaQuery.of(context).size.width;
|
||||||
late final _appBarHeight =
|
late final _appBarHeight = (_appBarWidth * kBannerAspectRatio).roundToDouble();
|
||||||
(_appBarWidth * kBannerAspectRatio).roundToDouble();
|
|
||||||
|
|
||||||
void _updateAppBarBlur() {
|
void _updateAppBarBlur() {
|
||||||
if (_scrollController.offset > _appBarHeight) return;
|
if (_scrollController.offset > _appBarHeight) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_appBarBlur =
|
_appBarBlur = (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
|
||||||
(_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,11 +166,73 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isWorking = false;
|
||||||
|
|
||||||
|
Future<void> _blockPublisher() async {
|
||||||
|
if (_isWorking) return;
|
||||||
|
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'publisherBlockHint'.tr(args: ['@${_publisher?.name ?? 'unknown'.tr()}']),
|
||||||
|
'publisherBlockHintDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
setState(() => _isWorking = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/id/users/me/relations/block', data: {
|
||||||
|
'related': _account!.name,
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}']));
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isWorking = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _unblockPublisher() async {
|
||||||
|
if (_isWorking) return;
|
||||||
|
|
||||||
|
setState(() => _isWorking = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
|
await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {});
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}']));
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isWorking = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _updateFetchType() {
|
void _updateFetchType() {
|
||||||
_posts.clear();
|
_posts.clear();
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showAbuseReportDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AbuseReportDialog(
|
||||||
|
resourceLocation: 'pub:${_publisher?.name}',
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true && mounted) {
|
||||||
|
_fetchPosts();
|
||||||
|
context.showSnackbar('abuseReportSubmitted'.tr());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -215,10 +287,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: _publisher!.nick,
|
text: _publisher!.nick,
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
.textTheme
|
|
||||||
.titleLarge!
|
|
||||||
.copyWith(
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
),
|
),
|
||||||
@ -226,10 +295,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
const TextSpan(text: '\n'),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '@${_publisher!.name}',
|
text: '@${_publisher!.name}',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
),
|
),
|
||||||
@ -241,14 +307,19 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
? Stack(
|
? Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
UniversalImage(
|
if (_publisher!.banner.isNotEmpty)
|
||||||
sn.getAttachmentUrl(_publisher!.banner),
|
UniversalImage(
|
||||||
fit: BoxFit.cover,
|
sn.getAttachmentUrl(_publisher!.banner),
|
||||||
height: imageHeight,
|
fit: BoxFit.cover,
|
||||||
width: _appBarWidth,
|
height: imageHeight,
|
||||||
cacheHeight: imageHeight,
|
width: _appBarWidth,
|
||||||
cacheWidth: _appBarWidth,
|
cacheHeight: imageHeight,
|
||||||
),
|
cacheWidth: _appBarWidth,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -288,14 +359,11 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
const Gap(16),
|
const Gap(16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_publisher!.nick,
|
_publisher!.nick,
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
.textTheme
|
|
||||||
.titleMedium,
|
|
||||||
).bold(),
|
).bold(),
|
||||||
Text('@${_publisher!.name}').fontSize(13),
|
Text('@${_publisher!.name}').fontSize(13),
|
||||||
],
|
],
|
||||||
@ -306,9 +374,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
elevation: WidgetStatePropertyAll(0),
|
elevation: WidgetStatePropertyAll(0),
|
||||||
),
|
),
|
||||||
onPressed: _isSubscribing
|
onPressed: _isSubscribing ? null : _toggleSubscription,
|
||||||
? null
|
|
||||||
: _toggleSubscription,
|
|
||||||
label: Text('subscribe').tr(),
|
label: Text('subscribe').tr(),
|
||||||
icon: const Icon(Symbols.add),
|
icon: const Icon(Symbols.add),
|
||||||
)
|
)
|
||||||
@ -317,17 +383,54 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
elevation: WidgetStatePropertyAll(0),
|
elevation: WidgetStatePropertyAll(0),
|
||||||
),
|
),
|
||||||
onPressed: _isSubscribing
|
onPressed: _isSubscribing ? null : _toggleSubscription,
|
||||||
? null
|
|
||||||
: _toggleSubscription,
|
|
||||||
label: Text('unsubscribe').tr(),
|
label: Text('unsubscribe').tr(),
|
||||||
icon: const Icon(Symbols.remove),
|
icon: const Icon(Symbols.remove),
|
||||||
),
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.flag),
|
||||||
|
const Gap(16),
|
||||||
|
Text('report').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => _showAbuseReportDialog(),
|
||||||
|
),
|
||||||
|
if (_accountRelationship?.status != 2)
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: _blockPublisher,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.block),
|
||||||
|
const Gap(16),
|
||||||
|
Text('friendBlock').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: _unblockPublisher,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.block),
|
||||||
|
const Gap(16),
|
||||||
|
Text('friendUnblock').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(right: 8),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Text(_publisher!.description)
|
Text(_publisher!.description).padding(horizontal: 8),
|
||||||
.padding(horizontal: 8),
|
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
@ -335,10 +438,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.calendar_add_on),
|
const Icon(Symbols.calendar_add_on),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text('publisherJoinedAt').tr(args: [
|
Text('publisherJoinedAt')
|
||||||
DateFormat('y/M/d')
|
.tr(args: [DateFormat('y/M/d').format(_publisher!.createdAt)]),
|
||||||
.format(_publisher!.createdAt)
|
|
||||||
]),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@ -346,11 +447,30 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
const Icon(Symbols.trending_up),
|
const Icon(Symbols.trending_up),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text('publisherSocialPointTotal').plural(
|
Text('publisherSocialPointTotal').plural(
|
||||||
_publisher!.totalUpvote -
|
_publisher!.totalUpvote - _publisher!.totalDownvote,
|
||||||
_publisher!.totalDownvote,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (_realm != null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.group_work),
|
||||||
|
const Gap(8),
|
||||||
|
InkWell(
|
||||||
|
child: Text('publisherAffiliatedBy').tr(args: [
|
||||||
|
'@${_realm?.alias ?? 'unknown'}',
|
||||||
|
]),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'realmDetail',
|
||||||
|
pathParameters: {'alias': _realm!.alias},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
AccountImage(content: _realm?.avatar, radius: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.tools_wrench),
|
const Icon(Symbols.tools_wrench),
|
||||||
@ -369,8 +489,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
AccountImage(
|
AccountImage(content: _account?.avatar, radius: 8),
|
||||||
content: _account?.avatar, radius: 8),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -447,6 +566,7 @@ class _PublisherPostList extends StatelessWidget {
|
|||||||
final void Function() fetchPosts;
|
final void Function() fetchPosts;
|
||||||
final void Function(int index, SnPost data) onChanged;
|
final void Function(int index, SnPost data) onChanged;
|
||||||
final void Function() onDeleted;
|
final void Function() onDeleted;
|
||||||
|
|
||||||
const _PublisherPostList({
|
const _PublisherPostList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.isBusy,
|
required this.isBusy,
|
||||||
|
@ -6,16 +6,15 @@ 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/providers/userinfo.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.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:surface/widgets/unauthorized_hint.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
import '../providers/userinfo.dart';
|
|
||||||
import '../widgets/unauthorized_hint.dart';
|
|
||||||
|
|
||||||
class RealmScreen extends StatefulWidget {
|
class RealmScreen extends StatefulWidget {
|
||||||
const RealmScreen({super.key});
|
const RealmScreen({super.key});
|
||||||
|
|
||||||
@ -101,9 +100,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
title: Text('screenRealm').tr(),
|
title: Text('screenRealm').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: !_isCompactView
|
icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
||||||
? const Icon(Symbols.view_list)
|
|
||||||
: const Icon(Symbols.view_module),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => _isCompactView = !_isCompactView);
|
setState(() => _isCompactView = !_isCompactView);
|
||||||
},
|
},
|
||||||
@ -129,8 +126,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
final realm = _realms![idx];
|
final realm = _realms![idx];
|
||||||
if (_isCompactView) {
|
if (_isCompactView) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
leading: AccountImage(
|
leading: AccountImage(
|
||||||
content: realm.avatar,
|
content: realm.avatar,
|
||||||
fallbackWidget: const Icon(Symbols.group, size: 20),
|
fallbackWidget: const Icon(Symbols.group, size: 20),
|
||||||
@ -201,9 +197,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
.colorScheme
|
|
||||||
.surfaceContainer,
|
|
||||||
child: (realm.banner?.isEmpty ?? true)
|
child: (realm.banner?.isEmpty ?? true)
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: AutoResizeUniversalImage(
|
: AutoResizeUniversalImage(
|
||||||
@ -217,8 +211,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
child: AccountImage(
|
child: AccountImage(
|
||||||
content: realm.avatar,
|
content: realm.avatar,
|
||||||
radius: 24,
|
radius: 24,
|
||||||
fallbackWidget:
|
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||||
const Icon(Symbols.group, size: 24),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -228,10 +221,8 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(realm.name).textStyle(
|
Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||||
Theme.of(context).textTheme.titleMedium!),
|
Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
Text(realm.description).textStyle(
|
|
||||||
Theme.of(context).textTheme.bodySmall!),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24, bottom: 14),
|
).padding(horizontal: 24, bottom: 14),
|
||||||
],
|
],
|
||||||
|
@ -13,8 +13,11 @@ import 'package:surface/widgets/account/account_image.dart';
|
|||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
import '../../types/post.dart';
|
||||||
|
|
||||||
class RealmDetailScreen extends StatefulWidget {
|
class RealmDetailScreen extends StatefulWidget {
|
||||||
final String alias;
|
final String alias;
|
||||||
|
|
||||||
const RealmDetailScreen({super.key, required this.alias});
|
const RealmDetailScreen({super.key, required this.alias});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -32,6 +35,24 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SnPublisher>? _publishers;
|
||||||
|
|
||||||
|
Future<void> _fetchPublishers() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/publishers?realm=${widget.alias}');
|
||||||
|
_publishers = List<SnPublisher>.from(
|
||||||
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (mounted) context.showErrorDialog(err);
|
||||||
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@ -40,7 +61,9 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchRealm();
|
_fetchRealm().then((_) {
|
||||||
|
_fetchPublishers();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -60,8 +83,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
// scroll view thinks it has not been scrolled.
|
// scroll view thinks it has not been scrolled.
|
||||||
// This is not necessary if the "headerSliverBuilder" only builds
|
// This is not necessary if the "headerSliverBuilder" only builds
|
||||||
// widgets that do not overlap the next sliver.
|
// widgets that do not overlap the next sliver.
|
||||||
handle:
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
|
||||||
sliver: SliverAppBar(
|
sliver: SliverAppBar(
|
||||||
title: Text(_realm?.name ?? 'loading'.tr()),
|
title: Text(_realm?.name ?? 'loading'.tr()),
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
@ -77,7 +99,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
},
|
},
|
||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
children: [
|
children: [
|
||||||
_RealmDetailHomeWidget(realm: _realm),
|
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers),
|
||||||
_RealmMemberListWidget(realm: _realm),
|
_RealmMemberListWidget(realm: _realm),
|
||||||
_RealmSettingsWidget(
|
_RealmSettingsWidget(
|
||||||
realm: _realm,
|
realm: _realm,
|
||||||
@ -95,7 +117,9 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
|
|
||||||
class _RealmDetailHomeWidget extends StatelessWidget {
|
class _RealmDetailHomeWidget extends StatelessWidget {
|
||||||
final SnRealm? realm;
|
final SnRealm? realm;
|
||||||
const _RealmDetailHomeWidget({super.key, required this.realm});
|
final List<SnPublisher>? publishers;
|
||||||
|
|
||||||
|
const _RealmDetailHomeWidget({super.key, required this.realm, this.publishers});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -118,6 +142,31 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
|||||||
).padding(horizontal: 24),
|
).padding(horizontal: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: publishers?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final ele = publishers![idx];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
leading: AccountImage(
|
||||||
|
content: ele.avatar,
|
||||||
|
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||||
|
),
|
||||||
|
title: Text(ele.nick),
|
||||||
|
subtitle: Text('@${ele.name}'),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'postPublisher',
|
||||||
|
pathParameters: {'name': ele.name},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -125,6 +174,7 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
|||||||
|
|
||||||
class _RealmMemberListWidget extends StatefulWidget {
|
class _RealmMemberListWidget extends StatefulWidget {
|
||||||
final SnRealm? realm;
|
final SnRealm? realm;
|
||||||
|
|
||||||
const _RealmMemberListWidget({super.key, this.realm});
|
const _RealmMemberListWidget({super.key, this.realm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -143,12 +193,10 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
try {
|
try {
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get(
|
final resp = await sn.client.get('/cgi/id/realms/${widget.realm!.alias}/members', queryParameters: {
|
||||||
'/cgi/id/realms/${widget.realm!.alias}/members',
|
'take': 10,
|
||||||
queryParameters: {
|
'offset': 0,
|
||||||
'take': 10,
|
});
|
||||||
'offset': 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
final out = List<SnRealmMember>.from(
|
final out = List<SnRealmMember>.from(
|
||||||
resp.data['data']?.map((e) => SnRealmMember.fromJson(e)) ?? [],
|
resp.data['data']?.map((e) => SnRealmMember.fromJson(e)) ?? [],
|
||||||
@ -236,12 +284,10 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
ud.getAccountFromCache(member.accountId)?.nick ??
|
ud.getAccountFromCache(member.accountId)?.nick ?? 'unknown'.tr(),
|
||||||
'unknown'.tr(),
|
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
ud.getAccountFromCache(member.accountId)?.name ??
|
ud.getAccountFromCache(member.accountId)?.name ?? 'unknown'.tr(),
|
||||||
'unknown'.tr(),
|
|
||||||
),
|
),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Symbols.person_remove),
|
icon: const Icon(Symbols.person_remove),
|
||||||
@ -257,6 +303,7 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
|
|
||||||
class _NewRealmMemberWidget extends StatefulWidget {
|
class _NewRealmMemberWidget extends StatefulWidget {
|
||||||
final SnRealm realm;
|
final SnRealm realm;
|
||||||
|
|
||||||
const _NewRealmMemberWidget({super.key, required this.realm});
|
const _NewRealmMemberWidget({super.key, required this.realm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -321,8 +368,7 @@ class _NewRealmMemberWidgetState extends State<_NewRealmMemberWidget> {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: _isBusy ? null : () => _performAction(),
|
onPressed: _isBusy ? null : () => _performAction(),
|
||||||
icon: Icon(Symbols.send),
|
icon: Icon(Symbols.send),
|
||||||
visualDensity:
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -337,8 +383,8 @@ class _NewRealmMemberWidgetState extends State<_NewRealmMemberWidget> {
|
|||||||
class _RealmSettingsWidget extends StatefulWidget {
|
class _RealmSettingsWidget extends StatefulWidget {
|
||||||
final SnRealm? realm;
|
final SnRealm? realm;
|
||||||
final Function() onUpdate;
|
final Function() onUpdate;
|
||||||
const _RealmSettingsWidget(
|
|
||||||
{super.key, required this.realm, required this.onUpdate});
|
const _RealmSettingsWidget({super.key, required this.realm, required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_RealmSettingsWidget> createState() => _RealmSettingsWidgetState();
|
State<_RealmSettingsWidget> createState() => _RealmSettingsWidgetState();
|
||||||
@ -382,6 +428,7 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
|
const Gap(16),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.edit),
|
leading: const Icon(Symbols.edit),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
@ -130,7 +130,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio: widget.data.firstOrNull?.metadata['ratio'] ?? 1,
|
aspectRatio: (widget.data.firstOrNull?.metadata['ratio'] ?? 1).toDouble(),
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
@ -142,7 +142,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
return Container(
|
return Container(
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: widget.data[idx]?.metadata['ratio'] ?? 1,
|
aspectRatio: (widget.data[idx]?.metadata['ratio'] ?? 1).toDouble(),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushTransparentRoute(
|
context.pushTransparentRoute(
|
||||||
|
@ -99,7 +99,7 @@ extension AppPromptExtension on BuildContext {
|
|||||||
|
|
||||||
if (exception.response != null) {
|
if (exception.response != null) {
|
||||||
content = Text(
|
content = Text(
|
||||||
'$preview\n\n(${exception.response?.statusCode}) ${exception.response?.data}',
|
'$preview\n\n${exception.requestOptions.uri.path}\n(${exception.response?.statusCode}) ${exception.response?.data}',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = Text(preview);
|
content = Text(preview);
|
||||||
|
@ -7,12 +7,11 @@ import 'package:responsive_framework/responsive_framework.dart';
|
|||||||
|
|
||||||
class AppBackground extends StatelessWidget {
|
class AppBackground extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final bool isLessOptimization;
|
|
||||||
final bool isRoot;
|
final bool isRoot;
|
||||||
|
|
||||||
const AppBackground({
|
const AppBackground({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.isLessOptimization = false,
|
|
||||||
this.isRoot = false,
|
this.isRoot = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -23,52 +22,25 @@ class AppBackground extends StatelessWidget {
|
|||||||
) {
|
) {
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
if (isLessOptimization) {
|
final size = MediaQuery.of(context).size;
|
||||||
final size = MediaQuery.of(context).size;
|
|
||||||
return Container(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
backgroundBlendMode: BlendMode.darken,
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
image: DecorationImage(
|
|
||||||
opacity: 0.2,
|
|
||||||
image: ResizeImage(
|
|
||||||
FileImage(imageFile),
|
|
||||||
width: (size.width * devicePixelRatio).round(),
|
|
||||||
height: (size.height * devicePixelRatio).round(),
|
|
||||||
policy: ResizeImagePolicy.fit,
|
|
||||||
),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: LayoutBuilder(
|
child: Container(
|
||||||
builder: (context, constraints) {
|
decoration: BoxDecoration(
|
||||||
return Container(
|
backgroundBlendMode: BlendMode.darken,
|
||||||
decoration: BoxDecoration(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
backgroundBlendMode: BlendMode.darken,
|
image: DecorationImage(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
opacity: 0.2,
|
||||||
image: DecorationImage(
|
image: ResizeImage(
|
||||||
opacity: 0.2,
|
FileImage(imageFile),
|
||||||
image: ResizeImage(
|
width: (size.width * devicePixelRatio).round(),
|
||||||
FileImage(imageFile),
|
height: (size.height * devicePixelRatio).round(),
|
||||||
width: (constraints.maxWidth * devicePixelRatio).round(),
|
policy: ResizeImagePolicy.fit,
|
||||||
height: (constraints.maxHeight * devicePixelRatio).round(),
|
|
||||||
policy: ResizeImagePolicy.fit,
|
|
||||||
),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: child,
|
fit: BoxFit.cover,
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -77,11 +49,9 @@ class AppBackground extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScaffoldMessenger(
|
return ScaffoldMessenger(
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future:
|
future: kIsWeb ? Future.value(null) : getApplicationDocumentsDirectory(),
|
||||||
kIsWeb ? Future.value(null) : getApplicationDocumentsDirectory(),
|
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (isRoot ||
|
if (isRoot || ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) {
|
||||||
ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) {
|
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final path = '${snapshot.data!.path}/app_background_image';
|
final path = '${snapshot.data!.path}/app_background_image';
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
|
Loading…
Reference in New Issue
Block a user