Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
d8e79fb4f9 | |||
06e0fa465b | |||
895a257f50 | |||
d9804ba00b | |||
62ff1c2f1c | |||
a157596a2e | |||
12102bf527 | |||
c00a018380 | |||
53b3cac4ca | |||
19eabfaba1 | |||
ec2eadad6d | |||
54e176e75d | |||
0a7ccaeefa | |||
a5f093e185 |
@ -112,14 +112,15 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
label: 'bsPreparingData',
|
label: 'bsPreparingData',
|
||||||
action: () async {
|
action: () async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isTrue) {
|
await Future.wait([
|
||||||
await Future.wait([
|
Get.find<StickerProvider>().refreshAvailableStickers(),
|
||||||
Get.find<RealmProvider>().refreshAvailableRealms(),
|
if (auth.isAuthorized.isTrue)
|
||||||
Get.find<ChannelProvider>().refreshAvailableChannel(),
|
Get.find<ChannelProvider>().refreshAvailableChannel(),
|
||||||
|
if (auth.isAuthorized.isTrue)
|
||||||
Get.find<RelationshipProvider>().refreshRelativeList(),
|
Get.find<RelationshipProvider>().refreshRelativeList(),
|
||||||
Get.find<StickerProvider>().refreshAvailableStickers(),
|
if (auth.isAuthorized.isTrue)
|
||||||
]);
|
Get.find<RealmProvider>().refreshAvailableRealms(),
|
||||||
}
|
]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -162,28 +163,29 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isBusy || _isErrored) {
|
if (_isBusy || _isErrored) {
|
||||||
return Material(
|
return GestureDetector(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
child: Material(
|
||||||
child: Column(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
mainAxisSize: MainAxisSize.max,
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
SizedBox(
|
children: [
|
||||||
height: 280,
|
SizedBox(
|
||||||
child: Align(
|
height: 280,
|
||||||
alignment: Alignment.bottomCenter,
|
child: Align(
|
||||||
child: ClipRRect(
|
alignment: Alignment.bottomCenter,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
child: ClipRRect(
|
||||||
child: Image.asset('assets/logo.png', width: 80, height: 80),
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
|
child:
|
||||||
|
Image.asset('assets/logo.png', width: 80, height: 80),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Column(
|
||||||
GestureDetector(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
if (_isErrored && !_isDismissable && !_isBusy)
|
if (_isErrored && !_isDismissable && !_isBusy)
|
||||||
const Icon(Icons.cancel, size: 24),
|
const Icon(Icons.cancel, size: 24),
|
||||||
if (_isErrored && _isDismissable)
|
if (_isErrored && _isDismissable && !_isBusy)
|
||||||
const Icon(Icons.warning, size: 24),
|
const Icon(Icons.warning, size: 24),
|
||||||
if ((_isErrored && _isDismissable && _isBusy) || _isBusy)
|
if ((_isErrored && _isDismissable && _isBusy) || _isBusy)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@ -214,6 +216,15 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
color: _unFocusColor,
|
color: _unFocusColor,
|
||||||
),
|
),
|
||||||
).paddingOnly(bottom: 4),
|
).paddingOnly(bottom: 4),
|
||||||
|
if (!_isBusy && _isErrored && _isDismissable)
|
||||||
|
Text(
|
||||||
|
'bsDismissibleErrorHint'.tr,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: _unFocusColor,
|
||||||
|
),
|
||||||
|
).paddingOnly(bottom: 5),
|
||||||
Text(
|
Text(
|
||||||
'2024 © Solsynth LLC',
|
'2024 © Solsynth LLC',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@ -227,25 +238,25 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
],
|
||||||
if (_isBusy) return;
|
),
|
||||||
if (_isDismissable) {
|
|
||||||
setState(() {
|
|
||||||
_isBusy = false;
|
|
||||||
_isErrored = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
_isBusy = true;
|
|
||||||
_isErrored = false;
|
|
||||||
_periodCursor = 0;
|
|
||||||
});
|
|
||||||
_runPeriods();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (_isBusy) return;
|
||||||
|
if (_isDismissable) {
|
||||||
|
setState(() {
|
||||||
|
_isBusy = false;
|
||||||
|
_isErrored = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_isBusy = true;
|
||||||
|
_isErrored = false;
|
||||||
|
_periodCursor = 0;
|
||||||
|
});
|
||||||
|
_runPeriods();
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,6 @@ abstract class AppRouter {
|
|||||||
reply: arguments?.reply,
|
reply: arguments?.reply,
|
||||||
repost: arguments?.repost,
|
repost: arguments?.repost,
|
||||||
realm: arguments?.realm,
|
realm: arguments?.realm,
|
||||||
postListController: arguments?.postListController,
|
|
||||||
mode: int.tryParse(state.uri.queryParameters['mode'] ?? '0') ?? 0,
|
mode: int.tryParse(state.uri.queryParameters['mode'] ?? '0') ?? 0,
|
||||||
),
|
),
|
||||||
transitionsBuilder:
|
transitionsBuilder:
|
||||||
|
@ -300,6 +300,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
PostWarpedListWidget(
|
PostWarpedListWidget(
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
controller: _postController.pagingController,
|
controller: _postController.pagingController,
|
||||||
|
onUpdate: () => _postController.reloadAllOver(),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
@ -21,7 +21,7 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
|
|||||||
final PagingController<int, Post> _pagingController =
|
final PagingController<int, Post> _pagingController =
|
||||||
PagingController(firstPageKey: 0);
|
PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
getPosts(int pageKey) async {
|
_getPosts(int pageKey) async {
|
||||||
final PostProvider provider = Get.find();
|
final PostProvider provider = Get.find();
|
||||||
|
|
||||||
Response resp;
|
Response resp;
|
||||||
@ -49,7 +49,7 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_pagingController.addPageRequestListener(getPosts);
|
_pagingController.addPageRequestListener(_getPosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -76,6 +76,9 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
|
|||||||
itemBuilder: (context, item, index) {
|
itemBuilder: (context, item, index) {
|
||||||
return PostOwnedListEntry(
|
return PostOwnedListEntry(
|
||||||
item: item,
|
item: item,
|
||||||
|
isFullContent: true,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
@ -85,7 +88,13 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
|
|||||||
noReact: true,
|
noReact: true,
|
||||||
),
|
),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value != null) _pagingController.refresh();
|
if (value is Future) {
|
||||||
|
value.then((_) {
|
||||||
|
_pagingController.refresh();
|
||||||
|
});
|
||||||
|
} else if (value != null) {
|
||||||
|
_pagingController.refresh();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
).paddingOnly(left: 12, right: 12, bottom: 4);
|
).paddingOnly(left: 12, right: 12, bottom: 4);
|
||||||
|
@ -77,7 +77,10 @@ class _FeedSearchScreenState extends State<FeedSearchScreen> {
|
|||||||
onRefresh: () => Future.sync(() => _pagingController.refresh()),
|
onRefresh: () => Future.sync(() => _pagingController.refresh()),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
PostWarpedListWidget(controller: _pagingController),
|
PostWarpedListWidget(
|
||||||
|
controller: _pagingController,
|
||||||
|
onUpdate: () => _pagingController.refresh(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,6 @@ import 'package:solian/controllers/post_list_controller.dart';
|
|||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/screens/account/notification.dart';
|
import 'package:solian/screens/account/notification.dart';
|
||||||
import 'package:solian/screens/posts/post_editor.dart';
|
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/app_bar_title.dart';
|
import 'package:solian/widgets/app_bar_title.dart';
|
||||||
import 'package:solian/widgets/current_state_action.dart';
|
import 'package:solian/widgets/current_state_action.dart';
|
||||||
@ -47,15 +46,20 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
showModalBottomSheet(
|
final value = await showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PostCreatePopup(
|
builder: (context) => const PostCreatePopup(),
|
||||||
controller: _postController,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if (value is Future) {
|
||||||
|
value.then((_) {
|
||||||
|
_postController.reloadAllOver();
|
||||||
|
});
|
||||||
|
} else if (value != null) {
|
||||||
|
_postController.reloadAllOver();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
@ -100,6 +104,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
child: CustomScrollView(slivers: [
|
child: CustomScrollView(slivers: [
|
||||||
PostWarpedListWidget(
|
PostWarpedListWidget(
|
||||||
controller: _postController.pagingController,
|
controller: _postController.pagingController,
|
||||||
|
onUpdate: () => _postController.reloadAllOver(),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
@ -121,12 +126,10 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
|
|
||||||
class PostCreatePopup extends StatelessWidget {
|
class PostCreatePopup extends StatelessWidget {
|
||||||
final bool hideDraftBox;
|
final bool hideDraftBox;
|
||||||
final PostListController controller;
|
|
||||||
|
|
||||||
const PostCreatePopup({
|
const PostCreatePopup({
|
||||||
super.key,
|
super.key,
|
||||||
this.hideDraftBox = false,
|
this.hideDraftBox = false,
|
||||||
required this.controller,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -142,13 +145,14 @@ class PostCreatePopup extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.post_add),
|
icon: const Icon(Icons.post_add),
|
||||||
label: 'postEditorModeStory'.tr,
|
label: 'postEditorModeStory'.tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(
|
||||||
AppRouter.instance.pushNamed(
|
context,
|
||||||
'postEditor',
|
AppRouter.instance.pushNamed(
|
||||||
extra: PostPublishArguments(postListController: controller),
|
'postEditor',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'mode': 0.toString(),
|
'mode': 0.toString(),
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -156,13 +160,14 @@ class PostCreatePopup extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.description),
|
icon: const Icon(Icons.description),
|
||||||
label: 'postEditorModeArticle'.tr,
|
label: 'postEditorModeArticle'.tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(
|
||||||
AppRouter.instance.pushNamed(
|
context,
|
||||||
'postEditor',
|
AppRouter.instance.pushNamed(
|
||||||
extra: PostPublishArguments(postListController: controller),
|
'postEditor',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'mode': 1.toString(),
|
'mode': 1.toString(),
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -170,8 +175,10 @@ class PostCreatePopup extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.drafts),
|
icon: const Icon(Icons.drafts),
|
||||||
label: 'draftBoxOpen'.tr,
|
label: 'draftBoxOpen'.tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(
|
||||||
AppRouter.instance.pushNamed('draftBox');
|
context,
|
||||||
|
AppRouter.instance.pushNamed('draftBox'),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -61,6 +61,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
item: item!,
|
item: item!,
|
||||||
isClickable: true,
|
isClickable: true,
|
||||||
isFullDate: true,
|
isFullDate: true,
|
||||||
|
isFullContent: true,
|
||||||
isShowReply: false,
|
isShowReply: false,
|
||||||
isContentSelectable: true,
|
isContentSelectable: true,
|
||||||
),
|
),
|
||||||
|
@ -5,7 +5,6 @@ import 'package:get/get.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:markdown_toolbar/markdown_toolbar.dart';
|
import 'package:markdown_toolbar/markdown_toolbar.dart';
|
||||||
import 'package:solian/controllers/post_editor_controller.dart';
|
import 'package:solian/controllers/post_editor_controller.dart';
|
||||||
import 'package:solian/controllers/post_list_controller.dart';
|
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/models/realm.dart';
|
import 'package:solian/models/realm.dart';
|
||||||
@ -23,14 +22,12 @@ class PostPublishArguments {
|
|||||||
final Post? reply;
|
final Post? reply;
|
||||||
final Post? repost;
|
final Post? repost;
|
||||||
final Realm? realm;
|
final Realm? realm;
|
||||||
final PostListController? postListController;
|
|
||||||
|
|
||||||
PostPublishArguments({
|
PostPublishArguments({
|
||||||
this.edit,
|
this.edit,
|
||||||
this.reply,
|
this.reply,
|
||||||
this.repost,
|
this.repost,
|
||||||
this.realm,
|
this.realm,
|
||||||
this.postListController,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +36,6 @@ class PostPublishScreen extends StatefulWidget {
|
|||||||
final Post? reply;
|
final Post? reply;
|
||||||
final Post? repost;
|
final Post? repost;
|
||||||
final Realm? realm;
|
final Realm? realm;
|
||||||
final PostListController? postListController;
|
|
||||||
final int mode;
|
final int mode;
|
||||||
|
|
||||||
const PostPublishScreen({
|
const PostPublishScreen({
|
||||||
@ -48,7 +44,6 @@ class PostPublishScreen extends StatefulWidget {
|
|||||||
this.reply,
|
this.reply,
|
||||||
this.repost,
|
this.repost,
|
||||||
this.realm,
|
this.realm,
|
||||||
this.postListController,
|
|
||||||
required this.mode,
|
required this.mode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,9 +90,6 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
|||||||
context.showErrorDialog(resp.bodyString);
|
context.showErrorDialog(resp.bodyString);
|
||||||
} else {
|
} else {
|
||||||
_editorController.localClear();
|
_editorController.localClear();
|
||||||
if (widget.postListController != null) {
|
|
||||||
widget.postListController!.reloadAllOver();
|
|
||||||
}
|
|
||||||
AppRouter.instance.pop(resp.body);
|
AppRouter.instance.pop(resp.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +120,15 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (widget.edit == null) _editorController.localRead();
|
if (widget.edit == null && widget.reply == null && widget.repost == null) {
|
||||||
|
_editorController.localRead();
|
||||||
|
}
|
||||||
|
if (widget.reply != null) {
|
||||||
|
_editorController.replyTo.value = widget.reply;
|
||||||
|
}
|
||||||
|
if (widget.repost != null) {
|
||||||
|
_editorController.repostTo.value = widget.repost;
|
||||||
|
}
|
||||||
_editorController.contentController.addListener(() => setState(() {}));
|
_editorController.contentController.addListener(() => setState(() {}));
|
||||||
_syncWidget();
|
_syncWidget();
|
||||||
}
|
}
|
||||||
@ -219,10 +219,15 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
|||||||
collapsedBackgroundColor:
|
collapsedBackgroundColor:
|
||||||
Theme.of(context).colorScheme.surfaceContainer,
|
Theme.of(context).colorScheme.surfaceContainer,
|
||||||
children: [
|
children: [
|
||||||
PostItem(
|
Container(
|
||||||
item: _replyTo!,
|
constraints: const BoxConstraints(maxHeight: 280),
|
||||||
isReactable: false,
|
child: SingleChildScrollView(
|
||||||
).paddingOnly(bottom: 8),
|
child: PostItem(
|
||||||
|
item: _replyTo!,
|
||||||
|
isReactable: false,
|
||||||
|
).paddingOnly(bottom: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_repostTo != null)
|
if (_repostTo != null)
|
||||||
@ -237,10 +242,15 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
|||||||
collapsedBackgroundColor:
|
collapsedBackgroundColor:
|
||||||
Theme.of(context).colorScheme.surfaceContainer,
|
Theme.of(context).colorScheme.surfaceContainer,
|
||||||
children: [
|
children: [
|
||||||
PostItem(
|
Container(
|
||||||
item: _repostTo!,
|
constraints: const BoxConstraints(maxHeight: 280),
|
||||||
isReactable: false,
|
child: SingleChildScrollView(
|
||||||
).paddingOnly(bottom: 8),
|
child: PostItem(
|
||||||
|
item: _repostTo!,
|
||||||
|
isReactable: false,
|
||||||
|
).paddingOnly(bottom: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -12,6 +12,7 @@ const i18nEnglish = {
|
|||||||
'draftBox': 'Draft Box',
|
'draftBox': 'Draft Box',
|
||||||
'more': 'More',
|
'more': 'More',
|
||||||
'share': 'Share',
|
'share': 'Share',
|
||||||
|
'shareNoUri': 'Share text content',
|
||||||
'feed': 'Feed',
|
'feed': 'Feed',
|
||||||
'unlink': 'Unlink',
|
'unlink': 'Unlink',
|
||||||
'feedSearch': 'Search Feed',
|
'feedSearch': 'Search Feed',
|
||||||
@ -333,6 +334,7 @@ const i18nEnglish = {
|
|||||||
'bsEstablishingConn': 'Establishing Connection',
|
'bsEstablishingConn': 'Establishing Connection',
|
||||||
'bsPreparingData': 'Preparing User Data',
|
'bsPreparingData': 'Preparing User Data',
|
||||||
'bsRegisteringPushNotify': 'Enabling Push Notifications',
|
'bsRegisteringPushNotify': 'Enabling Push Notifications',
|
||||||
|
'bsDismissibleErrorHint': 'Click anywhere to ignore this error',
|
||||||
'postShareContent':
|
'postShareContent':
|
||||||
'@content\n\n@username on the Solar Network\nCheck it out: @link',
|
'@content\n\n@username on the Solar Network\nCheck it out: @link',
|
||||||
'postShareSubject': '@username posted a post on the Solar Network',
|
'postShareSubject': '@username posted a post on the Solar Network',
|
||||||
@ -360,4 +362,5 @@ const i18nEnglish = {
|
|||||||
'stickerUploaderName': 'Name',
|
'stickerUploaderName': 'Name',
|
||||||
'stickerUploaderNameHint':
|
'stickerUploaderNameHint':
|
||||||
'A human-friendly name given to the user in the sticker selection interface.',
|
'A human-friendly name given to the user in the sticker selection interface.',
|
||||||
|
'readMore': 'Read more',
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,7 @@ const i18nSimplifiedChinese = {
|
|||||||
'draftBox': '草稿箱',
|
'draftBox': '草稿箱',
|
||||||
'more': '更多',
|
'more': '更多',
|
||||||
'share': '分享',
|
'share': '分享',
|
||||||
|
'shareNoUri': '分享文字内容',
|
||||||
'feed': '资讯',
|
'feed': '资讯',
|
||||||
'unlink': '移除链接',
|
'unlink': '移除链接',
|
||||||
'feedSearch': '搜索资讯',
|
'feedSearch': '搜索资讯',
|
||||||
@ -306,6 +307,7 @@ const i18nSimplifiedChinese = {
|
|||||||
'bsEstablishingConn': '部署连接中',
|
'bsEstablishingConn': '部署连接中',
|
||||||
'bsPreparingData': '正在准备用户资料',
|
'bsPreparingData': '正在准备用户资料',
|
||||||
'bsRegisteringPushNotify': '正在启用推送通知',
|
'bsRegisteringPushNotify': '正在启用推送通知',
|
||||||
|
'bsDismissibleErrorHint': '点击任意地方忽略此错误',
|
||||||
'postShareContent': '@content\n\n@username 在 Solar Network\n原帖地址:@link',
|
'postShareContent': '@content\n\n@username 在 Solar Network\n原帖地址:@link',
|
||||||
'postShareSubject': '@username 在 Solar Network 上发布了一篇帖子',
|
'postShareSubject': '@username 在 Solar Network 上发布了一篇帖子',
|
||||||
'themeColor': '全局主题色',
|
'themeColor': '全局主题色',
|
||||||
@ -328,4 +330,5 @@ const i18nSimplifiedChinese = {
|
|||||||
'stickerUploaderAliasHint': '将会在输入时使用和贴图包前缀组成占位符。',
|
'stickerUploaderAliasHint': '将会在输入时使用和贴图包前缀组成占位符。',
|
||||||
'stickerUploaderName': '贴图名称',
|
'stickerUploaderName': '贴图名称',
|
||||||
'stickerUploaderNameHint': '在贴图选择界面提供给用户的人类友好名称。',
|
'stickerUploaderNameHint': '在贴图选择界面提供给用户的人类友好名称。',
|
||||||
|
'readMore': '阅读更多',
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/exts.dart';
|
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
import 'package:solian/providers/account_status.dart';
|
import 'package:solian/providers/account_status.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
@ -18,23 +17,38 @@ class AccountProfilePopup extends StatefulWidget {
|
|||||||
|
|
||||||
class _AccountProfilePopupState extends State<AccountProfilePopup> {
|
class _AccountProfilePopupState extends State<AccountProfilePopup> {
|
||||||
bool _isBusy = true;
|
bool _isBusy = true;
|
||||||
|
dynamic _hasError;
|
||||||
|
|
||||||
Account? _userinfo;
|
Account? _userinfo;
|
||||||
|
|
||||||
void _getUserinfo() async {
|
void _getUserinfo() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = ServiceFinder.configureClient('auth');
|
try {
|
||||||
final resp = await client.get('/users/${widget.name}');
|
final client = ServiceFinder.configureClient('auth');
|
||||||
if (resp.statusCode == 200) {
|
final resp = await client.get('/users/${widget.name}');
|
||||||
_userinfo = Account.fromJson(resp.body);
|
if (resp.statusCode == 200) {
|
||||||
setState(() => _isBusy = false);
|
setState(() {
|
||||||
} else {
|
_userinfo = Account.fromJson(resp.body);
|
||||||
context.showErrorDialog(resp.bodyString);
|
_isBusy = false;
|
||||||
Navigator.pop(context);
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_hasError = resp.bodyString;
|
||||||
|
_isBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_hasError = e;
|
||||||
|
_isBusy = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color get _unFocusColor =>
|
||||||
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -43,13 +57,35 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isBusy || _userinfo == null) {
|
if (_isBusy) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.75,
|
height: MediaQuery.of(context).size.height * 0.75,
|
||||||
child: const Center(child: CircularProgressIndicator()),
|
child: const Center(child: CircularProgressIndicator()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_hasError != null) {
|
||||||
|
return SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.75,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.cancel, size: 24),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
_hasError.toString(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: _unFocusColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.75,
|
height: MediaQuery.of(context).size.height * 0.75,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -385,7 +385,9 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
|
|||||||
icon: const Icon(Icons.warning),
|
icon: const Icon(Icons.warning),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
if (!element.isCompleted && element.error == null && canBeCrop)
|
if (!element.isCompleted &&
|
||||||
|
element.error == null &&
|
||||||
|
canBeCrop)
|
||||||
Obx(
|
Obx(
|
||||||
() => IconButton(
|
() => IconButton(
|
||||||
color: Colors.teal,
|
color: Colors.teal,
|
||||||
@ -398,7 +400,9 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!element.isCompleted && !element.isUploading && element.error == null)
|
if (!element.isCompleted &&
|
||||||
|
!element.isUploading &&
|
||||||
|
element.error == null)
|
||||||
Obx(
|
Obx(
|
||||||
() => IconButton(
|
() => IconButton(
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
@ -592,9 +596,13 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
'attachmentAdd'.tr,
|
child: Text(
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
'attachmentAdd'.tr,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
|
@ -7,6 +7,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
|
import 'package:solian/platform.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/screens/posts/post_editor.dart';
|
import 'package:solian/screens/posts/post_editor.dart';
|
||||||
@ -38,19 +39,32 @@ class _PostActionState extends State<PostAction> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _doShare() async {
|
Future<void> _doShare({bool noUri = false}) async {
|
||||||
final box = context.findRenderObject() as RenderBox?;
|
final box = context.findRenderObject() as RenderBox?;
|
||||||
await Share.share(
|
if ((PlatformInfo.isAndroid || PlatformInfo.isIOS) && !noUri) {
|
||||||
'postShareContent'.trParams({
|
await Share.shareUri(
|
||||||
'username': widget.item.author.nick,
|
Uri.parse('https://sn.solsynth.dev/posts/view/${widget.item.id}'),
|
||||||
'content': widget.item.body['content'] ?? 'no content',
|
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||||
'link': 'https://sn.solsynth.dev/posts/view/${widget.item.id}',
|
);
|
||||||
}),
|
} else {
|
||||||
subject: 'postShareSubject'.trParams({
|
final extraContent = [
|
||||||
'username': widget.item.author.nick,
|
widget.item.body['title'],
|
||||||
}),
|
widget.item.body['description'],
|
||||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
];
|
||||||
);
|
final isExtraNotEmpty = extraContent.any((x) => x != null);
|
||||||
|
await Share.share(
|
||||||
|
'postShareContent'.trParams({
|
||||||
|
'username': widget.item.author.nick,
|
||||||
|
'content':
|
||||||
|
'${extraContent.join('\n')}${isExtraNotEmpty ? '\n\n' : ''}${widget.item.body['content'] ?? 'no content'}',
|
||||||
|
'link': 'https://sn.solsynth.dev/posts/view/${widget.item.id}',
|
||||||
|
}),
|
||||||
|
subject: 'postShareSubject'.trParams({
|
||||||
|
'username': widget.item.author.nick,
|
||||||
|
}),
|
||||||
|
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -86,6 +100,16 @@ class _PostActionState extends State<PostAction> {
|
|||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Icons.share),
|
leading: const Icon(Icons.share),
|
||||||
title: Text('share'.tr),
|
title: Text('share'.tr),
|
||||||
|
trailing: PlatformInfo.isIOS || PlatformInfo.isAndroid
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.link_off),
|
||||||
|
tooltip: 'shareNoUri'.tr,
|
||||||
|
onPressed: () async {
|
||||||
|
await _doShare(noUri: true);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await _doShare();
|
await _doShare();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -97,13 +121,13 @@ class _PostActionState extends State<PostAction> {
|
|||||||
leading: const FaIcon(FontAwesomeIcons.reply, size: 20),
|
leading: const FaIcon(FontAwesomeIcons.reply, size: 20),
|
||||||
title: Text('reply'.tr),
|
title: Text('reply'.tr),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final value = await AppRouter.instance.pushNamed(
|
Navigator.pop(
|
||||||
'postEditor',
|
context,
|
||||||
extra: PostPublishArguments(reply: widget.item),
|
AppRouter.instance.pushNamed(
|
||||||
|
'postEditor',
|
||||||
|
extra: PostPublishArguments(reply: widget.item),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (value != null) {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!widget.noReact)
|
if (!widget.noReact)
|
||||||
@ -112,13 +136,13 @@ class _PostActionState extends State<PostAction> {
|
|||||||
leading: const FaIcon(FontAwesomeIcons.retweet, size: 20),
|
leading: const FaIcon(FontAwesomeIcons.retweet, size: 20),
|
||||||
title: Text('repost'.tr),
|
title: Text('repost'.tr),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final value = await AppRouter.instance.pushNamed(
|
Navigator.pop(
|
||||||
'postEditor',
|
context,
|
||||||
extra: PostPublishArguments(repost: widget.item),
|
AppRouter.instance.pushNamed(
|
||||||
|
'postEditor',
|
||||||
|
extra: PostPublishArguments(repost: widget.item),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (value != null) {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_canModifyContent && !widget.noReact)
|
if (_canModifyContent && !widget.noReact)
|
||||||
@ -146,13 +170,13 @@ class _PostActionState extends State<PostAction> {
|
|||||||
leading: const Icon(Icons.edit),
|
leading: const Icon(Icons.edit),
|
||||||
title: Text('edit'.tr),
|
title: Text('edit'.tr),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final value = await AppRouter.instance.pushNamed(
|
Navigator.pop(
|
||||||
'postEditor',
|
context,
|
||||||
extra: PostPublishArguments(edit: widget.item),
|
AppRouter.instance.pushNamed(
|
||||||
|
'postEditor',
|
||||||
|
extra: PostPublishArguments(edit: widget.item),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (value != null) {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_canModifyContent)
|
if (_canModifyContent)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get_utils/get_utils.dart';
|
import 'package:get/get_utils/get_utils.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@ -23,6 +24,7 @@ class PostItem extends StatefulWidget {
|
|||||||
final bool isShowReply;
|
final bool isShowReply;
|
||||||
final bool isShowEmbed;
|
final bool isShowEmbed;
|
||||||
final bool isFullDate;
|
final bool isFullDate;
|
||||||
|
final bool isFullContent;
|
||||||
final bool isContentSelectable;
|
final bool isContentSelectable;
|
||||||
final String? attachmentParent;
|
final String? attachmentParent;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
@ -36,6 +38,7 @@ class PostItem extends StatefulWidget {
|
|||||||
this.isShowReply = true,
|
this.isShowReply = true,
|
||||||
this.isShowEmbed = true,
|
this.isShowEmbed = true,
|
||||||
this.isFullDate = false,
|
this.isFullDate = false,
|
||||||
|
this.isFullContent = false,
|
||||||
this.isContentSelectable = false,
|
this.isContentSelectable = false,
|
||||||
this.attachmentParent,
|
this.attachmentParent,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
@ -73,12 +76,13 @@ class _PostItemState extends State<PostItem> {
|
|||||||
|
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return Row(
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (widget.isCompact)
|
if (widget.isCompact)
|
||||||
AccountAvatar(
|
AccountAvatar(
|
||||||
content: item.author.avatar.toString(),
|
content: item.author.avatar.toString(),
|
||||||
radius: 10,
|
radius: 10,
|
||||||
).paddingOnly(left: 2),
|
).paddingOnly(left: 2, top: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -105,18 +109,26 @@ class _PostItemState extends State<PostItem> {
|
|||||||
item.body['description'],
|
item.body['description'],
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
if (item.body['description'] != null ||
|
|
||||||
item.body['title'] != null)
|
|
||||||
const Divider(thickness: 0.3, height: 1).paddingSymmetric(
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).paddingOnly(left: widget.isCompact ? 6 : 12),
|
).paddingOnly(left: widget.isCompact ? 6 : 12),
|
||||||
),
|
),
|
||||||
|
if (widget.item.type == 'article')
|
||||||
|
Badge(
|
||||||
|
label: Text('article'.tr),
|
||||||
|
).paddingOnly(top: 3),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildHeaderDivider() {
|
||||||
|
if (item.body['description'] != null || item.body['title'] != null) {
|
||||||
|
return const Divider(thickness: 0.3, height: 1).paddingSymmetric(
|
||||||
|
vertical: 8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildFooter() {
|
Widget _buildFooter() {
|
||||||
List<String> labels = List.empty(growable: true);
|
List<String> labels = List.empty(growable: true);
|
||||||
if (widget.item.editedAt != null) {
|
if (widget.item.editedAt != null) {
|
||||||
@ -164,6 +176,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
|
|
||||||
Widget _buildReply(BuildContext context) {
|
Widget _buildReply(BuildContext context) {
|
||||||
return OpenContainer(
|
return OpenContainer(
|
||||||
|
tappable: widget.isClickable,
|
||||||
closedBuilder: (_, openContainer) => Column(
|
closedBuilder: (_, openContainer) => Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@ -202,13 +215,15 @@ class _PostItemState extends State<PostItem> {
|
|||||||
),
|
),
|
||||||
closedElevation: 0,
|
closedElevation: 0,
|
||||||
openElevation: 0,
|
openElevation: 0,
|
||||||
closedColor: widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
closedColor:
|
||||||
|
widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
||||||
openColor: Theme.of(context).colorScheme.surface,
|
openColor: Theme.of(context).colorScheme.surface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRepost(BuildContext context) {
|
Widget _buildRepost(BuildContext context) {
|
||||||
return OpenContainer(
|
return OpenContainer(
|
||||||
|
tappable: widget.isClickable,
|
||||||
closedBuilder: (_, openContainer) => Column(
|
closedBuilder: (_, openContainer) => Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@ -247,11 +262,14 @@ class _PostItemState extends State<PostItem> {
|
|||||||
),
|
),
|
||||||
closedElevation: 0,
|
closedElevation: 0,
|
||||||
openElevation: 0,
|
openElevation: 0,
|
||||||
closedColor: widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
closedColor:
|
||||||
|
widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
||||||
openColor: Theme.of(context).colorScheme.surface,
|
openColor: Theme.of(context).colorScheme.surface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _contentHeight = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<int> attachments = item.body['attachments'] is List
|
final List<int> attachments = item.body['attachments'] is List
|
||||||
@ -264,14 +282,50 @@ class _PostItemState extends State<PostItem> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildHeader().paddingSymmetric(horizontal: 12),
|
_buildHeader().paddingSymmetric(horizontal: 12),
|
||||||
MarkdownTextContent(
|
_buildHeaderDivider().paddingSymmetric(horizontal: 12),
|
||||||
content: item.body['content'],
|
Stack(
|
||||||
isSelectable: widget.isContentSelectable,
|
children: [
|
||||||
).paddingOnly(
|
SizedContainer(
|
||||||
left: 16,
|
maxWidth: 640,
|
||||||
right: 12,
|
maxHeight: widget.isFullContent ? double.infinity : 80,
|
||||||
top: 2,
|
child: _MeasureSize(
|
||||||
bottom: hasAttachment ? 4 : 0,
|
onChange: (size) {
|
||||||
|
setState(() => _contentHeight = size.height);
|
||||||
|
},
|
||||||
|
child: MarkdownTextContent(
|
||||||
|
content: item.body['content'],
|
||||||
|
isSelectable: widget.isContentSelectable,
|
||||||
|
).paddingOnly(
|
||||||
|
left: 16,
|
||||||
|
right: 12,
|
||||||
|
top: 2,
|
||||||
|
bottom: hasAttachment ? 4 : 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_contentHeight >= 80 && !widget.isFullContent)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withOpacity(0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
_buildFooter().paddingOnly(left: 16),
|
_buildFooter().paddingOnly(left: 16),
|
||||||
if (attachments.isNotEmpty)
|
if (attachments.isNotEmpty)
|
||||||
@ -295,6 +349,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return OpenContainer(
|
return OpenContainer(
|
||||||
|
tappable: widget.isClickable,
|
||||||
closedBuilder: (_, openContainer) => Column(
|
closedBuilder: (_, openContainer) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -320,12 +375,46 @@ class _PostItemState extends State<PostItem> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildHeader(),
|
_buildHeader(),
|
||||||
SizedContainer(
|
_buildHeaderDivider(),
|
||||||
maxWidth: 640,
|
Stack(
|
||||||
child: MarkdownTextContent(
|
children: [
|
||||||
content: item.body['content'],
|
SizedContainer(
|
||||||
isSelectable: widget.isContentSelectable,
|
maxWidth: 640,
|
||||||
).paddingOnly(left: 12, right: 8),
|
maxHeight:
|
||||||
|
widget.isFullContent ? double.infinity : 320,
|
||||||
|
child: _MeasureSize(
|
||||||
|
onChange: (size) {
|
||||||
|
setState(() => _contentHeight = size.height);
|
||||||
|
},
|
||||||
|
child: MarkdownTextContent(
|
||||||
|
content: item.body['content'],
|
||||||
|
isSelectable: widget.isContentSelectable,
|
||||||
|
).paddingOnly(left: 12, right: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_contentHeight >= 320 && !widget.isFullContent)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
height: 320,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withOpacity(0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
if (widget.item.replyTo != null && widget.isShowEmbed)
|
if (widget.item.replyTo != null && widget.isShowEmbed)
|
||||||
_buildReply(context).paddingOnly(top: 4),
|
_buildReply(context).paddingOnly(top: 4),
|
||||||
@ -377,8 +466,52 @@ class _PostItemState extends State<PostItem> {
|
|||||||
),
|
),
|
||||||
closedElevation: 0,
|
closedElevation: 0,
|
||||||
openElevation: 0,
|
openElevation: 0,
|
||||||
closedColor: widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
closedColor:
|
||||||
|
widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
|
||||||
openColor: Theme.of(context).colorScheme.surface,
|
openColor: Theme.of(context).colorScheme.surface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef _OnWidgetSizeChange = void Function(Size size);
|
||||||
|
|
||||||
|
class _MeasureSizeRenderObject extends RenderProxyBox {
|
||||||
|
Size? oldSize;
|
||||||
|
_OnWidgetSizeChange onChange;
|
||||||
|
|
||||||
|
_MeasureSizeRenderObject(this.onChange);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performLayout() {
|
||||||
|
super.performLayout();
|
||||||
|
|
||||||
|
Size newSize = child!.size;
|
||||||
|
if (oldSize == newSize) return;
|
||||||
|
|
||||||
|
oldSize = newSize;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
onChange(newSize);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MeasureSize extends SingleChildRenderObjectWidget {
|
||||||
|
final _OnWidgetSizeChange onChange;
|
||||||
|
|
||||||
|
const _MeasureSize({
|
||||||
|
super.key,
|
||||||
|
required this.onChange,
|
||||||
|
required Widget super.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
return _MeasureSizeRenderObject(onChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(
|
||||||
|
BuildContext context, covariant _MeasureSizeRenderObject renderObject) {
|
||||||
|
renderObject.onChange = onChange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -85,7 +85,13 @@ class PostListEntryWidget extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PostAction(item: item),
|
builder: (context) => PostAction(item: item),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value != null) onUpdate();
|
if (value is Future) {
|
||||||
|
value.then((_) {
|
||||||
|
onUpdate();
|
||||||
|
});
|
||||||
|
} else if (value != null) {
|
||||||
|
onUpdate();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -6,18 +6,21 @@ import 'package:solian/widgets/posts/post_item.dart';
|
|||||||
class PostOwnedListEntry extends StatelessWidget {
|
class PostOwnedListEntry extends StatelessWidget {
|
||||||
final Post item;
|
final Post item;
|
||||||
final Function onTap;
|
final Function onTap;
|
||||||
|
final bool isFullContent;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
|
||||||
const PostOwnedListEntry({
|
const PostOwnedListEntry({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
this.isFullContent = false,
|
||||||
|
this.backgroundColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
child: InkWell(
|
child: GestureDetector(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -28,6 +31,8 @@ class PostOwnedListEntry extends StatelessWidget {
|
|||||||
isClickable: false,
|
isClickable: false,
|
||||||
isShowReply: false,
|
isShowReply: false,
|
||||||
isReactable: false,
|
isReactable: false,
|
||||||
|
isFullContent: isFullContent,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
).paddingSymmetric(vertical: 8),
|
).paddingSymmetric(vertical: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -9,6 +9,7 @@ class PostWarpedListWidget extends StatelessWidget {
|
|||||||
final bool isNestedClickable;
|
final bool isNestedClickable;
|
||||||
final bool isPinned;
|
final bool isPinned;
|
||||||
final PagingController<int, Post> controller;
|
final PagingController<int, Post> controller;
|
||||||
|
final Function? onUpdate;
|
||||||
|
|
||||||
const PostWarpedListWidget({
|
const PostWarpedListWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@ -17,6 +18,7 @@ class PostWarpedListWidget extends StatelessWidget {
|
|||||||
this.isClickable = true,
|
this.isClickable = true,
|
||||||
this.isNestedClickable = true,
|
this.isNestedClickable = true,
|
||||||
this.isPinned = true,
|
this.isPinned = true,
|
||||||
|
this.onUpdate,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,9 +37,7 @@ class PostWarpedListWidget extends StatelessWidget {
|
|||||||
isNestedClickable: isNestedClickable,
|
isNestedClickable: isNestedClickable,
|
||||||
isClickable: isClickable,
|
isClickable: isClickable,
|
||||||
item: item,
|
item: item,
|
||||||
onUpdate: () {
|
onUpdate: onUpdate ?? () {},
|
||||||
controller.refresh();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -3,11 +3,13 @@ import 'package:flutter/material.dart';
|
|||||||
class SizedContainer extends StatelessWidget {
|
class SizedContainer extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final double maxWidth;
|
final double maxWidth;
|
||||||
|
final double maxHeight;
|
||||||
|
|
||||||
const SizedContainer({
|
const SizedContainer({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.maxWidth = 720,
|
this.maxWidth = 720,
|
||||||
|
this.maxHeight = double.infinity,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -15,7 +17,7 @@ class SizedContainer extends StatelessWidget {
|
|||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
16
pubspec.lock
16
pubspec.lock
@ -385,6 +385,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
field_suggestion:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: field_suggestion
|
||||||
|
sha256: "596362ed67a661a18e01e4f51b8b92424f333baf37cebd48cc647a4c82c858d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.5"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -736,6 +744,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
highlightable:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: highlightable
|
||||||
|
sha256: "526793e148c91977b694d75d99cd34401ea3b65efd223e7b539c76916af86ffd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -2,7 +2,7 @@ name: solian
|
|||||||
description: "The Solar Network App"
|
description: "The Solar Network App"
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 1.2.1+1
|
version: 1.2.1+5
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.4 <4.0.0"
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
@ -66,6 +66,7 @@ dependencies:
|
|||||||
animations: ^2.0.11
|
animations: ^2.0.11
|
||||||
avatar_stack: ^1.2.0
|
avatar_stack: ^1.2.0
|
||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
|
field_suggestion: ^0.2.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user