From 8e73815a1dc0653c8ab79adc2cd2d5fdfe46a2e1 Mon Sep 17 00:00:00 2001 From: Shivansh-Tripathi Date: Sun, 29 Dec 2024 01:57:50 +0530 Subject: [PATCH 1/5] learning page --- lib/views/learning/learning_view.dart | 244 +++++++++++++++++--------- pubspec.lock | 44 ++--- 2 files changed, 187 insertions(+), 101 deletions(-) diff --git a/lib/views/learning/learning_view.dart b/lib/views/learning/learning_view.dart index f75c259..6a67444 100644 --- a/lib/views/learning/learning_view.dart +++ b/lib/views/learning/learning_view.dart @@ -1,6 +1,5 @@ -import '../../res/colors.dart'; -import '../../widgets/learning_card.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; class LearningPage extends StatefulWidget { const LearningPage({super.key}); @@ -10,102 +9,189 @@ class LearningPage extends StatefulWidget { } class _LearningPageState extends State { + + double height=Get.height; + double width=Get.width; + @override Widget build(BuildContext context) { - double screenWidth = MediaQuery.of(context).size.width; - double textScaleFactor = screenWidth * 0.0025; - return Scaffold( + appBar: AppBar( - toolbarHeight: 40, title: Row( children: [ - SizedBox(width: screenWidth * 0.15), - SizedBox( - height: 23, - width: 23, - child: Image.asset('assets/images/Booo.png'), + IconButton( + icon: Icon(Icons.menu,color: Colors.white,), + onPressed: () { + // Handle menu action + }, ), - SizedBox(width: screenWidth * 0.03), - Text('Learning', style: TextStyle(fontSize: 24 * textScaleFactor)), + SizedBox(width: 8), // Space between icon and text + Text('Hackslash',style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + ),), ], ), - leading: IconButton(onPressed: () {}, icon: Icon(Icons.menu)), - ), - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(screenWidth * 0.8, 10, 10, 5), - child: Image.asset('assets/images/imag.png'), - ), - Padding( - padding: EdgeInsets.fromLTRB(screenWidth * 0.1, 20, 0, 0), - child: Text( - 'Hello Rahul,', - style: TextStyle( - fontSize: 22 * textScaleFactor, - fontWeight: FontWeight.w500, - ), - ), - ), - const SizedBox(height: 10), - Padding( - padding: EdgeInsets.fromLTRB(screenWidth * 0.07, 0, 0, 0), - child: Text( - 'Continue\nLearning!', - style: TextStyle( - fontSize: 43 * textScaleFactor, - height: 1, - ), - ), - ), - const SizedBox(height: 28), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + backgroundColor: Color(0xFF223345), // Set your desired background color + actions: [ + Switch( + value: false, // This can be controlled with a state variable + onChanged: (value) { + // Handle toggle action + },),],), + body: + + Container( + color: Color(0xFF223345), + child: + Container( + decoration: BoxDecoration( + color:Colors.white, + borderRadius: BorderRadius.only(topLeft: Radius.circular(30),topRight: Radius.circular(30)), + + + ), + height: height*1, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ - Padding( - padding: EdgeInsets.only(left: screenWidth * 0.08), - child: Text( - 'Your Lessons', - style: TextStyle( - fontSize: 22 * textScaleFactor, - fontWeight: FontWeight.w600, + + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'Hello Tanay', + style: TextStyle( + fontSize: 50, + fontWeight: FontWeight.bold, + ), + ), + Text( + "Let's Continue", + style: TextStyle( + fontSize: 25, + color: Colors.grey, + ), + ), + Text( + " Learning!", + style: TextStyle( + fontSize: 25, + color: Colors.grey, + ), + ), + ], ), + const SizedBox(width: 16), + const CircleAvatar( + radius: 50, + //backgroundImage: , // Replace with your image + ), + ], + ), + const SizedBox(height: 50), + const Text( + 'Your Lessons', + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, ), ), - SizedBox(width: screenWidth * 0.25), - InkWell( - child: Image.asset('assets/images/Four.png'), - onTap: () {}, + const SizedBox(height: 5), + Divider( + thickness: 2, + color: Colors.blueGrey, ), - InkWell( - child: Image.asset('assets/images/red.png'), - onTap: () {}, + const SizedBox(height: 16), + TextField( + decoration: InputDecoration( + hintText: 'AI Search ?', + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), ), + const SizedBox(height: 16), + Expanded( + child: GridView.count( + crossAxisCount: 2, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + children: [ + _buildGridItem('Road Map', Icons.map, Colors.green), + _buildGridItem('Playlist', Icons.playlist_play, Colors.blue), + _buildGridItem('Projects', Icons.lightbulb, Colors.orange), + _buildGridItem('PDF Notes', Icons.picture_as_pdf, Colors.red), + ], + + + ), + ), + ], + ), - Container( - height: 2.5, - color: ColorPalette.brightEmeraldGreen, - ), - Row( - children: [ - LearningCard(imagePath: "assets/images/420.png", cardName: "Personalized\nLearning", onTap: (){}), - LearningCard(imagePath: "assets/images/jiji.png", cardName: "Playlist", onTap: (){}), - ], - ), - Row( - children: [ - LearningCard(imagePath: "assets/images/popo.png", cardName: "Projects", onTap: (){}), - LearningCard(imagePath: "assets/images/imag00.png", cardName: "Pdf Notes", onTap: (){}), - ], - ), - ], + ), ), ), + bottomNavigationBar: BottomNavigationBar( + selectedItemColor: Colors.teal, + unselectedItemColor: Colors.grey, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home), + label: 'Home', + ), + BottomNavigationBarItem( + icon: Icon(Icons.chat), + label: 'Chat', + ), + BottomNavigationBarItem( + icon: Icon(Icons.book), + label: 'Learning', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person), + label: 'Profile', + ), + ], + ), ); } } +Widget _buildGridItem(String title, IconData icon, Color color) { + return Container( + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + size: 48, + color: color, + ), + const SizedBox(height: 8), + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + + ], + ), + ); +} + + diff --git a/pubspec.lock b/pubspec.lock index 851d354..fade2e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,15 +21,15 @@ packages: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" args: dependency: transitive description: @@ -194,10 +194,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -697,18 +697,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -753,10 +753,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -937,7 +937,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -974,10 +974,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -998,10 +998,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -1014,10 +1014,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" timing: dependency: transitive description: @@ -1134,10 +1134,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: From 7853dce0eabce5b425f4504a84784d937b4ec6bd Mon Sep 17 00:00:00 2001 From: Shivansh-Tripathi Date: Mon, 30 Dec 2024 00:28:05 +0530 Subject: [PATCH 2/5] leaining page last work without images --- lib/views/learning/learning_view.dart | 136 ++++++++++++++++++++------ 1 file changed, 105 insertions(+), 31 deletions(-) diff --git a/lib/views/learning/learning_view.dart b/lib/views/learning/learning_view.dart index 6a67444..100c3fe 100644 --- a/lib/views/learning/learning_view.dart +++ b/lib/views/learning/learning_view.dart @@ -125,10 +125,111 @@ class _LearningPageState extends State { crossAxisSpacing: 16, mainAxisSpacing: 16, children: [ - _buildGridItem('Road Map', Icons.map, Colors.green), - _buildGridItem('Playlist', Icons.playlist_play, Colors.blue), - _buildGridItem('Projects', Icons.lightbulb, Colors.orange), - _buildGridItem('PDF Notes', Icons.picture_as_pdf, Colors.red), + GestureDetector( + onTap: (){}, + child: Container( + + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image(image: AssetImage('')), + const SizedBox(height: 10), + Text( + 'ROAD MAP', + + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + + ], + ), + ), + ), + GestureDetector( + onTap: (){}, + child: Container( + + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image(image: AssetImage('')), + const SizedBox(height: 8), + Text( + 'PLAYLIST', + + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + + ], + ), + ), + ), + GestureDetector( + onTap: (){}, + child: Container( + + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image(image: AssetImage('')), + const SizedBox(height: 8), + Text( + 'PROJECTS', + + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + + ], + ), + ), + ), + GestureDetector( + onTap: (){}, + child: Container( + + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image(image: AssetImage('')), + const SizedBox(height: 8), + Text( + 'ROAD MAP', + + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + + ], + ), + ), + ), + ], @@ -166,32 +267,5 @@ class _LearningPageState extends State { ); } } -Widget _buildGridItem(String title, IconData icon, Color color) { - return Container( - decoration: BoxDecoration( - color: Colors.grey.shade200, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - icon, - size: 48, - color: color, - ), - const SizedBox(height: 8), - Text( - title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - - ], - ), - ); -} From b082229f4dd9124b089e926510a1d2c91a46aec9 Mon Sep 17 00:00:00 2001 From: Shivansh-Tripathi Date: Thu, 23 Jan 2025 22:15:12 +0530 Subject: [PATCH 3/5] learning page -- raised pr --- lib/views/learning/learning_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/learning/learning_view.dart b/lib/views/learning/learning_view.dart index 100c3fe..231dcb0 100644 --- a/lib/views/learning/learning_view.dart +++ b/lib/views/learning/learning_view.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class LearningPage extends StatefulWidget { +class LearningPage extends StatefulWidget +{ const LearningPage({super.key}); @override From 775e57342ed6a15b9f21dd9c199f329fc7c0dfa8 Mon Sep 17 00:00:00 2001 From: Shivansh-Tripathi Date: Thu, 23 Jan 2025 22:17:39 +0530 Subject: [PATCH 4/5] learning page -- raised pr --- lib/views/learning/learning_view.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/views/learning/learning_view.dart b/lib/views/learning/learning_view.dart index 231dcb0..100c3fe 100644 --- a/lib/views/learning/learning_view.dart +++ b/lib/views/learning/learning_view.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class LearningPage extends StatefulWidget -{ +class LearningPage extends StatefulWidget { const LearningPage({super.key}); @override From d43a2c1e7726e75df3de46b9d75d886cf7662834 Mon Sep 17 00:00:00 2001 From: Shivansh-Tripathi Date: Sun, 16 Mar 2025 00:24:25 +0530 Subject: [PATCH 5/5] Add BlogModel class and update post page with image handling --- lib/controllers/blog_controller.dart | 154 ++++++---- lib/models/blog_model.dart | 54 ++++ lib/views/learning/blogs/blogpage.dart | 286 +++++++++--------- .../learning/blogs/detailed_post_view.dart | 191 ++++++++++++ .../blogs/{postPage.dart => post_page.dart} | 70 +++-- pubspec.lock | 106 ++++--- pubspec.yaml | 2 + 7 files changed, 606 insertions(+), 257 deletions(-) create mode 100644 lib/models/blog_model.dart create mode 100644 lib/views/learning/blogs/detailed_post_view.dart rename lib/views/learning/blogs/{postPage.dart => post_page.dart} (61%) diff --git a/lib/controllers/blog_controller.dart b/lib/controllers/blog_controller.dart index 554ec2e..e292152 100644 --- a/lib/controllers/blog_controller.dart +++ b/lib/controllers/blog_controller.dart @@ -1,75 +1,88 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:cloudinary_api/uploader/cloudinary_uploader.dart'; -import 'package:cloudinary_url_gen/cloudinary.dart'; +import 'package:cloudinary_public/cloudinary_public.dart'; +import 'package:communityapp/models/blog_model.dart'; import 'package:communityapp/views/learning/blogs/blogpage.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:logger/logger.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:get/get.dart'; class BlogController extends GetxController { final Logger log = Logger(); // Logger instance - RxBool _isLoading = false.obs; - bool get isLoading => _isLoading.value; - - RxBool _isUploadingImage = false.obs; - bool get isUploadingImage => _isUploadingImage.value; + RxBool isLoading = false.obs; final titleController = TextEditingController().obs; final contentController = TextEditingController().obs; - Rx localImage = Rx(null); - RxString imageUrl = ''.obs; - void changeLoad() { - _isLoading.value = !_isLoading.value; - } + var localImage = Rx(null); // For mobile + var webImageBytes = Rx(null); // For web + var imageUrl = RxString(""); // Stores uploaded image URL + var isUploadingImage = false.obs; // Uploading status - // PICK IMAGE AND UPLOAD TO CLOUDINARY - Future getImage() async { - log.i("🔑 Loaded Cloudinary API Key: ${dotenv.env['CloudinaryApi']}"); + final CloudinaryPublic cloudinary = + CloudinaryPublic('daj7vxuyb', 'nehi1ybz', cache: false); + Future getImage() async { final ImagePicker picker = ImagePicker(); - final XFile? image = await picker.pickImage( - source: ImageSource.gallery, imageQuality: 60, maxWidth: 150, maxHeight: 600); + XFile? image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { - final imageFile = File(image.path); - localImage.value = imageFile; - _isUploadingImage.value = true; // Mark image as uploading - - var cloudinary = Cloudinary.fromStringUrl( - 'cloudinary://239118281366527:${dotenv.env['CloudinaryApi']}@daj7vxuyb'); - - try { - log.i("📤 Uploading image..."); - final response = await cloudinary.uploader().upload(imageFile); - - if (response != null && response.data != null) { - if (response.data!.secureUrl != null) { - imageUrl.value = response.data!.secureUrl!; - log.i("✅ Image Uploaded Successfully: ${imageUrl.value}"); - } else { - log.e("❌ Upload failed, secureUrl is null"); - Get.snackbar("Upload Error", "Failed to get image URL from Cloudinary."); - } - } else { - log.e("❌ Upload failed, response data is null"); - Get.snackbar("Upload Error", "Cloudinary response is empty."); - } - } catch (e) { - log.e("🚨 Cloudinary Upload Error: $e"); - Get.snackbar("Upload Error", "Something went wrong while uploading."); - } finally { - _isUploadingImage.value = false; // Upload complete + if (kIsWeb) { + webImageBytes.value = await image.readAsBytes(); + } else { + localImage.value = File(image.path); } + update(); // Notify UI + await uploadImage(); } else { log.w("❌ No image selected"); } } + Future uploadImage() async { + isUploadingImage.value = true; + update(); + + try { + log.i("📤 Uploading image..."); + + CloudinaryResponse response; + + if (kIsWeb && webImageBytes.value != null) { + response = await cloudinary.uploadFile( + CloudinaryFile.fromByteData( + ByteData.view(webImageBytes.value!.buffer), + resourceType: CloudinaryResourceType.Image, + identifier: + 'web_upload_${DateTime.now().millisecondsSinceEpoch}'), + ); + } else if (!kIsWeb && localImage.value != null) { + response = await cloudinary.uploadFile( + CloudinaryFile.fromFile(localImage.value!.path, + resourceType: CloudinaryResourceType.Image), + ); + } else { + log.e("❌ No image found for upload."); + Get.snackbar("Upload Error", "No image selected."); + return; + } + + imageUrl.value = response.secureUrl; + log.i("✅ Image Uploaded Successfully: ${imageUrl.value}"); + } catch (e) { + log.e("🚨 Cloudinary Upload Error: $e"); + Get.snackbar("Upload Error", "Something went wrong while uploading."); + } finally { + isUploadingImage.value = false; + update(); + } + } + // SUBMIT POST TO FIREBASE Future submitPost() async { log.i("🚀 Submitting Post..."); @@ -84,7 +97,7 @@ class BlogController extends GetxController { log.w("❌ Submission Failed: Some fields are empty."); return; } - if ( content.isEmpty ) { + if (content.isEmpty) { Get.snackbar("Error", " Content is required."); log.w("❌ Submission Failed: Some fields are empty."); return; @@ -95,25 +108,33 @@ class BlogController extends GetxController { return; } - - // Check if image is still uploading - if (_isUploadingImage.value) { + if (isUploadingImage.value) { Get.snackbar("Uploading", "Please wait for image to finish uploading."); log.w("⏳ Waiting for image upload to complete..."); return; } - changeLoad(); + isUploadingImage.value = !isUploadingImage.value; try { log.i("📡 Uploading Data to Firebase..."); - await FirebaseFirestore.instance.collection('blogPosts').add({ - 'title': title, - 'content': content, - 'image': uploadedImageUrl, - 'timestamp': FieldValue.serverTimestamp(), - }); + + BlogModel blogModel = BlogModel( + title: title, + content: content, + author: FirebaseAuth.instance.currentUser?.displayName ?? "Anonymous", + imageUrl: uploadedImageUrl, + createdAt: DateTime.now().toIso8601String()); + + final blogRef = FirebaseFirestore.instance + .collection('blogPosts') + .withConverter( + fromFirestore: (snapshot, _) => + BlogModel.fromJson(snapshot.data() as Map), + toFirestore: (blog, _) => blog.toJson(), + ); + await blogRef.doc().set(blogModel); log.i("✅ Post Uploaded Successfully!"); @@ -122,6 +143,7 @@ class BlogController extends GetxController { contentController.value.clear(); imageUrl.value = ''; localImage.value = null; + webImageBytes.value = null; // 🔄 Navigate back after submission Get.snackbar("Success", "Post uploaded successfully!"); @@ -131,6 +153,20 @@ class BlogController extends GetxController { Get.snackbar("Error", "Failed to upload post."); } - changeLoad(); + isUploadingImage.value = !isUploadingImage.value; + } + + Stream> getAllBlogPostsStream() { + final blogRef = FirebaseFirestore.instance + .collection('blogPosts') + .orderBy('createdAt', descending: true) + .withConverter( + fromFirestore: (snapshot, _) => + BlogModel.fromJson(snapshot.data() as Map), + toFirestore: (blog, _) => blog.toJson(), + ); + + return blogRef.snapshots().map((querySnapshot) => + querySnapshot.docs.map((doc) => doc.data()).toList()); } } diff --git a/lib/models/blog_model.dart b/lib/models/blog_model.dart new file mode 100644 index 0000000..8b47417 --- /dev/null +++ b/lib/models/blog_model.dart @@ -0,0 +1,54 @@ +class BlogModel { + BlogModel({ + required this.title, + required this.content, + required this.author, + required this.imageUrl, + required this.createdAt, + }); + + final String? title; + final String? content; + final String? author; + final String? imageUrl; + final String? createdAt; + + BlogModel copyWith({ + String? title, + String? content, + String? author, + String? imageUrl, + String? createdAt, + }) { + return BlogModel( + title: title ?? this.title, + content: content ?? this.content, + author: author ?? this.author, + imageUrl: imageUrl ?? this.imageUrl, + createdAt: createdAt ?? this.createdAt, + ); + } + + factory BlogModel.fromJson(Map json) { + return BlogModel( + title: json["title"], + content: json["content"], + author: json["author"], + imageUrl: json["image"], + createdAt: json["createdAt"], + ); + } + + Map toJson() => { + "title": title, + "content": content, + "author": author, + "image": imageUrl, + "createdAt": createdAt, + }; + + @override + String toString() { + return "$title, $content, $author, $imageUrl, $createdAt, "; + } +} diff --git a/lib/views/learning/blogs/blogpage.dart b/lib/views/learning/blogs/blogpage.dart index f9760e9..cf1062f 100644 --- a/lib/views/learning/blogs/blogpage.dart +++ b/lib/views/learning/blogs/blogpage.dart @@ -1,14 +1,18 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:communityapp/controllers/blog_controller.dart'; +import 'package:communityapp/models/blog_model.dart'; import 'package:communityapp/views/learning/blogs/infoPage.dart'; -import 'package:communityapp/views/learning/blogs/postPage.dart'; +import 'package:communityapp/views/learning/blogs/post_page.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; +import 'package:http/http.dart'; import 'package:intl/intl.dart'; +import 'detailed_post_view.dart'; + class Blog_Page extends StatefulWidget { const Blog_Page({super.key}); @@ -21,49 +25,41 @@ class _Blog_PageState extends State { double height = Get.height; String truncateWithEllipsis(String text, int cutoff) { - return (text.length > cutoff) ? '${text.substring(0, cutoff)}...' : text; -} + return (text.length > cutoff) ? '${text.substring(0, cutoff)}...' : text; + } + // initalising controller BlogController blogController = Get.put(BlogController()); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF110E2B), - appBar: appBar(), - body: Stack( - children: [ - Positioned( - right: 0, - top: 0, - child: SvgPicture.asset('assets/svgs/box.svg'), - ), - Positioned( - right: 0, - top: height * 0.13, - child: SvgPicture.asset('assets/svgs/box.svg'), - ), - Positioned( - top: height * 0.4, - left: 0, - child: SvgPicture.asset('assets/svgs/rectangle.svg'), - ), - - - - - - Column( - + body: Stack(children: [ + Positioned( + right: 0, + top: 0, + child: SvgPicture.asset('assets/svgs/box.svg'), + ), + Positioned( + right: 0, + top: height * 0.13, + child: SvgPicture.asset('assets/svgs/box.svg'), + ), + Positioned( + top: height * 0.4, + left: 0, + child: SvgPicture.asset('assets/svgs/rectangle.svg'), + ), + Column( children: [ Expanded(child: buildBlock()), ], ), - ] - ), + ]), floatingActionButton: FloatingActionButton( onPressed: () { - Get.to(post_Page()); + Get.to(PostPage()); }, tooltip: 'Add Blog', splashColor: Colors.blue, @@ -80,16 +76,13 @@ class _Blog_PageState extends State { PreferredSizeWidget appBar() { return PreferredSize( - preferredSize: Size.fromHeight(height * 0.2), child: Container( decoration: BoxDecoration( - color: Color(0xFF110E2B), + color: Color(0xFF110E2B), ), - padding: EdgeInsets.only( top: height * 0.05, left: width * 0.03, right: width * 0.02), - width: width * 0.9, height: height * 0.125, child: Row( @@ -128,95 +121,106 @@ class _Blog_PageState extends State { } Widget buildBlock() { - return - StreamBuilder( - stream: FirebaseFirestore.instance - .collection('blogPosts') - .orderBy('timestamp', descending: true) - .snapshots(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center( - child: CircularProgressIndicator(), - ); - } - if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { - return const Center(child: Text("No posts yet!")); - } - return ListView.builder( - padding: EdgeInsets.all(height * 0.01), - itemCount: snapshot.data!.docs.length, - itemBuilder: (context, index) { - final doc = snapshot.data!.docs[index]; - // String content = doc['content'] ?? ''; - String title=doc['title'] ?? ''; - + return StreamBuilder>( + stream: blogController.getAllBlogPostsStream(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } - var timestamp = doc['timestamp']; - String formattedTimestamp = ''; - - if (timestamp != null) { - // Format the date and time up to seconds - DateFormat dateFormat = DateFormat('dd/MM/yyyy'); - formattedTimestamp = dateFormat.format(timestamp.toDate()); - formattedTimestamp='Posted: '+formattedTimestamp; - } - - return Card( - margin: EdgeInsets.symmetric(vertical: height*0.024,horizontal: width*0.02), - child: Padding( - padding: const EdgeInsets.all(14), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center(child: Text("No posts yet!")); + } - // Image - - Expanded( - child: SizedBox( - - width: width*0.3, - height: height*0.2, - // used for providing shape to images - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(15)), - child: Image.network(doc['image'] ?? FlutterLogo( - size: height*0.2, - ), + final blogPosts = snapshot.data!; + + return ListView.builder( + padding: const EdgeInsets.all(10), + itemCount: blogPosts.length, + itemBuilder: (context, index) { + final blog = blogPosts[index]; + + // Ensure non-null values + final String title = blog.title ?? 'Untitled'; + final String author = blog.author ?? 'Anonymous'; + final String imageUrl = blog.imageUrl ?? ''; + final String content = blog.content ?? ''; + final String createdAt = blog.createdAt ?? ''; + + // Format timestamp + String formattedTimestamp = 'Unknown Date'; + if (createdAt.isNotEmpty) { + try { + DateTime dateTime = DateTime.parse(createdAt); + formattedTimestamp = + 'Posted: ${DateFormat('dd/MM/yyyy').format(dateTime)}'; + } catch (e) { + formattedTimestamp = 'Invalid Date'; + } + } + + return GestureDetector( + onTap: () { + Get.to(() => DetailedPostView(blogModel: blog)); + }, + child: Card( + margin: EdgeInsets.symmetric( + vertical: height * 0.024, horizontal: width * 0.02), + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Image + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: imageUrl.isNotEmpty + ? Image.network( + imageUrl, + width: width * 0.3, + height: height * 0.2, fit: BoxFit.cover, - ))), - ), - SizedBox(width: width*0.04,), - // Markdown-formatted Content - Expanded( - child: Container( - height: height*0.2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - RichText( - maxLines: 4, // Prevents overflow + errorBuilder: (context, error, stackTrace) => + FlutterLogo( + size: height * 0.2), // Error fallback + ) + : FlutterLogo( + size: height * 0.2), // Default placeholder + ), + SizedBox(width: width * 0.04), + + // Content + Expanded( + child: SizedBox( + height: height * 0.2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Title (Markdown formatted) + RichText( + maxLines: 4, overflow: TextOverflow.ellipsis, text: TextSpan( - children: parseMarkdownText(title), - style: TextStyle( + children: parseMarkdownText(title), + style: const TextStyle( fontSize: 22, color: Colors.black, fontWeight: FontWeight.w800, - ), - ), + ), + ), ), - - Text(FirebaseAuth.instance.currentUser!.displayName ?? 'Anonymous', + + // Author Name + Text( + author, style: const TextStyle( - fontSize: 15, - color: Colors.black, - fontWeight: FontWeight.w500), + fontSize: 15, + color: Colors.black, + fontWeight: FontWeight.w500), ), - - - // timestamp view + + // Timestamp Align( alignment: Alignment.bottomRight, child: Text( @@ -227,40 +231,42 @@ class _Blog_PageState extends State { fontWeight: FontWeight.w500), ), ), - ], - ), - ), - ) - ], - ), - )); - }, + ], + ), + ), + ) + ], + ), + ), + ), ); - }); - + }, + ); + }, + ); } // code for converting markdown to text List parseMarkdownText(String text) { - List spans = []; - RegExp exp = RegExp(r"\*\*(.*?)\*\*"); // Match bold (**text**) - int lastIndex = 0; + List spans = []; + RegExp exp = RegExp(r"\*\*(.*?)\*\*"); // Match bold (**text**) + int lastIndex = 0; - for (Match match in exp.allMatches(text)) { - if (match.start > lastIndex) { - spans.add(TextSpan(text: text.substring(lastIndex, match.start))); + for (Match match in exp.allMatches(text)) { + if (match.start > lastIndex) { + spans.add(TextSpan(text: text.substring(lastIndex, match.start))); + } + spans.add(TextSpan( + text: match.group(1), + style: TextStyle(fontWeight: FontWeight.bold), // Make bold + )); + lastIndex = match.end; + } + if (lastIndex < text.length) { + spans.add(TextSpan(text: text.substring(lastIndex))); } - spans.add(TextSpan( - text: match.group(1), - style: TextStyle(fontWeight: FontWeight.bold), // Make bold - )); - lastIndex = match.end; - } - if (lastIndex < text.length) { - spans.add(TextSpan(text: text.substring(lastIndex))); - } - return spans; -} + return spans; + } } diff --git a/lib/views/learning/blogs/detailed_post_view.dart b/lib/views/learning/blogs/detailed_post_view.dart new file mode 100644 index 0000000..51f64ce --- /dev/null +++ b/lib/views/learning/blogs/detailed_post_view.dart @@ -0,0 +1,191 @@ +import 'package:communityapp/models/blog_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; + +class DetailedPostView extends StatelessWidget { + DetailedPostView({super.key, required this.blogModel}); + + final BlogModel blogModel; + @override + Widget build(BuildContext context) { + double width = Get.width; + double height = Get.height; + + return Scaffold( + backgroundColor: Color(0xFF110E2B), + appBar: appBar(), + body: Stack( + children: [ + Positioned( + right: 0, + top: 0, + child: SvgPicture.asset('assets/svgs/box.svg'), + ), + Positioned( + right: 0, + top: height * 0.13, + child: SvgPicture.asset('assets/svgs/box.svg'), + ), + Positioned( + top: height * 0.4, + left: 0, + child: SvgPicture.asset('assets/svgs/rectangle.svg'), + ), + SingleChildScrollView( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title + Text( + blogModel.title ?? "Title", + style: TextStyle( + fontSize: 24, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), + + // Image (Handles null imageUrl) + if (blogModel.imageUrl != null && + blogModel.imageUrl!.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.network( + blogModel.imageUrl!, + width: double.infinity, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center(child: CircularProgressIndicator()); + }, + errorBuilder: (context, error, stackTrace) => Icon( + Icons.broken_image, + size: 80, + color: Colors.grey), + ), + ), + SizedBox(height: 16), + + // Description (Expands dynamically) + _descriptionUI() + ], + ), + ), + ], + ), + ); + } + + PreferredSizeWidget appBar() { + double width = Get.width; + double height = Get.height; + return PreferredSize( + preferredSize: Size.fromHeight(height * 0.2), + child: Container( + decoration: BoxDecoration( + color: Color(0xFF110E2B), + ), + padding: EdgeInsets.only( + top: height * 0.05, left: width * 0.03, right: width * 0.02), + width: width * 0.9, + height: height * 0.125, + child: Row( + children: [ + // menu option + SizedBox(width: width * 0.27), + Text( + 'HackSlash', + style: TextStyle( + fontSize: 35, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + SizedBox(width: width * 0.18), + ], + ), + )); + } + + Widget _descriptionUI() { + return MarkdownBody( + data: blogModel.content ?? "No description available.", + styleSheet: MarkdownStyleSheet( + // 🌟 General Paragraph Text + p: const TextStyle(fontSize: 18, color: Colors.white70), + + // 🏆 Headers - Adjusted for Dark Blue Background + h1: const TextStyle( + fontSize: 26, fontWeight: FontWeight.bold, color: Colors.white), + h2: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Color(0xFFB0E0E6)), // Light Blue + h3: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Color(0xFFFFD700)), // Gold/Yellow + h4: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Color(0xFF87CEFA)), // Sky Blue + h5: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF98FB98)), // Light Green + h6: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFFFFA07A)), // Light Salmon + + // ✅ Bold and Italic Styling + strong: + const TextStyle(fontWeight: FontWeight.bold, color: Colors.white), + em: const TextStyle(fontStyle: FontStyle.italic, color: Colors.white70), + + // 🔗 Hyperlink Styling + a: const TextStyle( + color: Color(0xFFFFA500), + decoration: TextDecoration.underline), // Orange Links + + // 📌 Lists + listBullet: const TextStyle( + fontSize: 18, color: Color(0xFF87CEFA)), // Light Sky Blue + + // 🗒️ Blockquote Styling + blockquote: TextStyle( + fontSize: 18, + fontStyle: FontStyle.italic, + color: Colors.grey[300], + decoration: TextDecoration.underline, + ), + + // 🖋️ Code Block Styling + code: const TextStyle( + fontSize: 16, + fontFamily: 'monospace', + color: Color(0xFFFFA07A), // Light Salmon + backgroundColor: Color(0xFF2E2E2E), // Dark Gray Background + ), + + // 📤 Horizontal Line (Divider) + horizontalRuleDecoration: BoxDecoration( + border: Border( + top: BorderSide(width: 1.5, color: Colors.white70), + ), + ), + + // 📎 Table Styling + tableHead: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: Colors.white, + ), + tableBody: const TextStyle(fontSize: 16, color: Colors.white70), + ), + ); + } +} diff --git a/lib/views/learning/blogs/postPage.dart b/lib/views/learning/blogs/post_page.dart similarity index 61% rename from lib/views/learning/blogs/postPage.dart rename to lib/views/learning/blogs/post_page.dart index 798e40b..be6bffc 100644 --- a/lib/views/learning/blogs/postPage.dart +++ b/lib/views/learning/blogs/post_page.dart @@ -1,15 +1,16 @@ import 'package:communityapp/controllers/blog_controller.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class post_Page extends StatefulWidget { - const post_Page({super.key}); +class PostPage extends StatefulWidget { + const PostPage({super.key}); @override - State createState() => _PostPageState(); + State createState() => _PostPageState(); } -class _PostPageState extends State { +class _PostPageState extends State { BlogController blogController = Get.put(BlogController()); double height = Get.height; @@ -22,7 +23,7 @@ class _PostPageState extends State { body: Container( decoration: BoxDecoration( gradient: LinearGradient( - colors: [Color(0xFF2E3A59),Color(0xFF110E2B)], + colors: [Color(0xFF2E3A59), Color(0xFF110E2B)], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), @@ -46,9 +47,9 @@ class _PostPageState extends State { return AppBar( centerTitle: true, backgroundColor: Color.fromARGB(255, 56, 50, 112), - title: Text("New Post", style: TextStyle(color: Colors.grey, fontSize: 22, fontWeight: FontWeight.w600)), - - + title: Text("New Post", + style: TextStyle( + color: Colors.grey, fontSize: 22, fontWeight: FontWeight.w600)), ); } @@ -57,9 +58,12 @@ class _PostPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _inputField("Title", "Enter Title...", blogController.titleController.value), + _inputField( + "Title", "Enter Title...", blogController.titleController.value), SizedBox(height: height * 0.02), - _inputField("Content", "Write content in Markdown...", blogController.contentController.value, maxLines: 5), + _inputField("Content", "Write content in Markdown...", + blogController.contentController.value, + maxLines: 5), SizedBox(height: height * 0.03), _imagePreview(), ], @@ -67,11 +71,17 @@ class _PostPageState extends State { ); } - Widget _inputField(String label, String hint, TextEditingController controller, {int maxLines = 1}) { + Widget _inputField( + String label, String hint, TextEditingController controller, + {int maxLines = 1}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), + Text(label, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold)), SizedBox(height: 6), TextField( controller: controller, @@ -79,7 +89,6 @@ class _PostPageState extends State { cursorColor: Colors.white, style: TextStyle(color: Colors.white), decoration: InputDecoration( - hintText: hint, hintStyle: TextStyle(color: Colors.white54), filled: true, @@ -96,7 +105,17 @@ class _PostPageState extends State { Widget _imagePreview() { return Obx(() { - if (blogController.localImage.value != null) { + if (kIsWeb && blogController.webImageBytes.value != null) { + return ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.memory( + blogController.webImageBytes.value!, + height: height * 0.25, + width: double.infinity, + fit: BoxFit.cover, + ), + ); + } else if (!kIsWeb && blogController.localImage.value != null) { return ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.file( @@ -107,7 +126,7 @@ class _PostPageState extends State { ), ); } else { - return SizedBox(); + return const SizedBox(); } }); } @@ -117,25 +136,34 @@ class _PostPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ FloatingActionButton( - onPressed: blogController.getImage, + onPressed: () async { + await blogController.getImage(); + }, backgroundColor: Colors.blueAccent, child: Icon(Icons.image, color: Colors.white), ), SizedBox( width: width * 0.7, child: Obx(() => ElevatedButton( - onPressed: blogController.isLoading ? null : blogController.submitPost, + onPressed: blogController.isLoading.value + ? null + : blogController.submitPost, style: ElevatedButton.styleFrom( backgroundColor: Colors.blueAccent, padding: EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), ), - child: blogController.isLoading + child: blogController.isLoading.value ? CircularProgressIndicator(color: Colors.white) - : Text("Submit", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Colors.white)), + : Text("Submit", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white)), )), ), ], ); } -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index 5b4c150..34baf9b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,18 +50,18 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: @@ -138,10 +138,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -154,10 +154,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" cloud_firestore: dependency: "direct main" description: @@ -190,6 +190,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + cloudinary_public: + dependency: "direct main" + description: + name: cloudinary_public + sha256: "30c2aac5a31b468e5e955d2bbbac3eebcd1c66c9c7c2ffd75828606503bdda68" + url: "https://pub.dev" + source: hosted + version: "0.23.1" cloudinary_url_gen: dependency: "direct main" description: @@ -210,10 +218,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -270,6 +278,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.1" + dio: + dependency: transitive + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" dots_indicator: dependency: "direct main" description: @@ -290,10 +314,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -773,6 +797,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.10.1" + image_picker_web: + dependency: "direct main" + description: + name: image_picker_web + sha256: b5cf4faf66714f17b3e86b37a39d19743603163a08b968b28cadfc5df1dc2b75 + url: "https://pub.dev" + source: hosted + version: "4.0.0" image_picker_windows: dependency: transitive description: @@ -825,18 +857,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -897,10 +929,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -913,10 +945,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: "direct main" description: @@ -937,10 +969,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -1150,10 +1182,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -1166,18 +1198,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -1190,26 +1222,26 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" timeline_tile: dependency: "direct main" description: @@ -1358,10 +1390,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" watcher: dependency: transitive description: @@ -1435,5 +1467,5 @@ packages: source: hosted version: "9.1.1" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index c44a94a..e876c83 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,8 @@ dependencies: flutter_markdown: ^0.7.6 youtube_player_flutter: ^9.1.1 flutter_svg: ^2.0.17 + image_picker_web: ^4.0.0 + cloudinary_public: ^0.23.1 dev_dependencies: