Compare commits
No commits in common. "e44320e0fe5bde7977a7a11a72ddb4f33999d0e0" and "e88dea8858e4668aaffee8d50ae1a7bf4455412c" have entirely different histories.
e44320e0fe
...
e88dea8858
@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Trigger Fediverse Scan
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: {{endpoint}}/cgi/co/admin/fediverse
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
@ -791,8 +791,5 @@
|
|||||||
"fieldAccountStatusClearAt": "Clear At",
|
"fieldAccountStatusClearAt": "Clear At",
|
||||||
"accountStatusNegative": "Negative",
|
"accountStatusNegative": "Negative",
|
||||||
"accountStatusNeutral": "Neutral",
|
"accountStatusNeutral": "Neutral",
|
||||||
"accountStatusPositive": "Positive",
|
"accountStatusPositive": "Positive"
|
||||||
"mixedFeed": "Mixed Feed",
|
|
||||||
"mixedFeedDescription": "The Explore screen may not only display the user's posts, but may also contain other content. However, this mode does not apply to classification and filtering.",
|
|
||||||
"filterFeed": "Exploring Adjust"
|
|
||||||
}
|
}
|
||||||
|
@ -789,8 +789,5 @@
|
|||||||
"fieldAccountStatusClearAt": "清除时间",
|
"fieldAccountStatusClearAt": "清除时间",
|
||||||
"accountStatusNegative": "负面",
|
"accountStatusNegative": "负面",
|
||||||
"accountStatusNeutral": "中性",
|
"accountStatusNeutral": "中性",
|
||||||
"accountStatusPositive": "正面",
|
"accountStatusPositive": "正面"
|
||||||
"mixedFeed": "混合推荐流",
|
|
||||||
"mixedFeedDescription": "探索页面可能不只会展示用户的帖子,更可能包含其他的内容。但该模式不适用分类和过滤。",
|
|
||||||
"filterFeed": "探索队列调整"
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ const kAppExpandPostLink = 'app_expand_post_link';
|
|||||||
const kAppExpandChatLink = 'app_expand_chat_link';
|
const kAppExpandChatLink = 'app_expand_chat_link';
|
||||||
const kAppRealmCompactView = 'app_realm_compact_view';
|
const kAppRealmCompactView = 'app_realm_compact_view';
|
||||||
const kAppCustomFonts = 'app_custom_fonts';
|
const kAppCustomFonts = 'app_custom_fonts';
|
||||||
const kAppMixedFeed = 'app_mixed_feed';
|
|
||||||
|
|
||||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||||
'settingsImageQualityLowest': FilterQuality.none,
|
'settingsImageQualityLowest': FilterQuality.none,
|
||||||
@ -82,18 +81,8 @@ class ConfigProvider extends ChangeNotifier {
|
|||||||
return prefs.getBool(kAppRealmCompactView) ?? false;
|
return prefs.getBool(kAppRealmCompactView) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get mixedFeed {
|
|
||||||
return prefs.getBool(kAppMixedFeed) ?? true;
|
|
||||||
}
|
|
||||||
|
|
||||||
set mixedFeed(bool value) {
|
|
||||||
prefs.setBool(kAppMixedFeed, value);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set realmCompactView(bool value) {
|
set realmCompactView(bool value) {
|
||||||
prefs.setBool(kAppRealmCompactView, value);
|
prefs.setBool(kAppRealmCompactView, value);
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set serverUrl(String url) {
|
set serverUrl(String url) {
|
||||||
|
@ -145,36 +145,6 @@ class SnPostContentProvider {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SnFeedEntry>> getFeed({int take = 20, DateTime? cursor}) async {
|
|
||||||
final resp =
|
|
||||||
await _sn.client.get('/cgi/co/recommendations/feed', queryParameters: {
|
|
||||||
'take': take,
|
|
||||||
if (cursor != null) 'cursor': cursor.toUtc().millisecondsSinceEpoch,
|
|
||||||
});
|
|
||||||
final List<SnFeedEntry> out =
|
|
||||||
List.from(resp.data.map((ele) => SnFeedEntry.fromJson(ele)));
|
|
||||||
|
|
||||||
List<SnPost> posts = List.empty(growable: true);
|
|
||||||
for (var idx = 0; idx < out.length; idx++) {
|
|
||||||
final ele = out[idx];
|
|
||||||
if (ele.type == 'interactive.post') {
|
|
||||||
posts.add(SnPost.fromJson(ele.data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
posts = await _preloadRelatedDataInBatch(posts);
|
|
||||||
|
|
||||||
var postsIdx = 0;
|
|
||||||
for (var idx = 0; idx < out.length; idx++) {
|
|
||||||
final ele = out[idx];
|
|
||||||
if (ele.type == 'interactive.post') {
|
|
||||||
out[idx] = ele.copyWith(data: posts[postsIdx].toJson());
|
|
||||||
postsIdx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<(List<SnPost>, int)> listPosts({
|
Future<(List<SnPost>, int)> listPosts({
|
||||||
int take = 10,
|
int take = 10,
|
||||||
int offset = 0,
|
int offset = 0,
|
||||||
|
@ -7,7 +7,6 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/sn_realm.dart';
|
import 'package:surface/providers/sn_realm.dart';
|
||||||
@ -18,7 +17,6 @@ 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/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/fediverse_post_item.dart';
|
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
@ -153,7 +151,6 @@ class _ExploreScreenState extends State<ExploreScreen>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.watch<ConfigProvider>();
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
@ -275,14 +272,6 @@ class _ExploreScreenState extends State<ExploreScreen>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onMixedFeedChanged: (flag) {
|
|
||||||
_listKey.currentState?.setRealm(null);
|
|
||||||
_listKey.currentState?.setCategory(null);
|
|
||||||
if (_showCategories && flag) {
|
|
||||||
_toggleShowCategories();
|
|
||||||
}
|
|
||||||
_listKey.currentState?.refreshPosts();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -306,11 +295,9 @@ class _ExploreScreenState extends State<ExploreScreen>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
onPressed: cfg.mixedFeed
|
onPressed: () {
|
||||||
? null
|
_toggleShowCategories();
|
||||||
: () {
|
},
|
||||||
_toggleShowCategories();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.search),
|
icon: const Icon(Symbols.search),
|
||||||
@ -320,78 +307,74 @@ class _ExploreScreenState extends State<ExploreScreen>
|
|||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
bottom: cfg.mixedFeed
|
bottom: TabBar(
|
||||||
? null
|
isScrollable: _showCategories,
|
||||||
: TabBar(
|
controller: _tabController,
|
||||||
isScrollable: _showCategories,
|
tabs: _showCategories
|
||||||
controller: _tabController,
|
? [
|
||||||
tabs: _showCategories
|
for (final category in _categories)
|
||||||
? [
|
Tab(
|
||||||
for (final category in _categories)
|
child: Row(
|
||||||
Tab(
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Row(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
crossAxisAlignment:
|
Icon(
|
||||||
CrossAxisAlignment.center,
|
kCategoryIcons[category.alias] ??
|
||||||
children: [
|
Symbols.question_mark,
|
||||||
Icon(
|
color: Theme.of(context)
|
||||||
kCategoryIcons[category.alias] ??
|
.appBarTheme
|
||||||
Symbols.question_mark,
|
.foregroundColor!,
|
||||||
color: Theme.of(context)
|
),
|
||||||
.appBarTheme
|
const Gap(8),
|
||||||
.foregroundColor!,
|
Flexible(
|
||||||
),
|
child: Text(
|
||||||
const Gap(8),
|
'postCategory${category.alias.capitalize()}'
|
||||||
Flexible(
|
.trExists()
|
||||||
child: Text(
|
? 'postCategory${category.alias.capitalize()}'
|
||||||
'postCategory${category.alias.capitalize()}'
|
.tr()
|
||||||
.trExists()
|
: category.name,
|
||||||
? 'postCategory${category.alias.capitalize()}'
|
maxLines: 1,
|
||||||
.tr()
|
).textColor(
|
||||||
: category.name,
|
Theme.of(context)
|
||||||
maxLines: 1,
|
.appBarTheme
|
||||||
).textColor(
|
.foregroundColor!,
|
||||||
Theme.of(context)
|
|
||||||
.appBarTheme
|
|
||||||
.foregroundColor!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
: [
|
),
|
||||||
for (final channel in kPostChannels)
|
),
|
||||||
Tab(
|
]
|
||||||
child: Row(
|
: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
for (final channel in kPostChannels)
|
||||||
crossAxisAlignment:
|
Tab(
|
||||||
CrossAxisAlignment.center,
|
child: Row(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
Icon(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
kPostChannelIcons[
|
children: [
|
||||||
kPostChannels.indexOf(channel)],
|
Icon(
|
||||||
size: 20,
|
kPostChannelIcons[
|
||||||
color: Theme.of(context)
|
kPostChannels.indexOf(channel)],
|
||||||
|
size: 20,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.appBarTheme
|
||||||
|
.foregroundColor,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'postChannel$channel',
|
||||||
|
maxLines: 1,
|
||||||
|
).tr().textColor(
|
||||||
|
Theme.of(context)
|
||||||
.appBarTheme
|
.appBarTheme
|
||||||
.foregroundColor,
|
.foregroundColor,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
'postChannel$channel',
|
|
||||||
maxLines: 1,
|
|
||||||
).tr().textColor(
|
|
||||||
Theme.of(context)
|
|
||||||
.appBarTheme
|
|
||||||
.foregroundColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -416,22 +399,21 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
|
|
||||||
SnRealm? get realm => _selectedRealm;
|
SnRealm? get realm => _selectedRealm;
|
||||||
|
|
||||||
final List<SnFeedEntry> _feed = List.empty(growable: true);
|
final List<SnPost> _posts = List.empty(growable: true);
|
||||||
SnRealm? _selectedRealm;
|
SnRealm? _selectedRealm;
|
||||||
String? _selectedChannel;
|
String? _selectedChannel;
|
||||||
SnPostCategory? _selectedCategory;
|
SnPostCategory? _selectedCategory;
|
||||||
bool _hasLoadedAll = false;
|
int? _postCount;
|
||||||
|
|
||||||
// Called when using regular feed
|
|
||||||
Future<void> _fetchPosts() async {
|
Future<void> _fetchPosts() async {
|
||||||
if (_hasLoadedAll) return;
|
if (_postCount != null && _posts.length >= _postCount!) return;
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
final result = await pt.listPosts(
|
final result = await pt.listPosts(
|
||||||
take: 10,
|
take: 10,
|
||||||
offset: _feed.length,
|
offset: _posts.length,
|
||||||
categories: _selectedCategory != null ? [_selectedCategory!.alias] : null,
|
categories: _selectedCategory != null ? [_selectedCategory!.alias] : null,
|
||||||
channel: _selectedChannel,
|
channel: _selectedChannel,
|
||||||
realm: _selectedRealm?.alias,
|
realm: _selectedRealm?.alias,
|
||||||
@ -440,31 +422,8 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
final postCount = result.$2;
|
_postCount = result.$2;
|
||||||
_feed.addAll(
|
_posts.addAll(out);
|
||||||
out.map((ele) => SnFeedEntry(
|
|
||||||
type: 'interactive.post',
|
|
||||||
data: ele.toJson(),
|
|
||||||
createdAt: ele.createdAt)),
|
|
||||||
);
|
|
||||||
_hasLoadedAll = postCount >= _feed.length;
|
|
||||||
|
|
||||||
if (mounted) setState(() => _isBusy = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when mixed feed is enabled
|
|
||||||
Future<void> _fetchFeed() async {
|
|
||||||
if (_hasLoadedAll) return;
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
|
||||||
final result = await pt.getFeed(cursor: _feed.lastOrNull?.createdAt);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
_feed.addAll(result);
|
|
||||||
_hasLoadedAll = result.isEmpty;
|
|
||||||
|
|
||||||
if (mounted) setState(() => _isBusy = false);
|
if (mounted) setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
@ -485,14 +444,9 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refreshPosts() {
|
Future<void> refreshPosts() {
|
||||||
_hasLoadedAll = false;
|
_postCount = null;
|
||||||
_feed.clear();
|
_posts.clear();
|
||||||
final cfg = context.read<ConfigProvider>();
|
return _fetchPosts();
|
||||||
if (cfg.mixedFeed) {
|
|
||||||
return _fetchFeed();
|
|
||||||
} else {
|
|
||||||
return _fetchPosts();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -503,48 +457,64 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.watch<ConfigProvider>();
|
return Column(
|
||||||
return MediaQuery.removePadding(
|
children: [
|
||||||
context: context,
|
if (_selectedCategory != null)
|
||||||
removeTop: true,
|
MaterialBanner(
|
||||||
child: RefreshIndicator(
|
content: Text(
|
||||||
displacement: 40 + MediaQuery.of(context).padding.top,
|
'postFilterWithCategory'.tr(args: [
|
||||||
onRefresh: () => refreshPosts(),
|
'postCategory${_selectedCategory!.alias.capitalize()}'.trExists()
|
||||||
child: InfiniteList(
|
? 'postCategory${_selectedCategory!.alias.capitalize()}'
|
||||||
padding: EdgeInsets.only(top: 8),
|
.tr()
|
||||||
itemCount: _feed.length,
|
: _selectedCategory!.name,
|
||||||
isLoading: _isBusy,
|
]),
|
||||||
centerLoading: true,
|
),
|
||||||
hasReachedMax: _hasLoadedAll,
|
leading: Icon(kCategoryIcons[_selectedCategory!.alias] ??
|
||||||
onFetchData: cfg.mixedFeed ? _fetchFeed : _fetchPosts,
|
Symbols.question_mark),
|
||||||
itemBuilder: (context, idx) {
|
actions: [
|
||||||
final ele = _feed[idx];
|
IconButton(
|
||||||
switch (ele.type) {
|
icon: const Icon(Symbols.clear),
|
||||||
case 'interactive.post':
|
onPressed: () {
|
||||||
return OpenablePostItem(
|
setState(() => _selectedCategory = null);
|
||||||
data: SnPost.fromJson(ele.data),
|
refreshPosts();
|
||||||
maxWidth: 640,
|
},
|
||||||
onChanged: (data) {
|
),
|
||||||
setState(() {
|
],
|
||||||
_feed[idx] = _feed[idx].copyWith(data: data.toJson());
|
padding: const EdgeInsets.only(left: 20, right: 4),
|
||||||
});
|
),
|
||||||
},
|
Expanded(
|
||||||
onDeleted: () {
|
child: MediaQuery.removePadding(
|
||||||
refreshPosts();
|
context: context,
|
||||||
},
|
removeTop: true,
|
||||||
);
|
child: RefreshIndicator(
|
||||||
case 'fediverse.post':
|
displacement: 40 + MediaQuery.of(context).padding.top,
|
||||||
return FediversePostWidget(
|
onRefresh: () => refreshPosts(),
|
||||||
data: SnFediversePost.fromJson(ele.data),
|
child: InfiniteList(
|
||||||
maxWidth: 640,
|
padding: EdgeInsets.only(top: 8),
|
||||||
);
|
itemCount: _posts.length,
|
||||||
default:
|
isLoading: _isBusy,
|
||||||
return Placeholder();
|
centerLoading: true,
|
||||||
}
|
hasReachedMax:
|
||||||
},
|
_postCount != null && _posts.length >= _postCount!,
|
||||||
separatorBuilder: (_, __) => const Gap(8),
|
onFetchData: _fetchPosts,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
return OpenablePostItem(
|
||||||
|
data: _posts[idx],
|
||||||
|
maxWidth: 640,
|
||||||
|
onChanged: (data) {
|
||||||
|
setState(() => _posts[idx] = data);
|
||||||
|
},
|
||||||
|
onDeleted: () {
|
||||||
|
refreshPosts();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -552,71 +522,54 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
class _PostListRealmPopup extends StatelessWidget {
|
class _PostListRealmPopup extends StatelessWidget {
|
||||||
final List<SnRealm>? realms;
|
final List<SnRealm>? realms;
|
||||||
final Function(SnRealm?) onUpdate;
|
final Function(SnRealm?) onUpdate;
|
||||||
final Function(bool) onMixedFeedChanged;
|
|
||||||
|
|
||||||
const _PostListRealmPopup({
|
const _PostListRealmPopup({
|
||||||
required this.realms,
|
required this.realms,
|
||||||
required this.onUpdate,
|
required this.onUpdate,
|
||||||
required this.onMixedFeedChanged,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.watch<ConfigProvider>();
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.tune, size: 24),
|
const Icon(Symbols.face, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('filterFeed', style: Theme.of(context).textTheme.titleLarge)
|
Text('accountRealms', style: Theme.of(context).textTheme.titleLarge)
|
||||||
.tr(),
|
.tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(Symbols.merge_type),
|
leading: const Icon(Symbols.close),
|
||||||
|
title: Text('postInGlobal').tr(),
|
||||||
|
subtitle: Text('postViewInGlobalDescription').tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('mixedFeed').tr(),
|
onTap: () {
|
||||||
subtitle: Text('mixedFeedDescription').tr(),
|
onUpdate.call(null);
|
||||||
value: cfg.mixedFeed,
|
Navigator.pop(context);
|
||||||
onChanged: (value) {
|
|
||||||
cfg.mixedFeed = value;
|
|
||||||
onMixedFeedChanged.call(value);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!cfg.mixedFeed)
|
const Divider(height: 1),
|
||||||
ListTile(
|
Expanded(
|
||||||
leading: const Icon(Symbols.close),
|
child: ListView.builder(
|
||||||
title: Text('postInGlobal').tr(),
|
itemCount: realms?.length ?? 0,
|
||||||
subtitle: Text('postViewInGlobalDescription').tr(),
|
itemBuilder: (context, idx) {
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
final realm = realms![idx];
|
||||||
onTap: () {
|
return ListTile(
|
||||||
onUpdate.call(null);
|
title: Text(realm.name),
|
||||||
Navigator.pop(context);
|
subtitle: Text('@${realm.alias}'),
|
||||||
|
leading: AccountImage(content: realm.avatar, radius: 18),
|
||||||
|
onTap: () {
|
||||||
|
onUpdate.call(realm);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!cfg.mixedFeed) const Divider(height: 1),
|
),
|
||||||
if (!cfg.mixedFeed)
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: realms?.length ?? 0,
|
|
||||||
itemBuilder: (context, idx) {
|
|
||||||
final realm = realms![idx];
|
|
||||||
return ListTile(
|
|
||||||
title: Text(realm.name),
|
|
||||||
subtitle: Text('@${realm.alias}'),
|
|
||||||
leading: AccountImage(content: realm.avatar, radius: 18),
|
|
||||||
onTap: () {
|
|
||||||
onUpdate.call(realm);
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:html/dom.dart' as dom;
|
import 'package:html/dom.dart' as dom;
|
||||||
@ -9,9 +10,9 @@ 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/news.dart';
|
import 'package:surface/types/news.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/html.dart';
|
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class NewsDetailScreen extends StatefulWidget {
|
class NewsDetailScreen extends StatefulWidget {
|
||||||
@ -44,6 +45,104 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _parseHtmlToWidgets(Iterable<dom.Element>? elements) {
|
||||||
|
if (elements == null) return [];
|
||||||
|
|
||||||
|
final List<Widget> widgets = [];
|
||||||
|
|
||||||
|
for (final node in elements) {
|
||||||
|
switch (node.localName) {
|
||||||
|
case 'h1':
|
||||||
|
case 'h2':
|
||||||
|
case 'h3':
|
||||||
|
case 'h4':
|
||||||
|
case 'h5':
|
||||||
|
case 'h6':
|
||||||
|
widgets.add(Text(node.text.trim(), style: Theme.of(context).textTheme.titleMedium));
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
if (node.text.trim().isEmpty) continue;
|
||||||
|
widgets.add(
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: node.text.trim(),
|
||||||
|
children: [
|
||||||
|
for (final child in node.children)
|
||||||
|
switch (child.localName) {
|
||||||
|
'a' => TextSpan(
|
||||||
|
text: child.text.trim(),
|
||||||
|
style: const TextStyle(decoration: TextDecoration.underline),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
launchUrlString(child.attributes['href']!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => TextSpan(text: child.text.trim()),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
// drop single link
|
||||||
|
break;
|
||||||
|
case 'div':
|
||||||
|
// ignore div text, normally it is not meaningful
|
||||||
|
widgets.addAll(_parseHtmlToWidgets(node.children));
|
||||||
|
break;
|
||||||
|
case 'hr':
|
||||||
|
widgets.add(const Divider());
|
||||||
|
break;
|
||||||
|
case 'img':
|
||||||
|
var src = node.attributes['src'];
|
||||||
|
if (src == null) break;
|
||||||
|
final width = double.tryParse(node.attributes['width'] ?? 'null');
|
||||||
|
final height = double.tryParse(node.attributes['height'] ?? 'null');
|
||||||
|
final ratio = width != null && height != null ? width / height : 1.0;
|
||||||
|
if (src.startsWith('//')) {
|
||||||
|
src = 'https:$src';
|
||||||
|
} else if (!src.startsWith('http')) {
|
||||||
|
final baseUri = Uri.parse(_article!.url);
|
||||||
|
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
||||||
|
src = '$baseUrl/$src';
|
||||||
|
}
|
||||||
|
widgets.add(
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: ratio,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
height: height ?? double.infinity,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: AutoResizeUniversalImage(
|
||||||
|
src,
|
||||||
|
fit: width != null && height != null ? BoxFit.cover : BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
widgets.addAll(_parseHtmlToWidgets(node.children));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -64,9 +163,7 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
|||||||
MaterialBanner(
|
MaterialBanner(
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
leading: const Icon(Icons.info),
|
leading: const Icon(Icons.info),
|
||||||
content: Text(_isReadingFromReader
|
content: Text(_isReadingFromReader ? 'newsReadingFromReader'.tr() : 'newsReadingFromOriginal'.tr()),
|
||||||
? 'newsReadingFromReader'.tr()
|
|
||||||
: 'newsReadingFromOriginal'.tr()),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text('newsReadingProviderSwap').tr(),
|
child: Text('newsReadingProviderSwap').tr(),
|
||||||
@ -85,41 +182,28 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Text(_article!.title,
|
Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
|
||||||
style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
Builder(builder: (context) {
|
Builder(builder: (context) {
|
||||||
final htmlDescription = parse(_article!.description);
|
final htmlDescription = parse(_article!.description);
|
||||||
return Text(
|
return Text(
|
||||||
htmlDescription.children
|
htmlDescription.children.map((ele) => ele.text.trim()).join(),
|
||||||
.map((ele) => ele.text.trim())
|
|
||||||
.join(),
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
Builder(builder: (context) {
|
Builder(builder: (context) {
|
||||||
final date =
|
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||||
_article!.publishedAt ?? _article!.createdAt;
|
|
||||||
return Row(
|
return Row(
|
||||||
spacing: 2,
|
spacing: 2,
|
||||||
children: [
|
children: [
|
||||||
Text(DateFormat().format(date)).textStyle(
|
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
Theme.of(context).textTheme.bodySmall!),
|
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
||||||
Text(' · ')
|
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
.textStyle(
|
|
||||||
Theme.of(context).textTheme.bodySmall!)
|
|
||||||
.bold(),
|
|
||||||
Text(RelativeTime(context).format(date)).textStyle(
|
|
||||||
Theme.of(context).textTheme.bodySmall!),
|
|
||||||
],
|
],
|
||||||
).opacity(0.75);
|
).opacity(0.75);
|
||||||
}),
|
}),
|
||||||
Text('newsDisclaimer')
|
Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
|
||||||
.tr()
|
|
||||||
.textStyle(Theme.of(context).textTheme.bodySmall!)
|
|
||||||
.opacity(0.75),
|
|
||||||
const Divider(),
|
const Divider(),
|
||||||
...parseHtmlToWidgets(
|
..._parseHtmlToWidgets(_articleFragment!.children),
|
||||||
context, _articleFragment!.children),
|
|
||||||
const Divider(),
|
const Divider(),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -127,8 +211,7 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Reference from original website',
|
'Reference from original website',
|
||||||
style: TextStyle(
|
style: TextStyle(decoration: TextDecoration.underline),
|
||||||
decoration: TextDecoration.underline),
|
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Icon(Icons.launch, size: 16),
|
Icon(Icons.launch, size: 16),
|
||||||
|
@ -166,53 +166,3 @@ abstract class SnSubscription with _$SnSubscription {
|
|||||||
factory SnSubscription.fromJson(Map<String, Object?> json) =>
|
factory SnSubscription.fromJson(Map<String, Object?> json) =>
|
||||||
_$SnSubscriptionFromJson(json);
|
_$SnSubscriptionFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
|
||||||
abstract class SnFeedEntry with _$SnFeedEntry {
|
|
||||||
const factory SnFeedEntry({
|
|
||||||
required String type,
|
|
||||||
required Map<String, dynamic> data,
|
|
||||||
required DateTime createdAt,
|
|
||||||
}) = _SnFeedEntry;
|
|
||||||
|
|
||||||
factory SnFeedEntry.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnFeedEntryFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
abstract class SnFediversePost with _$SnFediversePost {
|
|
||||||
const factory SnFediversePost({
|
|
||||||
required int id,
|
|
||||||
required DateTime createdAt,
|
|
||||||
required DateTime updatedAt,
|
|
||||||
required DateTime? deletedAt,
|
|
||||||
required String identifier,
|
|
||||||
required String origin,
|
|
||||||
required String content,
|
|
||||||
required String language,
|
|
||||||
required List<String> images,
|
|
||||||
required SnFediverseUser user,
|
|
||||||
required int userId,
|
|
||||||
}) = _SnFediversePost;
|
|
||||||
|
|
||||||
factory SnFediversePost.fromJson(Map<String, Object?> json) =>
|
|
||||||
_$SnFediversePostFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
abstract class SnFediverseUser with _$SnFediverseUser {
|
|
||||||
const factory SnFediverseUser({
|
|
||||||
required int id,
|
|
||||||
required DateTime createdAt,
|
|
||||||
required DateTime updatedAt,
|
|
||||||
required DateTime? deletedAt,
|
|
||||||
required String identifier,
|
|
||||||
required String origin,
|
|
||||||
required String avatar,
|
|
||||||
required String name,
|
|
||||||
required String nick,
|
|
||||||
}) = _SnFediverseUser;
|
|
||||||
|
|
||||||
factory SnFediverseUser.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnFediverseUserFromJson(json);
|
|
||||||
}
|
|
||||||
|
@ -3120,883 +3120,4 @@ class __$SnSubscriptionCopyWithImpl<$Res>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SnFeedEntry {
|
|
||||||
String get type;
|
|
||||||
Map<String, dynamic> get data;
|
|
||||||
DateTime get createdAt;
|
|
||||||
|
|
||||||
/// Create a copy of SnFeedEntry
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnFeedEntryCopyWith<SnFeedEntry> get copyWith =>
|
|
||||||
_$SnFeedEntryCopyWithImpl<SnFeedEntry>(this as SnFeedEntry, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SnFeedEntry to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is SnFeedEntry &&
|
|
||||||
(identical(other.type, type) || other.type == type) &&
|
|
||||||
const DeepCollectionEquality().equals(other.data, data) &&
|
|
||||||
(identical(other.createdAt, createdAt) ||
|
|
||||||
other.createdAt == createdAt));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
runtimeType, type, const DeepCollectionEquality().hash(data), createdAt);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnFeedEntry(type: $type, data: $data, createdAt: $createdAt)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SnFeedEntryCopyWith<$Res> {
|
|
||||||
factory $SnFeedEntryCopyWith(
|
|
||||||
SnFeedEntry value, $Res Function(SnFeedEntry) _then) =
|
|
||||||
_$SnFeedEntryCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({String type, Map<String, dynamic> data, DateTime createdAt});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class _$SnFeedEntryCopyWithImpl<$Res> implements $SnFeedEntryCopyWith<$Res> {
|
|
||||||
_$SnFeedEntryCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SnFeedEntry _self;
|
|
||||||
final $Res Function(SnFeedEntry) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnFeedEntry
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
@override
|
|
||||||
$Res call({
|
|
||||||
Object? type = null,
|
|
||||||
Object? data = null,
|
|
||||||
Object? createdAt = null,
|
|
||||||
}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
type: null == type
|
|
||||||
? _self.type
|
|
||||||
: type // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
data: null == data
|
|
||||||
? _self.data
|
|
||||||
: data // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, dynamic>,
|
|
||||||
createdAt: null == createdAt
|
|
||||||
? _self.createdAt
|
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
class _SnFeedEntry implements SnFeedEntry {
|
|
||||||
const _SnFeedEntry(
|
|
||||||
{required this.type,
|
|
||||||
required final Map<String, dynamic> data,
|
|
||||||
required this.createdAt})
|
|
||||||
: _data = data;
|
|
||||||
factory _SnFeedEntry.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnFeedEntryFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
final String type;
|
|
||||||
final Map<String, dynamic> _data;
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> get data {
|
|
||||||
if (_data is EqualUnmodifiableMapView) return _data;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableMapView(_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
final DateTime createdAt;
|
|
||||||
|
|
||||||
/// Create a copy of SnFeedEntry
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SnFeedEntryCopyWith<_SnFeedEntry> get copyWith =>
|
|
||||||
__$SnFeedEntryCopyWithImpl<_SnFeedEntry>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SnFeedEntryToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is _SnFeedEntry &&
|
|
||||||
(identical(other.type, type) || other.type == type) &&
|
|
||||||
const DeepCollectionEquality().equals(other._data, _data) &&
|
|
||||||
(identical(other.createdAt, createdAt) ||
|
|
||||||
other.createdAt == createdAt));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
runtimeType, type, const DeepCollectionEquality().hash(_data), createdAt);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnFeedEntry(type: $type, data: $data, createdAt: $createdAt)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SnFeedEntryCopyWith<$Res>
|
|
||||||
implements $SnFeedEntryCopyWith<$Res> {
|
|
||||||
factory _$SnFeedEntryCopyWith(
|
|
||||||
_SnFeedEntry value, $Res Function(_SnFeedEntry) _then) =
|
|
||||||
__$SnFeedEntryCopyWithImpl;
|
|
||||||
@override
|
|
||||||
@useResult
|
|
||||||
$Res call({String type, Map<String, dynamic> data, DateTime createdAt});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$SnFeedEntryCopyWithImpl<$Res> implements _$SnFeedEntryCopyWith<$Res> {
|
|
||||||
__$SnFeedEntryCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SnFeedEntry _self;
|
|
||||||
final $Res Function(_SnFeedEntry) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnFeedEntry
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$Res call({
|
|
||||||
Object? type = null,
|
|
||||||
Object? data = null,
|
|
||||||
Object? createdAt = null,
|
|
||||||
}) {
|
|
||||||
return _then(_SnFeedEntry(
|
|
||||||
type: null == type
|
|
||||||
? _self.type
|
|
||||||
: type // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
data: null == data
|
|
||||||
? _self._data
|
|
||||||
: data // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, dynamic>,
|
|
||||||
createdAt: null == createdAt
|
|
||||||
? _self.createdAt
|
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SnFediversePost {
|
|
||||||
int get id;
|
|
||||||
DateTime get createdAt;
|
|
||||||
DateTime get updatedAt;
|
|
||||||
DateTime? get deletedAt;
|
|
||||||
String get identifier;
|
|
||||||
String get origin;
|
|
||||||
String get content;
|
|
||||||
String get language;
|
|
||||||
List<String> get images;
|
|
||||||
SnFediverseUser get user;
|
|
||||||
int get userId;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediversePost
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnFediversePostCopyWith<SnFediversePost> get copyWith =>
|
|
||||||
_$SnFediversePostCopyWithImpl<SnFediversePost>(
|
|
||||||
this as SnFediversePost, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SnFediversePost to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is SnFediversePost &&
|
|
||||||
(identical(other.id, id) || other.id == id) &&
|
|
||||||
(identical(other.createdAt, createdAt) ||
|
|
||||||
other.createdAt == createdAt) &&
|
|
||||||
(identical(other.updatedAt, updatedAt) ||
|
|
||||||
other.updatedAt == updatedAt) &&
|
|
||||||
(identical(other.deletedAt, deletedAt) ||
|
|
||||||
other.deletedAt == deletedAt) &&
|
|
||||||
(identical(other.identifier, identifier) ||
|
|
||||||
other.identifier == identifier) &&
|
|
||||||
(identical(other.origin, origin) || other.origin == origin) &&
|
|
||||||
(identical(other.content, content) || other.content == content) &&
|
|
||||||
(identical(other.language, language) ||
|
|
||||||
other.language == language) &&
|
|
||||||
const DeepCollectionEquality().equals(other.images, images) &&
|
|
||||||
(identical(other.user, user) || other.user == user) &&
|
|
||||||
(identical(other.userId, userId) || other.userId == userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
runtimeType,
|
|
||||||
id,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
deletedAt,
|
|
||||||
identifier,
|
|
||||||
origin,
|
|
||||||
content,
|
|
||||||
language,
|
|
||||||
const DeepCollectionEquality().hash(images),
|
|
||||||
user,
|
|
||||||
userId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnFediversePost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, content: $content, language: $language, images: $images, user: $user, userId: $userId)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SnFediversePostCopyWith<$Res> {
|
|
||||||
factory $SnFediversePostCopyWith(
|
|
||||||
SnFediversePost value, $Res Function(SnFediversePost) _then) =
|
|
||||||
_$SnFediversePostCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{int id,
|
|
||||||
DateTime createdAt,
|
|
||||||
DateTime updatedAt,
|
|
||||||
DateTime? deletedAt,
|
|
||||||
String identifier,
|
|
||||||
String origin,
|
|
||||||
String content,
|
|
||||||
String language,
|
|
||||||
List<String> images,
|
|
||||||
SnFediverseUser user,
|
|
||||||
int userId});
|
|
||||||
|
|
||||||
$SnFediverseUserCopyWith<$Res> get user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class _$SnFediversePostCopyWithImpl<$Res>
|
|
||||||
implements $SnFediversePostCopyWith<$Res> {
|
|
||||||
_$SnFediversePostCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SnFediversePost _self;
|
|
||||||
final $Res Function(SnFediversePost) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediversePost
|
|
||||||
/// 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? identifier = null,
|
|
||||||
Object? origin = null,
|
|
||||||
Object? content = null,
|
|
||||||
Object? language = null,
|
|
||||||
Object? images = null,
|
|
||||||
Object? user = null,
|
|
||||||
Object? userId = null,
|
|
||||||
}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
id: null == id
|
|
||||||
? _self.id
|
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
createdAt: null == createdAt
|
|
||||||
? _self.createdAt
|
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
updatedAt: null == updatedAt
|
|
||||||
? _self.updatedAt
|
|
||||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
deletedAt: freezed == deletedAt
|
|
||||||
? _self.deletedAt
|
|
||||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
identifier: null == identifier
|
|
||||||
? _self.identifier
|
|
||||||
: identifier // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
origin: null == origin
|
|
||||||
? _self.origin
|
|
||||||
: origin // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
content: null == content
|
|
||||||
? _self.content
|
|
||||||
: content // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
language: null == language
|
|
||||||
? _self.language
|
|
||||||
: language // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
images: null == images
|
|
||||||
? _self.images
|
|
||||||
: images // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
user: null == user
|
|
||||||
? _self.user
|
|
||||||
: user // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnFediverseUser,
|
|
||||||
userId: null == userId
|
|
||||||
? _self.userId
|
|
||||||
: userId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a copy of SnFediversePost
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnFediverseUserCopyWith<$Res> get user {
|
|
||||||
return $SnFediverseUserCopyWith<$Res>(_self.user, (value) {
|
|
||||||
return _then(_self.copyWith(user: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
class _SnFediversePost implements SnFediversePost {
|
|
||||||
const _SnFediversePost(
|
|
||||||
{required this.id,
|
|
||||||
required this.createdAt,
|
|
||||||
required this.updatedAt,
|
|
||||||
required this.deletedAt,
|
|
||||||
required this.identifier,
|
|
||||||
required this.origin,
|
|
||||||
required this.content,
|
|
||||||
required this.language,
|
|
||||||
required final List<String> images,
|
|
||||||
required this.user,
|
|
||||||
required this.userId})
|
|
||||||
: _images = images;
|
|
||||||
factory _SnFediversePost.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnFediversePostFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
final int id;
|
|
||||||
@override
|
|
||||||
final DateTime createdAt;
|
|
||||||
@override
|
|
||||||
final DateTime updatedAt;
|
|
||||||
@override
|
|
||||||
final DateTime? deletedAt;
|
|
||||||
@override
|
|
||||||
final String identifier;
|
|
||||||
@override
|
|
||||||
final String origin;
|
|
||||||
@override
|
|
||||||
final String content;
|
|
||||||
@override
|
|
||||||
final String language;
|
|
||||||
final List<String> _images;
|
|
||||||
@override
|
|
||||||
List<String> get images {
|
|
||||||
if (_images is EqualUnmodifiableListView) return _images;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableListView(_images);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
final SnFediverseUser user;
|
|
||||||
@override
|
|
||||||
final int userId;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediversePost
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SnFediversePostCopyWith<_SnFediversePost> get copyWith =>
|
|
||||||
__$SnFediversePostCopyWithImpl<_SnFediversePost>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SnFediversePostToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is _SnFediversePost &&
|
|
||||||
(identical(other.id, id) || other.id == id) &&
|
|
||||||
(identical(other.createdAt, createdAt) ||
|
|
||||||
other.createdAt == createdAt) &&
|
|
||||||
(identical(other.updatedAt, updatedAt) ||
|
|
||||||
other.updatedAt == updatedAt) &&
|
|
||||||
(identical(other.deletedAt, deletedAt) ||
|
|
||||||
other.deletedAt == deletedAt) &&
|
|
||||||
(identical(other.identifier, identifier) ||
|
|
||||||
other.identifier == identifier) &&
|
|
||||||
(identical(other.origin, origin) || other.origin == origin) &&
|
|
||||||
(identical(other.content, content) || other.content == content) &&
|
|
||||||
(identical(other.language, language) ||
|
|
||||||
other.language == language) &&
|
|
||||||
const DeepCollectionEquality().equals(other._images, _images) &&
|
|
||||||
(identical(other.user, user) || other.user == user) &&
|
|
||||||
(identical(other.userId, userId) || other.userId == userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
runtimeType,
|
|
||||||
id,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
deletedAt,
|
|
||||||
identifier,
|
|
||||||
origin,
|
|
||||||
content,
|
|
||||||
language,
|
|
||||||
const DeepCollectionEquality().hash(_images),
|
|
||||||
user,
|
|
||||||
userId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnFediversePost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, content: $content, language: $language, images: $images, user: $user, userId: $userId)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SnFediversePostCopyWith<$Res>
|
|
||||||
implements $SnFediversePostCopyWith<$Res> {
|
|
||||||
factory _$SnFediversePostCopyWith(
|
|
||||||
_SnFediversePost value, $Res Function(_SnFediversePost) _then) =
|
|
||||||
__$SnFediversePostCopyWithImpl;
|
|
||||||
@override
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{int id,
|
|
||||||
DateTime createdAt,
|
|
||||||
DateTime updatedAt,
|
|
||||||
DateTime? deletedAt,
|
|
||||||
String identifier,
|
|
||||||
String origin,
|
|
||||||
String content,
|
|
||||||
String language,
|
|
||||||
List<String> images,
|
|
||||||
SnFediverseUser user,
|
|
||||||
int userId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
$SnFediverseUserCopyWith<$Res> get user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$SnFediversePostCopyWithImpl<$Res>
|
|
||||||
implements _$SnFediversePostCopyWith<$Res> {
|
|
||||||
__$SnFediversePostCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SnFediversePost _self;
|
|
||||||
final $Res Function(_SnFediversePost) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediversePost
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$Res call({
|
|
||||||
Object? id = null,
|
|
||||||
Object? createdAt = null,
|
|
||||||
Object? updatedAt = null,
|
|
||||||
Object? deletedAt = freezed,
|
|
||||||
Object? identifier = null,
|
|
||||||
Object? origin = null,
|
|
||||||
Object? content = null,
|
|
||||||
Object? language = null,
|
|
||||||
Object? images = null,
|
|
||||||
Object? user = null,
|
|
||||||
Object? userId = null,
|
|
||||||
}) {
|
|
||||||
return _then(_SnFediversePost(
|
|
||||||
id: null == id
|
|
||||||
? _self.id
|
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
createdAt: null == createdAt
|
|
||||||
? _self.createdAt
|
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
updatedAt: null == updatedAt
|
|
||||||
? _self.updatedAt
|
|
||||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
deletedAt: freezed == deletedAt
|
|
||||||
? _self.deletedAt
|
|
||||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
identifier: null == identifier
|
|
||||||
? _self.identifier
|
|
||||||
: identifier // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
origin: null == origin
|
|
||||||
? _self.origin
|
|
||||||
: origin // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
content: null == content
|
|
||||||
? _self.content
|
|
||||||
: content // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
language: null == language
|
|
||||||
? _self.language
|
|
||||||
: language // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
images: null == images
|
|
||||||
? _self._images
|
|
||||||
: images // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>,
|
|
||||||
user: null == user
|
|
||||||
? _self.user
|
|
||||||
: user // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnFediverseUser,
|
|
||||||
userId: null == userId
|
|
||||||
? _self.userId
|
|
||||||
: userId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a copy of SnFediversePost
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnFediverseUserCopyWith<$Res> get user {
|
|
||||||
return $SnFediverseUserCopyWith<$Res>(_self.user, (value) {
|
|
||||||
return _then(_self.copyWith(user: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SnFediverseUser {
|
|
||||||
int get id;
|
|
||||||
DateTime get createdAt;
|
|
||||||
DateTime get updatedAt;
|
|
||||||
DateTime? get deletedAt;
|
|
||||||
String get identifier;
|
|
||||||
String get origin;
|
|
||||||
String get avatar;
|
|
||||||
String get name;
|
|
||||||
String get nick;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediverseUser
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnFediverseUserCopyWith<SnFediverseUser> get copyWith =>
|
|
||||||
_$SnFediverseUserCopyWithImpl<SnFediverseUser>(
|
|
||||||
this as SnFediverseUser, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SnFediverseUser to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is SnFediverseUser &&
|
|
||||||
(identical(other.id, id) || other.id == id) &&
|
|
||||||
(identical(other.createdAt, createdAt) ||
|
|
||||||
other.createdAt == createdAt) &&
|
|
||||||
(identical(other.updatedAt, updatedAt) ||
|
|
||||||
other.updatedAt == updatedAt) &&
|
|
||||||
(identical(other.deletedAt, deletedAt) ||
|
|
||||||
other.deletedAt == deletedAt) &&
|
|
||||||
(identical(other.identifier, identifier) ||
|
|
||||||
other.identifier == identifier) &&
|
|
||||||
(identical(other.origin, origin) || other.origin == origin) &&
|
|
||||||
(identical(other.avatar, avatar) || other.avatar == avatar) &&
|
|
||||||
(identical(other.name, name) || other.name == name) &&
|
|
||||||
(identical(other.nick, nick) || other.nick == nick));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
|
||||||
deletedAt, identifier, origin, avatar, name, nick);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnFediverseUser(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, avatar: $avatar, name: $name, nick: $nick)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SnFediverseUserCopyWith<$Res> {
|
|
||||||
factory $SnFediverseUserCopyWith(
|
|
||||||
SnFediverseUser value, $Res Function(SnFediverseUser) _then) =
|
|
||||||
_$SnFediverseUserCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{int id,
|
|
||||||
DateTime createdAt,
|
|
||||||
DateTime updatedAt,
|
|
||||||
DateTime? deletedAt,
|
|
||||||
String identifier,
|
|
||||||
String origin,
|
|
||||||
String avatar,
|
|
||||||
String name,
|
|
||||||
String nick});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class _$SnFediverseUserCopyWithImpl<$Res>
|
|
||||||
implements $SnFediverseUserCopyWith<$Res> {
|
|
||||||
_$SnFediverseUserCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SnFediverseUser _self;
|
|
||||||
final $Res Function(SnFediverseUser) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediverseUser
|
|
||||||
/// 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? identifier = null,
|
|
||||||
Object? origin = null,
|
|
||||||
Object? avatar = null,
|
|
||||||
Object? name = null,
|
|
||||||
Object? nick = null,
|
|
||||||
}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
id: null == id
|
|
||||||
? _self.id
|
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
createdAt: null == createdAt
|
|
||||||
? _self.createdAt
|
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
updatedAt: null == updatedAt
|
|
||||||
? _self.updatedAt
|
|
||||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
deletedAt: freezed == deletedAt
|
|
||||||
? _self.deletedAt
|
|
||||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
identifier: null == identifier
|
|
||||||
? _self.identifier
|
|
||||||
: identifier // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
origin: null == origin
|
|
||||||
? _self.origin
|
|
||||||
: origin // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
avatar: null == avatar
|
|
||||||
? _self.avatar
|
|
||||||
: avatar // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
name: null == name
|
|
||||||
? _self.name
|
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
nick: null == nick
|
|
||||||
? _self.nick
|
|
||||||
: nick // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
class _SnFediverseUser implements SnFediverseUser {
|
|
||||||
const _SnFediverseUser(
|
|
||||||
{required this.id,
|
|
||||||
required this.createdAt,
|
|
||||||
required this.updatedAt,
|
|
||||||
required this.deletedAt,
|
|
||||||
required this.identifier,
|
|
||||||
required this.origin,
|
|
||||||
required this.avatar,
|
|
||||||
required this.name,
|
|
||||||
required this.nick});
|
|
||||||
factory _SnFediverseUser.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnFediverseUserFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
final int id;
|
|
||||||
@override
|
|
||||||
final DateTime createdAt;
|
|
||||||
@override
|
|
||||||
final DateTime updatedAt;
|
|
||||||
@override
|
|
||||||
final DateTime? deletedAt;
|
|
||||||
@override
|
|
||||||
final String identifier;
|
|
||||||
@override
|
|
||||||
final String origin;
|
|
||||||
@override
|
|
||||||
final String avatar;
|
|
||||||
@override
|
|
||||||
final String name;
|
|
||||||
@override
|
|
||||||
final String nick;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediverseUser
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SnFediverseUserCopyWith<_SnFediverseUser> get copyWith =>
|
|
||||||
__$SnFediverseUserCopyWithImpl<_SnFediverseUser>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SnFediverseUserToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is _SnFediverseUser &&
|
|
||||||
(identical(other.id, id) || other.id == id) &&
|
|
||||||
(identical(other.createdAt, createdAt) ||
|
|
||||||
other.createdAt == createdAt) &&
|
|
||||||
(identical(other.updatedAt, updatedAt) ||
|
|
||||||
other.updatedAt == updatedAt) &&
|
|
||||||
(identical(other.deletedAt, deletedAt) ||
|
|
||||||
other.deletedAt == deletedAt) &&
|
|
||||||
(identical(other.identifier, identifier) ||
|
|
||||||
other.identifier == identifier) &&
|
|
||||||
(identical(other.origin, origin) || other.origin == origin) &&
|
|
||||||
(identical(other.avatar, avatar) || other.avatar == avatar) &&
|
|
||||||
(identical(other.name, name) || other.name == name) &&
|
|
||||||
(identical(other.nick, nick) || other.nick == nick));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
|
||||||
deletedAt, identifier, origin, avatar, name, nick);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnFediverseUser(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, identifier: $identifier, origin: $origin, avatar: $avatar, name: $name, nick: $nick)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SnFediverseUserCopyWith<$Res>
|
|
||||||
implements $SnFediverseUserCopyWith<$Res> {
|
|
||||||
factory _$SnFediverseUserCopyWith(
|
|
||||||
_SnFediverseUser value, $Res Function(_SnFediverseUser) _then) =
|
|
||||||
__$SnFediverseUserCopyWithImpl;
|
|
||||||
@override
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{int id,
|
|
||||||
DateTime createdAt,
|
|
||||||
DateTime updatedAt,
|
|
||||||
DateTime? deletedAt,
|
|
||||||
String identifier,
|
|
||||||
String origin,
|
|
||||||
String avatar,
|
|
||||||
String name,
|
|
||||||
String nick});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$SnFediverseUserCopyWithImpl<$Res>
|
|
||||||
implements _$SnFediverseUserCopyWith<$Res> {
|
|
||||||
__$SnFediverseUserCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SnFediverseUser _self;
|
|
||||||
final $Res Function(_SnFediverseUser) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnFediverseUser
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$Res call({
|
|
||||||
Object? id = null,
|
|
||||||
Object? createdAt = null,
|
|
||||||
Object? updatedAt = null,
|
|
||||||
Object? deletedAt = freezed,
|
|
||||||
Object? identifier = null,
|
|
||||||
Object? origin = null,
|
|
||||||
Object? avatar = null,
|
|
||||||
Object? name = null,
|
|
||||||
Object? nick = null,
|
|
||||||
}) {
|
|
||||||
return _then(_SnFediverseUser(
|
|
||||||
id: null == id
|
|
||||||
? _self.id
|
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
createdAt: null == createdAt
|
|
||||||
? _self.createdAt
|
|
||||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
updatedAt: null == updatedAt
|
|
||||||
? _self.updatedAt
|
|
||||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime,
|
|
||||||
deletedAt: freezed == deletedAt
|
|
||||||
? _self.deletedAt
|
|
||||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
identifier: null == identifier
|
|
||||||
? _self.identifier
|
|
||||||
: identifier // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
origin: null == origin
|
|
||||||
? _self.origin
|
|
||||||
: origin // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
avatar: null == avatar
|
|
||||||
? _self.avatar
|
|
||||||
: avatar // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
name: null == name
|
|
||||||
? _self.name
|
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
nick: null == nick
|
|
||||||
? _self.nick
|
|
||||||
: nick // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
// dart format on
|
||||||
|
@ -282,77 +282,3 @@ Map<String, dynamic> _$SnSubscriptionToJson(_SnSubscription instance) =>
|
|||||||
'follower_id': instance.followerId,
|
'follower_id': instance.followerId,
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnFeedEntry _$SnFeedEntryFromJson(Map<String, dynamic> json) => _SnFeedEntry(
|
|
||||||
type: json['type'] as String,
|
|
||||||
data: json['data'] as Map<String, dynamic>,
|
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SnFeedEntryToJson(_SnFeedEntry instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'type': instance.type,
|
|
||||||
'data': instance.data,
|
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_SnFediversePost _$SnFediversePostFromJson(Map<String, dynamic> json) =>
|
|
||||||
_SnFediversePost(
|
|
||||||
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'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
|
||||||
identifier: json['identifier'] as String,
|
|
||||||
origin: json['origin'] as String,
|
|
||||||
content: json['content'] as String,
|
|
||||||
language: json['language'] as String,
|
|
||||||
images:
|
|
||||||
(json['images'] as List<dynamic>).map((e) => e as String).toList(),
|
|
||||||
user: SnFediverseUser.fromJson(json['user'] as Map<String, dynamic>),
|
|
||||||
userId: (json['user_id'] as num).toInt(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SnFediversePostToJson(_SnFediversePost instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
|
||||||
'identifier': instance.identifier,
|
|
||||||
'origin': instance.origin,
|
|
||||||
'content': instance.content,
|
|
||||||
'language': instance.language,
|
|
||||||
'images': instance.images,
|
|
||||||
'user': instance.user.toJson(),
|
|
||||||
'user_id': instance.userId,
|
|
||||||
};
|
|
||||||
|
|
||||||
_SnFediverseUser _$SnFediverseUserFromJson(Map<String, dynamic> json) =>
|
|
||||||
_SnFediverseUser(
|
|
||||||
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'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
|
||||||
identifier: json['identifier'] as String,
|
|
||||||
origin: json['origin'] as String,
|
|
||||||
avatar: json['avatar'] as String,
|
|
||||||
name: json['name'] as String,
|
|
||||||
nick: json['nick'] as String,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SnFediverseUserToJson(_SnFediverseUser instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
|
||||||
'identifier': instance.identifier,
|
|
||||||
'origin': instance.origin,
|
|
||||||
'avatar': instance.avatar,
|
|
||||||
'name': instance.name,
|
|
||||||
'nick': instance.nick,
|
|
||||||
};
|
|
||||||
|
@ -95,9 +95,8 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.data.firstOrNull?.mediaType != SnMediaType.image) {
|
if (widget.data.firstOrNull?.mediaType != SnMediaType.image)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
context.pushTransparentRoute(
|
context.pushTransparentRoute(
|
||||||
AttachmentZoomView(
|
AttachmentZoomView(
|
||||||
data: widget.data.where((ele) => ele != null).cast(),
|
data: widget.data.where((ele) => ele != null).cast(),
|
||||||
@ -210,7 +209,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1,
|
aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1,
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior: AttachmentListScrollBehavior(),
|
behavior: _AttachmentListScrollBehavior(),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
padding: widget.padding,
|
padding: widget.padding,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@ -284,7 +283,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentListScrollBehavior extends MaterialScrollBehavior {
|
class _AttachmentListScrollBehavior extends MaterialScrollBehavior {
|
||||||
@override
|
@override
|
||||||
Set<PointerDeviceKind> get dragDevices =>
|
Set<PointerDeviceKind> get dragDevices =>
|
||||||
{PointerDeviceKind.touch, PointerDeviceKind.mouse};
|
{PointerDeviceKind.touch, PointerDeviceKind.mouse};
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:html/dom.dart' as dom;
|
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
List<Widget> parseHtmlToWidgets(
|
|
||||||
BuildContext context, Iterable<dom.Element>? elements) {
|
|
||||||
if (elements == null) return [];
|
|
||||||
|
|
||||||
final List<Widget> widgets = [];
|
|
||||||
|
|
||||||
for (final node in elements) {
|
|
||||||
switch (node.localName) {
|
|
||||||
case 'h1':
|
|
||||||
case 'h2':
|
|
||||||
case 'h3':
|
|
||||||
case 'h4':
|
|
||||||
case 'h5':
|
|
||||||
case 'h6':
|
|
||||||
widgets.add(Text(node.text.trim(),
|
|
||||||
style: Theme.of(context).textTheme.titleMedium));
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
if (node.text.trim().isEmpty) continue;
|
|
||||||
widgets.add(
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
text: node.text.trim(),
|
|
||||||
children: [
|
|
||||||
for (final child in node.children)
|
|
||||||
switch (child.localName) {
|
|
||||||
'a' => TextSpan(
|
|
||||||
text: child.text.trim(),
|
|
||||||
style: const TextStyle(
|
|
||||||
decoration: TextDecoration.underline),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
launchUrlString(child.attributes['href']!);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ => TextSpan(text: child.text.trim()),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
// drop single link
|
|
||||||
break;
|
|
||||||
case 'div':
|
|
||||||
// ignore div text, normally it is not meaningful
|
|
||||||
widgets.addAll(parseHtmlToWidgets(context, node.children));
|
|
||||||
break;
|
|
||||||
case 'hr':
|
|
||||||
widgets.add(const Divider());
|
|
||||||
break;
|
|
||||||
case 'img':
|
|
||||||
var src = node.attributes['src'];
|
|
||||||
if (src == null) break;
|
|
||||||
final width = double.tryParse(node.attributes['width'] ?? 'null');
|
|
||||||
final height = double.tryParse(node.attributes['height'] ?? 'null');
|
|
||||||
final ratio = width != null && height != null ? width / height : 1.0;
|
|
||||||
if (src.startsWith('//')) {
|
|
||||||
src = 'https:$src';
|
|
||||||
} else if (!src.startsWith('http')) {
|
|
||||||
// final baseUri = Uri.parse(_article!.url);
|
|
||||||
// final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
|
||||||
src = src;
|
|
||||||
}
|
|
||||||
widgets.add(
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: ratio,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
height: height ?? double.infinity,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
|
||||||
child: AutoResizeUniversalImage(
|
|
||||||
src,
|
|
||||||
fit: width != null && height != null
|
|
||||||
? BoxFit.cover
|
|
||||||
: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
widgets.addAll(parseHtmlToWidgets(context, node.children));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return widgets;
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
|
||||||
import 'package:surface/types/post.dart';
|
|
||||||
import 'package:html/parser.dart';
|
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
|
||||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
|
||||||
import 'package:surface/widgets/html.dart';
|
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
|
||||||
|
|
||||||
class FediversePostWidget extends StatelessWidget {
|
|
||||||
final SnFediversePost data;
|
|
||||||
final double maxWidth;
|
|
||||||
const FediversePostWidget(
|
|
||||||
{super.key, required this.data, required this.maxWidth});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final borderSide =
|
|
||||||
BorderSide(width: 1, color: Theme.of(context).dividerColor);
|
|
||||||
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
AccountImage(content: data.user.avatar),
|
|
||||||
const Gap(8),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
data.user.nick.isNotEmpty
|
|
||||||
? data.user.nick
|
|
||||||
: '@${data.user.name}',
|
|
||||||
).bold(),
|
|
||||||
Text(data.user.identifier),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
...parseHtmlToWidgets(context, parse(data.content).children),
|
|
||||||
if (data.images.isNotEmpty)
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: 1,
|
|
||||||
child: ScrollConfiguration(
|
|
||||||
behavior: AttachmentListScrollBehavior(),
|
|
||||||
child: ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: data.images.length,
|
|
||||||
itemBuilder: (context, idx) {
|
|
||||||
return Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: backgroundColor,
|
|
||||||
border: Border(
|
|
||||||
top: borderSide,
|
|
||||||
bottom: borderSide,
|
|
||||||
),
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
|
||||||
child: AutoResizeUniversalImage(
|
|
||||||
data.images[idx],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 8,
|
|
||||||
bottom: 8,
|
|
||||||
child: Chip(
|
|
||||||
label: Text(
|
|
||||||
'${idx + 1}/${data.images.length}'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (context, index) => const Gap(8),
|
|
||||||
physics: const BouncingScrollPhysics(),
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(all: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,37 +14,36 @@ class UnauthorizedHint extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 280),
|
constraints: const BoxConstraints(maxWidth: 280),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.login, size: 36),
|
const Icon(Symbols.login, size: 36),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
'unauthorized',
|
'unauthorized',
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
textAlign: TextAlign.center,
|
).tr(),
|
||||||
).tr(),
|
const Gap(8),
|
||||||
const Gap(8),
|
Text(
|
||||||
Text(
|
'unauthorizedDescription',
|
||||||
'unauthorizedDescription',
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
).tr(),
|
||||||
textAlign: TextAlign.center,
|
],
|
||||||
).tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
),
|
||||||
GoRouter.of(context).pushNamed('authLogin').then((value) {
|
onTap: () {
|
||||||
if (value == true && context.mounted) {
|
GoRouter.of(context).pushNamed('authLogin').then((value) {
|
||||||
final ua = context.read<UserProvider>();
|
if (value == true && context.mounted) {
|
||||||
context.showSnackbar('loginSuccess'.tr(args: [
|
final ua = context.read<UserProvider>();
|
||||||
'@${ua.user?.name} (${ua.user?.nick})',
|
context.showSnackbar('loginSuccess'.tr(args: [
|
||||||
]));
|
'@${ua.user?.name} (${ua.user?.nick})',
|
||||||
}
|
]));
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user