Implement core screens for authentication, home, automation, pricing, and social connection features.

This commit is contained in:
Alaguraj0361 2025-12-31 23:31:40 +05:30
parent 7ba974fc38
commit b3f9e30d4f
11 changed files with 1329 additions and 214 deletions

BIN
analysis_errors.txt Normal file

Binary file not shown.

BIN
analysis_full.txt Normal file

Binary file not shown.

BIN
analysis_report.txt Normal file

Binary file not shown.

View File

@ -0,0 +1,187 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:socialbuddy_mobile/core/constants/colors.dart';
class ForgotPasswordScreen extends StatefulWidget {
const ForgotPasswordScreen({super.key});
@override
State<ForgotPasswordScreen> createState() => _ForgotPasswordScreenState();
}
class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
final _emailController = TextEditingController();
bool _isLoading = false;
@override
void dispose() {
_emailController.dispose();
super.dispose();
}
Future<void> _handleResetPassword() async {
if (_emailController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Please enter your email")),
);
return;
}
setState(() => _isLoading = true);
// Mimic API call logic as in web
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Password reset link sent to your email!")),
);
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0A0B17),
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
body: Stack(
children: [
// Background Glows
Positioned(
top: 100,
right: 50,
child: _buildGlowBlob(const Color(0xFF1D8BE0), 120),
),
Positioned(
bottom: 100,
left: 50,
child: _buildGlowBlob(const Color(0xFFDB21D9), 120),
),
Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Colors.white.withOpacity(0.2)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.lock_reset, size: 80, color: Colors.blueAccent),
const SizedBox(height: 24),
Text(
"Forgot Password?",
style: GoogleFonts.nunito(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
"No worries! Enter your email and we'll send you a link to reset your password.",
textAlign: TextAlign.center,
style: GoogleFonts.nunito(
color: Colors.white.withOpacity(0.6),
fontSize: 14,
),
),
const SizedBox(height: 32),
TextField(
controller: _emailController,
style: const TextStyle(color: Colors.white),
decoration: _inputDecoration("Email Address", Icons.email_outlined),
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 50,
child: Container(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF2563EB), Color(0xFFEC4899)],
),
borderRadius: BorderRadius.circular(12),
),
child: ElevatedButton(
onPressed: _isLoading ? null : _handleResetPassword,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: _isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text("SEND RESET LINK", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white)),
),
),
),
],
),
),
),
),
),
),
],
),
);
}
Widget _buildGlowBlob(Color color, double size) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: color.withOpacity(0.3),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(color: color.withOpacity(0.5), blurRadius: 100, spreadRadius: 20),
],
),
);
}
InputDecoration _inputDecoration(String label, IconData icon) {
return InputDecoration(
hintText: label,
hintStyle: TextStyle(color: Colors.white.withOpacity(0.5)),
prefixIcon: Icon(icon, color: Colors.white70),
filled: true,
fillColor: const Color(0xFF070D1E).withOpacity(0.7),
contentPadding: const EdgeInsets.symmetric(vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.white.withOpacity(0.15)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.white.withOpacity(0.15)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.white.withOpacity(0.3)),
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
import '../providers/auth_provider.dart'; import '../providers/auth_provider.dart';
import '../../home/screens/home_screen.dart'; import '../../home/screens/home_screen.dart';
import 'signup_screen.dart'; import 'signup_screen.dart';
import 'forgot_password_screen.dart';
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
const LoginScreen({super.key}); const LoginScreen({super.key});
@ -139,7 +140,12 @@ class _LoginScreenState extends State<LoginScreen> {
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: TextButton( child: TextButton(
onPressed: () {}, onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ForgotPasswordScreen()),
);
},
child: const Text("Forgot Password?", style: TextStyle(color: Colors.blueAccent)), child: const Text("Forgot Password?", style: TextStyle(color: Colors.blueAccent)),
), ),
), ),

View File

