✨ Can edit avatar and banner
This commit is contained in:
parent
e742338d92
commit
740c704fb8
@ -20,7 +20,7 @@ pluginManagement {
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.9.23" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
@ -75,7 +75,7 @@
|
||||
"chatNew": "New Chat",
|
||||
"chatNewCreate": "Create a channel",
|
||||
"chatNewJoin": "Join a exists channel",
|
||||
"chatManage": "Manage Chat",
|
||||
"chatDetail": "Chat Details",
|
||||
"chatMember": "Member",
|
||||
"chatNotifySetting": "Notify Settings",
|
||||
"chatChannelUsage": "Channel",
|
||||
|
@ -73,7 +73,7 @@
|
||||
"reactionAdded": "你的反应已被添加。",
|
||||
"reactionRemoved": "你的反应已被移除。",
|
||||
"chatNew": "新聊天",
|
||||
"chatManage": "管理聊天",
|
||||
"chatDetail": "聊天详情",
|
||||
"chatMember": "成员",
|
||||
"chatNotifySetting": "通知设定",
|
||||
"chatNewCreate": "新建频道",
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/utils/theme.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:solian/widgets/scaffold.dart';
|
||||
@ -14,7 +15,7 @@ class AccountScreen extends StatelessWidget {
|
||||
return IndentScaffold(
|
||||
title: AppLocalizations.of(context)!.account,
|
||||
noSafeArea: true,
|
||||
fixedAppBarColor: true,
|
||||
fixedAppBarColor: SolianTheme.isLargeScreen(context),
|
||||
child: AccountScreenWidget(
|
||||
onSelect: (item) {
|
||||
SolianRouter.router.pushNamed(item);
|
||||
|
@ -1,11 +1,15 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.dart';
|
||||
import 'package:solian/widgets/exts.dart';
|
||||
import 'package:solian/widgets/scaffold.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
@ -31,6 +35,8 @@ class PersonalizeScreenWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PersonalizeScreenWidgetState extends State<PersonalizeScreenWidget> {
|
||||
final _imagePicker = ImagePicker();
|
||||
|
||||
final _usernameController = TextEditingController();
|
||||
final _nicknameController = TextEditingController();
|
||||
final _firstNameController = TextEditingController();
|
||||
@ -38,6 +44,8 @@ class _PersonalizeScreenWidgetState extends State<PersonalizeScreenWidget> {
|
||||
final _descriptionController = TextEditingController();
|
||||
final _birthdayController = TextEditingController();
|
||||
|
||||
String? _avatar;
|
||||
String? _banner;
|
||||
DateTime? _birthday;
|
||||
|
||||
bool _isSubmitting = false;
|
||||
@ -65,6 +73,12 @@ class _PersonalizeScreenWidgetState extends State<PersonalizeScreenWidget> {
|
||||
_descriptionController.text = prof['description'];
|
||||
_firstNameController.text = prof['profile']['first_name'];
|
||||
_lastNameController.text = prof['profile']['last_name'];
|
||||
if (prof['avatar'] != null && prof['avatar'].isNotEmpty) {
|
||||
_avatar = getRequestUri('passport', '/api/avatar/${prof['avatar']}').toString();
|
||||
}
|
||||
if (prof['banner'] != null && prof['banner'].isNotEmpty) {
|
||||
_banner = getRequestUri('passport', '/api/avatar/${prof['banner']}').toString();
|
||||
}
|
||||
if (prof['profile']['birthday'] != null) {
|
||||
_birthday = DateTime.parse(prof['profile']['birthday']);
|
||||
_birthdayController.text = DateFormat('yyyy-MM-dd hh:mm').format(_birthday!);
|
||||
@ -93,7 +107,9 @@ class _PersonalizeScreenWidgetState extends State<PersonalizeScreenWidget> {
|
||||
);
|
||||
if (res.statusCode == 200) {
|
||||
await auth.fetchProfiles();
|
||||
resetInputs();
|
||||
setState(() {
|
||||
resetInputs();
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.personalizeApplied),
|
||||
@ -106,20 +122,115 @@ class _PersonalizeScreenWidgetState extends State<PersonalizeScreenWidget> {
|
||||
setState(() => _isSubmitting = false);
|
||||
}
|
||||
|
||||
Future<void> applyAvatar(String position) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
if (!await auth.isAuthorized()) return;
|
||||
|
||||
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
||||
if (image == null) return;
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
|
||||
final file = File(image.path);
|
||||
try {
|
||||
final req = MultipartRequest('PUT', getRequestUri('passport', '/api/users/me/$position'));
|
||||
req.files.add(await MultipartFile.fromPath(position, file.path));
|
||||
|
||||
var res = await auth.client!.send(req);
|
||||
if (res.statusCode == 200) {
|
||||
await auth.fetchProfiles();
|
||||
setState(() {
|
||||
resetInputs();
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.personalizeApplied),
|
||||
));
|
||||
} else {
|
||||
throw Exception(utf8.decode(await res.stream.toBytes()));
|
||||
}
|
||||
} catch (err) {
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
|
||||
setState(() => _isSubmitting = false);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
Future.delayed(Duration.zero, () => resetInputs());
|
||||
Future.delayed(Duration.zero, () {
|
||||
setState(() {
|
||||
resetInputs();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 28, horizontal: 32),
|
||||
child: Column(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: ListView(
|
||||
children: [
|
||||
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
||||
const SizedBox(height: 24),
|
||||
Stack(
|
||||
children: [
|
||||
AccountAvatar(source: _avatar ?? '', radius: 40, direct: true),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 40,
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: () => applyAvatar('avatar'),
|
||||
child: const Icon(
|
||||
Icons.camera,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: _banner != null
|
||||
? Image.network(
|
||||
_banner!,
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
child: FloatingActionButton(
|
||||
onPressed: () => applyAvatar('banner'),
|
||||
child: const Icon(
|
||||
Icons.camera_alt,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
|
@ -63,7 +63,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||
];
|
||||
|
||||
return IndentScaffold(
|
||||
title: AppLocalizations.of(context)!.chatManage,
|
||||
title: AppLocalizations.of(context)!.chatDetail,
|
||||
hideDrawer: true,
|
||||
noSafeArea: true,
|
||||
child: Column(
|
||||
|
9
lib/utils/file.dart
Normal file
9
lib/utils/file.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
Future<String> calculateFileSha256(File file) async {
|
||||
final bytes = await file.readAsBytes();
|
||||
final digest = sha256.convert(bytes);
|
||||
return digest.toString();
|
||||
}
|
@ -2,7 +2,6 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
@ -11,6 +10,7 @@ import 'package:image_picker/image_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/utils/file.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:solian/widgets/exts.dart';
|
||||
@ -62,7 +62,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
bool isPopped = false;
|
||||
for (final media in medias) {
|
||||
final file = File(media.path);
|
||||
final hashcode = await calculateSha256(file);
|
||||
final hashcode = await calculateFileSha256(file);
|
||||
try {
|
||||
await uploadAttachment(file, hashcode);
|
||||
} catch (err) {
|
||||
@ -90,7 +90,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
|
||||
bool isPopped = false;
|
||||
for (final file in files) {
|
||||
final hashcode = await calculateSha256(file);
|
||||
final hashcode = await calculateFileSha256(file);
|
||||
try {
|
||||
await uploadAttachment(file, hashcode);
|
||||
} catch (err) {
|
||||
@ -120,7 +120,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
setState(() => _isSubmitting = true);
|
||||
|
||||
final file = File(media.path);
|
||||
final hashcode = await calculateSha256(file);
|
||||
final hashcode = await calculateFileSha256(file);
|
||||
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
@ -171,12 +171,6 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
setState(() => _isSubmitting = false);
|
||||
}
|
||||
|
||||
Future<String> calculateSha256(File file) async {
|
||||
final bytes = await file.readAsBytes();
|
||||
final digest = sha256.convert(bytes);
|
||||
return digest.toString();
|
||||
}
|
||||
|
||||
String getFileName(Attachment item) {
|
||||
return item.filename.replaceAll(RegExp(r'\.[^/.]+$'), '');
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ PODS:
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- desktop_drop (0.0.1):
|
||||
- FlutterMacOS
|
||||
- device_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_selector_macos (0.0.1):
|
||||
@ -30,6 +32,9 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- screen_brightness_macos (0.1.0):
|
||||
- FlutterMacOS
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
@ -38,6 +43,7 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`)
|
||||
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||
@ -51,6 +57,7 @@ DEPENDENCIES:
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
|
||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
|
||||
@ -61,6 +68,8 @@ SPEC REPOS:
|
||||
EXTERNAL SOURCES:
|
||||
connectivity_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin
|
||||
desktop_drop:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
|
||||
device_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
file_selector_macos:
|
||||
@ -87,6 +96,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
screen_brightness_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos
|
||||
sqflite:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
wakelock_plus:
|
||||
@ -94,6 +105,7 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
||||
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
|
||||
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
||||
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
|
||||
@ -107,6 +119,7 @@ SPEC CHECKSUMS:
|
||||
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||
WebRTC-SDK: efc3e67e0355b1ee14bfe3c91188cada6000cb94
|
||||
|
Loading…
Reference in New Issue
Block a user