Compare commits
4 Commits
2.4.2+84
...
21a1d4a2ad
Author | SHA1 | Date | |
---|---|---|---|
21a1d4a2ad | |||
603875b1af | |||
4209a13c84 | |||
55b79bfd8f |
@ -890,5 +890,12 @@
|
||||
},
|
||||
"settingsHideBottomNav": "Hide Bottom Navigation",
|
||||
"settingsHideBottomNavDescription": "Hide the bottom navigation bar, and show the navigation buttons in the drawer.",
|
||||
"reCaptcha": "reCaptcha"
|
||||
"reCaptcha": "reCaptcha",
|
||||
"friends": "Friends",
|
||||
"friendsDescription": "Manage your friendships.",
|
||||
"album": "Album",
|
||||
"albumDescription": "View albums and manage attachments.",
|
||||
"stickers": "Stickers",
|
||||
"stickersDescription": "View sticker packs and manage stickers.",
|
||||
"navBottomUnauthorizedCaption": "Or create an account"
|
||||
}
|
||||
|
@ -888,5 +888,12 @@
|
||||
},
|
||||
"settingsHideBottomNav": "隐藏底部导航栏",
|
||||
"settingsHideBottomNavDescription": "隐藏底部导航栏,在侧边栏抽屉显示导航按钮。",
|
||||
"reCaptcha": "人机验证"
|
||||
"reCaptcha": "人机验证",
|
||||
"friends": "好友",
|
||||
"friendsDescription": "管理好友关系。",
|
||||
"album": "相册",
|
||||
"albumDescription": "查看相册与管理上传附件。",
|
||||
"stickers": "贴图",
|
||||
"stickersDescription": "查看贴图包与管理贴图。",
|
||||
"navBottomUnauthorizedCaption": "或者注册一个账号"
|
||||
}
|
||||
|
@ -888,5 +888,12 @@
|
||||
},
|
||||
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||
"reCaptcha": "人機驗證"
|
||||
"reCaptcha": "人機驗證",
|
||||
"friends": "好友",
|
||||
"friendsDescription": "管理好友關係。",
|
||||
"album": "相冊",
|
||||
"albumDescription": "查看相冊與管理上傳附件。",
|
||||
"stickers": "貼圖",
|
||||
"stickersDescription": "查看貼圖包與管理貼圖。",
|
||||
"navBottomUnauthorizedCaption": "或者註冊一個賬號"
|
||||
}
|
||||
|
@ -888,5 +888,12 @@
|
||||
},
|
||||
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||
"reCaptcha": "人機驗證"
|
||||
"reCaptcha": "人機驗證",
|
||||
"friends": "好友",
|
||||
"friendsDescription": "管理好友關係。",
|
||||
"album": "相冊",
|
||||
"albumDescription": "查看相冊與管理上傳附件。",
|
||||
"stickers": "貼圖",
|
||||
"stickersDescription": "查看貼圖包與管理貼圖。",
|
||||
"navBottomUnauthorizedCaption": "或者註冊一個賬號"
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' hide log;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
@ -161,7 +160,7 @@ class SolianApp extends StatelessWidget {
|
||||
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
||||
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||
Provider(create: (ctx) => SnRealmProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => SnRealmProvider(ctx)),
|
||||
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
||||
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
||||
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
||||
@ -331,25 +330,29 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
if (!mounted) return;
|
||||
_setPhaseText('keyPair');
|
||||
final kp = context.read<KeyPairProvider>();
|
||||
await kp.reloadActive();
|
||||
kp.listen();
|
||||
if (!mounted) return;
|
||||
_setPhaseText('stickers');
|
||||
final sticker = context.read<SnStickerProvider>();
|
||||
await sticker.listSticker();
|
||||
if (!mounted) return;
|
||||
_setPhaseText('userDirectory');
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
await ud.loadAccountCache();
|
||||
if (!mounted) return;
|
||||
_setPhaseText('realm');
|
||||
final rm = context.read<SnRealmProvider>();
|
||||
await rm.refreshAvailableRealms();
|
||||
if (!mounted) return;
|
||||
_setPhaseText('chat');
|
||||
final ct = context.read<ChatChannelProvider>();
|
||||
await ct.refreshAvailableChannels();
|
||||
_setPhaseText('done');
|
||||
try {
|
||||
await kp.reloadActive();
|
||||
kp.listen();
|
||||
} catch (_) {}
|
||||
if (ua.isAuthorized) {
|
||||
if (!mounted) return;
|
||||
_setPhaseText('stickers');
|
||||
final sticker = context.read<SnStickerProvider>();
|
||||
await sticker.listSticker();
|
||||
if (!mounted) return;
|
||||
_setPhaseText('userDirectory');
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
await ud.loadAccountCache();
|
||||
if (!mounted) return;
|
||||
_setPhaseText('realm');
|
||||
final rm = context.read<SnRealmProvider>();
|
||||
await rm.refreshAvailableRealms();
|
||||
if (!mounted) return;
|
||||
_setPhaseText('chat');
|
||||
final ct = context.read<ChatChannelProvider>();
|
||||
await ct.refreshAvailableChannels();
|
||||
_setPhaseText('done');
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
await context.showErrorDialog(err);
|
||||
@ -534,7 +537,6 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
key: Key('app-splash-screen-$_isBusy'),
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomPaint(painter: GraphPainter()),
|
||||
Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
@ -574,44 +576,3 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GraphPainter extends CustomPainter {
|
||||
final Random random = Random();
|
||||
final int numNodes = 20;
|
||||
final double maxDistance = 100; // Max distance to draw a line
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paintNode = Paint()..color = Colors.white;
|
||||
final paintEdge = Paint()
|
||||
..color = Colors.white.withOpacity(0.3)
|
||||
..strokeWidth = 1;
|
||||
|
||||
// Generate random points
|
||||
List<Offset> nodes = List.generate(
|
||||
numNodes,
|
||||
(_) => Offset(
|
||||
random.nextDouble() * size.width,
|
||||
random.nextDouble() * size.height,
|
||||
),
|
||||
);
|
||||
|
||||
// Draw edges between close nodes
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
for (var j = i + 1; j < nodes.length; j++) {
|
||||
double distance = (nodes[i] - nodes[j]).distance;
|
||||
if (distance < maxDistance) {
|
||||
canvas.drawLine(nodes[i], nodes[j], paintEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw nodes
|
||||
for (var node in nodes) {
|
||||
canvas.drawCircle(node, 4, paintNode);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
@ -41,6 +41,11 @@ class ChatChannelProvider extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
void addAvailableChannel(SnChannel channel) {
|
||||
_availableChannels.add(channel);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
||||
await Future.wait(
|
||||
channels.map(
|
||||
|
@ -61,26 +61,6 @@ class NavigationProvider extends ChangeNotifier {
|
||||
screen: 'news',
|
||||
label: 'screenNews',
|
||||
),
|
||||
AppNavDestination(
|
||||
icon: Icon(Symbols.emoji_emotions, weight: 400, opticalSize: 20),
|
||||
screen: 'stickers',
|
||||
label: 'screenStickers',
|
||||
),
|
||||
AppNavDestination(
|
||||
icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20),
|
||||
screen: 'album',
|
||||
label: 'screenAlbum',
|
||||
),
|
||||
AppNavDestination(
|
||||
icon: Icon(Symbols.diversity_4, weight: 400, opticalSize: 20),
|
||||
screen: 'friend',
|
||||
label: 'screenFriend',
|
||||
),
|
||||
AppNavDestination(
|
||||
icon: Icon(Symbols.notifications, weight: 400, opticalSize: 20),
|
||||
screen: 'notification',
|
||||
label: 'screenNotification',
|
||||
),
|
||||
];
|
||||
static const List<String> kDefaultPinnedDestination = [
|
||||
'home',
|
||||
|
@ -8,7 +8,7 @@ import 'package:surface/providers/database.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
class SnRealmProvider {
|
||||
class SnRealmProvider extends ChangeNotifier {
|
||||
late final SnNetworkProvider _sn;
|
||||
late final DatabaseProvider _dt;
|
||||
|
||||
@ -39,6 +39,11 @@ class SnRealmProvider {
|
||||
return out;
|
||||
}
|
||||
|
||||
void addAvailableRealm(SnRealm realm) {
|
||||
_availableRealms.add(realm);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<SnRealm> getRealm(dynamic aliasOrId) async {
|
||||
if (_cache.containsKey(aliasOrId.toString())) {
|
||||
return _cache[aliasOrId.toString()]!;
|
||||
|
@ -64,6 +64,7 @@ class UserProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<SnAccount?> refreshUser() async {
|
||||
if (!isAuthorized) return null;
|
||||
final resp = await _sn.client.get('/cgi/id/users/me');
|
||||
final out = SnAccount.fromJson(resp.data);
|
||||
|
||||
|
@ -30,19 +30,7 @@ class AccountScreen extends StatelessWidget {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text(
|
||||
"screenAccount",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(1, 1),
|
||||
blurRadius: 5.0,
|
||||
color: Color.fromARGB(255, 0, 0, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
).tr(),
|
||||
title: Text("screenAccount").tr(),
|
||||
flexibleSpace: ua.user != null && ua.user!.banner.isNotEmpty
|
||||
? Stack(
|
||||
fit: StackFit.expand,
|
||||
@ -158,23 +146,33 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('abuseReport').tr(),
|
||||
subtitle: Text('abuseReportActionDescription').tr(),
|
||||
title: Text('friends').tr(),
|
||||
subtitle: Text('friendsDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.flag),
|
||||
leading: const Icon(Symbols.person),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed('abuseReport');
|
||||
GoRouter.of(context).pushNamed('friend');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('factorSettings').tr(),
|
||||
subtitle: Text('factorSettingsSubtitle').tr(),
|
||||
title: Text('album').tr(),
|
||||
subtitle: Text('albumDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.lock),
|
||||
leading: const Icon(Symbols.photo_library),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed('factorSettings');
|
||||
GoRouter.of(context).pushNamed('album');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('stickers').tr(),
|
||||
subtitle: Text('stickersDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.emoji_emotions),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed('stickers');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
@ -237,6 +235,16 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
||||
GoRouter.of(context).pushNamed('accountSettings');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('abuseReport').tr(),
|
||||
subtitle: Text('abuseReportActionDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.flag),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed('abuseReport');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('accountLogout').tr(),
|
||||
subtitle: Text('accountLogoutSubtitle').tr(),
|
||||
|
@ -117,6 +117,16 @@ class AccountSettingsScreen extends StatelessWidget {
|
||||
GoRouter.of(context).pushNamed('accountSettingsSecurity');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('factorSettings').tr(),
|
||||
subtitle: Text('factorSettingsSubtitle').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.lock),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed('factorSettings');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('accountProfileEdit').tr(),
|
||||
subtitle: Text('accountProfileEditSubtitle').tr(),
|
||||
|
@ -10,7 +10,6 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_zoom.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
@ -106,7 +105,7 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
leading: PageBackButton(),
|
||||
title: Text('screenAlbum').tr(),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
@ -119,7 +118,8 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
||||
child: CircularProgressIndicator(
|
||||
value: _billing?.includedRatio ?? 0,
|
||||
strokeWidth: 8,
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
).padding(all: 12),
|
||||
const Gap(24),
|
||||
@ -129,7 +129,8 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
||||
children: [
|
||||
Text('attachmentBillingUploaded').tr().bold(),
|
||||
Text(
|
||||
(_billing?.currentBytes ?? 0).formatBytes(decimals: 4),
|
||||
(_billing?.currentBytes ?? 0)
|
||||
.formatBytes(decimals: 4),
|
||||
style: GoogleFonts.robotoMono(),
|
||||
),
|
||||
Text('attachmentBillingDiscount').tr().bold(),
|
||||
|
@ -201,7 +201,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
if (!ua.isAuthorized) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
leading: PageBackButton(),
|
||||
title: Text('screenFriend').tr(),
|
||||
),
|
||||
body: Center(
|
||||
@ -254,7 +254,8 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: _showBlocks,
|
||||
),
|
||||
if (_requests.isNotEmpty || _blocks.isNotEmpty) const Divider(height: 1),
|
||||
if (_requests.isNotEmpty || _blocks.isNotEmpty)
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: MediaQuery.removePadding(
|
||||
context: context,
|
||||
@ -270,7 +271,8 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
final relation = _relations[index];
|
||||
final other = relation.related;
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.only(right: 24, left: 16),
|
||||
contentPadding:
|
||||
const EdgeInsets.only(right: 24, left: 16),
|
||||
leading: AccountImage(content: other?.avatar),
|
||||
title: Text(other?.nick ?? 'unknown'),
|
||||
subtitle: Text(other?.nick ?? 'unknown'),
|
||||
@ -286,12 +288,16 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: _isUpdating ? null : () => _changeRelation(relation, 2),
|
||||
onTap: _isUpdating
|
||||
? null
|
||||
: () => _changeRelation(relation, 2),
|
||||
child: Text('friendBlock').tr(),
|
||||
),
|
||||
const Gap(8),
|
||||
InkWell(
|
||||
onTap: _isUpdating ? null : () => _deleteRelation(relation),
|
||||
onTap: _isUpdating
|
||||
? null
|
||||
: () => _deleteRelation(relation),
|
||||
child: Text('friendDeleteAction').tr(),
|
||||
),
|
||||
],
|
||||
@ -420,7 +426,9 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(kFriendStatus[relation.status] ?? 'unknown').tr().opacity(0.75),
|
||||
Text(kFriendStatus[relation.status] ?? 'unknown')
|
||||
.tr()
|
||||
.opacity(0.75),
|
||||
if (relation.status == 0)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
@ -441,7 +449,8 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: _isBusy ? null : () => _changeRelation(relation, 1),
|
||||
onTap:
|
||||
_isBusy ? null : () => _changeRelation(relation, 1),
|
||||
child: Text('friendUnblock').tr(),
|
||||
),
|
||||
const Gap(8),
|
||||
|
@ -806,7 +806,7 @@ class _HomeDashNotificationWidgetState
|
||||
child: IconButton(
|
||||
icon: const Icon(Symbols.arrow_right_alt),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).goNamed('notification');
|
||||
GoRouter.of(context).pushNamed('notification');
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -149,8 +149,9 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
if (!ua.isAuthorized) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text('screenNotification').tr()),
|
||||
leading: PageBackButton(),
|
||||
title: Text('screenNotification').tr(),
|
||||
),
|
||||
body: Center(child: UnauthorizedHint()),
|
||||
);
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ import 'package:gap/gap.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/channel.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_realm.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
@ -57,7 +59,9 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
||||
title: Text('screenRealmDiscovery').tr(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: _isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
||||
icon: _isCompactView
|
||||
? const Icon(Symbols.view_list)
|
||||
: const Icon(Symbols.view_module),
|
||||
onPressed: () {
|
||||
setState(() => _isCompactView = !_isCompactView);
|
||||
context.read<ConfigProvider>().realmCompactView = _isCompactView;
|
||||
@ -117,7 +121,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||
try {
|
||||
setState(() => _isBusy = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/im/channels/${widget.realm.alias}/public');
|
||||
final resp =
|
||||
await sn.client.get('/cgi/im/channels/${widget.realm.alias}/public');
|
||||
final out = List<SnChannel>.from(
|
||||
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
||||
);
|
||||
@ -135,10 +140,13 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||
setState(() => _isJoining = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ua = context.read<UserProvider>();
|
||||
await sn.client.post('/cgi/id/realms/${widget.realm.alias}/members', data: {
|
||||
final rel = context.read<SnRealmProvider>();
|
||||
await sn.client
|
||||
.post('/cgi/id/realms/${widget.realm.alias}/members', data: {
|
||||
'related': ua.user?.name,
|
||||
});
|
||||
await _joinSelectedChannels();
|
||||
rel.addAvailableRealm(widget.realm);
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('realmJoined'.tr(args: [widget.realm.name]));
|
||||
Navigator.pop(context);
|
||||
@ -156,13 +164,20 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ua = context.read<UserProvider>();
|
||||
await sn.client.post('/cgi/im/channels/${widget.realm.alias}/$channel/members', data: {
|
||||
'related': ua.user?.name,
|
||||
});
|
||||
await sn.client.post(
|
||||
'/cgi/im/channels/${widget.realm.alias}/$channel/members',
|
||||
data: {
|
||||
'related': ua.user?.name,
|
||||
});
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
final ct = context.read<ChatChannelProvider>();
|
||||
for (final channel
|
||||
in _channels!.where((ele) => _planJoinChannels.contains(ele.alias))) {
|
||||
ct.addAvailableChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +197,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||
children: [
|
||||
const Icon(Symbols.group_add, size: 24),
|
||||
const Gap(16),
|
||||
Text('realmJoin', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||
Text('realmJoin', style: Theme.of(context).textTheme.titleLarge)
|
||||
.tr(),
|
||||
],
|
||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||
Row(
|
||||
@ -216,7 +232,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||
Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: Text('realmCommunityPublicChannelsHint'.tr(), style: Theme.of(context).textTheme.bodyMedium)
|
||||
child: Text('realmCommunityPublicChannelsHint'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium)
|
||||
.padding(horizontal: 24, vertical: 8),
|
||||
),
|
||||
Expanded(
|
||||
|
@ -9,7 +9,6 @@ import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_sticker.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/loading_indicator.dart';
|
||||
@ -134,7 +133,7 @@ class _StickerScreenState extends State<StickerScreen>
|
||||
Widget build(BuildContext context) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
leading: PageBackButton(),
|
||||
title: Text('screenStickers').tr(),
|
||||
actions: [
|
||||
IconButton(
|
||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -9,6 +10,7 @@ import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/channel.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
@ -46,6 +48,17 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
||||
final nav = context.watch<NavigationProvider>();
|
||||
final cfg = context.watch<ConfigProvider>();
|
||||
|
||||
final routeName = GoRouter.of(context)
|
||||
.routerDelegate
|
||||
.currentConfiguration
|
||||
.last
|
||||
.route
|
||||
.name;
|
||||
final showNavButtons = cfg.hideBottomNav ||
|
||||
!(nav.showBottomNavScreen.contains(routeName)
|
||||
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
|
||||
: false);
|
||||
|
||||
final backgroundColor = cfg.drawerIsExpanded ? Colors.transparent : null;
|
||||
|
||||
return ListenableBuilder(
|
||||
@ -78,49 +91,70 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
||||
Expanded(
|
||||
child: _DrawerContentList(),
|
||||
),
|
||||
if (cfg.hideBottomNav)
|
||||
if (showNavButtons)
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: nav.destinations.where((ele) => ele.isPinned).map(
|
||||
(ele) {
|
||||
children:
|
||||
nav.destinations.where((ele) => ele.isPinned).mapIndexed(
|
||||
(idx, ele) {
|
||||
return Expanded(
|
||||
child: Tooltip(
|
||||
message: ele.label.tr(),
|
||||
child: IconButton.filledTonal(
|
||||
child: IconButton(
|
||||
icon: ele.icon,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
color: nav.currentIndex == idx
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
nav.currentIndex == idx
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).goNamed(ele.screen);
|
||||
Scaffold.of(context).closeDrawer();
|
||||
nav.setIndex(idx);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
).padding(horizontal: 16),
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: AccountImage(content: ua.user?.avatar),
|
||||
title: Text(ua.user?.nick ?? 'unknown'.tr()).fontSize(15),
|
||||
subtitle:
|
||||
Text('@${ua.user?.name ?? 'unknown'.tr()}').fontSize(13),
|
||||
leading: AccountImage(
|
||||
content: ua.user?.avatar,
|
||||
fallbackWidget:
|
||||
ua.isAuthorized ? null : const Icon(Symbols.login),
|
||||
),
|
||||
title: ua.isAuthorized
|
||||
? Text(ua.user?.nick ?? 'unknown'.tr()).fontSize(15)
|
||||
: Text('screenAuthLogin').tr(),
|
||||
subtitle: ua.isAuthorized
|
||||
? Text('@${ua.user?.name ?? 'unknown'.tr()}').fontSize(13)
|
||||
: Text('navBottomUnauthorizedCaption').fontSize(13).tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.notifications, fill: 1),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pushNamed('notification');
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
),
|
||||
if (ua.isAuthorized)
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.notifications, fill: 1),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pushNamed('notification');
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.settings, fill: 1),
|
||||
padding: EdgeInsets.zero,
|
||||
@ -138,7 +172,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
||||
},
|
||||
),
|
||||
),
|
||||
Gap(MediaQuery.of(context).padding.bottom),
|
||||
Gap(MediaQuery.of(context).padding.bottom + 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -155,7 +189,7 @@ class _DrawerContentList extends StatelessWidget {
|
||||
final ct = context.read<ChatChannelProvider>();
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final nav = context.watch<NavigationProvider>();
|
||||
final rel = context.read<SnRealmProvider>();
|
||||
final rel = context.watch<SnRealmProvider>();
|
||||
|
||||
return PageTransitionSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
@ -185,16 +219,6 @@ class _DrawerContentList extends StatelessWidget {
|
||||
horizontal: 32,
|
||||
vertical: 12,
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
contentPadding: EdgeInsets.only(left: 28, right: 16),
|
||||
leading: const Icon(Symbols.home),
|
||||
title: Text('screenHome').tr(),
|
||||
onTap: () {
|
||||
GoRouter.of(context).goNamed('home');
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
),
|
||||
...rel.availableRealms.map((ele) {
|
||||
return ListTile(
|
||||
minTileHeight: 48,
|
||||
@ -209,6 +233,16 @@ class _DrawerContentList extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
contentPadding: EdgeInsets.only(left: 28, right: 16),
|
||||
leading: const Icon(Symbols.globe).padding(right: 4),
|
||||
title: Text('screenRealmDiscovery').tr(),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed('realmDiscovery');
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: ListView(
|
||||
@ -247,7 +281,7 @@ class _DrawerContentList extends StatelessWidget {
|
||||
),
|
||||
title: Text(nav.focusedRealm!.name),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
GoRouter.of(context).goNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {
|
||||
'alias': nav.focusedRealm!.alias,
|
||||
@ -265,7 +299,7 @@ class _DrawerContentList extends StatelessWidget {
|
||||
leading: const Icon(Symbols.globe),
|
||||
title: Text('community').tr(),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
GoRouter.of(context).goNamed(
|
||||
'realmCommunity',
|
||||
pathParameters: {
|
||||
'alias': nav.focusedRealm!.alias,
|
||||
@ -290,7 +324,7 @@ class _DrawerContentList extends StatelessWidget {
|
||||
leading: const Icon(Symbols.tag),
|
||||
title: Text(ele.name),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
GoRouter.of(context).goNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {
|
||||
'scope': ele.realm?.alias ?? 'global',
|
||||
|
@ -103,7 +103,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client
|
||||
.put('/cgi/co/questions/${widget.parentPost.id}/answer', data: {
|
||||
'publisher': answer.publisherId,
|
||||
'publisher': widget.parentPost.publisherId,
|
||||
'answer_id': answer.id,
|
||||
});
|
||||
if (!mounted) return;
|
||||
|
@ -279,6 +279,8 @@ class _PostItemState extends State<PostItem> {
|
||||
final ua = context.read<UserProvider>();
|
||||
final isAuthor =
|
||||
ua.isAuthorized && widget.data.publisher.accountId == ua.user?.id;
|
||||
final isParentAuthor = ua.isAuthorized &&
|
||||
widget.data.replyTo?.publisher.accountId == ua.user?.id;
|
||||
|
||||
final displayableAttachments = widget.data.preload?.attachments
|
||||
?.where((ele) =>
|
||||
@ -333,6 +335,7 @@ class _PostItemState extends State<PostItem> {
|
||||
_PostActionPopup(
|
||||
data: widget.data,
|
||||
isAuthor: isAuthor,
|
||||
isParentAuthor: isParentAuthor,
|
||||
onShare: () => _doShare(context),
|
||||
onShareImage: () => _doShareViaPicture(context),
|
||||
onSelectAnswer: widget.onSelectAnswer,
|
||||
@ -577,6 +580,7 @@ class _PostItemState extends State<PostItem> {
|
||||
_PostActionPopup(
|
||||
data: widget.data,
|
||||
isAuthor: isAuthor,
|
||||
isParentAuthor: isParentAuthor,
|
||||
onShare: () => _doShare(context),
|
||||
onShareImage: () => _doShareViaPicture(context),
|
||||
onSelectAnswer: widget.onSelectAnswer,
|
||||
@ -1317,6 +1321,7 @@ class _PostAvatar extends StatelessWidget {
|
||||
class _PostActionPopup extends StatelessWidget {
|
||||
final SnPost data;
|
||||
final bool isAuthor;
|
||||
final bool isParentAuthor;
|
||||
final Function onDeleted;
|
||||
final Function() onShare, onShareImage;
|
||||
final Function()? onSelectAnswer;
|
||||
@ -1324,6 +1329,7 @@ class _PostActionPopup extends StatelessWidget {
|
||||
const _PostActionPopup({
|
||||
required this.data,
|
||||
required this.isAuthor,
|
||||
required this.isParentAuthor,
|
||||
required this.onDeleted,
|
||||
required this.onShare,
|
||||
required this.onShareImage,
|
||||
@ -1397,7 +1403,7 @@ class _PostActionPopup extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
if (onTranslate != null) PopupMenuDivider(),
|
||||
if (isAuthor && onSelectAnswer != null)
|
||||
if (isParentAuthor && onSelectAnswer != null)
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
@ -1410,7 +1416,7 @@ class _PostActionPopup extends StatelessWidget {
|
||||
onSelectAnswer?.call();
|
||||
},
|
||||
),
|
||||
if (isAuthor && onSelectAnswer != null) PopupMenuDivider(),
|
||||
if (isParentAuthor && onSelectAnswer != null) PopupMenuDivider(),
|
||||
if (isAuthor)
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
|
@ -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
|
||||
# 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.
|
||||
version: 2.4.2+83
|
||||
version: 2.4.2+84
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
|
Reference in New Issue
Block a user