@ -5,6 +5,9 @@ import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
import '../../../core/constants/api_constants.dart'; import '../../../core/constants/api_constants.dart';
import '../../connect/screens/social_connect_details_screen.dart';
import '../../connect/screens/channels_screen.dart';
import '../../posts/screens/posts_screen.dart';
class AutomationScreen extends StatefulWidget { class AutomationScreen extends StatefulWidget {
const AutomationScreen({super.key}); const AutomationScreen({super.key});

View File

@ -5,6 +5,8 @@ import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
import '../../../core/constants/api_constants.dart'; import '../../../core/constants/api_constants.dart';
import '../../posts/screens/posts_screen.dart';
import '../../automation/screens/automation_screen.dart';
class ChannelsScreen extends StatefulWidget { class ChannelsScreen extends StatefulWidget {
const ChannelsScreen({super.key}); const ChannelsScreen({super.key});
@ -37,16 +39,18 @@ class _ChannelsScreenState extends State<ChannelsScreen> {
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
_channels = jsonDecode(response.body); setState(() {
_channels = jsonDecode(response.body);
});
} }
if (_connectedChannelId != null) { if (_connectedChannelId != null) {
await _loadAccountDetails(); await _loadAccountDetails();
} }
} catch (e) { } catch (e) {
print("Error loading channels: $e"); debugPrint("Error loading channels: $e");
} finally { } finally {
setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
} }
@ -58,10 +62,12 @@ class _ChannelsScreenState extends State<ChannelsScreen> {
Uri.parse('${ApiConstants.liveBaseUrl}/social/account?userId=$email'), Uri.parse('${ApiConstants.liveBaseUrl}/social/account?userId=$email'),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
_accountData = jsonDecode(response.body); setState(() {
_accountData = jsonDecode(response.body);
});
} }
} catch (e) { } catch (e) {
print("Error loading account details: $e"); debugPrint("Error loading account details: $e");
} }
} }
@ -79,20 +85,31 @@ class _ChannelsScreenState extends State<ChannelsScreen> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = jsonDecode(response.body); final data = jsonDecode(response.body);
final savedId = data['data']['channel_id'] ?? channelId; final savedId = data['data']?['channel_id'] ?? channelId;
await prefs.setString('connectedChannel', savedId); await prefs.setString('connectedChannel', savedId);
setState(() => _connectedChannelId = savedId); setState(() => _connectedChannelId = savedId.toString());
await _loadAccountDetails(); await _loadAccountDetails();
ScaffoldMessenger.of(context).showSnackBar( if (mounted) {
SnackBar(content: Text("$channelName connected successfully!")), ScaffoldMessenger.of(context).showSnackBar(
); SnackBar(content: Text("$channelName connected successfully!")),
);
}
} else {
final error = jsonDecode(response.body);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error['error'] ?? "Failed to connect $channelName")),
);
}
} }
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( if (mounted) {
SnackBar(content: Text("Failed to connect $channelName")), ScaffoldMessenger.of(context).showSnackBar(
); SnackBar(content: Text("Error: $e")),
);
}
} finally { } finally {
setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
} }
@ -103,17 +120,28 @@ class _ChannelsScreenState extends State<ChannelsScreen> {
_connectedChannelId = null; _connectedChannelId = null;
_accountData = null; _accountData = null;
}); });
ScaffoldMessenger.of(context).showSnackBar( if (mounted) {
const SnackBar(content: Text("Channel removed successfully!")), ScaffoldMessenger.of(context).showSnackBar(
); const SnackBar(content: Text("Channel removed successfully!")),
);
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.darkBg, backgroundColor: AppColors.darkBg,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text("Instagram Channels", style: GoogleFonts.nunito(color: Colors.white, fontWeight: FontWeight.bold)),
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
body: _isLoading body: _isLoading
? const Center(child: CircularProgressIndicator(color: AppColors.primary)) ? const Center(child: CircularProgressIndicator(color: Colors.pinkAccent))
: _connectedChannelId != null : _connectedChannelId != null
? _buildAccountDetails() ? _buildAccountDetails()
: _buildChannelSelection(), : _buildChannelSelection(),
@ -122,7 +150,7 @@ class _ChannelsScreenState extends State<ChannelsScreen> {
Widget _buildAccountDetails() { Widget _buildAccountDetails() {
if (_accountData == null) { if (_accountData == null) {
return const Center(child: CircularProgressIndicator(color: AppColors.primary)); return const Center(child: CircularProgressIndicator(color: Colors.pinkAccent));
} }
return SingleChildScrollView( return SingleChildScrollView(
@ -130,59 +158,180 @@ class _ChannelsScreenState extends State<ChannelsScreen> {
child: Column( child: Column(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(32),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBg, gradient: LinearGradient(
colors: [
AppColors.cardBg.withOpacity(0.9),
AppColors.cardBg.withOpacity(0.7),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
border: Border.all(color: Colors.white.withOpacity(0.1)), border: Border.all(color: Colors.white.withOpacity(0.1)),
boxShadow: [
BoxShadow(color: const Color(0xFF833AB4).withOpacity(0.15), blurRadius: 30, offset: const Offset(0, 10)),
BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 10)),
],
), ),
child: Column( child: Column(
children: [ children: [
CircleAvatar( Stack(
radius: 60, alignment: Alignment.center,
backgroundImage: NetworkImage(_accountData!['profile_picture_url'] ?? ''),
),
const SizedBox(height: 20),
Text(
_accountData!['username'] ?? 'User',
style: GoogleFonts.nunito(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_buildStatItem(_accountData!['media_count'].toString(), "Posts"), Container(
_buildStatItem(_accountData!['followers_count'].toString(), "Followers"), width: 130,
_buildStatItem(_accountData!['follows_count'].toString(), "Following"), height: 130,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const LinearGradient(
colors: [Color(0xFF833AB4), Color(0xFFFD1D1D), Color(0xFFFCAF45)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: const Color(0xFFFD1D1D).withOpacity(0.4),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
),
Container(
width: 122,
height: 122,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: AppColors.cardBg, // Border effect
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: CircleAvatar(
radius: 56,
backgroundColor: Colors.black,
backgroundImage: NetworkImage(_accountData!['profile_picture_url'] ?? ''),
),
),
),
Positioned(
bottom: 0,
right: 10,
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFFFD1D1D),
shape: BoxShape.circle,
border: Border.all(color: AppColors.cardBg, width: 3),
),
child: const Icon(Icons.check, color: Colors.white, size: 16),
),
),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
if (_accountData!['biography'] != null) Text(
Text( _accountData!['username'] ?? 'User',
_accountData!['biography'], style: GoogleFonts.nunito(
textAlign: TextAlign.center, fontSize: 28,
style: GoogleFonts.nunito(color: Colors.white70), fontWeight: FontWeight.w900,
color: Colors.white,
letterSpacing: 0.5,
),
),
if (_accountData!['name'] != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
_accountData!['name'],
style: GoogleFonts.nunito(
fontSize: 16,
color: Colors.white54,
fontWeight: FontWeight.w600,
),
),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
Container(
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.03),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white.withOpacity(0.05)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem(_accountData!['media_count']?.toString() ?? "0", "Posts"),
Container(width: 1, height: 40, color: Colors.white10),
_buildStatItem(_accountData!['followers_count']?.toString() ?? "0", "Followers"),
Container(width: 1, height: 40, color: Colors.white10),
_buildStatItem(_accountData!['follows_count']?.toString() ?? "0", "Following"),
],
),
),
const SizedBox(height: 32),
if (_accountData!['biography'] != null)
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: Text(
_accountData!['biography'],
textAlign: TextAlign.center,
style: GoogleFonts.nunito(color: Colors.white70, fontSize: 14, height: 1.5),
),
),
const SizedBox(height: 40),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 56,
child: ElevatedButton( child: ElevatedButton(
onPressed: _removeChannel, onPressed: _removeChannel,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade700, backgroundColor: Colors.red.withOpacity(0.1),
padding: const EdgeInsets.symmetric(vertical: 16), side: BorderSide(color: Colors.red.withOpacity(0.5), width: 1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 0,
).copyWith(
overlayColor: MaterialStateProperty.all(Colors.red.withOpacity(0.1)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.logout, color: Colors.red),
const SizedBox(width: 12),
Text(
"Disconnect Channel",
style: GoogleFonts.nunito(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
), ),
child: const Text("Remove Channel"),
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.info_outline, color: Colors.white38, size: 16),
const SizedBox(width: 8),
Text(
"Switch channels by disconnecting current one",
textAlign: TextAlign.center,
style: GoogleFonts.nunito(color: Colors.white38, fontSize: 12),
),
],
),
], ],
), ),
); );
@ -191,47 +340,236 @@ class _ChannelsScreenState extends State<ChannelsScreen> {
Widget _buildStatItem(String value, String label) { Widget _buildStatItem(String value, String label) {
return Column( return Column(
children: [ children: [
Text(value, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white)), Text(
Text(label, style: const TextStyle(fontSize: 12, color: Colors.white54)), value,
style: GoogleFonts.nunito(
fontSize: 24,
fontWeight: FontWeight.w900,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
label,
style: GoogleFonts.nunito(
fontSize: 12,
color: Colors.white54,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
], ],
); );
} }
Widget _buildChannelSelection() { Widget _buildChannelSelection() {
if (_channels.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [Colors.purple.withOpacity(0.2), Colors.pink.withOpacity(0.2)],
),
),
child: const Icon(Icons.account_circle_outlined, size: 64, color: Colors.white24),
),
const SizedBox(height: 16),
Text(
"No Instagram Channels",
style: GoogleFonts.nunito(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Text(
"Connect via Facebook to get started",
textAlign: TextAlign.center,
style: GoogleFonts.nunito(
color: Colors.white54,
fontSize: 13,
height: 1.5,
),
),
),
],
),
);
}
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
itemCount: _channels.length, itemCount: _channels.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final ch = _channels[index]; final ch = _channels[index];
final hasLinked = ch['has_linked_account'] == true; final hasLinked = ch['has_linked_account'] == true;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBg, borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(20), gradient: LinearGradient(
border: Border.all(color: Colors.white.withOpacity(0.1)), colors: hasLinked
), ? [Colors.purple.withOpacity(0.1), Colors.pink.withOpacity(0.1)]
child: Column( : [Colors.grey.withOpacity(0.05), Colors.grey.withOpacity(0.05)],
children: [ begin: Alignment.topLeft,
Text( end: Alignment.bottomRight,
ch['name'], ),
style: GoogleFonts.nunito(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white), border: Border.all(
), width: 1,
const SizedBox(height: 16), color: hasLinked
SizedBox( ? Colors.purpleAccent.withOpacity(0.3)
width: double.infinity, : Colors.white.withOpacity(0.1),
child: ElevatedButton( ),
onPressed: hasLinked ? () => _connectChannel(ch['id'], ch['name']) : null, boxShadow: [
style: ElevatedButton.styleFrom( BoxShadow(
backgroundColor: hasLinked ? AppColors.primary : Colors.grey.shade800, color: hasLinked
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ? Colors.purpleAccent.withOpacity(0.1)
), : Colors.black.withOpacity(0.2),
child: Text(hasLinked ? "Connect" : "Not Linked"), blurRadius: 8,
), offset: const Offset(0, 2),
), ),
], ],
), ),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: hasLinked ? () => _connectChannel(ch['id'].toString(), ch['name']) : null,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
// Instagram Icon
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: hasLinked
? const LinearGradient(
colors: [Color(0xFF833AB4), Color(0xFFFD1D1D), Color(0xFFFCAF45)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: LinearGradient(
colors: [Colors.grey.shade800, Colors.grey.shade700],
),
boxShadow: [
BoxShadow(
color: hasLinked
? Colors.purpleAccent.withOpacity(0.2)
: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: const Icon(
Icons.camera_alt_rounded,
color: Colors.white,
size: 24,
),
),
const SizedBox(width: 12),
// Channel Info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
ch['name'] ?? 'Channel',
style: GoogleFonts.nunito(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.white,
),
overflow: TextOverflow.ellipsis,
),
),
if (hasLinked) ...[
const SizedBox(width: 6),
Icon(Icons.check_circle, size: 14, color: Colors.greenAccent),
],
],
),
const SizedBox(height: 4),
Text(
hasLinked ? "Ready" : "Auth Required",
style: GoogleFonts.nunito(
fontSize: 11,
color: hasLinked ? Colors.greenAccent : Colors.orangeAccent,
fontWeight: FontWeight.w600,
),
),
],
),
),
const SizedBox(width: 12),
// Connect Button
Container(
height: 36,
decoration: BoxDecoration(
gradient: hasLinked
? const LinearGradient(
colors: [Color(0xFF667EEA), Color(0xFF764BA2)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: hasLinked ? null : Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: hasLinked ? () => _connectChannel(ch['id'].toString(), ch['name']) : null,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
hasLinked ? "Link" : "Lock",
style: GoogleFonts.nunito(
color: hasLinked ? Colors.white : Colors.white38,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
const SizedBox(width: 4),
Icon(
hasLinked ? Icons.arrow_forward_rounded : Icons.lock_outline,
color: hasLinked ? Colors.white : Colors.white38,
size: 14,
),
],
),
),
),
),
),
],
),
),
),
),
); );
}, },
); );

