Implement core screens for authentication, home, automation, pricing, and social connection features.
This commit is contained in:
parent
7ba974fc38
commit
b3f9e30d4f
BIN
analysis_errors.txt
Normal file
BIN
analysis_errors.txt
Normal file
Binary file not shown.
BIN
analysis_full.txt
Normal file
BIN
analysis_full.txt
Normal file
Binary file not shown.
BIN
analysis_report.txt
Normal file
BIN
analysis_report.txt
Normal file
Binary file not shown.
187
lib/features/auth/screens/forgot_password_screen.dart
Normal file
187
lib/features/auth/screens/forgot_password_screen.dart
Normal 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)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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});
|
||||||
|
|||||||
@ -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) {
|
||||||
|
setState(() {
|
||||||
_channels = jsonDecode(response.body);
|
_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) {
|
||||||
|
setState(() {
|
||||||
_accountData = jsonDecode(response.body);
|
_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();
|
||||||
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text("$channelName connected successfully!")),
|
SnackBar(content: Text("$channelName connected successfully!")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} else {
|
||||||
|
final error = jsonDecode(response.body);
|
||||||
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text("Failed to connect $channelName")),
|
SnackBar(content: Text(error['error'] ?? "Failed to connect $channelName")),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
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;
|
||||||
});
|
});
|
||||||
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text("Channel removed successfully!")),
|
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,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 130,
|
||||||
|
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'] ?? ''),
|
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),
|
Positioned(
|
||||||
Row(
|
bottom: 0,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
right: 10,
|
||||||
children: [
|
child: Container(
|
||||||
_buildStatItem(_accountData!['media_count'].toString(), "Posts"),
|
padding: const EdgeInsets.all(6),
|
||||||
_buildStatItem(_accountData!['followers_count'].toString(), "Followers"),
|
decoration: BoxDecoration(
|
||||||
_buildStatItem(_accountData!['follows_count'].toString(), "Following"),
|
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!['biography'],
|
_accountData!['username'] ?? 'User',
|
||||||
textAlign: TextAlign.center,
|
style: GoogleFonts.nunito(
|
||||||
style: GoogleFonts.nunito(color: Colors.white70),
|
fontSize: 28,
|
||||||
|
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: const Text("Remove Channel"),
|
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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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() {
|
||||||
return ListView.builder(
|
if (_channels.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
padding: const EdgeInsets.all(24),
|
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(
|
||||||
|
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)]
|
||||||
|
: [Colors.grey.withOpacity(0.05), Colors.grey.withOpacity(0.05)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
child: Column(
|
border: Border.all(
|
||||||
|
width: 1,
|
||||||
|
color: hasLinked
|
||||||
|
? Colors.purpleAccent.withOpacity(0.3)
|
||||||
|
: Colors.white.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: hasLinked
|
||||||
|
? Colors.purpleAccent.withOpacity(0.1)
|
||||||
|
: Colors.black.withOpacity(0.2),
|
||||||
|
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: [
|
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(
|
Text(
|
||||||
ch['name'],
|
hasLinked ? "Ready" : "Auth Required",
|
||||||
style: GoogleFonts.nunito(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white),
|
style: GoogleFonts.nunito(
|
||||||
),
|
fontSize: 11,
|
||||||
const SizedBox(height: 16),
|
color: hasLinked ? Colors.greenAccent : Colors.orangeAccent,
|
||||||
SizedBox(
|
fontWeight: FontWeight.w600,
|
||||||
width: double.infinity,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: hasLinked ? () => _connectChannel(ch['id'], ch['name']) : null,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: hasLinked ? AppColors.primary : Colors.grey.shade800,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
||||||
),
|
|
||||||
child: Text(hasLinked ? "Connect" : "Not Linked"),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: GoogleFonts.nunito(fontSize: 18, fontWeight: FontWeight.w800, color: Colors.white),
|
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(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: GoogleFonts.nunito(fontSize: 18, fontWeight: FontWeight.w800, color: Colors.white),
|
style: GoogleFonts.nunito(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -1,39 +1,261 @@
|
|||||||
|
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(
|
||||||
|
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),
|
padding: const EdgeInsets.all(16),
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
childAspectRatio: 0.65,
|
childAspectRatio: 0.65,
|
||||||
crossAxisSpacing: 16,
|
crossAxisSpacing: 16,
|
||||||
mainAxisSpacing: 16,
|
mainAxisSpacing: 16,
|
||||||
),
|
),
|
||||||
itemCount: posts.length,
|
itemCount: _media.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final post = posts[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(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.cardBg,
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.05)),
|
border: Border.all(color: Colors.white.withOpacity(0.05)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(color: Colors.black26, blurRadius: 10, offset: const Offset(0, 5)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -44,24 +266,41 @@ class PostsScreen extends StatelessWidget {
|
|||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
post['image']!,
|
mediaUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 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(
|
Positioned(
|
||||||
top: 8,
|
top: 8,
|
||||||
right: 8,
|
right: 8,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black54,
|
color: Colors.black54.withOpacity(0.6),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
post['type']!,
|
_getMediaTypeName(mediaType),
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 8),
|
style: const TextStyle(color: Colors.white, fontSize: 8, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -69,34 +308,22 @@ class PostsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
post['caption']!,
|
caption,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: GoogleFonts.nunito(fontSize: 12, color: Colors.white),
|
style: GoogleFonts.nunito(fontSize: 12, color: Colors.white, height: 1.3),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
_buildStatChip(Icons.favorite, likes, Colors.pinkAccent),
|
||||||
children: [
|
_buildStatChip(Icons.comment, comments, Colors.blueAccent),
|
||||||
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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -106,12 +333,25 @@ class PostsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
floatingActionButton: FloatingActionButton(
|
}
|
||||||
onPressed: () {},
|
|
||||||
backgroundColor: AppColors.primary,
|
String _getMediaTypeName(String type) {
|
||||||
child: const Icon(Icons.add, color: Colors.white),
|
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)),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
306
lib/features/profile/screens/pricing_screen.dart
Normal file
306
lib/features/profile/screens/pricing_screen.dart
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user