Compare commits
8 Commits
2.3.2+67
...
c91cf7c813
Author | SHA1 | Date | |
---|---|---|---|
|
c91cf7c813 | ||
|
42ac12b53e | ||
|
63567bf708 | ||
|
5d3cadefef | ||
|
251fbb2503 | ||
|
0b31d32217 | ||
|
5ddd4fed2e | ||
|
48b6d5f6c1 |
23
.github/workflows/nightly.yml
vendored
23
.github/workflows/nightly.yml
vendored
@@ -39,3 +39,26 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: build-output-windows
|
name: build-output-windows
|
||||||
path: build/windows/x64/runner/Release
|
path: build/windows/x64/runner/Release
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update -y
|
||||||
|
sudo apt-get install -y ninja-build libgtk-3-dev
|
||||||
|
sudo apt-get install libmpv-dev mpv
|
||||||
|
sudo apt-get install libayatana-appindicator3-dev
|
||||||
|
sudo apt-get install keybinder-3.0
|
||||||
|
- run: flutter pub get
|
||||||
|
- run: flutter build linux
|
||||||
|
- name: Archive production artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build-output-linux
|
||||||
|
path: build/linux/x64/release/bundle
|
@@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: {{endpoint}}/cgi/id/dev/notify/1
|
url: {{endpoint}}/cgi/id/dev/notify/122
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
@@ -15,12 +15,9 @@ body:json {
|
|||||||
"client_id": "{{third_client_id}}",
|
"client_id": "{{third_client_id}}",
|
||||||
"client_secret":"{{third_client_tk}}",
|
"client_secret":"{{third_client_tk}}",
|
||||||
"type": "general",
|
"type": "general",
|
||||||
"subject": "测试",
|
"subject": "处理该帐号 @solian 的决定",
|
||||||
"subtitle": "Alphabot です",
|
"subtitle": "违反用户协议",
|
||||||
"content": "全新通知动画",
|
"content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。",
|
||||||
"metadata": {
|
|
||||||
"image": "D2EDbcrsTugs3xk5"
|
|
||||||
},
|
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -47,6 +47,8 @@ import 'package:tray_manager/tray_manager.dart';
|
|||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'package:in_app_review/in_app_review.dart';
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
|
import 'package:image_picker_android/image_picker_android.dart';
|
||||||
|
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void appBackgroundDispatcher() {
|
void appBackgroundDispatcher() {
|
||||||
@@ -107,6 +109,13 @@ void main() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!kIsWeb && Platform.isAndroid) {
|
||||||
|
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
|
||||||
|
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||||
|
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(const SolianApp());
|
runApp(const SolianApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +169,8 @@ class SolianApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
breakpoints: [
|
breakpoints: [
|
||||||
const Breakpoint(start: 0, end: 450, name: MOBILE),
|
const Breakpoint(start: 0, end: 600, name: MOBILE),
|
||||||
const Breakpoint(start: 451, end: 800, name: TABLET),
|
const Breakpoint(start: 601, end: 800, name: TABLET),
|
||||||
const Breakpoint(start: 801, end: 1920, name: DESKTOP),
|
const Breakpoint(start: 801, end: 1920, name: DESKTOP),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@@ -42,13 +42,14 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
_connectCompleter = null;
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectCompleter = Completer<void>();
|
|
||||||
|
|
||||||
if (!_ua.isAuthorized) return;
|
if (!_ua.isAuthorized) return;
|
||||||
if (isConnected || conn != null) {
|
if (isConnected || conn != null) {
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_connectCompleter = Completer<void>();
|
||||||
|
|
||||||
final atk = await _sn.getFreshAtk();
|
final atk = await _sn.getFreshAtk();
|
||||||
final uri = Uri.parse(
|
final uri = Uri.parse(
|
||||||
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
||||||
@@ -57,7 +58,6 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
isBusy = true;
|
isBusy = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
|
||||||
conn = WebSocketChannel.connect(uri);
|
conn = WebSocketChannel.connect(uri);
|
||||||
await conn!.ready;
|
await conn!.ready;
|
||||||
_wsStream = conn!.stream.asBroadcastStream();
|
_wsStream = conn!.stream.asBroadcastStream();
|
||||||
@@ -82,6 +82,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
isBusy = false;
|
isBusy = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_connectCompleter!.complete();
|
_connectCompleter!.complete();
|
||||||
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -74,7 +74,10 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
if (_isBusy)
|
if (_isBusy)
|
||||||
const CircularProgressIndicator().padding(all: 24).center()
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
).center()
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
@@ -123,8 +123,10 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
),
|
),
|
||||||
if (_isBusy)
|
if (_isBusy)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child:
|
child: Padding(
|
||||||
const CircularProgressIndicator().padding(all: 24).center(),
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
).center(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@@ -131,6 +131,7 @@ class _HomeDashUpdateWidget extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Card(
|
child: Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(Symbols.update),
|
leading: Icon(Symbols.update),
|
||||||
title: Text('updateAvailable').tr(),
|
title: Text('updateAvailable').tr(),
|
||||||
@@ -180,6 +181,7 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: days.map((ele) {
|
children: days.map((ele) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24),
|
leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24),
|
||||||
title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']),
|
title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']),
|
||||||
@@ -203,6 +205,7 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
||||||
final diff = nextOne.$2.difference(DateTime.now());
|
final diff = nextOne.$2.difference(DateTime.now());
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
||||||
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]),
|
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]),
|
||||||
@@ -270,6 +273,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -469,6 +473,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -594,6 +599,7 @@ class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -667,11 +673,13 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isBusy) {
|
if (_isBusy) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: CircularProgressIndicator().center(),
|
child: CircularProgressIndicator().center(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
@@ -90,8 +91,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
||||||
_writeController
|
_writeController.setPublisher(
|
||||||
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull);
|
_publishers?.where((ele) => ele.id == beforeId).firstOrNull ??
|
||||||
|
_publishers?.firstOrNull);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -110,7 +112,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -160,7 +162,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_writeController.dispose();
|
_writeController.dispose();
|
||||||
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS))
|
||||||
|
hotKeyManager.unregister(_pasteHotKey);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +187,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
if (widget.extraProps != null) {
|
if (widget.extraProps != null) {
|
||||||
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
||||||
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
||||||
_writeController.descriptionController.text = widget.extraProps!.description ?? '';
|
_writeController.descriptionController.text =
|
||||||
|
widget.extraProps!.description ?? '';
|
||||||
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +209,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
|
text: _writeController.title.isNotEmpty
|
||||||
|
? _writeController.title
|
||||||
|
: 'untitled'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
),
|
),
|
||||||
@@ -232,7 +238,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
if (_writeController.editingPost != null)
|
if (_writeController.editingPost != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 20, right: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@@ -246,7 +253,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.edit, size: 16),
|
const Icon(Icons.edit, size: 16),
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
Text('postEditingNotice').tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
Text('postEditingNotice').tr(args: [
|
||||||
|
'@${_writeController.editingPost!.publisher.name}'
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -276,7 +285,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
})
|
})
|
||||||
.padding(top: 8),
|
.padding(top: 8),
|
||||||
),
|
),
|
||||||
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
if (_writeController.attachments.isNotEmpty ||
|
||||||
|
_writeController.thumbnail != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -286,7 +296,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
attachments: _writeController.attachments,
|
attachments: _writeController.attachments,
|
||||||
isBusy: _writeController.isBusy,
|
isBusy: _writeController.isBusy,
|
||||||
onUpload: (int idx) async {
|
onUpload: (int idx) async {
|
||||||
await _writeController.uploadSingleAttachment(context, idx);
|
await _writeController.uploadSingleAttachment(
|
||||||
|
context, idx);
|
||||||
},
|
},
|
||||||
onPostSetThumbnail: (int? idx) {
|
onPostSetThumbnail: (int? idx) {
|
||||||
_writeController.setThumbnail(idx);
|
_writeController.setThumbnail(idx);
|
||||||
@@ -295,10 +306,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
_writeController.contentController.text +=
|
_writeController.contentController.text +=
|
||||||
'\n';
|
'\n';
|
||||||
},
|
},
|
||||||
onUpdate: (int idx, PostWriteMedia updatedMedia) async {
|
onUpdate:
|
||||||
|
(int idx, PostWriteMedia updatedMedia) async {
|
||||||
_writeController.setIsBusy(true);
|
_writeController.setIsBusy(true);
|
||||||
try {
|
try {
|
||||||
_writeController.setAttachmentAt(idx, updatedMedia);
|
_writeController.setAttachmentAt(
|
||||||
|
idx, updatedMedia);
|
||||||
} finally {
|
} finally {
|
||||||
_writeController.setIsBusy(false);
|
_writeController.setIsBusy(false);
|
||||||
}
|
}
|
||||||
@@ -311,7 +324,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
_writeController.setIsBusy(false);
|
_writeController.setIsBusy(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUpdateBusy: (state) => _writeController.setIsBusy(state),
|
onUpdateBusy: (state) =>
|
||||||
|
_writeController.setIsBusy(state),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -322,11 +336,13 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (_writeController.isBusy && _writeController.progress != null)
|
if (_writeController.isBusy &&
|
||||||
|
_writeController.progress != null)
|
||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: _writeController.progress),
|
tween: Tween(begin: 0, end: _writeController.progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
builder: (context, value, _) =>
|
||||||
|
LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
)
|
)
|
||||||
else if (_writeController.isBusy)
|
else if (_writeController.isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
@@ -335,12 +351,14 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
Container(
|
Container(
|
||||||
child: _writeController.temporaryRestored
|
child: _writeController.temporaryRestored
|
||||||
? Container(
|
? Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 28, right: 22),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 28, right: 22),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
width: 1 /
|
||||||
|
MediaQuery.of(context).devicePixelRatio,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -349,7 +367,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.restore, size: 20),
|
const Icon(Icons.restore, size: 20),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(child: Text('postLocalDraftRestored').tr()),
|
Expanded(
|
||||||
|
child:
|
||||||
|
Text('postLocalDraftRestored').tr()),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Text('dialogDismiss').tr(),
|
child: Text('dialogDismiss').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -360,8 +380,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
))
|
))
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
)
|
)
|
||||||
.height(_writeController.temporaryRestored ? 32 : 0, animate: true)
|
.height(_writeController.temporaryRestored ? 32 : 0,
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
|
animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300),
|
||||||
|
Curves.fastLinearToSlowEaseIn),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -381,11 +403,18 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
if (_writeController.mode == 'stories')
|
if (_writeController.mode == 'stories')
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Symbols.poll, color: Theme.of(context).colorScheme.primary),
|
icon: Icon(Symbols.poll,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: _writeController.poll == null
|
backgroundColor:
|
||||||
|
_writeController.poll == null
|
||||||
? null
|
? null
|
||||||
: WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer),
|
: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainer),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showPollEditorDialog();
|
_showPollEditorDialog();
|
||||||
@@ -397,7 +426,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: (_writeController.isBusy || _writeController.publisher == null)
|
onPressed: (_writeController.isBusy ||
|
||||||
|
_writeController.publisher == null)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
_writeController.sendPost(context).then((_) {
|
_writeController.sendPost(context).then((_) {
|
||||||
@@ -447,9 +477,21 @@ class _PostPublisherPopup extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.face, size: 24),
|
const Icon(Symbols.face, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(),
|
Text('accountPublishers',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge)
|
||||||
|
.tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('publishersNew').tr(),
|
||||||
|
subtitle: Text('publisherNewSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountPublisherNew');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: publishers?.length ?? 0,
|
itemCount: publishers?.length ?? 0,
|
||||||
@@ -509,7 +551,8 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@@ -524,7 +567,8 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -614,7 +658,8 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@@ -693,7 +738,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@@ -704,7 +750,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isCollapsed: true,
|
isCollapsed: true,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@@ -719,7 +766,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -755,7 +803,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
|
|
||||||
final result = await showDialog<SnAttachment?>(
|
final result = await showDialog<SnAttachment?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PendingAttachmentAltDialog(media: PostWriteMedia(controller.videoAttachment)),
|
builder: (context) => PendingAttachmentAltDialog(
|
||||||
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
@@ -767,7 +816,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
|
|
||||||
final result = await showDialog<SnAttachmentBoost?>(
|
final result = await showDialog<SnAttachmentBoost?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PendingAttachmentBoostDialog(media: PostWriteMedia(controller.videoAttachment)),
|
builder: (context) => PendingAttachmentBoostDialog(
|
||||||
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
@@ -810,7 +860,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
await sn.client
|
||||||
|
.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
||||||
controller.setVideoAttachment(null);
|
controller.setVideoAttachment(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@@ -902,7 +953,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
label: 'attachmentCopyRandomId'.tr(),
|
label: 'attachmentCopyRandomId'.tr(),
|
||||||
icon: Symbols.content_copy,
|
icon: Symbols.content_copy,
|
||||||
onSelected: () {
|
onSelected: () {
|
||||||
Clipboard.setData(ClipboardData(text: controller.videoAttachment!.rid));
|
Clipboard.setData(
|
||||||
|
ClipboardData(text: controller.videoAttachment!.rid));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
@@ -921,7 +973,9 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
onTap: controller.videoAttachment == null ? () => _selectVideo(context) : null,
|
onTap: controller.videoAttachment == null
|
||||||
|
? () => _selectVideo(context)
|
||||||
|
: null,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: controller.videoAttachment == null
|
child: controller.videoAttachment == null
|
||||||
|
@@ -154,7 +154,7 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
|||||||
try {
|
try {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/im/channels/${widget.realm.alias}');
|
final resp = await sn.client.get('/cgi/im/channels/${widget.realm.alias}/public');
|
||||||
final out = List<SnChannel>.from(
|
final out = List<SnChannel>.from(
|
||||||
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
||||||
);
|
);
|
||||||
|
@@ -16,8 +16,7 @@ class SnPoll with _$SnPoll {
|
|||||||
required SnPollMetric metric,
|
required SnPollMetric metric,
|
||||||
}) = _SnPoll;
|
}) = _SnPoll;
|
||||||
|
|
||||||
factory SnPoll.fromJson(Map<String, Object?> json) =>
|
factory SnPoll.fromJson(Map<String, Object?> json) => _$SnPollFromJson(json);
|
||||||
_$SnPollFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -25,11 +24,11 @@ class SnPollMetric with _$SnPollMetric {
|
|||||||
const factory SnPollMetric({
|
const factory SnPollMetric({
|
||||||
required int totalAnswer,
|
required int totalAnswer,
|
||||||
@Default({}) Map<String, int> byOptions,
|
@Default({}) Map<String, int> byOptions,
|
||||||
@Default({}) Map<String, int> byOptionsPercentage,
|
@Default({}) Map<String, double> byOptionsPercentage,
|
||||||
}) = _SnPollMetric;
|
}) = _SnPollMetric;
|
||||||
|
|
||||||
factory SnPollMetric.fromJson(Map<String, Object?> json)
|
factory SnPollMetric.fromJson(Map<String, Object?> json) =>
|
||||||
=> _$SnPollMetricFromJson(json);
|
_$SnPollMetricFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -41,6 +40,6 @@ class SnPollOption with _$SnPollOption {
|
|||||||
required String description,
|
required String description,
|
||||||
}) = _SnPollOption;
|
}) = _SnPollOption;
|
||||||
|
|
||||||
factory SnPollOption.fromJson(Map<String, Object?> json)
|
factory SnPollOption.fromJson(Map<String, Object?> json) =>
|
||||||
=> _$SnPollOptionFromJson(json);
|
_$SnPollOptionFromJson(json);
|
||||||
}
|
}
|
||||||
|
@@ -345,7 +345,7 @@ SnPollMetric _$SnPollMetricFromJson(Map<String, dynamic> json) {
|
|||||||
mixin _$SnPollMetric {
|
mixin _$SnPollMetric {
|
||||||
int get totalAnswer => throw _privateConstructorUsedError;
|
int get totalAnswer => throw _privateConstructorUsedError;
|
||||||
Map<String, int> get byOptions => throw _privateConstructorUsedError;
|
Map<String, int> get byOptions => throw _privateConstructorUsedError;
|
||||||
Map<String, int> get byOptionsPercentage =>
|
Map<String, double> get byOptionsPercentage =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this SnPollMetric to a JSON map.
|
/// Serializes this SnPollMetric to a JSON map.
|
||||||
@@ -367,7 +367,7 @@ abstract class $SnPollMetricCopyWith<$Res> {
|
|||||||
$Res call(
|
$Res call(
|
||||||
{int totalAnswer,
|
{int totalAnswer,
|
||||||
Map<String, int> byOptions,
|
Map<String, int> byOptions,
|
||||||
Map<String, int> byOptionsPercentage});
|
Map<String, double> byOptionsPercentage});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -401,7 +401,7 @@ class _$SnPollMetricCopyWithImpl<$Res, $Val extends SnPollMetric>
|
|||||||
byOptionsPercentage: null == byOptionsPercentage
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
? _value.byOptionsPercentage
|
? _value.byOptionsPercentage
|
||||||
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, int>,
|
as Map<String, double>,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,7 +417,7 @@ abstract class _$$SnPollMetricImplCopyWith<$Res>
|
|||||||
$Res call(
|
$Res call(
|
||||||
{int totalAnswer,
|
{int totalAnswer,
|
||||||
Map<String, int> byOptions,
|
Map<String, int> byOptions,
|
||||||
Map<String, int> byOptionsPercentage});
|
Map<String, double> byOptionsPercentage});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -449,7 +449,7 @@ class __$$SnPollMetricImplCopyWithImpl<$Res>
|
|||||||
byOptionsPercentage: null == byOptionsPercentage
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
? _value._byOptionsPercentage
|
? _value._byOptionsPercentage
|
||||||
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, int>,
|
as Map<String, double>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,7 +460,7 @@ class _$SnPollMetricImpl implements _SnPollMetric {
|
|||||||
const _$SnPollMetricImpl(
|
const _$SnPollMetricImpl(
|
||||||
{required this.totalAnswer,
|
{required this.totalAnswer,
|
||||||
final Map<String, int> byOptions = const {},
|
final Map<String, int> byOptions = const {},
|
||||||
final Map<String, int> byOptionsPercentage = const {}})
|
final Map<String, double> byOptionsPercentage = const {}})
|
||||||
: _byOptions = byOptions,
|
: _byOptions = byOptions,
|
||||||
_byOptionsPercentage = byOptionsPercentage;
|
_byOptionsPercentage = byOptionsPercentage;
|
||||||
|
|
||||||
@@ -478,10 +478,10 @@ class _$SnPollMetricImpl implements _SnPollMetric {
|
|||||||
return EqualUnmodifiableMapView(_byOptions);
|
return EqualUnmodifiableMapView(_byOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, int> _byOptionsPercentage;
|
final Map<String, double> _byOptionsPercentage;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
Map<String, int> get byOptionsPercentage {
|
Map<String, double> get byOptionsPercentage {
|
||||||
if (_byOptionsPercentage is EqualUnmodifiableMapView)
|
if (_byOptionsPercentage is EqualUnmodifiableMapView)
|
||||||
return _byOptionsPercentage;
|
return _byOptionsPercentage;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
@@ -534,7 +534,7 @@ abstract class _SnPollMetric implements SnPollMetric {
|
|||||||
const factory _SnPollMetric(
|
const factory _SnPollMetric(
|
||||||
{required final int totalAnswer,
|
{required final int totalAnswer,
|
||||||
final Map<String, int> byOptions,
|
final Map<String, int> byOptions,
|
||||||
final Map<String, int> byOptionsPercentage}) = _$SnPollMetricImpl;
|
final Map<String, double> byOptionsPercentage}) = _$SnPollMetricImpl;
|
||||||
|
|
||||||
factory _SnPollMetric.fromJson(Map<String, dynamic> json) =
|
factory _SnPollMetric.fromJson(Map<String, dynamic> json) =
|
||||||
_$SnPollMetricImpl.fromJson;
|
_$SnPollMetricImpl.fromJson;
|
||||||
@@ -544,7 +544,7 @@ abstract class _SnPollMetric implements SnPollMetric {
|
|||||||
@override
|
@override
|
||||||
Map<String, int> get byOptions;
|
Map<String, int> get byOptions;
|
||||||
@override
|
@override
|
||||||
Map<String, int> get byOptionsPercentage;
|
Map<String, double> get byOptionsPercentage;
|
||||||
|
|
||||||
/// Create a copy of SnPollMetric
|
/// Create a copy of SnPollMetric
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@@ -40,7 +40,7 @@ _$SnPollMetricImpl _$$SnPollMetricImplFromJson(Map<String, dynamic> json) =>
|
|||||||
const {},
|
const {},
|
||||||
byOptionsPercentage:
|
byOptionsPercentage:
|
||||||
(json['by_options_percentage'] as Map<String, dynamic>?)?.map(
|
(json['by_options_percentage'] as Map<String, dynamic>?)?.map(
|
||||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
(k, e) => MapEntry(k, (e as num).toDouble()),
|
||||||
) ??
|
) ??
|
||||||
const {},
|
const {},
|
||||||
);
|
);
|
||||||
|
@@ -45,7 +45,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -112,6 +112,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _sendMessage() async {
|
Future<void> _sendMessage() async {
|
||||||
|
if (_contentController.text.isEmpty && _attachments.isEmpty) return;
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
|
|
||||||
final attach = context.read<SnAttachmentProvider>();
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
|
@@ -31,19 +31,27 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
|
|
||||||
String? _answeredChoice;
|
String? _answeredChoice;
|
||||||
|
|
||||||
|
Future<void> _refreshPoll() async {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/polls/${widget.poll.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _poll = SnPoll.fromJson(resp.data!));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchAnswer() async {
|
Future<void> _fetchAnswer() async {
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
if (!ua.isAuthorized) return;
|
if (!ua.isAuthorized) return;
|
||||||
try {
|
try {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/polls/${widget.poll.id}/answer');
|
final resp =
|
||||||
|
await sn.client.get('/cgi/co/polls/${widget.poll.id}/answer');
|
||||||
_answeredChoice = resp.data?['answer'];
|
_answeredChoice = resp.data?['answer'];
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
// ignore because it may not found
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
@@ -59,8 +67,9 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
'answer': option.id,
|
'answer': option.id,
|
||||||
});
|
});
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar('pollAnswered'.tr());
|
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
|
_answeredChoice = option.id;
|
||||||
|
_refreshPoll();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -78,15 +87,24 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
for (final option in _poll.options)
|
for (final option in _poll.options)
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
height: 60,
|
height: 60,
|
||||||
width: MediaQuery.of(context).size.width * (_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble(),
|
width: MediaQuery.of(context).size.width *
|
||||||
|
(_poll.metric.byOptionsPercentage[option.id] ?? 0)
|
||||||
|
.toDouble(),
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
minTileHeight: 60,
|
minTileHeight: 60,
|
||||||
leading: _answeredChoice == option.id ? const Icon(Symbols.circle, fill: 1) : const Icon(Symbols.circle),
|
leading: _answeredChoice == option.id
|
||||||
|
? const Icon(Symbols.circle, fill: 1)
|
||||||
|
: const Icon(Symbols.circle),
|
||||||
title: Text(option.name),
|
title: Text(option.name),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -95,14 +113,18 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text('pollVotes'.plural(_poll.metric.byOptions[option.id] ?? 0)),
|
Text(
|
||||||
|
'pollVotes'
|
||||||
|
.plural(_poll.metric.byOptions[option.id] ?? 0),
|
||||||
|
),
|
||||||
Text(' · ').padding(horizontal: 4),
|
Text(' · ').padding(horizontal: 4),
|
||||||
Text(
|
Text(
|
||||||
'${((_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble() * 100).toStringAsFixed(2)}%',
|
'${((_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble() * 100).toStringAsFixed(2)}%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (option.description.isNotEmpty) Text(option.description),
|
if (option.description.isNotEmpty)
|
||||||
|
Text(option.description),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: _isBusy ? null : () => _voteForOption(option),
|
onTap: _isBusy ? null : () => _voteForOption(option),
|
||||||
|
@@ -498,10 +498,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "3d57312a53746ed4eb8c843dc50372454bbda37dd0c01a4d40fedc83e2ce4921"
|
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.5"
|
version: "8.3.7"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1043,7 +1043,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
|
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
|
||||||
|
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 2.3.2+67
|
version: 2.3.2+68
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@@ -120,6 +120,7 @@ dependencies:
|
|||||||
xml: ^6.5.0
|
xml: ^6.5.0
|
||||||
tray_manager: ^0.3.2
|
tray_manager: ^0.3.2
|
||||||
hotkey_manager: ^0.2.3
|
hotkey_manager: ^0.2.3
|
||||||
|
image_picker_android: ^0.8.12+20
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user