View File

@ -5,6 +5,9 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
import '../services/social_auth_service.dart'; import '../services/social_auth_service.dart';
import 'channels_screen.dart';
import '../../posts/screens/posts_screen.dart';
import '../../automation/screens/automation_screen.dart';
class SocialMediaConnectScreen extends StatefulWidget { class SocialMediaConnectScreen extends StatefulWidget {
final bool isTab; final bool isTab;
@ -224,7 +227,7 @@ class _SocialMediaConnectScreenState extends State<SocialMediaConnectScreen> wit
// Disconnect Button // Disconnect Button
if (_status == "connected") if (_status == "connected")
_buildFullWidthButton( _buildFullWidthButton(
text: "Remove Facebook Account", text: "Disconnect Facebook",
onPressed: _isLoading ? null : _handleDisconnect, onPressed: _isLoading ? null : _handleDisconnect,
color: const Color(0xFFE11D48), color: const Color(0xFFE11D48),
), ),
@ -264,16 +267,16 @@ class _SocialMediaConnectScreenState extends State<SocialMediaConnectScreen> wit
Widget _buildGradientButton({required String text, required VoidCallback? onPressed, required List<Color> colors}) { Widget _buildGradientButton({required String text, required VoidCallback? onPressed, required List<Color> colors}) {
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
height: 60, height: 56,
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient(colors: colors), gradient: LinearGradient(colors: colors),
borderRadius: BorderRadius.circular(50), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: colors.first.withOpacity(0.3), color: colors.first.withOpacity(0.3),
blurRadius: 15, blurRadius: 12,
offset: const Offset(0, 5), offset: const Offset(0, 4),
), ),
], ],
), ),
@ -281,12 +284,16 @@ class _SocialMediaConnectScreenState extends State<SocialMediaConnectScreen> wit
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 24),
), ),
onPressed: onPressed, onPressed: onPressed,
child: Text( child: FittedBox(
text, fit: BoxFit.scaleDown,
style: GoogleFonts.nunito(fontSize: 18, fontWeight: FontWeight.w800, color: Colors.white), child: Text(
text,
style: GoogleFonts.nunito(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
),
), ),
), ),
), ),
@ -296,20 +303,26 @@ class _SocialMediaConnectScreenState extends State<SocialMediaConnectScreen> wit
Widget _buildFullWidthButton({required String text, required VoidCallback? onPressed, required Color color}) { Widget _buildFullWidthButton({required String text, required VoidCallback? onPressed, required Color color}) {
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
height: 60, height: 56,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: color, backgroundColor: color,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
elevation: 4, elevation: 0,
shadowColor: color.withOpacity(0.4), padding: const EdgeInsets.symmetric(horizontal: 24),
shadowColor: Colors.transparent,
).copyWith(
overlayColor: MaterialStateProperty.all(Colors.white.withOpacity(0.1)),
), ),
onPressed: onPressed, onPressed: onPressed,
child: _isLoading child: _isLoading
? const SizedBox(height: 24, width: 24, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) ? const SizedBox(height: 24, width: 24, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
: Text( : FittedBox(
text, fit: BoxFit.scaleDown,
style: GoogleFonts.nunito(fontSize: 18, fontWeight: FontWeight.w800, color: Colors.white), child: Text(
text,
style: GoogleFonts.nunito(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
),
), ),
), ),
); );

