diff --git a/lib/router.dart b/lib/router.dart index c0c6880..d539b68 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -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(), + ), ], -); \ No newline at end of file +); diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index c1282d6..efe791d 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -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 { ), ), ), + 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(); + } + }), ); } diff --git a/lib/screens/publish/moment_editor.dart b/lib/screens/publish/moment_editor.dart new file mode 100644 index 0000000..02cd4a8 --- /dev/null +++ b/lib/screens/publish/moment_editor.dart @@ -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 createState() => _MomentEditorScreenState(); +} + +class _MomentEditorScreenState extends State { + final contentController = TextEditingController(); + + bool isSubmitting = false; + + bool showRecommendationBanner = true; + + Future 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: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + '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: [ + 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: [ + 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(); + } +} diff --git a/lib/widgets/feed.dart b/lib/widgets/feed.dart index 3fbdee0..d12b664 100644 --- a/lib/widgets/feed.dart +++ b/lib/widgets/feed.dart @@ -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(), + ], ); } }