✨ Moment editor
This commit is contained in:
		| @@ -2,6 +2,7 @@ import 'package:go_router/go_router.dart'; | ||||
| import 'package:solaragent/screens/account.dart'; | ||||
| import 'package:solaragent/screens/explore.dart'; | ||||
| import 'package:solaragent/screens/notifications.dart'; | ||||
| import 'package:solaragent/screens/publish/moment_editor.dart'; | ||||
|  | ||||
| final router = GoRouter( | ||||
|   routes: [ | ||||
| @@ -16,6 +17,11 @@ final router = GoRouter( | ||||
|     GoRoute( | ||||
|       path: '/account', | ||||
|       builder: (context, state) => const AccountScreen(), | ||||
|     ) | ||||
|     ), | ||||
|  | ||||
|     GoRoute( | ||||
|       path: '/post/moments', | ||||
|       builder: (context, state) => const MomentEditorScreen(), | ||||
|     ), | ||||
|   ], | ||||
| ); | ||||
| @@ -1,11 +1,15 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | ||||
| import 'package:solaragent/auth.dart'; | ||||
| import 'package:solaragent/models/feed.dart'; | ||||
| import 'package:solaragent/models/pagination.dart'; | ||||
| import 'package:http/http.dart' as http; | ||||
| import 'package:solaragent/router.dart'; | ||||
| import 'package:solaragent/widgets/feed.dart'; | ||||
| import 'package:solaragent/screens/publish/moment_editor.dart'; | ||||
|  | ||||
| class ExploreScreen extends StatefulWidget { | ||||
|   const ExploreScreen({super.key}); | ||||
| @@ -67,6 +71,20 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       floatingActionButton: FutureBuilder( | ||||
|           future: authClient.isAuthorized(), | ||||
|           builder: (context, snapshot) { | ||||
|             if (snapshot.hasData && snapshot.data == true) { | ||||
|               return FloatingActionButton( | ||||
|                 child: const Icon(Icons.edit), | ||||
|                 onPressed: () { | ||||
|                   router.push("/post/moments"); | ||||
|                 }, | ||||
|               ); | ||||
|             } else { | ||||
|               return Container(); | ||||
|             } | ||||
|           }), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   | ||||
							
								
								
									
										156
									
								
								lib/screens/publish/moment_editor.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/screens/publish/moment_editor.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:solaragent/auth.dart'; | ||||
| import 'package:solaragent/router.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
|  | ||||
| class MomentEditorScreen extends StatefulWidget { | ||||
|   const MomentEditorScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<MomentEditorScreen> createState() => _MomentEditorScreenState(); | ||||
| } | ||||
|  | ||||
| class _MomentEditorScreenState extends State<MomentEditorScreen> { | ||||
|   final contentController = TextEditingController(); | ||||
|  | ||||
|   bool isSubmitting = false; | ||||
|  | ||||
|   bool showRecommendationBanner = true; | ||||
|  | ||||
|   Future<void> postMoment() async { | ||||
|     if (authClient.client == null) return; | ||||
|  | ||||
|     setState(() => isSubmitting = true); | ||||
|     var res = await authClient.client!.post( | ||||
|       Uri.parse("https://co.solsynth.dev/api/p/moments"), | ||||
|       headers: <String, String>{ | ||||
|         'Content-Type': 'application/json', | ||||
|       }, | ||||
|       body: jsonEncode(<String, dynamic>{ | ||||
|         'content': contentController.value.text, | ||||
|       }), | ||||
|     ); | ||||
|     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(); | ||||
|       } | ||||
|     } | ||||
|     setState(() => isSubmitting = false); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: const Text("Record a moment"), | ||||
|         actions: <Widget>[ | ||||
|           TextButton( | ||||
|             onPressed: !isSubmitting ? postMoment : null, | ||||
|             child: const Text('POST'), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       body: Column( | ||||
|         children: [ | ||||
|           // Loading indicator | ||||
|           isSubmitting ? const LinearProgressIndicator() : Container(), | ||||
|           // Userinfo | ||||
|           FutureBuilder( | ||||
|               future: authClient.getProfiles(), | ||||
|               builder: (context, snapshot) { | ||||
|                 if (snapshot.hasData) { | ||||
|                   var userinfo = snapshot.data; | ||||
|                   return Container( | ||||
|                     color: Colors.grey[50], | ||||
|                     margin: const EdgeInsets.only(bottom: 12), | ||||
|                     child: ListTile( | ||||
|                       title: Text(userinfo["nick"]), | ||||
|                       subtitle: const Text("You will post this post as"), | ||||
|                       leading: CircleAvatar( | ||||
|                         backgroundImage: NetworkImage(userinfo["picture"]), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ); | ||||
|                 } else { | ||||
|                   return Container(); | ||||
|                 } | ||||
|               }), | ||||
|           // Editor | ||||
|           Expanded( | ||||
|             child: Container( | ||||
|               padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), | ||||
|               child: TextField( | ||||
|                 maxLines: null, | ||||
|                 autofocus: true, | ||||
|                 autocorrect: true, | ||||
|                 keyboardType: TextInputType.multiline, | ||||
|                 controller: contentController, | ||||
|                 decoration: const InputDecoration.collapsed( | ||||
|                     hintText: "What\'s happened?!"), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           // Recommend website banner | ||||
|           showRecommendationBanner | ||||
|               ? FutureBuilder( | ||||
|                   future: SharedPreferences.getInstance(), | ||||
|                   builder: (context, snapshot) { | ||||
|                     if (snapshot.hasData && | ||||
|                         snapshot.data?.getBool( | ||||
|                                 "editor.hide_website_recommendation") == | ||||
|                             null) { | ||||
|                       snapshot.data?.remove("editor.hide_website_recommendation"); | ||||
|                       return MaterialBanner( | ||||
|                         padding: const EdgeInsets.all(20), | ||||
|                         content: const Text( | ||||
|                           'SolarAgent still in early stage development. Some features isn\'t available. We recommend use our website, also optimized for moblie!', | ||||
|                         ), | ||||
|                         leading: const Icon(Icons.construction), | ||||
|                         backgroundColor: const Color(0xFFE0E0E0), | ||||
|                         actions: <Widget>[ | ||||
|                           TextButton( | ||||
|                             child: const Text('OPEN'), | ||||
|                             onPressed: () async { | ||||
|                               await launchUrl( | ||||
|                                   Uri.parse("https://co.solsynth.dev")); | ||||
|                             }, | ||||
|                           ), | ||||
|                           TextButton( | ||||
|                             child: const Text('DISMISS'), | ||||
|                             onPressed: () async { | ||||
|                               await snapshot.data?.setBool( | ||||
|                                 "editor.hide_website_recommendation", | ||||
|                                 true, | ||||
|                               ); | ||||
|                               setState(() { | ||||
|                                 showRecommendationBanner = false; | ||||
|                               }); | ||||
|                             }, | ||||
|                           ), | ||||
|                         ], | ||||
|                       ); | ||||
|                     } else { | ||||
|                       return Container(); | ||||
|                     } | ||||
|                   }) | ||||
|               : Container(), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     contentController.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
| } | ||||
| @@ -20,70 +20,68 @@ class FeedItem extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Container( | ||||
|       child: Column( | ||||
|         children: [ | ||||
|           Container( | ||||
|             color: Colors.grey[50], | ||||
|             child: ListTile( | ||||
|               title: Text(item.author.name), | ||||
|               leading: CircleAvatar( | ||||
|                 backgroundImage: NetworkImage(item.author.avatar), | ||||
|               ), | ||||
|               subtitle: Text( | ||||
|                 getDescription(item.author.description), | ||||
|                 overflow: TextOverflow.ellipsis, | ||||
|                 maxLines: 1, | ||||
|                 softWrap: false, | ||||
|               ), | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Container( | ||||
|           color: Colors.grey[50], | ||||
|           child: ListTile( | ||||
|             title: Text(item.author.name), | ||||
|             leading: CircleAvatar( | ||||
|               backgroundImage: NetworkImage(item.author.avatar), | ||||
|             ), | ||||
|             subtitle: Text( | ||||
|               getDescription(item.author.description), | ||||
|               overflow: TextOverflow.ellipsis, | ||||
|               maxLines: 1, | ||||
|               softWrap: false, | ||||
|             ), | ||||
|           ), | ||||
|           Markdown( | ||||
|             data: item.content, | ||||
|             shrinkWrap: true, | ||||
|             physics: const NeverScrollableScrollPhysics(), | ||||
|           ), | ||||
|           hasAttachments() | ||||
|               ? Container( | ||||
|                   decoration: const BoxDecoration( | ||||
|                     border: Border( | ||||
|                         top: BorderSide(width: 0.3, color: Color(0xffdedede))), | ||||
|         ), | ||||
|         Markdown( | ||||
|           data: item.content, | ||||
|           shrinkWrap: true, | ||||
|           physics: const NeverScrollableScrollPhysics(), | ||||
|         ), | ||||
|         hasAttachments() | ||||
|             ? Container( | ||||
|                 decoration: const BoxDecoration( | ||||
|                   border: Border( | ||||
|                       top: BorderSide(width: 0.3, color: Color(0xffdedede))), | ||||
|                 ), | ||||
|                 child: FlutterCarousel( | ||||
|                   options: CarouselOptions( | ||||
|                     height: 240.0, | ||||
|                     showIndicator: true, | ||||
|                     slideIndicator: const CircularSlideIndicator(), | ||||
|                   ), | ||||
|                   child: FlutterCarousel( | ||||
|                     options: CarouselOptions( | ||||
|                       height: 240.0, | ||||
|                       showIndicator: true, | ||||
|                       slideIndicator: const CircularSlideIndicator(), | ||||
|                     ), | ||||
|                     items: item.attachments?.map((x) { | ||||
|                       return Builder( | ||||
|                         builder: (BuildContext context) { | ||||
|                           return Container( | ||||
|                             width: MediaQuery.of(context).size.width, | ||||
|                             margin: const EdgeInsets.symmetric(horizontal: 5.0), | ||||
|                             child: InkWell( | ||||
|                               child: Image.network( | ||||
|                                 getFileUrl(x.fileId), | ||||
|                                 fit: BoxFit.cover, | ||||
|                               ), | ||||
|                               onTap: () { | ||||
|                                 Navigator.push(context, | ||||
|                                     MaterialPageRoute(builder: (_) { | ||||
|                                   return ImageLightbox( | ||||
|                                     url: getFileUrl(x.fileId), | ||||
|                                   ); | ||||
|                                 })); | ||||
|                               }, | ||||
|                   items: item.attachments?.map((x) { | ||||
|                     return Builder( | ||||
|                       builder: (BuildContext context) { | ||||
|                         return Container( | ||||
|                           width: MediaQuery.of(context).size.width, | ||||
|                           margin: const EdgeInsets.symmetric(horizontal: 5.0), | ||||
|                           child: InkWell( | ||||
|                             child: Image.network( | ||||
|                               getFileUrl(x.fileId), | ||||
|                               fit: BoxFit.cover, | ||||
|                             ), | ||||
|                           ); | ||||
|                         }, | ||||
|                       ); | ||||
|                     }).toList(), | ||||
|                   ), | ||||
|                 ) | ||||
|               : Container(), | ||||
|         ], | ||||
|       ), | ||||
|                             onTap: () { | ||||
|                               Navigator.push(context, | ||||
|                                   MaterialPageRoute(builder: (_) { | ||||
|                                 return ImageLightbox( | ||||
|                                   url: getFileUrl(x.fileId), | ||||
|                                 ); | ||||
|                               })); | ||||
|                             }, | ||||
|                           ), | ||||
|                         ); | ||||
|                       }, | ||||
|                     ); | ||||
|                   }).toList(), | ||||
|                 ), | ||||
|               ) | ||||
|             : Container(), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user