View File

@ -10,6 +10,7 @@ import 'package:socialbuddy_mobile/features/automation/screens/automation_screen
import 'package:socialbuddy_mobile/features/connect/screens/channels_screen.dart'; import 'package:socialbuddy_mobile/features/connect/screens/channels_screen.dart';
import 'package:socialbuddy_mobile/features/profile/screens/profile_screen.dart'; import 'package:socialbuddy_mobile/features/profile/screens/profile_screen.dart';
import 'package:socialbuddy_mobile/features/profile/screens/pricing_screen.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@ -23,8 +24,10 @@ class _HomeScreenState extends State<HomeScreen> {
static final List<Widget> _widgetOptions = <Widget>[ static final List<Widget> _widgetOptions = <Widget>[
const DashboardTab(), const DashboardTab(),
const PostsScreen(),
const SocialMediaConnectScreen(), const SocialMediaConnectScreen(),
const ChannelsScreen(),
const PostsScreen(),
const AutomationScreen(),
const ProfileScreen(), const ProfileScreen(),
]; ];
@ -46,17 +49,6 @@ class _HomeScreenState extends State<HomeScreen> {
child: Image.asset('assets/images/logo_sb.png', errorBuilder: (c, e, s) => const Icon(Icons.hub, color: Colors.blue)), child: Image.asset('assets/images/logo_sb.png', errorBuilder: (c, e, s) => const Icon(Icons.hub, color: Colors.blue)),
), ),
actions: [ actions: [
IconButton(
icon: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
shape: BoxShape.circle,
),
child: const Icon(Icons.grid_view_rounded, color: Colors.white70, size: 20),
),
onPressed: () {},
),
IconButton( IconButton(
icon: Container( icon: Container(
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
@ -66,7 +58,7 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
child: const Icon(Icons.person_outline, color: Colors.white70, size: 20), child: const Icon(Icons.person_outline, color: Colors.white70, size: 20),
), ),
onPressed: () => _onItemTapped(3), onPressed: () => _onItemTapped(5), // Navigate to Profile (index 5)
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
], ],
@ -83,14 +75,25 @@ class _HomeScreenState extends State<HomeScreen> {
activeIcon: Icon(Icons.grid_view), activeIcon: Icon(Icons.grid_view),
label: 'Home', label: 'Home',
), ),
BottomNavigationBarItem(
icon: Icon(Icons.link),
activeIcon: Icon(Icons.link),
label: 'Connect',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle_outlined),
activeIcon: Icon(Icons.account_circle),
label: 'Channels',
),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.post_add_outlined), icon: Icon(Icons.post_add_outlined),
activeIcon: Icon(Icons.post_add), activeIcon: Icon(Icons.post_add),
label: 'Posts', label: 'Posts',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.link), icon: Icon(Icons.comment_outlined),
label: 'Connect', activeIcon: Icon(Icons.comment),
label: 'Automation',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.person_outline), icon: Icon(Icons.person_outline),
@ -127,10 +130,10 @@ class DashboardTab extends StatelessWidget {
if (role == 'customer') { if (role == 'customer') {
final session = prefs.getString('payment_session'); final session = prefs.getString('payment_session');
if (session == null) { if (session == null) {
ScaffoldMessenger.of(context).showSnackBar( Navigator.push(
const SnackBar(content: Text("Pricing check required. Please subscribe.")), context,
MaterialPageRoute(builder: (context) => const PricingScreen()),
); );
// In a real app, you would navigate to PricingScreen
return; return;
} }
} }
@ -205,14 +208,21 @@ class DashboardTab extends StatelessWidget {
description: "Automatically reply to comments/messages using smart automation.", description: "Automatically reply to comments/messages using smart automation.",
icon: Icons.chat_bubble_outline_rounded, icon: Icons.chat_bubble_outline_rounded,
iconColor: Colors.blueAccent, iconColor: Colors.blueAccent,
onTap: () {}, // Tab removed in 4-tab layout onTap: () => _checkAndNavigate(context, const AutomationScreen()),
),
FeatureCard(
title: "Instagram Channels",
description: "Select and connect specific Instagram business accounts to manage.",
icon: Icons.account_tree_outlined,
iconColor: Colors.pinkAccent,
onTap: () => _checkAndNavigate(context, const ChannelsScreen()),
), ),
FeatureCard( FeatureCard(
title: "Instagram Media", title: "Instagram Media",
description: "View all Instagram media from your connected accounts with insights.", description: "View all Instagram media from your connected accounts with insights.",
icon: Icons.camera_alt_outlined, icon: Icons.camera_alt_outlined,
iconColor: Colors.pinkAccent, iconColor: Colors.cyanAccent,
onTap: () => homeState?._onItemTapped(2), onTap: () => homeState?._onItemTapped(1),
), ),
FeatureCard( FeatureCard(
title: "Social Media Posts", title: "Social Media Posts",
@ -226,21 +236,33 @@ class DashboardTab extends StatelessWidget {
description: "Track auto reply logs and automation actions with timestamps.", description: "Track auto reply logs and automation actions with timestamps.",
icon: Icons.history_rounded, icon: Icons.history_rounded,
iconColor: Colors.orangeAccent, iconColor: Colors.orangeAccent,
onTap: () {}, onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Automation Logs coming soon!")),
);
},
), ),
FeatureCard( FeatureCard(
title: "Users", title: "Users",
description: "Manage users, roles, and access permissions.", description: "Manage users, roles, and access permissions.",
icon: Icons.person_search_outlined, icon: Icons.person_search_outlined,
iconColor: Colors.purpleAccent, iconColor: Colors.purpleAccent,
onTap: () {}, onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("User Management coming soon!")),
);
},
), ),
FeatureCard( FeatureCard(
title: "Reports", title: "Reports",
description: "Analyze insights, performance charts, and engagement metrics.", description: "Analyze insights, performance charts, and engagement metrics.",
icon: Icons.auto_graph_rounded, icon: Icons.auto_graph_rounded,
iconColor: Colors.cyanAccent, iconColor: Colors.cyanAccent,
onTap: () {}, onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Reports and Analytics coming soon!")),
);
},
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
Center( Center(

View File

@ -1,117 +1,357 @@
import 'dart:convert';
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.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/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 StatelessWidget { class PostsScreen extends StatefulWidget {
const PostsScreen({super.key}); const PostsScreen({super.key});
@override @override
Widget build(BuildContext context) { State<PostsScreen> createState() => _PostsScreenState();
// Dummy posts data }
final List<Map<String, String>> posts = List.generate(10, (index) => {
'image': 'https://picsum.photos/500/500?random=$index', class _PostsScreenState extends State<PostsScreen> {
'caption': 'This is a sample post caption for Social Buddy #social #marketing', List<dynamic> _media = [];
'likes': '${(index + 1) * 12}', bool _isLoading = true;
'comments': '${index * 3}', String? _error;
'type': index % 3 == 0 ? 'VIDEO' : 'IMAGE', 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( return Scaffold(
backgroundColor: AppColors.darkBg, backgroundColor: AppColors.darkBg,
body: GridView.builder( appBar: AppBar(
padding: const EdgeInsets.all(16), backgroundColor: Colors.transparent,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( elevation: 0,
crossAxisCount: 2, title: Column(
childAspectRatio: 0.65, crossAxisAlignment: CrossAxisAlignment.start,
crossAxisSpacing: 16, children: [
mainAxisSpacing: 16, Text("Media Library", style: GoogleFonts.nunito(fontWeight: FontWeight.bold)),
if (_connectedChannelName != null)
Text("Account: @$_connectedChannelName",
style: GoogleFonts.nunito(fontSize: 12, color: Colors.white54)),
],
), ),
itemCount: posts.length, actions: [
itemBuilder: (context, index) { IconButton(
final post = posts[index]; icon: const Icon(Icons.refresh),
return Container( onPressed: _fetchMedia,
decoration: BoxDecoration( ),
color: AppColors.cardBg, ],
borderRadius: BorderRadius.circular(16), ),
border: Border.all(color: Colors.white.withOpacity(0.05)), body: RefreshIndicator(
), onRefresh: _fetchMedia,
child: Column( color: Colors.pinkAccent,
crossAxisAlignment: CrossAxisAlignment.start, child: _buildBody(),
children: [
Expanded(
child: Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
child: Image.network(
post['image']!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
),
Positioned(
top: 8,
right: 8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(4),
),
child: Text(
post['type']!,
style: const TextStyle(color: Colors.white, fontSize: 8),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post['caption']!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.nunito(fontSize: 12, color: Colors.white),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(Icons.favorite, size: 14, color: AppColors.danger),
const SizedBox(width: 4),
Text(post['likes']!, style: const TextStyle(fontSize: 10, color: Colors.white70)),
],
),
Row(
children: [
const Icon(Icons.comment, size: 14, color: Colors.blueAccent),
const SizedBox(width: 4),
Text(post['comments']!, style: const TextStyle(fontSize: 10, color: Colors.white70)),
],
),
],
),
],
),
),
],
),
);
},
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () {}, onPressed: () {
backgroundColor: AppColors.primary, ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Create Post feature coming soon!")),
);
},
backgroundColor: Colors.blueAccent,
child: const Icon(Icons.add, color: Colors.white), 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)),
],
);
}
} }

