358 lines
12 KiB
Dart
358 lines
12 KiB
Dart
import 'dart:convert';
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:socialbuddy_mobile/core/constants/colors.dart';
|
|
import 'package:socialbuddy_mobile/core/constants/api_constants.dart';
|
|
import '../../connect/screens/channels_screen.dart';
|
|
import '../../connect/screens/social_connect_details_screen.dart';
|
|
import '../../automation/screens/automation_screen.dart';
|
|
import '../../../core/constants/colors.dart';
|
|
|
|
class PostsScreen extends StatefulWidget {
|
|
const PostsScreen({super.key});
|
|
|
|
@override
|
|
State<PostsScreen> createState() => _PostsScreenState();
|
|
}
|
|
|
|
class _PostsScreenState extends State<PostsScreen> {
|
|
List<dynamic> _media = [];
|
|
bool _isLoading = true;
|
|
String? _error;
|
|
String? _connectedChannelName;
|
|
Map<String, dynamic>? _accountData;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_fetchMedia();
|
|
}
|
|
|
|
Future<void> _fetchMedia() async {
|
|
if (!mounted) return;
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
_error = null;
|
|
});
|
|
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final userEmail = prefs.getString('user_email');
|
|
final connectedChannelId = prefs.getString('connectedChannel');
|
|
|
|
if (userEmail == null) {
|
|
setState(() {
|
|
_error = "User email not found. Please log in again.";
|
|
_isLoading = false;
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check if we have account details saved or fetch them
|
|
if (connectedChannelId != null) {
|
|
await _fetchAccountDetails(userEmail);
|
|
}
|
|
|
|
final url = Uri.parse('${ApiConstants.liveBaseUrl}/social/media?userId=$userEmail&limit=20');
|
|
final response = await http.get(url);
|
|
|
|
if (!mounted) return;
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
setState(() {
|
|
_media = data['data'] ?? [];
|
|
_isLoading = false;
|
|
});
|
|
} else {
|
|
final data = jsonDecode(response.body);
|
|
setState(() {
|
|
// Backend might return 'error' or 'message'
|
|
_error = data['error'] ?? data['message'] ?? "Failed to load media";
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
setState(() {
|
|
_error = "An error occurred: $e";
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _fetchAccountDetails(String email) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('${ApiConstants.liveBaseUrl}/social/account?userId=$email'),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
setState(() {
|
|
_accountData = data;
|
|
_connectedChannelName = data['username'];
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error fetching account for posts header: $e");
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppColors.darkBg,
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
title: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("Media Library", style: GoogleFonts.nunito(fontWeight: FontWeight.bold)),
|
|
if (_connectedChannelName != null)
|
|
Text("Account: @$_connectedChannelName",
|
|
style: GoogleFonts.nunito(fontSize: 12, color: Colors.white54)),
|
|
],
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
onPressed: _fetchMedia,
|
|
),
|
|
],
|
|
),
|
|
body: RefreshIndicator(
|
|
onRefresh: _fetchMedia,
|
|
color: Colors.pinkAccent,
|
|
child: _buildBody(),
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Create Post feature coming soon!")),
|
|
);
|
|
},
|
|
backgroundColor: Colors.blueAccent,
|
|
child: const Icon(Icons.add, color: Colors.white),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBody() {
|
|
if (_isLoading) {
|
|
return const Center(child: CircularProgressIndicator(color: Colors.pinkAccent));
|
|
}
|
|
|
|
if (_error != null) {
|
|
return SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
child: Container(
|
|
height: MediaQuery.of(context).size.height * 0.7,
|
|
alignment: Alignment.center,
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.error_outline, size: 64, color: Colors.orangeAccent),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
_error!,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(color: Colors.white70, fontSize: 16),
|
|
),
|
|
const SizedBox(height: 24),
|
|
if (_error!.contains("Connect a channel"))
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (context) => const ChannelsScreen()),
|
|
).then((_) => _fetchMedia()),
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.pinkAccent),
|
|
child: const Text("Go to Channels"),
|
|
)
|
|
else
|
|
ElevatedButton(
|
|
onPressed: _fetchMedia,
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.blueAccent),
|
|
child: const Text("Retry"),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (_media.isEmpty) {
|
|
return SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
child: Container(
|
|
height: MediaQuery.of(context).size.height * 0.7,
|
|
alignment: Alignment.center,
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.photo_library_outlined, size: 80, color: Colors.white10),
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
"No Instagram media found.",
|
|
textAlign: TextAlign.center,
|
|
style: GoogleFonts.nunito(fontSize: 20, color: Colors.white, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text(
|
|
"Make sure you have an active Instagram business account connected.",
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: Colors.white38),
|
|
),
|
|
const SizedBox(height: 32),
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (context) => const ChannelsScreen()),
|
|
).then((_) => _fetchMedia()),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.primary,
|
|
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
child: const Text("Connect Channel"),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return GridView.builder(
|
|
padding: const EdgeInsets.all(16),
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 2,
|
|
childAspectRatio: 0.65,
|
|
crossAxisSpacing: 16,
|
|
mainAxisSpacing: 16,
|
|
),
|
|
itemCount: _media.length,
|
|
itemBuilder: (context, index) {
|
|
final item = _media[index];
|
|
final mediaType = item['media_type'];
|
|
final mediaUrl = item['media_url'];
|
|
final caption = item['caption'] ?? "";
|
|
final likes = item['like_count']?.toString() ?? "0";
|
|
final comments = item['comments_count']?.toString() ?? "0";
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: AppColors.cardBg,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: Colors.white.withOpacity(0.05)),
|
|
boxShadow: [
|
|
BoxShadow(color: Colors.black26, blurRadius: 10, offset: const Offset(0, 5)),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Stack(
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
|
child: Image.network(
|
|
mediaUrl,
|
|
fit: BoxFit.cover,
|
|
width: double.infinity,
|
|
height: double.infinity,
|
|
loadingBuilder: (context, child, loadingProgress) {
|
|
if (loadingProgress == null) return child;
|
|
return Container(
|
|
color: Colors.white.withOpacity(0.02),
|
|
child: const Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
|
);
|
|
},
|
|
errorBuilder: (c, e, s) => Container(
|
|
color: Colors.white10,
|
|
child: const Center(child: Icon(Icons.broken_image, color: Colors.white24, size: 32)),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 8,
|
|
right: 8,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black54.withOpacity(0.6),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Text(
|
|
_getMediaTypeName(mediaType),
|
|
style: const TextStyle(color: Colors.white, fontSize: 8, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
caption,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: GoogleFonts.nunito(fontSize: 12, color: Colors.white, height: 1.3),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildStatChip(Icons.favorite, likes, Colors.pinkAccent),
|
|
_buildStatChip(Icons.comment, comments, Colors.blueAccent),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
String _getMediaTypeName(String type) {
|
|
switch (type) {
|
|
case 'IMAGE': return 'PHOTO';
|
|
case 'VIDEO': return 'VIDEO';
|
|
case 'CAROUSEL_ALBUM': return 'CAROUSEL';
|
|
default: return type;
|
|
}
|
|
}
|
|
|
|
Widget _buildStatChip(IconData icon, String value, Color color) {
|
|
return Row(
|
|
children: [
|
|
Icon(icon, size: 14, color: color),
|
|
const SizedBox(width: 4),
|
|
Text(value, style: const TextStyle(fontSize: 10, color: Colors.white70, fontWeight: FontWeight.bold)),
|
|
],
|
|
);
|
|
}
|
|
}
|