Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 95 additions & 59 deletions lib/controllers/blog_controller.dart
Original file line number Diff line number Diff line change
@@ -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<File?> localImage = Rx<File?>(null);
RxString imageUrl = ''.obs;

void changeLoad() {
_isLoading.value = !_isLoading.value;
}
var localImage = Rx<File?>(null); // For mobile
var webImageBytes = Rx<Uint8List?>(null); // For web
var imageUrl = RxString(""); // Stores uploaded image URL
var isUploadingImage = false.obs; // Uploading status

// PICK IMAGE AND UPLOAD TO CLOUDINARY
Future<void> getImage() async {
log.i("🔑 Loaded Cloudinary API Key: ${dotenv.env['CloudinaryApi']}");
final CloudinaryPublic cloudinary =
CloudinaryPublic('daj7vxuyb', 'nehi1ybz', cache: false);

Future<void> 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<void> 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<void> submitPost() async {
log.i("🚀 Submitting Post...");
Expand All @@ -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;
Expand All @@ -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<BlogModel>(
fromFirestore: (snapshot, _) =>
BlogModel.fromJson(snapshot.data() as Map<String, dynamic>),
toFirestore: (blog, _) => blog.toJson(),
);
await blogRef.doc().set(blogModel);

log.i("✅ Post Uploaded Successfully!");

Expand All @@ -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!");
Expand All @@ -131,6 +153,20 @@ class BlogController extends GetxController {
Get.snackbar("Error", "Failed to upload post.");
}

changeLoad();
isUploadingImage.value = !isUploadingImage.value;
}

Stream<List<BlogModel>> getAllBlogPostsStream() {
final blogRef = FirebaseFirestore.instance
.collection('blogPosts')
.orderBy('createdAt', descending: true)
.withConverter<BlogModel>(
fromFirestore: (snapshot, _) =>
BlogModel.fromJson(snapshot.data() as Map<String, dynamic>),
toFirestore: (blog, _) => blog.toJson(),
);

return blogRef.snapshots().map((querySnapshot) =>
querySnapshot.docs.map((doc) => doc.data()).toList());
}
}
54 changes: 54 additions & 0 deletions lib/models/blog_model.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> json) {
return BlogModel(
title: json["title"],
content: json["content"],
author: json["author"],
imageUrl: json["image"],
createdAt: json["createdAt"],
);
}

Map<String, dynamic> toJson() => {
"title": title,
"content": content,
"author": author,
"image": imageUrl,
"createdAt": createdAt,
};

@override
String toString() {
return "$title, $content, $author, $imageUrl, $createdAt, ";
}
}
Loading