⚡ Optimize
This commit is contained in:
parent
57ff5e44ba
commit
1eb42fa351
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/content/attachment_list.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/translations.dart';
|
import 'package:solian/translations.dart';
|
||||||
@ -28,7 +28,7 @@ class SolianApp extends StatelessWidget {
|
|||||||
fallbackLocale: const Locale('en', 'US'),
|
fallbackLocale: const Locale('en', 'US'),
|
||||||
onInit: () {
|
onInit: () {
|
||||||
Get.lazyPut(() => AuthProvider());
|
Get.lazyPut(() => AuthProvider());
|
||||||
Get.lazyPut(() => AttachmentListProvider());
|
Get.lazyPut(() => AttachmentProvider());
|
||||||
},
|
},
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return ScaffoldMessenger(
|
return ScaffoldMessenger(
|
||||||
|
114
lib/providers/content/attachment.dart
Normal file
114
lib/providers/content/attachment.dart
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/services.dart';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
Future<String> calculateFileSha256(File file) async {
|
||||||
|
final bytes = await Isolate.run(() => file.readAsBytesSync());
|
||||||
|
final digest = await Isolate.run(() => sha256.convert(bytes));
|
||||||
|
return digest.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<double> calculateFileAspectRatio(File file) async {
|
||||||
|
final bytes = await Isolate.run(() => file.readAsBytesSync());
|
||||||
|
final decoder = await Isolate.run(() => img.findDecoderForData(bytes));
|
||||||
|
if (decoder == null) return 1;
|
||||||
|
final image = await Isolate.run(() => decoder.decode(bytes));
|
||||||
|
if (image == null) return 1;
|
||||||
|
return image.width / image.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AttachmentProvider extends GetConnect {
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> getMetadata(int id) => get('/api/attachments/$id/meta');
|
||||||
|
|
||||||
|
Future<Response> createAttachment(File file, String hash, String usage,
|
||||||
|
{double? ratio}) async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
||||||
|
|
||||||
|
final client = GetConnect();
|
||||||
|
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
||||||
|
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
||||||
|
|
||||||
|
final filePayload =
|
||||||
|
MultipartFile(await file.readAsBytes(), filename: basename(file.path));
|
||||||
|
final fileAlt = basename(file.path).contains('.')
|
||||||
|
? basename(file.path).substring(0, basename(file.path).lastIndexOf('.'))
|
||||||
|
: basename(file.path);
|
||||||
|
|
||||||
|
final resp = await client.post(
|
||||||
|
'/api/attachments',
|
||||||
|
FormData({
|
||||||
|
'alt': fileAlt,
|
||||||
|
'file': filePayload,
|
||||||
|
'hash': hash,
|
||||||
|
'usage': usage,
|
||||||
|
'metadata': jsonEncode({
|
||||||
|
if (ratio != null) 'ratio': ratio,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (resp.statusCode == 200) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception(resp.bodyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> updateAttachment(
|
||||||
|
int id,
|
||||||
|
String alt,
|
||||||
|
String usage, {
|
||||||
|
double? ratio,
|
||||||
|
bool isMature = false,
|
||||||
|
}) async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
||||||
|
|
||||||
|
final client = GetConnect();
|
||||||
|
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
||||||
|
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
||||||
|
|
||||||
|
var resp = await client.put('/api/attachments/$id', {
|
||||||
|
'metadata': {
|
||||||
|
if (ratio != null) 'ratio': ratio,
|
||||||
|
},
|
||||||
|
'alt': alt,
|
||||||
|
'usage': usage,
|
||||||
|
'is_mature': isMature,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
throw Exception(resp.bodyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> deleteAttachment(int id) async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
||||||
|
|
||||||
|
final client = GetConnect();
|
||||||
|
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
||||||
|
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
||||||
|
|
||||||
|
var resp = await client.delete('/api/attachments/$id');
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
throw Exception(resp.bodyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:solian/services.dart';
|
|
||||||
|
|
||||||
class AttachmentListProvider extends GetConnect {
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Response> getMetadata(int id) => get('/api/attachments/$id/meta');
|
|
||||||
}
|
|
@ -1,9 +1,11 @@
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:solian/screens/account.dart';
|
import 'package:solian/screens/account.dart';
|
||||||
|
import 'package:solian/screens/account/personalize.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';
|
||||||
import 'package:solian/screens/home.dart';
|
import 'package:solian/screens/home.dart';
|
||||||
import 'package:solian/screens/posts/publish.dart';
|
import 'package:solian/screens/posts/publish.dart';
|
||||||
|
import 'package:solian/shells/basic_shell.dart';
|
||||||
import 'package:solian/shells/nav_shell.dart';
|
import 'package:solian/shells/nav_shell.dart';
|
||||||
|
|
||||||
abstract class AppRouter {
|
abstract class AppRouter {
|
||||||
@ -14,30 +16,41 @@ abstract class AppRouter {
|
|||||||
NavShell(state: state, child: child),
|
NavShell(state: state, child: child),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/",
|
path: '/',
|
||||||
name: "home",
|
name: 'home',
|
||||||
builder: (context, state) => const HomeScreen(),
|
builder: (context, state) => const HomeScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/account",
|
path: '/account',
|
||||||
name: "account",
|
name: 'account',
|
||||||
builder: (context, state) => const AccountScreen(),
|
builder: (context, state) => const AccountScreen(),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ShellRoute(
|
||||||
|
builder: (context, state, child) =>
|
||||||
|
BasicShell(state: state, child: child),
|
||||||
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/auth/sign-in",
|
path: '/account/personalize',
|
||||||
name: "signin",
|
name: 'accountPersonalize',
|
||||||
|
builder: (context, state) => const PersonalizeScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/auth/sign-in',
|
||||||
|
name: 'signin',
|
||||||
builder: (context, state) => const SignInScreen(),
|
builder: (context, state) => const SignInScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/auth/sign-up",
|
path: '/auth/sign-up',
|
||||||
name: "signup",
|
name: 'signup',
|
||||||
builder: (context, state) => const SignUpScreen(),
|
builder: (context, state) => const SignUpScreen(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/posts/publish",
|
path: '/posts/publish',
|
||||||
name: "postPublishing",
|
name: 'postPublishing',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final arguments = state.extra as PostPublishingArguments?;
|
final arguments = state.extra as PostPublishingArguments?;
|
||||||
return PostPublishingScreen(
|
return PostPublishingScreen(
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/account/account_avatar.dart';
|
import 'package:solian/widgets/account/account_avatar.dart';
|
||||||
|
|
||||||
class AccountScreen extends StatefulWidget {
|
class AccountScreen extends StatefulWidget {
|
||||||
@ -15,8 +16,12 @@ class _AccountScreenState extends State<AccountScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final actionItems = [
|
final actionItems = [
|
||||||
// (const Icon(Icons.color_lens), 'personalize'.tr, 'account.personalize'),
|
(
|
||||||
// (const Icon(Icons.diversity_1), 'friend'.tr, 'account.friend'),
|
const Icon(Icons.color_lens),
|
||||||
|
'accountPersonalize'.tr,
|
||||||
|
'accountPersonalize'
|
||||||
|
),
|
||||||
|
(const Icon(Icons.diversity_1), 'accountFriend'.tr, 'accountFriend'),
|
||||||
];
|
];
|
||||||
|
|
||||||
final AuthProvider provider = Get.find();
|
final AuthProvider provider = Get.find();
|
||||||
@ -101,14 +106,19 @@ class AccountNameCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 2,
|
child: Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.only(left: 22, right: 34, top: 4, bottom: 4),
|
const EdgeInsets.only(left: 22, right: 34, top: 4, bottom: 4),
|
||||||
leading: AccountAvatar(
|
leading: AccountAvatar(
|
||||||
content: snapshot.data!.body?['avatar'], radius: 24),
|
content: snapshot.data!.body?['avatar'], radius: 24),
|
||||||
title: Text(snapshot.data!.body?['nick']),
|
title: Text(snapshot.data!.body?['nick']),
|
||||||
subtitle: Text(snapshot.data!.body?['email']),
|
subtitle: Text(snapshot.data!.body?['email']),
|
||||||
|
),
|
||||||
|
).paddingOnly(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: SolianTheme.isLargeScreen(context) ? 8 : 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
294
lib/screens/account/personalize.dart
Normal file
294
lib/screens/account/personalize.dart
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/providers/content/attachment.dart';
|
||||||
|
import 'package:solian/services.dart';
|
||||||
|
import 'package:solian/widgets/account/account_avatar.dart';
|
||||||
|
|
||||||
|
class PersonalizeScreen extends StatefulWidget {
|
||||||
|
const PersonalizeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PersonalizeScreen> createState() => _PersonalizeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
||||||
|
final _imagePicker = ImagePicker();
|
||||||
|
|
||||||
|
final _usernameController = TextEditingController();
|
||||||
|
final _nicknameController = TextEditingController();
|
||||||
|
final _firstNameController = TextEditingController();
|
||||||
|
final _lastNameController = TextEditingController();
|
||||||
|
final _descriptionController = TextEditingController();
|
||||||
|
final _birthdayController = TextEditingController();
|
||||||
|
|
||||||
|
int? _avatar;
|
||||||
|
int? _banner;
|
||||||
|
DateTime? _birthday;
|
||||||
|
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
void selectBirthday() async {
|
||||||
|
final DateTime? picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _birthday,
|
||||||
|
firstDate: DateTime(DateTime.now().year - 200),
|
||||||
|
lastDate: DateTime(DateTime.now().year + 200),
|
||||||
|
);
|
||||||
|
if (picked != null && picked != _birthday) {
|
||||||
|
setState(() {
|
||||||
|
_birthday = picked;
|
||||||
|
_birthdayController.text =
|
||||||
|
DateFormat('yyyy-MM-dd hh:mm').format(_birthday!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void syncWidget() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
final prof = await auth.getProfile(noCache: true);
|
||||||
|
setState(() {
|
||||||
|
_usernameController.text = prof.body['name'];
|
||||||
|
_nicknameController.text = prof.body['nick'];
|
||||||
|
_descriptionController.text = prof.body['description'];
|
||||||
|
_firstNameController.text = prof.body['profile']['first_name'];
|
||||||
|
_lastNameController.text = prof.body['profile']['last_name'];
|
||||||
|
_avatar = prof.body['avatar'];
|
||||||
|
_banner = prof.body['banner'];
|
||||||
|
if (prof.body['profile']['birthday'] != null) {
|
||||||
|
_birthday = DateTime.parse(prof.body['profile']['birthday']);
|
||||||
|
_birthdayController.text =
|
||||||
|
DateFormat('yyyy-MM-dd hh:mm').format(_birthday!);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateImage(String position) async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) return;
|
||||||
|
|
||||||
|
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (image == null) return;
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final AttachmentProvider provider = Get.find();
|
||||||
|
|
||||||
|
late Response attachResp;
|
||||||
|
try {
|
||||||
|
final file = File(image.path);
|
||||||
|
final hash = await calculateFileSha256(file);
|
||||||
|
attachResp = await provider.createAttachment(
|
||||||
|
file,
|
||||||
|
hash,
|
||||||
|
'p.$position',
|
||||||
|
ratio: await calculateFileAspectRatio(file),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
context.showErrorDialog(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final client = GetConnect();
|
||||||
|
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
||||||
|
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
||||||
|
|
||||||
|
final resp = await client.put(
|
||||||
|
'/api/users/me/$position',
|
||||||
|
{'attachment': attachResp.body['id']},
|
||||||
|
);
|
||||||
|
if (resp.statusCode == 200) {
|
||||||
|
syncWidget();
|
||||||
|
|
||||||
|
context.showSnackbar('accountPersonalizeApplied'.tr);
|
||||||
|
} else {
|
||||||
|
context.showErrorDialog(resp.bodyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
Future.delayed(Duration.zero, () => syncWidget());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
AccountAvatar(content: _avatar, radius: 40),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 40,
|
||||||
|
child: FloatingActionButton.small(
|
||||||
|
heroTag: const Key('avatar-editor'),
|
||||||
|
onPressed: () => updateImage('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.surfaceContainerHigh,
|
||||||
|
child: _banner != null
|
||||||
|
? Image.network(
|
||||||
|
'${ServiceFinder.services['paperclip']}/api/attachments/$_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(
|
||||||
|
heroTag: const Key('banner-editor'),
|
||||||
|
onPressed: () => updateImage('banner'),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.camera_alt,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
flex: 1,
|
||||||
|
child: TextField(
|
||||||
|
readOnly: true,
|
||||||
|
controller: _usernameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: 'username'.tr,
|
||||||
|
prefixText: '@',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Flexible(
|
||||||
|
flex: 1,
|
||||||
|
child: TextField(
|
||||||
|
controller: _nicknameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: 'nickname'.tr,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
flex: 1,
|
||||||
|
child: TextField(
|
||||||
|
controller: _firstNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: 'firstName'.tr,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Flexible(
|
||||||
|
flex: 1,
|
||||||
|
child: TextField(
|
||||||
|
controller: _lastNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: 'lastName'.tr,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: _descriptionController,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
maxLines: null,
|
||||||
|
minLines: 3,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: 'description'.tr,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: _birthdayController,
|
||||||
|
readOnly: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: 'birthday'.tr,
|
||||||
|
),
|
||||||
|
onTap: () => selectBirthday(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: null,
|
||||||
|
child: Text('reset'.tr),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: null,
|
||||||
|
child: Text('apply'.tr),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,9 +7,9 @@ import 'package:solian/providers/auth.dart';
|
|||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/services.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/shells/nav_shell.dart' as shell;
|
|
||||||
import 'package:solian/widgets/attachments/attachment_publish.dart';
|
import 'package:solian/widgets/attachments/attachment_publish.dart';
|
||||||
import 'package:solian/widgets/posts/post_item.dart';
|
import 'package:solian/widgets/posts/post_item.dart';
|
||||||
|
import 'package:solian/widgets/prev_page.dart';
|
||||||
|
|
||||||
class PostPublishingArguments {
|
class PostPublishingArguments {
|
||||||
final Post? edit;
|
final Post? edit;
|
||||||
@ -122,7 +122,7 @@ class _PostPublishingScreenState extends State<PostPublishingScreen> {
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('postPublishing'.tr),
|
title: Text('postPublishing'.tr),
|
||||||
leading: const shell.BackButton(),
|
leading: const PrevPageButton(),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text('postAction'.tr.toUpperCase()),
|
child: Text('postAction'.tr.toUpperCase()),
|
||||||
|
30
lib/shells/basic_shell.dart
Normal file
30
lib/shells/basic_shell.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
|
import 'package:solian/theme.dart';
|
||||||
|
import 'package:solian/widgets/prev_page.dart';
|
||||||
|
|
||||||
|
class BasicShell extends StatelessWidget {
|
||||||
|
final GoRouterState state;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const BasicShell({super.key, required this.child, required this.state});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final canPop = AppRouter.instance.canPop();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(state.topRoute?.name?.tr ?? 'page'.tr),
|
||||||
|
centerTitle: false,
|
||||||
|
titleSpacing: canPop ? null : 24,
|
||||||
|
elevation: SolianTheme.isLargeScreen(context) ? 1 : 0,
|
||||||
|
leading: canPop ? const PrevPageButton() : null,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
),
|
||||||
|
body: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.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/prev_page.dart';
|
||||||
import 'package:solian/widgets/navigation/app_navigation_bottom_bar.dart';
|
import 'package:solian/widgets/navigation/app_navigation_bottom_bar.dart';
|
||||||
import 'package:solian/widgets/navigation/app_navigation_rail.dart';
|
import 'package:solian/widgets/navigation/app_navigation_rail.dart';
|
||||||
|
|
||||||
@ -22,9 +23,12 @@ class NavShell extends StatelessWidget {
|
|||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
titleSpacing: canPop ? null : 24,
|
titleSpacing: canPop ? null : 24,
|
||||||
elevation: SolianTheme.isLargeScreen(context) ? 1 : 0,
|
elevation: SolianTheme.isLargeScreen(context) ? 1 : 0,
|
||||||
leading: canPop ? const BackButton() : null,
|
leading: canPop ? const PrevPageButton() : null,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
),
|
),
|
||||||
bottomNavigationBar: SolianTheme.isLargeScreen(context) ? null : const AppNavigationBottomBar(),
|
bottomNavigationBar: SolianTheme.isLargeScreen(context)
|
||||||
|
? null
|
||||||
|
: const AppNavigationBottomBar(),
|
||||||
body: SolianTheme.isLargeScreen(context)
|
body: SolianTheme.isLargeScreen(context)
|
||||||
? Row(
|
? Row(
|
||||||
children: [
|
children: [
|
||||||
@ -37,20 +41,3 @@ class NavShell extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackButton extends StatelessWidget {
|
|
||||||
const BackButton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
|
||||||
onPressed: () {
|
|
||||||
if (AppRouter.instance.canPop()) {
|
|
||||||
AppRouter.instance.pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -7,6 +7,7 @@ class SolianMessages extends Translations {
|
|||||||
'hide': 'Hide',
|
'hide': 'Hide',
|
||||||
'okay': 'Okay',
|
'okay': 'Okay',
|
||||||
'next': 'Next',
|
'next': 'Next',
|
||||||
|
'reset': 'Reset',
|
||||||
'page': 'Page',
|
'page': 'Page',
|
||||||
'home': 'Home',
|
'home': 'Home',
|
||||||
'apply': 'Apply',
|
'apply': 'Apply',
|
||||||
@ -21,9 +22,15 @@ class SolianMessages extends Translations {
|
|||||||
'username': 'Username',
|
'username': 'Username',
|
||||||
'nickname': 'Nickname',
|
'nickname': 'Nickname',
|
||||||
'password': 'Password',
|
'password': 'Password',
|
||||||
|
'description': 'Description',
|
||||||
|
'birthday': 'Birthday',
|
||||||
|
'firstName': 'First Name',
|
||||||
|
'lastName': 'Last Name',
|
||||||
'account': 'Account',
|
'account': 'Account',
|
||||||
'personalize': 'Personalize',
|
'accountPersonalize': 'Personalize',
|
||||||
'friend': 'Friend',
|
'accountPersonalizeApplied':
|
||||||
|
'Account personalize settings has been saved.',
|
||||||
|
'accountFriend': 'Friend',
|
||||||
'aspectRatio': 'Aspect Ratio',
|
'aspectRatio': 'Aspect Ratio',
|
||||||
'aspectRatioSquare': 'Square',
|
'aspectRatioSquare': 'Square',
|
||||||
'aspectRatioPortrait': 'Portrait',
|
'aspectRatioPortrait': 'Portrait',
|
||||||
@ -71,6 +78,7 @@ class SolianMessages extends Translations {
|
|||||||
'hide': '隐藏',
|
'hide': '隐藏',
|
||||||
'okay': '确认',
|
'okay': '确认',
|
||||||
'next': '下一步',
|
'next': '下一步',
|
||||||
|
'reset': '重置',
|
||||||
'cancel': '取消',
|
'cancel': '取消',
|
||||||
'confirm': '确认',
|
'confirm': '确认',
|
||||||
'edit': '编辑',
|
'edit': '编辑',
|
||||||
@ -85,9 +93,14 @@ class SolianMessages extends Translations {
|
|||||||
'username': '用户名',
|
'username': '用户名',
|
||||||
'nickname': '显示名',
|
'nickname': '显示名',
|
||||||
'password': '密码',
|
'password': '密码',
|
||||||
|
'description': '简介',
|
||||||
|
'birthday': '生日',
|
||||||
|
'firstName': '名称',
|
||||||
|
'lastName': '姓氏',
|
||||||
'account': '账号',
|
'account': '账号',
|
||||||
'personalize': '个性化',
|
'accountPersonalize': '个性化',
|
||||||
'friend': '好友',
|
'accountPersonalizeApplied': '账户的个性化设置已保存。',
|
||||||
|
'accountFriend': '好友',
|
||||||
'aspectRatio': '纵横比',
|
'aspectRatio': '纵横比',
|
||||||
'aspectRatioSquare': '方型',
|
'aspectRatioSquare': '方型',
|
||||||
'aspectRatioPortrait': '竖型',
|
'aspectRatioPortrait': '竖型',
|
||||||
|
@ -4,8 +4,8 @@ import 'package:carousel_slider/carousel_slider.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/providers/content/attachment_item.dart';
|
import 'package:solian/widgets/attachments/attachment_item.dart';
|
||||||
import 'package:solian/providers/content/attachment_list.dart';
|
import 'package:solian/providers/content/attachment.dart';
|
||||||
import 'package:solian/widgets/attachments/attachment_list_fullscreen.dart';
|
import 'package:solian/widgets/attachments/attachment_list_fullscreen.dart';
|
||||||
|
|
||||||
class AttachmentList extends StatefulWidget {
|
class AttachmentList extends StatefulWidget {
|
||||||
@ -26,7 +26,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
List<Attachment?> _attachmentsMeta = List.empty();
|
List<Attachment?> _attachmentsMeta = List.empty();
|
||||||
|
|
||||||
void getMetadataList() {
|
void getMetadataList() {
|
||||||
final AttachmentListProvider provider = Get.find();
|
final AttachmentProvider provider = Get.find();
|
||||||
|
|
||||||
if (widget.attachmentsId.isEmpty) {
|
if (widget.attachmentsId.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/providers/content/attachment_item.dart';
|
import 'package:solian/widgets/attachments/attachment_item.dart';
|
||||||
|
|
||||||
class AttachmentListFullscreen extends StatefulWidget {
|
class AttachmentListFullscreen extends StatefulWidget {
|
||||||
final Attachment attachment;
|
final Attachment attachment;
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
@ -8,19 +6,10 @@ 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_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:solian/providers/content/attachment.dart';
|
||||||
import 'package:solian/providers/content/attachment_list.dart';
|
|
||||||
import 'package:solian/services.dart';
|
|
||||||
|
|
||||||
Future<String> calculateFileSha256(File file) async {
|
|
||||||
final bytes = await file.readAsBytes();
|
|
||||||
final digest = await Isolate.run(() => sha256.convert(bytes));
|
|
||||||
return digest.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AttachmentPublishingPopup extends StatefulWidget {
|
class AttachmentPublishingPopup extends StatefulWidget {
|
||||||
final String usage;
|
final String usage;
|
||||||
@ -59,11 +48,13 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
|||||||
for (final media in medias) {
|
for (final media in medias) {
|
||||||
final file = File(media.path);
|
final file = File(media.path);
|
||||||
final hash = await calculateFileSha256(file);
|
final hash = await calculateFileSha256(file);
|
||||||
final image = await decodeImageFromList(await file.readAsBytes());
|
|
||||||
final ratio = image.width / image.height;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await uploadAttachment(file, hash, ratio: ratio);
|
await uploadAttachment(
|
||||||
|
file,
|
||||||
|
hash,
|
||||||
|
ratio: await calculateFileAspectRatio(file),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.context.showErrorDialog(err);
|
this.context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
@ -102,10 +93,10 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
|||||||
await FilePicker.platform.pickFiles(allowMultiple: true);
|
await FilePicker.platform.pickFiles(allowMultiple: true);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
List<File> files = result.paths.map((path) => File(path!)).toList();
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||||
|
|
||||||
for (final file in files) {
|
for (final file in files) {
|
||||||
final hash = await calculateFileSha256(file);
|
final hash = await calculateFileSha256(file);
|
||||||
try {
|
try {
|
||||||
@ -139,8 +130,7 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
|||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
ratio = 16 / 9;
|
ratio = 16 / 9;
|
||||||
} else {
|
} else {
|
||||||
final image = await decodeImageFromList(await file.readAsBytes());
|
ratio = await calculateFileAspectRatio(file);
|
||||||
ratio = image.width / image.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -153,36 +143,19 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> uploadAttachment(File file, String hash, {double? ratio}) async {
|
Future<void> uploadAttachment(File file, String hash, {double? ratio}) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AttachmentProvider provider = Get.find();
|
||||||
|
try {
|
||||||
final client = GetConnect();
|
final resp = await provider.createAttachment(
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
file,
|
||||||
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
hash,
|
||||||
|
widget.usage,
|
||||||
final filePayload =
|
ratio: ratio,
|
||||||
MultipartFile(await file.readAsBytes(), filename: basename(file.path));
|
);
|
||||||
final fileAlt = basename(file.path).contains('.')
|
|
||||||
? basename(file.path).substring(0, basename(file.path).lastIndexOf('.'))
|
|
||||||
: basename(file.path);
|
|
||||||
|
|
||||||
final resp = await client.post(
|
|
||||||
'/api/attachments',
|
|
||||||
FormData({
|
|
||||||
'alt': fileAlt,
|
|
||||||
'file': filePayload,
|
|
||||||
'hash': hash,
|
|
||||||
'usage': widget.usage,
|
|
||||||
'metadata': jsonEncode({
|
|
||||||
if (ratio != null) 'ratio': ratio,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (resp.statusCode == 200) {
|
|
||||||
var result = Attachment.fromJson(resp.body);
|
var result = Attachment.fromJson(resp.body);
|
||||||
setState(() => _attachments.add(result));
|
setState(() => _attachments.add(result));
|
||||||
widget.onUpdate(_attachments.map((e) => e!.id).toList());
|
widget.onUpdate(_attachments.map((e) => e!.id).toList());
|
||||||
} else {
|
} catch (e) {
|
||||||
throw Exception(resp.bodyString);
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +179,7 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void revertMetadataList() {
|
void revertMetadataList() {
|
||||||
final AttachmentListProvider provider = Get.find();
|
final AttachmentProvider provider = Get.find();
|
||||||
|
|
||||||
if (widget.current.isEmpty) {
|
if (widget.current.isEmpty) {
|
||||||
_isFirstTimeBusy = false;
|
_isFirstTimeBusy = false;
|
||||||
@ -299,7 +272,7 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AttachmentEditingPopup(
|
return AttachmentEditingDialog(
|
||||||
item: element,
|
item: element,
|
||||||
onDelete: () {
|
onDelete: () {
|
||||||
setState(
|
setState(
|
||||||
@ -379,22 +352,23 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentEditingPopup extends StatefulWidget {
|
class AttachmentEditingDialog extends StatefulWidget {
|
||||||
final Attachment item;
|
final Attachment item;
|
||||||
final Function onDelete;
|
final Function onDelete;
|
||||||
final Function(Attachment item) onUpdate;
|
final Function(Attachment item) onUpdate;
|
||||||
|
|
||||||
const AttachmentEditingPopup(
|
const AttachmentEditingDialog(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
required this.onDelete,
|
required this.onDelete,
|
||||||
required this.onUpdate});
|
required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AttachmentEditingPopup> createState() => _AttachmentEditingPopupState();
|
State<AttachmentEditingDialog> createState() =>
|
||||||
|
_AttachmentEditingDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AttachmentEditingPopupState extends State<AttachmentEditingPopup> {
|
class _AttachmentEditingDialogState extends State<AttachmentEditingDialog> {
|
||||||
final _ratioController = TextEditingController();
|
final _ratioController = TextEditingController();
|
||||||
final _altController = TextEditingController();
|
final _altController = TextEditingController();
|
||||||
|
|
||||||
@ -402,49 +376,40 @@ class _AttachmentEditingPopupState extends State<AttachmentEditingPopup> {
|
|||||||
bool _isMature = false;
|
bool _isMature = false;
|
||||||
bool _hasAspectRatio = false;
|
bool _hasAspectRatio = false;
|
||||||
|
|
||||||
Future<Attachment?> applyAttachment() async {
|
Future<Attachment?> updateAttachment() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AttachmentProvider provider = Get.find();
|
||||||
|
|
||||||
final client = GetConnect();
|
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
|
||||||
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
var resp = await client.put('/api/attachments/${widget.item.id}', {
|
try {
|
||||||
'metadata': {
|
final resp = await provider.updateAttachment(
|
||||||
if (_hasAspectRatio)
|
widget.item.id,
|
||||||
'ratio': double.tryParse(_ratioController.value.text) ?? 1,
|
_altController.value.text,
|
||||||
},
|
widget.item.usage,
|
||||||
'alt': _altController.value.text,
|
ratio: _hasAspectRatio
|
||||||
'usage': widget.item.usage,
|
? (double.tryParse(_ratioController.value.text) ?? 1)
|
||||||
'is_mature': _isMature,
|
: null,
|
||||||
});
|
isMature: _isMature,
|
||||||
|
);
|
||||||
setState(() => _isBusy = false);
|
|
||||||
|
|
||||||
if (resp.statusCode != 200) {
|
|
||||||
this.context.showErrorDialog(resp.bodyString);
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return Attachment.fromJson(resp.body);
|
return Attachment.fromJson(resp.body);
|
||||||
|
} catch (e) {
|
||||||
|
this.context.showErrorDialog(e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteAttachment() async {
|
Future<void> deleteAttachment() async {
|
||||||
final AuthProvider auth = Get.find();
|
|
||||||
|
|
||||||
final client = GetConnect();
|
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
|
||||||
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
var resp = await client.delete('/api/attachments/${widget.item.id}');
|
try {
|
||||||
if (resp.statusCode == 200) {
|
final AttachmentProvider provider = Get.find();
|
||||||
|
await provider.deleteAttachment(widget.item.id);
|
||||||
widget.onDelete();
|
widget.onDelete();
|
||||||
} else {
|
} catch (e) {
|
||||||
this.context.showErrorDialog(resp.bodyString);
|
this.context.showErrorDialog(e);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
setState(() => _isBusy = false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncWidget() {
|
void syncWidget() {
|
||||||
@ -586,7 +551,7 @@ class _AttachmentEditingPopupState extends State<AttachmentEditingPopup> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
child: Text('apply'.tr),
|
child: Text('apply'.tr),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
applyAttachment().then((value) {
|
updateAttachment().then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
widget.onUpdate(value);
|
widget.onUpdate(value);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
19
lib/widgets/prev_page.dart
Normal file
19
lib/widgets/prev_page.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
|
|
||||||
|
class PrevPageButton extends StatelessWidget {
|
||||||
|
const PrevPageButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||||
|
onPressed: () {
|
||||||
|
if (AppRouter.instance.canPop()) {
|
||||||
|
AppRouter.instance.pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
pubspec.lock
34
pubspec.lock
@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: "6bd38d335f0954f5fad9c79e614604fbf03a0e5b975923dd001b6ea965ef5b4b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.6.0"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -280,6 +288,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
image:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.7"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -353,7 +369,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
@ -512,6 +528,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -709,6 +733,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.4 <4.0.0"
|
dart: ">=3.3.4 <4.0.0"
|
||||||
flutter: ">=3.19.0"
|
flutter: ">=3.19.0"
|
||||||
|
@ -49,6 +49,8 @@ dependencies:
|
|||||||
file_picker: ^8.0.3
|
file_picker: ^8.0.3
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
|
intl: ^0.19.0
|
||||||
|
image: ^4.1.7
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user