Compare commits
3 Commits
c9fbe47337
...
1.2.1+40
| Author | SHA1 | Date | |
|---|---|---|---|
| e4b741ff0c | |||
| e69abb7f9d | |||
| 565a8e41cc |
@@ -228,6 +228,8 @@
|
|||||||
"realmDescription": "Description",
|
"realmDescription": "Description",
|
||||||
"realmPublic": "Public Realm",
|
"realmPublic": "Public Realm",
|
||||||
"realmCommunity": "Community Realm",
|
"realmCommunity": "Community Realm",
|
||||||
|
"realmAvatar": "Realm avatar",
|
||||||
|
"realmBanner": "Realm banner",
|
||||||
"realmDetail": "Realm detail",
|
"realmDetail": "Realm detail",
|
||||||
"realmMember": "Realm member",
|
"realmMember": "Realm member",
|
||||||
"realmMembers": "Realm members",
|
"realmMembers": "Realm members",
|
||||||
@@ -414,5 +416,11 @@
|
|||||||
"holdToSeeDetail": "Long press / Mouse hover to see detail",
|
"holdToSeeDetail": "Long press / Mouse hover to see detail",
|
||||||
"subscribe": "Subscribe",
|
"subscribe": "Subscribe",
|
||||||
"subscribed": "Subscribed",
|
"subscribed": "Subscribed",
|
||||||
"unsubscribe": "Unsubscribe"
|
"unsubscribe": "Unsubscribe",
|
||||||
|
"preferences": "Preferences",
|
||||||
|
"notificationPreferences": "Notification preferences",
|
||||||
|
"notificationTopicPostFeedback": "Post feedbacks",
|
||||||
|
"notificationTopicPostSubscription": "Post subscriptions",
|
||||||
|
"preferencesApplied": "Preferences has been applied.",
|
||||||
|
"save": "Save"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,6 +229,8 @@
|
|||||||
"realmDescription": "领域简介",
|
"realmDescription": "领域简介",
|
||||||
"realmPublic": "公开领域",
|
"realmPublic": "公开领域",
|
||||||
"realmCommunity": "社区领域",
|
"realmCommunity": "社区领域",
|
||||||
|
"realmAvatar": "领域头像",
|
||||||
|
"realmBanner": "领域横幅",
|
||||||
"realmDetail": "领域详情",
|
"realmDetail": "领域详情",
|
||||||
"realmMember": "领域成员",
|
"realmMember": "领域成员",
|
||||||
"realmMembers": "领域成员",
|
"realmMembers": "领域成员",
|
||||||
@@ -415,5 +417,11 @@
|
|||||||
"holdToSeeDetail": "长按 / 鼠标悬浮来查看详情",
|
"holdToSeeDetail": "长按 / 鼠标悬浮来查看详情",
|
||||||
"subscribe": "订阅",
|
"subscribe": "订阅",
|
||||||
"subscribed": "已订阅",
|
"subscribed": "已订阅",
|
||||||
"unsubscribe": "取消订阅"
|
"unsubscribe": "取消订阅",
|
||||||
|
"preferences": "偏好设置",
|
||||||
|
"notificationPreferences": "通知偏好设置",
|
||||||
|
"notificationTopicPostFeedback": "帖子反馈",
|
||||||
|
"notificationTopicPostSubscription": "订阅源",
|
||||||
|
"preferencesApplied": "偏好设置已应用",
|
||||||
|
"save": "保存"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import 'package:solian/models/realm.dart';
|
|||||||
import 'package:solian/screens/about.dart';
|
import 'package:solian/screens/about.dart';
|
||||||
import 'package:solian/screens/account.dart';
|
import 'package:solian/screens/account.dart';
|
||||||
import 'package:solian/screens/account/friend.dart';
|
import 'package:solian/screens/account/friend.dart';
|
||||||
import 'package:solian/screens/account/personalize.dart';
|
import 'package:solian/screens/account/preferences/notifications.dart';
|
||||||
|
import 'package:solian/screens/account/profile_edit.dart';
|
||||||
import 'package:solian/screens/account/profile_page.dart';
|
import 'package:solian/screens/account/profile_page.dart';
|
||||||
import 'package:solian/screens/auth/signin.dart';
|
import 'package:solian/screens/auth/signin.dart';
|
||||||
import 'package:solian/screens/auth/signup.dart';
|
import 'package:solian/screens/auth/signup.dart';
|
||||||
@@ -245,6 +246,14 @@ abstract class AppRouter {
|
|||||||
child: const PersonalizeScreen(),
|
child: const PersonalizeScreen(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/preferences/notifications',
|
||||||
|
name: 'notificationPreferences',
|
||||||
|
builder: (context, state) => TitleShell(
|
||||||
|
state: state,
|
||||||
|
child: const NotificationPreferencesScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/view/:name',
|
path: '/account/view/:name',
|
||||||
name: 'accountProfilePage',
|
name: 'accountProfilePage',
|
||||||
|
|||||||
@@ -131,6 +131,15 @@ class _AccountScreenState extends State<AccountScreen> {
|
|||||||
AppRouter.instance.pushNamed('settings');
|
AppRouter.instance.pushNamed('settings');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (auth.isAuthorized.value)
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
|
||||||
|
leading: const Icon(Icons.edit_notifications),
|
||||||
|
title: Text('notificationPreferences'.tr),
|
||||||
|
onTap: () {
|
||||||
|
AppRouter.instance.pushNamed('notificationPreferences');
|
||||||
|
},
|
||||||
|
),
|
||||||
const Divider(thickness: 0.3, height: 1)
|
const Divider(thickness: 0.3, height: 1)
|
||||||
.paddingSymmetric(vertical: 4),
|
.paddingSymmetric(vertical: 4),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|||||||
118
lib/screens/account/preferences/notifications.dart
Normal file
118
lib/screens/account/preferences/notifications.dart
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get/get_connect/http/src/exceptions/exceptions.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
|
||||||
|
class NotificationPreferencesScreen extends StatefulWidget {
|
||||||
|
const NotificationPreferencesScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NotificationPreferencesScreen> createState() =>
|
||||||
|
_NotificationPreferencesScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NotificationPreferencesScreenState
|
||||||
|
extends State<NotificationPreferencesScreen> {
|
||||||
|
bool _isBusy = true;
|
||||||
|
|
||||||
|
Map<String, bool> _config = {};
|
||||||
|
|
||||||
|
final Map<String, String> _topicMap = {
|
||||||
|
'interactive.feedback': 'notificationTopicPostFeedback'.tr,
|
||||||
|
'interactive.subscription': 'notificationTopicPostSubscription'.tr,
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<void> _getPreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final auth = Get.find<AuthProvider>();
|
||||||
|
if (!auth.isAuthorized.value) throw UnauthorizedException();
|
||||||
|
|
||||||
|
final client = await auth.configureClient('id');
|
||||||
|
final resp = await client.get('/preferences/notifications');
|
||||||
|
if (resp.statusCode != 200 && resp.statusCode != 404) {
|
||||||
|
context.showErrorDialog(RequestException(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.statusCode == 200) {
|
||||||
|
_config = resp.body['config']
|
||||||
|
.map((k, v) => MapEntry(k, v as bool))
|
||||||
|
.cast<String, bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _savePreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final auth = Get.find<AuthProvider>();
|
||||||
|
if (!auth.isAuthorized.value) throw UnauthorizedException();
|
||||||
|
|
||||||
|
final client = await auth.configureClient('id');
|
||||||
|
final resp = await client.put('/preferences/notifications', {
|
||||||
|
'config': _config,
|
||||||
|
});
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
context.showErrorDialog(RequestException(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.showSnackbar('preferencesApplied'.tr);
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
||||||
|
ListTile(
|
||||||
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Icons.save),
|
||||||
|
title: Text('save'.tr),
|
||||||
|
enabled: !_isBusy,
|
||||||
|
onTap: () {
|
||||||
|
_savePreferences();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _topicMap.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final element = _topicMap.entries.elementAt(index);
|
||||||
|
return CheckboxListTile(
|
||||||
|
title: Text(element.value),
|
||||||
|
subtitle: Text(
|
||||||
|
element.key,
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
|
),
|
||||||
|
value: _config[element.key] ?? true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_config[element.key] = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -400,7 +400,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
child: AttachmentListEntry(
|
child: AttachmentListEntry(
|
||||||
item: item,
|
item: item,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
parentId: 'album',
|
parentId: 'album-$index',
|
||||||
showMature: _showMature,
|
showMature: _showMature,
|
||||||
onReveal: (value) {
|
onReveal: (value) {
|
||||||
setState(() => _showMature = value);
|
setState(() => _showMature = value);
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.settings),
|
leading: const Icon(Icons.settings),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('channelSettings'.tr.capitalize!),
|
title: Text('channelSettings'.tr),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
AppRouter.instance
|
AppRouter.instance
|
||||||
.pushNamed(
|
.pushNamed(
|
||||||
@@ -173,7 +173,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.notifications_active),
|
leading: const Icon(Icons.notifications_active),
|
||||||
title: Text('channelNotifyLevel'.tr.capitalize!),
|
title: Text('channelNotifyLevel'.tr),
|
||||||
trailing: DropdownButtonHideUnderline(
|
trailing: DropdownButtonHideUnderline(
|
||||||
child: DropdownButton2<int>(
|
child: DropdownButton2<int>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
@@ -208,7 +208,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.supervisor_account),
|
leading: const Icon(Icons.supervisor_account),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('channelMembers'.tr.capitalize!),
|
title: Text('channelMembers'.tr),
|
||||||
onTap: () => showMemberList(),
|
onTap: () => showMemberList(),
|
||||||
),
|
),
|
||||||
...(_isOwned ? ownerActions : List.empty()),
|
...(_isOwned ? ownerActions : List.empty()),
|
||||||
|
|||||||
@@ -97,6 +97,14 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_aliasController.dispose();
|
||||||
|
_nameController.dispose();
|
||||||
|
_descriptionController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final notifyBannerActions = [
|
final notifyBannerActions = [
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ import 'package:solian/providers/auth.dart';
|
|||||||
import 'package:solian/providers/content/realm.dart';
|
import 'package:solian/providers/content/realm.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/services.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
|
import 'package:solian/widgets/account/account_avatar.dart';
|
||||||
import 'package:solian/widgets/account/signin_required_overlay.dart';
|
import 'package:solian/widgets/account/signin_required_overlay.dart';
|
||||||
import 'package:solian/widgets/app_bar_leading.dart';
|
import 'package:solian/widgets/app_bar_leading.dart';
|
||||||
import 'package:solian/widgets/app_bar_title.dart';
|
import 'package:solian/widgets/app_bar_title.dart';
|
||||||
|
import 'package:solian/widgets/auto_cache_image.dart';
|
||||||
import 'package:solian/widgets/current_state_action.dart';
|
import 'package:solian/widgets/current_state_action.dart';
|
||||||
import 'package:solian/widgets/sized_container.dart';
|
import 'package:solian/widgets/sized_container.dart';
|
||||||
|
|
||||||
@@ -128,19 +131,34 @@ class _RealmListScreenState extends State<RealmListScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: (element.banner?.isEmpty ?? true)
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: AutoCacheImage(
|
||||||
|
ServiceFinder.buildUrl(
|
||||||
|
'uc',
|
||||||
|
'/attachments/${element.banner}',
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Positioned(
|
Positioned(
|
||||||
bottom: -30,
|
bottom: -30,
|
||||||
left: 18,
|
left: 18,
|
||||||
child: CircleAvatar(
|
child: (element.avatar?.isEmpty ?? true)
|
||||||
radius: 24,
|
? CircleAvatar(
|
||||||
backgroundColor: Colors.indigo,
|
radius: 24,
|
||||||
child: FaIcon(
|
backgroundColor:
|
||||||
FontAwesomeIcons.globe,
|
Theme.of(context).colorScheme.primary,
|
||||||
color: Colors.white,
|
child: const FaIcon(
|
||||||
size: 18,
|
FontAwesomeIcons.globe,
|
||||||
),
|
color: Colors.white,
|
||||||
),
|
size: 18,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: AccountAvatar(
|
||||||
|
content: element.avatar!,
|
||||||
|
bgColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.settings),
|
leading: const Icon(Icons.settings),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('realmSettings'.tr.capitalize!),
|
title: Text('realmSettings'.tr),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
AppRouter.instance
|
AppRouter.instance
|
||||||
.pushNamed(
|
.pushNamed(
|
||||||
@@ -120,14 +121,16 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Icons.supervisor_account),
|
leading: const Icon(Icons.supervisor_account),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
title: Text('realmMembers'.tr.capitalize!),
|
title: Text('realmMembers'.tr),
|
||||||
onTap: () => showMemberList(),
|
onTap: () => showMemberList(),
|
||||||
),
|
),
|
||||||
...(_isOwned ? ownerActions : List.empty()),
|
...(_isOwned ? ownerActions : List.empty()),
|
||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: _isOwned
|
leading: _isOwned
|
||||||
? const Icon(Icons.delete)
|
? const Icon(Icons.delete)
|
||||||
: const Icon(Icons.exit_to_app),
|
: const Icon(Icons.exit_to_app),
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_cropper/image_cropper.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/models/realm.dart';
|
import 'package:solian/models/realm.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/providers/content/attachment.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/app_bar_leading.dart';
|
import 'package:solian/widgets/app_bar_leading.dart';
|
||||||
@@ -29,17 +35,19 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
final _aliasController = TextEditingController();
|
final _aliasController = TextEditingController();
|
||||||
|
final _avatarController = TextEditingController();
|
||||||
|
final _bannerController = TextEditingController();
|
||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
final _descriptionController = TextEditingController();
|
final _descriptionController = TextEditingController();
|
||||||
|
|
||||||
bool _isCommunity = false;
|
bool _isCommunity = false;
|
||||||
bool _isPublic = false;
|
bool _isPublic = false;
|
||||||
|
|
||||||
void applyRealm() async {
|
void _applyRealm() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) return;
|
if (auth.isAuthorized.isFalse) return;
|
||||||
|
|
||||||
if (_aliasController.value.text.isEmpty) randomizeAlias();
|
if (_aliasController.value.text.isEmpty) _randomizeAlias();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@@ -49,6 +57,8 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
'alias': _aliasController.value.text.toLowerCase(),
|
'alias': _aliasController.value.text.toLowerCase(),
|
||||||
'name': _nameController.value.text,
|
'name': _nameController.value.text,
|
||||||
'description': _descriptionController.value.text,
|
'description': _descriptionController.value.text,
|
||||||
|
'avatar': _avatarController.value.text,
|
||||||
|
'banner': _bannerController.value.text,
|
||||||
'is_public': _isPublic,
|
'is_public': _isPublic,
|
||||||
'is_community': _isCommunity,
|
'is_community': _isCommunity,
|
||||||
};
|
};
|
||||||
@@ -68,31 +78,110 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void randomizeAlias() {
|
final _imagePicker = ImagePicker();
|
||||||
|
|
||||||
|
Future<void> _editImage(String position) async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (auth.isAuthorized.isFalse) return;
|
||||||
|
|
||||||
|
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (image == null) return;
|
||||||
|
|
||||||
|
CroppedFile? croppedFile = await ImageCropper().cropImage(
|
||||||
|
sourcePath: image.path,
|
||||||
|
uiSettings: [
|
||||||
|
AndroidUiSettings(
|
||||||
|
toolbarTitle: 'cropImage'.tr,
|
||||||
|
toolbarColor: Theme.of(context).colorScheme.primary,
|
||||||
|
toolbarWidgetColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
aspectRatioPresets: [
|
||||||
|
if (position == 'avatar') CropAspectRatioPreset.square,
|
||||||
|
if (position == 'banner') _BannerCropAspectRatioPreset(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IOSUiSettings(
|
||||||
|
title: 'cropImage'.tr,
|
||||||
|
aspectRatioPresets: [
|
||||||
|
if (position == 'avatar') CropAspectRatioPreset.square,
|
||||||
|
if (position == 'banner') _BannerCropAspectRatioPreset(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
WebUiSettings(
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (croppedFile == null) return;
|
||||||
|
final file = File(croppedFile.path);
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final AttachmentProvider attach = Get.find();
|
||||||
|
|
||||||
|
Attachment? attachResult;
|
||||||
|
try {
|
||||||
|
attachResult = await attach.createAttachmentDirectly(
|
||||||
|
await file.readAsBytes(),
|
||||||
|
file.path,
|
||||||
|
'avatar',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
context.showErrorDialog(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 'avatar':
|
||||||
|
_avatarController.text = attachResult.rid;
|
||||||
|
break;
|
||||||
|
case 'banner':
|
||||||
|
_bannerController.text = attachResult.rid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _randomizeAlias() {
|
||||||
_aliasController.text =
|
_aliasController.text =
|
||||||
const Uuid().v4().replaceAll('-', '').substring(0, 12);
|
const Uuid().v4().replaceAll('-', '').substring(0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncWidget() {
|
void _syncWidget() {
|
||||||
if (widget.edit != null) {
|
if (widget.edit != null) {
|
||||||
_aliasController.text = widget.edit!.alias;
|
_aliasController.text = widget.edit!.alias;
|
||||||
_nameController.text = widget.edit!.name;
|
_nameController.text = widget.edit!.name;
|
||||||
_descriptionController.text = widget.edit!.description;
|
_descriptionController.text = widget.edit!.description;
|
||||||
|
_avatarController.text = widget.edit!.avatar ?? '';
|
||||||
|
_bannerController.text = widget.edit!.banner ?? '';
|
||||||
_isPublic = widget.edit!.isPublic;
|
_isPublic = widget.edit!.isPublic;
|
||||||
_isCommunity = widget.edit!.isCommunity;
|
_isCommunity = widget.edit!.isCommunity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelAction() {
|
void _cancelAction() {
|
||||||
AppRouter.instance.pop();
|
AppRouter.instance.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
syncWidget();
|
_syncWidget();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_aliasController.dispose();
|
||||||
|
_avatarController.dispose();
|
||||||
|
_bannerController.dispose();
|
||||||
|
_nameController.dispose();
|
||||||
|
_descriptionController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
@@ -105,7 +194,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isBusy ? null : () => applyRealm(),
|
onPressed: _isBusy ? null : () => _applyRealm(),
|
||||||
child: Text('apply'.tr.toUpperCase()),
|
child: Text('apply'.tr.toUpperCase()),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -126,7 +215,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: cancelAction,
|
onPressed: _cancelAction,
|
||||||
child: Text('cancel'.tr),
|
child: Text('cancel'.tr),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -150,7 +239,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
visualDensity:
|
visualDensity:
|
||||||
const VisualDensity(horizontal: -2, vertical: -2),
|
const VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () => randomizeAlias(),
|
onPressed: () => _randomizeAlias(),
|
||||||
child: const Icon(Icons.refresh),
|
child: const Icon(Icons.refresh),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -166,6 +255,55 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).paddingSymmetric(horizontal: 16, vertical: 8),
|
).paddingSymmetric(horizontal: 16, vertical: 8),
|
||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _avatarController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'realmAvatar'.tr,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -2, vertical: -2),
|
||||||
|
),
|
||||||
|
onPressed: _isBusy ? null : () => _editImage('avatar'),
|
||||||
|
child: const Icon(Icons.upload),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
).paddingSymmetric(horizontal: 16, vertical: 2),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _bannerController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'realmBanner'.tr,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -2, vertical: -2),
|
||||||
|
),
|
||||||
|
onPressed: _isBusy ? null : () => _editImage('banner'),
|
||||||
|
child: const Icon(Icons.upload),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
).paddingSymmetric(horizontal: 16, vertical: 2),
|
||||||
|
const Divider(thickness: 0.3),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
minLines: 5,
|
minLines: 5,
|
||||||
@@ -202,3 +340,11 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BannerCropAspectRatioPreset extends CropAspectRatioPresetData {
|
||||||
|
@override
|
||||||
|
(int, int)? get data => (16, 7);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => '16x7';
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import 'package:solian/providers/auth.dart';
|
|||||||
import 'package:solian/providers/content/channel.dart';
|
import 'package:solian/providers/content/channel.dart';
|
||||||
import 'package:solian/providers/content/realm.dart';
|
import 'package:solian/providers/content/realm.dart';
|
||||||
import 'package:solian/providers/navigation.dart';
|
import 'package:solian/providers/navigation.dart';
|
||||||
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/widgets/account/account_avatar.dart';
|
import 'package:solian/widgets/account/account_avatar.dart';
|
||||||
|
import 'package:solian/widgets/auto_cache_image.dart';
|
||||||
import 'package:solian/widgets/channel/channel_list.dart';
|
import 'package:solian/widgets/channel/channel_list.dart';
|
||||||
|
|
||||||
class AppNavigationRegion extends StatefulWidget {
|
class AppNavigationRegion extends StatefulWidget {
|
||||||
@@ -169,6 +171,19 @@ class _AppNavigationRegionState extends State<AppNavigationRegion> {
|
|||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (!widget.isCollapsed &&
|
||||||
|
(navState.focusedRealm.value!.banner?.isNotEmpty ??
|
||||||
|
false))
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 7,
|
||||||
|
child: AutoCacheImage(
|
||||||
|
ServiceFinder.buildUrl(
|
||||||
|
'uc',
|
||||||
|
'/attachments/${navState.focusedRealm.value!.banner}',
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
if (widget.isCollapsed)
|
if (widget.isCollapsed)
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: navState.focusedRealm.value!.name,
|
message: navState.focusedRealm.value!.name,
|
||||||
|
|||||||
@@ -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+39
|
version: 1.2.1+40
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.4 <4.0.0"
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user