✨ New moment
This commit is contained in:
parent
07935a4017
commit
c693b0dcd7
@ -2,6 +2,8 @@ PODS:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- media_kit_video (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
@ -26,6 +28,7 @@ PODS:
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
@ -41,6 +44,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter
|
||||
flutter_secure_storage:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
media_kit_video:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
package_info_plus:
|
||||
@ -63,6 +68,7 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
image_picker_ios: b545a5f16c0fa88e3ecbbce3ed4de45567a8ec18
|
||||
media_kit_video: 26c5b265a4094a2df3e8d41e6724d9b964c13151
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
|
@ -35,6 +35,12 @@
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Allow you add photo to your message or post</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Allow you take photo/video for your message or post</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Allow you record audio for your message or post</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
@ -7,5 +7,12 @@
|
||||
"signUp": "Sign Up",
|
||||
"signUpCaption": "Create an account on Solarpass and then get the access of entire Solar Networks!",
|
||||
"post": "Post",
|
||||
"comment": "Comment"
|
||||
}
|
||||
"postVerb": "Post",
|
||||
"comment": "Comment",
|
||||
"attachment": "Attachment",
|
||||
"attachmentAdd": "Add new attachment",
|
||||
"pickPhoto": "Gallery photo",
|
||||
"newMoment": "Record a moment",
|
||||
"postIdentityNotify": "You will create this post as",
|
||||
"postContentPlaceholder": "What's happened?!"
|
||||
}
|
||||
|
@ -7,5 +7,12 @@
|
||||
"signUp": "注册",
|
||||
"signUpCaption": "在 Solarpass 注册一个账号以获得整个 Solar Networks 的存取权!",
|
||||
"post": "帖子",
|
||||
"comment": "评论"
|
||||
"postVerb": "发表",
|
||||
"comment": "评论",
|
||||
"attachment": "附件",
|
||||
"attachmentAdd": "附加新附件",
|
||||
"pickPhoto": "相册照片",
|
||||
"newMoment": "记录时刻",
|
||||
"postIdentityNotify": "你将会以该身份发表本帖子",
|
||||
"postContentPlaceholder": "发生什么事了?!"
|
||||
}
|
@ -7,10 +7,10 @@ import 'package:solian/screens/auth.dart';
|
||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
|
||||
final authClient = AuthProvider();
|
||||
|
||||
class AuthProvider {
|
||||
AuthProvider();
|
||||
AuthProvider() {
|
||||
pickClient();
|
||||
}
|
||||
|
||||
final deviceEndpoint =
|
||||
getRequestUri('passport', '/api/notifications/subscribe');
|
||||
@ -61,7 +61,8 @@ class AuthProvider {
|
||||
basicAuth: false,
|
||||
);
|
||||
|
||||
var authorizationUrl = grant.getAuthorizationUrl(redirectUrl, scopes: ["openid"]);
|
||||
var authorizationUrl =
|
||||
grant.getAuthorizationUrl(redirectUrl, scopes: ["openid"]);
|
||||
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
// Use WebView to get authorization url
|
||||
|
@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/wrapper.dart';
|
||||
import 'package:solian/widgets/common_wrapper.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AccountScreen extends StatefulWidget {
|
||||
@ -66,7 +66,7 @@ class _AccountScreenState extends State<AccountScreen> {
|
||||
caption: AppLocalizations.of(context)!.signInCaption,
|
||||
onTap: () {
|
||||
auth.signIn(context).then((_) {
|
||||
authClient.isAuthorized().then((val) {
|
||||
auth.isAuthorized().then((val) {
|
||||
setState(() => isAuthorized = val);
|
||||
});
|
||||
});
|
||||
@ -90,13 +90,15 @@ class _AccountScreenState extends State<AccountScreen> {
|
||||
class NameCard extends StatelessWidget {
|
||||
const NameCard({super.key});
|
||||
|
||||
Future<Widget> renderAvatar() async {
|
||||
final profiles = await authClient.getProfiles();
|
||||
Future<Widget> renderAvatar(BuildContext context) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
final profiles = await auth.getProfiles();
|
||||
return CircleAvatar(backgroundImage: NetworkImage(profiles["picture"]));
|
||||
}
|
||||
|
||||
Future<Column> renderLabel() async {
|
||||
final profiles = await authClient.getProfiles();
|
||||
Future<Column> renderLabel(BuildContext context) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
final profiles = await auth.getProfiles();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -122,7 +124,7 @@ class NameCard extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: renderAvatar(),
|
||||
future: renderAvatar(context),
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<Widget> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
@ -134,7 +136,7 @@ class NameCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
FutureBuilder(
|
||||
future: renderLabel(),
|
||||
future: renderLabel(context),
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<Column> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
|
@ -8,8 +8,8 @@ import 'package:solian/utils/service_url.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:solian/widgets/posts/item.dart';
|
||||
import 'package:solian/widgets/wrapper.dart';
|
||||
|
||||
class ExploreScreen extends StatefulWidget {
|
||||
const ExploreScreen({super.key});
|
||||
@ -58,7 +58,15 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutWrapper(
|
||||
return IndentWrapper(
|
||||
noSafeArea: true,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: () async {
|
||||
final did = await router.pushNamed("posts.moments.new");
|
||||
if (did == true) _pagingController.refresh();
|
||||
},
|
||||
),
|
||||
title: AppLocalizations.of(context)!.explore,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => Future.sync(
|
||||
@ -75,7 +83,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
itemBuilder: (context, item, index) => GestureDetector(
|
||||
child: PostItem(item: item),
|
||||
onTap: () {
|
||||
router.goNamed(
|
||||
router.pushNamed(
|
||||
'posts.screen',
|
||||
pathParameters: {
|
||||
'alias': item.alias,
|
||||
|
@ -1,19 +1,143 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class NewMomentScreen extends StatelessWidget {
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:solian/widgets/posts/attachment_editor.dart';
|
||||
|
||||
class NewMomentScreen extends StatefulWidget {
|
||||
const NewMomentScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
child: Column(
|
||||
children: [
|
||||
State<NewMomentScreen> createState() => _NewMomentScreenState();
|
||||
}
|
||||
|
||||
],
|
||||
class _NewMomentScreenState extends State<NewMomentScreen> {
|
||||
final _textController = TextEditingController();
|
||||
|
||||
bool _isSubmitting = false;
|
||||
|
||||
List<Attachment> _attachments = List.empty(growable: true);
|
||||
|
||||
void viewAttachments(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AttachmentEditor(
|
||||
current: _attachments,
|
||||
onUpdate: (value) => _attachments = value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> createPost(BuildContext context) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
if (!await auth.isAuthorized()) return;
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
var res = await auth.client!.post(
|
||||
getRequestUri('interactive', '/api/p/moments'),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: jsonEncode(<String, dynamic>{
|
||||
'content': _textController.value.text,
|
||||
'attachments': _attachments,
|
||||
}),
|
||||
);
|
||||
if (res.statusCode != 200) {
|
||||
var message = utf8.decode(res.bodyBytes);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Something went wrong... $message")),
|
||||
);
|
||||
} else {
|
||||
if (router.canPop()) {
|
||||
router.pop(true);
|
||||
}
|
||||
}
|
||||
setState(() => _isSubmitting = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
return IndentWrapper(
|
||||
hideDrawer: true,
|
||||
title: AppLocalizations.of(context)!.newMoment,
|
||||
appBarActions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: !_isSubmitting ? () => createPost(context) : null,
|
||||
child: Text(AppLocalizations.of(context)!.postVerb),
|
||||
),
|
||||
],
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
child: Column(
|
||||
children: [
|
||||
_isSubmitting ? const LinearProgressIndicator() : Container(),
|
||||
FutureBuilder(
|
||||
future: auth.getProfiles(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var userinfo = snapshot.data;
|
||||
return ListTile(
|
||||
title: Text(userinfo["nick"]),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.postIdentityNotify,
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: NetworkImage(userinfo["picture"]),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _textController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText:
|
||||
AppLocalizations.of(context)!.postContentPlaceholder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.camera_alt),
|
||||
onPressed: () => viewAttachments(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:solian/widgets/posts/comment_list.dart';
|
||||
import 'package:solian/widgets/posts/item.dart';
|
||||
import 'package:solian/widgets/wrapper.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class PostScreen extends StatefulWidget {
|
||||
@ -43,7 +43,8 @@ class _PostScreenState extends State<PostScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutWrapper(
|
||||
return IndentWrapper(
|
||||
hideDrawer: true,
|
||||
title: AppLocalizations.of(context)!.post,
|
||||
child: FutureBuilder(
|
||||
future: fetchPost(context),
|
||||
|
@ -12,7 +12,9 @@ class LayoutWrapper extends StatelessWidget {
|
||||
return Scaffold(
|
||||
drawer: const SolianNavigationDrawer(),
|
||||
appBar: AppBar(title: Text(title)),
|
||||
body: child ?? Container(),
|
||||
body: SafeArea(
|
||||
child: child ?? Container(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
25
lib/widgets/indent_wrapper.dart
Normal file
25
lib/widgets/indent_wrapper.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:solian/widgets/navigation_drawer.dart';
|
||||
|
||||
class IndentWrapper extends StatelessWidget {
|
||||
final Widget? child;
|
||||
final Widget? floatingActionButton;
|
||||
final List<Widget>? appBarActions;
|
||||
final bool? hideDrawer;
|
||||
final bool? noSafeArea;
|
||||
final String title;
|
||||
|
||||
const IndentWrapper({super.key, this.child, required this.title, this.floatingActionButton, this.appBarActions, this.hideDrawer, this.noSafeArea});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final content = child ?? Container();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(title), actions: appBarActions),
|
||||
floatingActionButton: floatingActionButton,
|
||||
drawer: (hideDrawer ?? false) ? null : const SolianNavigationDrawer(),
|
||||
body: (noSafeArea ?? false) ? content : SafeArea(child: content),
|
||||
);
|
||||
}
|
||||
}
|
300
lib/widgets/posts/attachment_editor.dart
Executable file
300
lib/widgets/posts/attachment_editor.dart
Executable file
@ -0,0 +1,300 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
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/service_url.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class AttachmentEditor extends StatefulWidget {
|
||||
final List<Attachment> current;
|
||||
final void Function(List<Attachment> data) onUpdate;
|
||||
|
||||
const AttachmentEditor(
|
||||
{super.key, required this.current, required this.onUpdate});
|
||||
|
||||
@override
|
||||
State<AttachmentEditor> createState() => _AttachmentEditorState();
|
||||
}
|
||||
|
||||
class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
final _imagePicker = ImagePicker();
|
||||
|
||||
bool isSubmitting = false;
|
||||
|
||||
List<Attachment> _attachments = List.empty(growable: true);
|
||||
|
||||
void viewAttachMethods(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AttachmentEditorMethodPopup(
|
||||
pickImage: () => pickImageToUpload(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pickImageToUpload(BuildContext context) 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);
|
||||
final hashcode = await calculateSha256(file);
|
||||
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
try {
|
||||
await uploadAttachment(file, hashcode);
|
||||
} catch (err) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Something went wrong... $err")),
|
||||
);
|
||||
} finally {
|
||||
setState(() => isSubmitting = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadAttachment(File file, String hashcode) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
final req = MultipartRequest(
|
||||
'POST',
|
||||
getRequestUri('interactive', '/api/attachments'),
|
||||
);
|
||||
req.files.add(await MultipartFile.fromPath('attachment', file.path));
|
||||
req.fields['hashcode'] = hashcode;
|
||||
|
||||
var res = await auth.client!.send(req);
|
||||
print(res);
|
||||
if (res.statusCode == 200) {
|
||||
var result = Attachment.fromJson(
|
||||
jsonDecode(utf8.decode(await res.stream.toBytes()))["info"],
|
||||
);
|
||||
setState(() => _attachments.add(result));
|
||||
widget.onUpdate(_attachments);
|
||||
} else {
|
||||
throw Exception(utf8.decode(await res.stream.toBytes()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> disposeAttachment(
|
||||
BuildContext context, Attachment item, int index) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
final req = MultipartRequest(
|
||||
'DELETE',
|
||||
getRequestUri('interactive', '/api/attachments/${item.id}'),
|
||||
);
|
||||
|
||||
setState(() => isSubmitting = true);
|
||||
var res = await auth.client!.send(req);
|
||||
if (res.statusCode == 200) {
|
||||
setState(() => _attachments.removeAt(index));
|
||||
widget.onUpdate(_attachments);
|
||||
} else {
|
||||
final err = utf8.decode(await res.stream.toBytes());
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Something went wrong... $err")),
|
||||
);
|
||||
}
|
||||
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'\.[^/.]+$'), '');
|
||||
}
|
||||
|
||||
String getFileType(Attachment item) {
|
||||
switch (item.type) {
|
||||
case 1:
|
||||
return 'Photo';
|
||||
case 2:
|
||||
return 'Video';
|
||||
case 3:
|
||||
return 'Audio';
|
||||
default:
|
||||
return 'Others';
|
||||
}
|
||||
}
|
||||
|
||||
String formatBytes(int bytes, {int decimals = 2}) {
|
||||
if (bytes == 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
final dm = decimals < 0 ? 0 : decimals;
|
||||
final sizes = [
|
||||
'Bytes',
|
||||
'KiB',
|
||||
'MiB',
|
||||
'GiB',
|
||||
'TiB',
|
||||
'PiB',
|
||||
'EiB',
|
||||
'ZiB',
|
||||
'YiB'
|
||||
];
|
||||
final i = (math.log(bytes) / math.log(k)).floor().toInt();
|
||||
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_attachments = widget.current;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.attachment,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: auth.isAuthorized(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data == true) {
|
||||
return TextButton(
|
||||
onPressed: isSubmitting
|
||||
? null
|
||||
: () => viewAttachMethods(context),
|
||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.add_circle),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
isSubmitting ? const LinearProgressIndicator() : Container(),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: _attachments.length,
|
||||
itemBuilder: (context, index) {
|
||||
var element = _attachments[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
getFileName(element),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Text(
|
||||
"${getFileType(element)} · ${formatBytes(element.filesize)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
child: const Icon(Icons.delete),
|
||||
onPressed: () =>
|
||||
disposeAttachment(context, element, index),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AttachmentEditorMethodPopup extends StatelessWidget {
|
||||
final Function pickImage;
|
||||
|
||||
const AttachmentEditorMethodPopup({super.key, required this.pickImage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 320,
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.attachmentAdd,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => pickImage(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add_photo_alternate,
|
||||
color: Colors.indigo),
|
||||
const SizedBox(height: 8),
|
||||
Text(AppLocalizations.of(context)!.pickPhoto),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -80,7 +80,7 @@ class _PostItemState extends State<PostItem> {
|
||||
children: [
|
||||
...headingParts,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 4),
|
||||
child: renderContent(),
|
||||
),
|
||||
renderAttachments(),
|
||||
|
@ -6,11 +6,15 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
flutter_secure_storage_linux
|
||||
media_kit_video
|
||||
url_launcher_linux
|
||||
|
@ -5,6 +5,7 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import file_selector_macos
|
||||
import flutter_secure_storage_macos
|
||||
import media_kit_video
|
||||
import package_info_plus
|
||||
@ -15,6 +16,7 @@ import video_player_avfoundation
|
||||
import wakelock_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
|
122
pubspec.lock
122
pubspec.lock
@ -73,8 +73,16 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
crypto:
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+1"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
@ -121,6 +129,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.2+1"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+3"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -163,6 +203,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.22+1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.19"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -269,6 +317,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.7"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "1f498d086203360cca099d20ffea2963f48c39ce91bdd8a3b6d4a045786b02c8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "844c6da4e4f2829dffdab97816bca09d0e0977e8dcef7450864aba4e07967a58"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.9+6"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "6a1704fdd75022272e7e7a897a9068e9c2ff3cd6a66820bf3ded810633eac954"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "917a5cadd67d052554cfb258595e54217de53fac5b52939426e26319a02e6297"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.9+2"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
infinite_scroll_pagination:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -381,6 +493,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -53,6 +53,8 @@ dependencies:
|
||||
flutter_secure_storage: ^9.0.0
|
||||
oauth2: ^2.0.2
|
||||
webview_flutter: ^4.7.0
|
||||
crypto: ^3.0.3
|
||||
image_picker: ^1.0.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -6,12 +6,15 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
MediaKitVideoPluginCApiRegisterWithRegistrar(
|
||||
|
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
flutter_secure_storage_windows
|
||||
media_kit_video
|
||||
screen_brightness_windows
|
||||
|
Loading…
Reference in New Issue
Block a user