View File

@ -0,0 +1,306 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class PricingScreen extends StatefulWidget {
const PricingScreen({super.key});
@override
State<PricingScreen> createState() => _PricingScreenState();
}
class _PricingScreenState extends State<PricingScreen> {
bool _isYearly = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0A0B17),
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text("Pricing Plans",
style: GoogleFonts.nunito(color: Colors.white, fontWeight: FontWeight.bold)),
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
body: Stack(
children: [
// Background Glows
Positioned(top: 100, left: -50, child: _buildGlowBlob(const Color(0xFF1D8BE0), 150)),
Positioned(bottom: 50, right: -50, child: _buildGlowBlob(const Color(0xFFEC4899), 150)),
SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Text(
"Choose Your Plan",
style: GoogleFonts.nunito(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 8),
Text(
"Unlock premium features and grow your social presence.",
textAlign: TextAlign.center,
style: GoogleFonts.nunito(
color: Colors.white.withOpacity(0.6),
fontSize: 16,
),
),
const SizedBox(height: 32),
// Billing Toggle
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
borderRadius: BorderRadius.circular(50),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildToggleButton("Monthly", !_isYearly),
_buildToggleButton("Yearly (Save 20%)", _isYearly),
],
),
),
const SizedBox(height: 40),
// Free Trial
_buildPricingCard(
title: "7-Day Free Trial",
price: "\$0",
duration: "7 Days",
desc: "Experience SocialBuddy with no commitment.",
features: ["Full Platform Access", "All Premium Features", "No Credits Required", "Cancel Anytime"],
isPopular: false,
color: Colors.cyanAccent,
buttonText: "START FREE TRIAL",
),
const SizedBox(height: 24),
// Basic Buddy
_buildPricingCard(
title: "Basic Buddy",
price: _isYearly ? "\$100" : "\$10",
duration: _isYearly ? "Year" : "Month",
desc: "Perfect for individual creators.",
features: ["1 Social Profile", "Basic Analytics", "Content Scheduling", "Standard Support"],
isPopular: false,
color: Colors.blueAccent,
),
const SizedBox(height: 24),
// Standard Buddy
_buildPricingCard(
title: "Standard Buddy",
price: _isYearly ? "\$500" : "\$50",
duration: _isYearly ? "Year" : "Month",
desc: "For growing businesses and influencers.",
features: ["10 Social Profiles", "Advanced Analytics", "AI Content Generation", "Priority Support", "Team Collaboration (3)"],
isPopular: true,
color: const Color(0xFFEC4899),
),
const SizedBox(height: 24),
// Premium Buddy
_buildPricingCard(
title: "Premium Buddy",
price: _isYearly ? "\$1500" : "\$150",
duration: _isYearly ? "Year" : "Month",
desc: "Ultimate solution for agencies.",
features: ["25 Social Profiles", "White Label Reports", "Dedicated Account Manager", "API Access", "Unlimited Team Members"],
isPopular: false,
color: Colors.purpleAccent,
),
const SizedBox(height: 40),
],
),
),
],
),
);
}
Widget _buildToggleButton(String label, bool isActive) {
return GestureDetector(
onTap: () => setState(() => _isYearly = label.contains("Yearly")),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
gradient: isActive
? const LinearGradient(colors: [Color(0xFF4361EE), Color(0xFFC026D3)])
: null,
color: isActive ? null : Colors.transparent,
),
child: Text(
label,
style: GoogleFonts.nunito(
color: isActive ? Colors.white : Colors.white38,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
);
}
Widget _buildPricingCard({
required String title,
required String price,
required String duration,
required String desc,
required List<String> features,
required bool isPopular,
required Color color,
String buttonText = "GET STARTED",
}) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0xFF1A1D26).withOpacity(0.8),
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: isPopular ? color : Colors.white.withOpacity(0.1),
width: isPopular ? 2 : 1,
),
),
child: Stack(
children: [
if (isPopular)
Positioned(
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: color,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(22),
bottomLeft: Radius.circular(24),
),
),
child: const Text(
"POPULAR",
style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold),
),
),
),
Padding(
padding: const EdgeInsets.all(28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: GoogleFonts.nunito(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
desc,
style: GoogleFonts.nunito(color: Colors.white54, fontSize: 14),
),
const SizedBox(height: 24),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
price,
style: GoogleFonts.nunito(
color: Colors.white,
fontSize: 40,
fontWeight: FontWeight.w800,
),
),
Padding(
padding: const EdgeInsets.only(bottom: 8, left: 4),
child: Text(
"/$duration",
style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 16),
),
),
],
),
const SizedBox(height: 24),
const Divider(color: Colors.white10),
const SizedBox(height: 24),
...features.map((f) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.check, color: color, size: 16),
),
const SizedBox(width: 12),
Expanded(
child: Text(f, style: const TextStyle(color: Colors.white70, fontSize: 14)),
),
],
),
)),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 56,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [color, color.withOpacity(0.7)]),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: color.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4)),
],
),
child: ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Subscription to $title coming soon!")),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
child: Text(buttonText,
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white, fontSize: 16)),
),
),
)
],
),
),
],
),
);
}
Widget _buildGlowBlob(Color color, double size) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: color.withOpacity(0.2),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(color: color.withOpacity(0.4), blurRadius: 100, spreadRadius: 10),
],
),
);
}
}