5 Commits

Author SHA1 Message Date
LittleSheep
807ece57dd 🚀 Launch SolarAgent Technology Preview 2024-03-24 23:15:01 +08:00
LittleSheep
91d238bc2f Post details
💄 Somewhere optimization
2024-03-24 22:57:12 +08:00
LittleSheep
217c20164f Render article in feed 2024-03-24 22:23:23 +08:00
LittleSheep
52304a7633 Upload photos 2024-03-24 21:55:20 +08:00
LittleSheep
a303f5c30c 🐛 Fix and optimize something 2024-03-24 20:51:12 +08:00
18 changed files with 537 additions and 55 deletions

View File

@@ -1,4 +1,38 @@
PODS:
- DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_secure_storage (6.0.0):
- Flutter
@@ -9,25 +43,49 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SDWebImage (5.19.0):
- SDWebImage/Core (= 5.19.0)
- SDWebImage/Core (5.19.0)
- Sentry/HybridSDK (8.21.0):
- SentryPrivate (= 8.21.0)
- sentry_flutter (0.0.1):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.21.0)
- SentryPrivate (8.21.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.4)
- url_launcher_ios (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- Sentry
- SentryPrivate
- SwiftyGif
EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_secure_storage:
@@ -38,6 +96,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
@@ -46,12 +106,20 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
image_picker_ios: b545a5f16c0fa88e3ecbbce3ed4de45567a8ec18
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
SDWebImage: 981fd7e860af070920f249fd092420006014c3eb
Sentry: ebc12276bd17613a114ab359074096b6b3725203
sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36

View File

@@ -477,7 +477,7 @@
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolarAgent;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -666,7 +666,7 @@
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolarAgent;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -693,7 +693,7 @@
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolarAgent;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",

View File

@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>SolarAgent</string>
<string>Solian</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -15,7 +15,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>SolarAgent</string>
<string>Solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

View File

@@ -27,6 +27,7 @@ class AuthGuard {
static const profileKey = "profiles";
oauth2.Client? client;
DateTime? lastRefreshedAt;
Future<bool> pickClient() async {
if (await storage.containsKey(key: storageKey)) {
@@ -110,7 +111,13 @@ class AuthGuard {
const storage = FlutterSecureStorage();
if (await storage.containsKey(key: storageKey)) {
if (client != null) {
await refreshToken();
if (lastRefreshedAt == null ||
lastRefreshedAt!
.add(const Duration(minutes: 3))
.isAfter(DateTime.now())) {
await refreshToken();
lastRefreshedAt = DateTime.now();
}
}
return true;
} else {

View File

@@ -3,6 +3,7 @@ import 'package:solaragent/models/feed.dart';
import 'package:solaragent/screens/account.dart';
import 'package:solaragent/screens/explore.dart';
import 'package:solaragent/screens/notifications.dart';
import 'package:solaragent/screens/posts/screen.dart';
import 'package:solaragent/screens/publish/comment_editor.dart';
import 'package:solaragent/screens/publish/moment_editor.dart';
@@ -20,14 +21,23 @@ final router = GoRouter(
path: '/account',
builder: (context, state) => const AccountScreen(),
),
GoRoute(
path: '/post/moments',
path: '/post/new/moments',
builder: (context, state) => const MomentEditorScreen(),
),
GoRoute(
path: '/post/comments',
path: '/post/new/comments',
builder: (context, state) =>
CommentEditorScreen(parent: state.extra as Feed),
),
GoRoute(
path: '/post/:modelType/:alias',
builder: (context, state) => PostScreen(
modelType: state.pathParameters['modelType'] as String,
alias: state.pathParameters['alias'] as String,
),
),
],
);

View File

@@ -16,7 +16,7 @@ class AboutScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('SolarAgent',
Text('Solian',
style: Theme.of(context).textTheme.headlineMedium),
Text('Solar Networks Official Mobile Application',
style: Theme.of(context).textTheme.bodyMedium),

View File

@@ -71,6 +71,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
builderDelegate: PagedChildBuilderDelegate<Feed>(
itemBuilder: (context, item, index) => FeedItem(
item: item,
brief: true,
),
),
),
@@ -82,7 +83,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
return FloatingActionButton(
child: const Icon(Icons.edit),
onPressed: () {
router.push("/post/moments").then((value) {
router.push("/post/new/moments").then((value) {
if (value == true) paginationController.refresh();
});
},

View File

@@ -0,0 +1,64 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:solaragent/models/feed.dart';
import 'package:solaragent/widgets/feed.dart';
class PostScreen extends StatefulWidget {
final Client client = Client();
final String modelType;
final String alias;
PostScreen({super.key, required this.modelType, required this.alias});
@override
State<PostScreen> createState() => _PostScreenState();
}
class _PostScreenState extends State<PostScreen> {
Future<Feed?> pullPost(BuildContext context) async {
var uri = Uri.parse(
"https://co.solsynth.dev/api/p/${widget.modelType}s/${widget.alias}",
);
var res = await widget.client.get(uri);
if (res.statusCode != 200) {
var err = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Something went wrong... $err")),
);
return null;
} else {
return Feed.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
}
}
Widget buildItem(BuildContext context, Feed item) {
return FeedItem(
item: item,
brief: false,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Post")),
body: FutureBuilder(
future: pullPost(context),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return SingleChildScrollView(
child: buildItem(context, snapshot.data!),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}

View File

@@ -0,0 +1,212 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart';
import 'package:solaragent/auth.dart';
import 'package:solaragent/models/feed.dart';
class AttachmentList extends StatefulWidget {
final List<Attachment> current;
final void Function(List<Attachment> data) onUpdate;
const AttachmentList(
{super.key, required this.current, required this.onUpdate});
@override
State<AttachmentList> createState() => _AttachmentListState();
}
class _AttachmentListState extends State<AttachmentList> {
final imagePicker = ImagePicker();
bool isSubmitting = false;
List<Attachment> attachments = List.empty(growable: true);
Future<void> pickImageToUpload(BuildContext context) async {
if (!await authClient.isAuthorized()) return;
final image = await imagePicker.pickImage(source: ImageSource.gallery);
if (image == null) return;
final file = File(image.path);
final hashcode = await calculateSha256(file);
try {
await uploadAttachment(file, hashcode);
} catch (err) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Something went wrong... $err")),
);
}
}
Future<void> uploadAttachment(File file, String hashcode) async {
var req = MultipartRequest(
'POST',
Uri.parse('https://co.solsynth.dev/api/attachments'),
);
req.files.add(await MultipartFile.fromPath('attachment', file.path));
req.fields['hashcode'] = hashcode;
setState(() => isSubmitting = true);
var res = await authClient.client!.send(req);
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()));
}
setState(() => isSubmitting = false);
}
Future<void> disposeAttachment(
BuildContext context, Attachment item, int index) async {
var req = MultipartRequest(
'DELETE',
Uri.parse('https://co.solsynth.dev/api/attachments/${item.id}'),
);
setState(() => isSubmitting = true);
var res = await authClient.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) {
return Column(
children: [
Container(
padding: const EdgeInsets.only(left: 10, right: 10, top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 12.0,
),
child: Text(
'Attachments',
style: Theme.of(context).textTheme.headlineSmall,
),
),
FutureBuilder(
future: authClient.isAuthorized(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data == true) {
return Tooltip(
message: "Add a photo",
child: TextButton(
onPressed: isSubmitting
? null
: () => pickImageToUpload(context),
style: TextButton.styleFrom(
shape: const CircleBorder(),
),
child: const Icon(Icons.add_photo_alternate),
),
);
} 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: 8.0),
child: ListTile(
title: Text(getFileName(element)),
subtitle: Text(
"${getFileType(element)} · ${formatBytes(element.filesize)}",
),
trailing: 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(),
),
),
],
);
}
}

View File

@@ -1,6 +1,5 @@
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';
@@ -124,7 +123,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
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!',
'Solian 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),
@@ -133,7 +132,9 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
child: const Text('OPEN'),
onPressed: () async {
await launchUrl(
Uri.parse("https://co.solsynth.dev"));
Uri.parse("https://co.solsynth.dev"),
mode: LaunchMode.externalApplication,
);
},
),
TextButton(

View File

@@ -1,11 +1,11 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solaragent/auth.dart';
import 'package:solaragent/models/feed.dart';
import 'package:solaragent/router.dart';
import 'package:solaragent/screens/publish/attachment_list.dart';
import 'package:url_launcher/url_launcher.dart';
class MomentEditorScreen extends StatefulWidget {
@@ -16,13 +16,14 @@ class MomentEditorScreen extends StatefulWidget {
}
class _MomentEditorScreenState extends State<MomentEditorScreen> {
final picker = ImagePicker();
final contentController = TextEditingController();
bool isSubmitting = false;
bool showRecommendationBanner = true;
List<Attachment> attachments = List.empty(growable: true);
Future<void> postMoment() async {
if (!await authClient.isAuthorized()) return;
@@ -34,6 +35,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
},
body: jsonEncode(<String, dynamic>{
'content': contentController.value.text,
'attachments': attachments,
}),
);
if (res.statusCode != 200) {
@@ -49,6 +51,16 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
setState(() => isSubmitting = false);
}
void viewAttachments(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentList(
current: attachments,
onUpdate: (value) => attachments = value,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -112,9 +124,9 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
child: Row(
children: [
TextButton(
onPressed: null,
style: TextButton.styleFrom(shape: const CircleBorder()),
child: const Icon(Icons.camera_alt),
onPressed: () => viewAttachments(context),
)
],
),
@@ -133,7 +145,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
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!',
'Solian 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),
@@ -142,7 +154,9 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
child: const Text('OPEN'),
onPressed: () async {
await launchUrl(
Uri.parse("https://co.solsynth.dev"));
Uri.parse("https://co.solsynth.dev"),
mode: LaunchMode.externalApplication,
);
},
),
TextButton(

View File

@@ -1,15 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:go_router/go_router.dart';
import 'package:solaragent/models/feed.dart';
import 'package:solaragent/router.dart';
import 'package:solaragent/widgets/image.dart';
import 'package:solaragent/widgets/posts/comments.dart';
import 'package:solaragent/widgets/posts/content/article.dart';
import 'package:solaragent/widgets/posts/content/moment.dart';
import 'package:solaragent/widgets/posts/reactions.dart';
class FeedItem extends StatefulWidget {
final Feed item;
final bool? brief;
const FeedItem({super.key, required this.item});
const FeedItem({super.key, required this.item, this.brief});
@override
State<FeedItem> createState() => _FeedItemState();
@@ -47,7 +52,9 @@ class _FeedItemState extends State<FeedItem> {
}
bool hasAttachments() =>
widget.item.attachments != null && widget.item.attachments!.isNotEmpty;
widget.item.modelType == "moment" &&
widget.item.attachments != null &&
widget.item.attachments!.isNotEmpty;
String getDescription(String desc) =>
desc.isEmpty ? "No description yet." : desc;
@@ -55,18 +62,18 @@ class _FeedItemState extends State<FeedItem> {
String getFileUrl(String fileId) =>
'https://co.solsynth.dev/api/attachments/o/$fileId';
@override
void initState() {
reactionCount = widget.item.reactionCount;
reactionList = widget.item.reactionList ?? <String, dynamic>{};
super.initState();
Widget buildContent() {
switch (widget.item.modelType) {
case "article":
return ArticleContent(item: widget.item, brief: widget.brief ?? false);
default:
return MomentContent(item: widget.item, brief: widget.brief ?? false);
}
}
@override
Widget build(BuildContext context) {
Widget buildItem(BuildContext context) {
return Column(
children: [
// Author info
Container(
color: Colors.grey[50],
child: ListTile(
@@ -82,13 +89,7 @@ class _FeedItemState extends State<FeedItem> {
),
),
),
// Content
Markdown(
data: widget.item.content,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
),
// Attachments view
buildContent(),
hasAttachments()
? Container(
decoration: const BoxDecoration(
@@ -97,7 +98,7 @@ class _FeedItemState extends State<FeedItem> {
),
child: FlutterCarousel(
options: CarouselOptions(
height: 240.0,
height: 360.0,
viewportFraction: 1.0,
showIndicator: true,
slideIndicator: const CircularSlideIndicator(),
@@ -128,12 +129,12 @@ class _FeedItemState extends State<FeedItem> {
),
)
: Container(),
// Actions
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: const BoxDecoration(
border:
Border(top: BorderSide(width: 0.3, color: Color(0xffdedede))),
border: Border(
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
),
),
child: Row(
children: [
@@ -154,4 +155,23 @@ class _FeedItemState extends State<FeedItem> {
],
);
}
@override
void initState() {
reactionCount = widget.item.reactionCount;
reactionList = widget.item.reactionList ?? <String, dynamic>{};
super.initState();
}
@override
Widget build(BuildContext context) {
return (widget.brief ?? false)
? GestureDetector(
child: buildItem(context),
onTap: () => router.push(
"/post/${widget.item.modelType}/${widget.item.alias}",
),
)
: buildItem(context);
}
}

View File

@@ -93,7 +93,7 @@ class _CommentListState extends State<CommentList> {
label: const Text("LEAVE COMMENT"),
onPressed: () {
router
.push("/post/comments", extra: widget.parent)
.push("/post/new/comments", extra: widget.parent)
.then((value) {
if (value == true) paginationController.refresh();
});
@@ -113,6 +113,7 @@ class _CommentListState extends State<CommentList> {
builderDelegate: PagedChildBuilderDelegate<Feed>(
itemBuilder: (context, item, index) => FeedItem(
item: item,
brief: true,
),
),
),

View File

@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:solaragent/models/feed.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
class ArticleContent extends StatelessWidget {
final Feed item;
final bool brief;
const ArticleContent({super.key, required this.item, required this.brief});
@override
Widget build(BuildContext context) {
return brief
? ListTile(
title: Text(item.title),
subtitle: Text(item.description),
)
: Column(
children: [
ListTile(
title: Text(item.title),
subtitle: Text(item.description),
),
const Divider(color: Color(0xffefefef)),
Markdown(
selectable: !brief,
data: item.content,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
extensionSet: md.ExtensionSet(
md.ExtensionSet.gitHubFlavored.blockSyntaxes,
md.ExtensionSet.gitHubFlavored.inlineSyntaxes,
),
onTapLink: (text, href, title) async {
if (href == null) return;
await launchUrlString(
href,
mode: LaunchMode.externalApplication,
);
}),
],
);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:solaragent/models/feed.dart';
class MomentContent extends StatelessWidget {
final Feed item;
final bool brief;
const MomentContent({super.key, required this.brief, required this.item});
@override
Widget build(BuildContext context) {
return Markdown(
selectable: !brief,
data: item.content,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
);
}
}

View File

@@ -74,8 +74,18 @@ class _ReactionListState extends State<ReactionList> {
);
if (res.statusCode == 201) {
widget.onReact(symbol, 1);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Your reaction has been added onto this post."),
),
);
} else if (res.statusCode == 204) {
widget.onReact(symbol, -1);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Your reaction has been removed from this post."),
),
);
} else {
var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar(
@@ -105,10 +115,7 @@ class _ReactionListState extends State<ReactionList> {
),
child: Text(
'Reactions',
style: Theme
.of(context)
.textTheme
.headlineSmall,
style: Theme.of(context).textTheme.headlineSmall,
),
),
FutureBuilder(
@@ -119,7 +126,7 @@ class _ReactionListState extends State<ReactionList> {
icon: const Icon(Icons.add_reaction),
label: const Text("REACT"),
onPressed:
isSubmitting ? null : () => viewReactMenu(context),
isSubmitting ? null : () => viewReactMenu(context),
);
} else {
return Container();
@@ -141,17 +148,16 @@ class _ReactionListState extends State<ReactionList> {
onTap: isSubmitting
? null
: () {
doReact(
element.key,
ReactionList.emojis[element.key]!['attitude'],
);
},
doReact(
element.key,
ReactionList.emojis[element.key]!['attitude'],
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: ListTile(
title: Text(
"${ReactionList.emojis[element.key]!['icon']} x${element
.value.toString()}",
"${ReactionList.emojis[element.key]!['icon']} x${element.value.toString()}",
),
subtitle: Text(
":${element.key}:",

View File

@@ -90,7 +90,7 @@ packages:
source: hosted
version: "0.3.3+8"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
@@ -129,6 +129,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: d1d0ac3966b36dc3e66eeefb40280c17feb87fa2099c6e22e6a1fc959327bd03
url: "https://pub.dev"
source: hosted
version: "8.0.0+1"
file_selector_linux:
dependency: transitive
description:
@@ -441,7 +449,7 @@ packages:
source: hosted
version: "1.2.0"
markdown:
dependency: transitive
dependency: "direct main"
description:
name: markdown
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051

View File

@@ -48,6 +48,9 @@ dependencies:
flutter_carousel_widget: ^2.2.0
image_picker: ^1.0.7
sentry_flutter: ^7.18.0
crypto: ^3.0.3
file_picker: ^8.0.0+1
markdown: ^7.2.2
dev_dependencies:
flutter_test: