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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
354 changes: 354 additions & 0 deletions lib/core/app_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
import 'package:flutter/foundation.dart';
import 'package:uuid/uuid.dart';
import 'models/enums.dart';
import 'models/user.dart';
import 'models/subscription.dart';
import 'models/booking.dart';
import 'models/wallet.dart';
import 'models/chat.dart';
import 'models/ads.dart';
import 'models/notification.dart';
import 'models/admin.dart';

class AppState extends ChangeNotifier {
final _uuid = const Uuid();

// Auth
UserAccount? _currentUser;
UserAccount? get currentUser => _currentUser;

// Data stores (in-memory)
final List<UserAccount> _users = [];
final List<CustomerProfile> _customers = [];
final List<WorkerProfile> _workers = [];
final List<StoreProfile> _stores = [];

final List<ServiceBooking> _bookings = [];
final List<WalletTransaction> _wallet = [];
final List<ChatThread> _chats = [];
final List<AdPackage> _ads = [];
final List<SubscriptionPlan> _plans = [];
final List<NotificationItem> _notifications = [];
final List<PremiumApprovalRequest> _premiumApprovals = [];

AppState() {
// Seed subscription plans
_plans.addAll([
SubscriptionPlan(
id: _uuid.v4(),
name: 'Basic',
tier: SubscriptionTier.basic,
monthlyPrice: 9.99,
premiumFlag: false,
features: ['Standard listing', 'Basic tools'],
),
SubscriptionPlan(
id: _uuid.v4(),
name: 'Pro',
tier: SubscriptionTier.pro,
monthlyPrice: 19.99,
premiumFlag: false,
features: ['Priority listing', 'Pro tools'],
),
SubscriptionPlan(
id: _uuid.v4(),
name: 'Premium',
tier: SubscriptionTier.premium,
monthlyPrice: 49.99,
premiumFlag: true,
features: ['Top placement', 'Advanced tools'],
),
]);

// Seed a few workers
final worker1 = UserAccount(id: _uuid.v4(), role: UserRole.worker, displayName: 'Ali Electric', walletBalance: 0);
final worker2 = UserAccount(id: _uuid.v4(), role: UserRole.worker, displayName: 'Mona Plumbing', walletBalance: 0);
_users.addAll([worker1, worker2]);
_workers.addAll([
WorkerProfile(userId: worker1.id, skills: ['Electrician'], hourlyRate: 20, serviceAreas: ['City A']),
WorkerProfile(userId: worker2.id, skills: ['Plumber'], hourlyRate: 25, serviceAreas: ['City B']),
]);

// Seed a store
final store = UserAccount(id: _uuid.v4(), role: UserRole.store, displayName: 'FixIt Store', walletBalance: 0);
_users.add(store);
_stores.add(StoreProfile(userId: store.id, storeName: 'FixIt Store'));

// Seed an admin
final admin = UserAccount(id: _uuid.v4(), role: UserRole.admin, displayName: 'Admin');
_users.add(admin);
}

// Public getters
List<WorkerProfile> get workers => List.unmodifiable(_workers);
List<StoreProfile> get stores => List.unmodifiable(_stores);
List<ServiceBooking> get bookings => List.unmodifiable(_bookings);
List<SubscriptionPlan> get plans => List.unmodifiable(_plans);
List<NotificationItem> get notificationsForCurrent =>
_currentUser == null ? [] : _notifications.where((n) => n.userId == _currentUser!.id).toList();
List<PremiumApprovalRequest> get premiumApprovals => List.unmodifiable(_premiumApprovals);
List<ChatThread> get chatThreadsForCurrent => _currentUser == null
? []
: _chats.where((t) => t.customerId == _currentUser!.id || t.workerId == _currentUser!.id).toList();

// Auth actions (mock)
void loginAsRole(UserRole role) {
// Create or reuse a user per role for demo
final existing = _users.firstWhere((u) => u.role == role, orElse: () => UserAccount(id: _uuid.v4(), role: role, displayName: role.name));
if (!_users.contains(existing)) _users.add(existing);
_currentUser = existing;
notifyListeners();
}

void logout() {
_currentUser = null;
notifyListeners();
}

// Profile helpers
WorkerProfile ensureWorkerProfile(String userId) {
final existing = _workers.where((w) => w.userId == userId).toList();
if (existing.isNotEmpty) return existing.first;
final profile = WorkerProfile(userId: userId, skills: ['General'], hourlyRate: 20, serviceAreas: ['City']);
_workers.add(profile);
notifyListeners();
return profile;
}

StoreProfile ensureStoreProfile(String userId, {String storeName = 'My Store'}) {
final existing = _stores.where((s) => s.userId == userId).toList();
if (existing.isNotEmpty) return existing.first;
final profile = StoreProfile(userId: userId, storeName: storeName);
_stores.add(profile);
notifyListeners();
return profile;
}

void submitKycApproved(String userId) {
final profile = _workers.firstWhere((w) => w.userId == userId, orElse: () => ensureWorkerProfile(userId));
profile.kycStatus = KycStatus.approved;
notifyListeners();
}

// Wallet operations
void _addWalletTx({required String userId, required WalletTransactionType type, required double amount, required String description}) {
_wallet.add(WalletTransaction(
id: _uuid.v4(),
userId: userId,
createdAt: DateTime.now(),
type: type,
amount: amount,
description: description,
));
}

void topUpWallet(double amount) {
if (_currentUser == null) return;
_currentUser!.walletBalance += amount;
_addWalletTx(userId: _currentUser!.id, type: WalletTransactionType.topUp, amount: amount, description: 'Top-up');
notifyListeners();
}

bool withdraw(double amount) {
if (_currentUser == null) return false;
if (_currentUser!.walletBalance < amount) return false;
_currentUser!.walletBalance -= amount;
_addWalletTx(userId: _currentUser!.id, type: WalletTransactionType.withdraw, amount: -amount, description: 'Withdraw');
notifyListeners();
return true;
}

// Booking lifecycle
ServiceBooking createBooking({required String workerUserId, required DateTime scheduledAt, required double amount}) {
final customer = _currentUser;
if (customer == null || customer.role != UserRole.customer) {
throw StateError('Only customer can create booking');
}
final booking = ServiceBooking(
id: _uuid.v4(),
customerId: customer.id,
workerId: workerUserId,
scheduledAt: scheduledAt,
amount: amount,
);
_bookings.add(booking);
_notify(userId: workerUserId, title: 'New booking request', body: 'Customer requested a booking.');
notifyListeners();
return booking;
}

void workerRespond({required String bookingId, required bool accept}) {
final booking = _bookings.firstWhere((b) => b.id == bookingId);
if (accept) {
booking.status = BookingStatus.awaitingPrepayment;
_notify(userId: booking.customerId, title: 'Booking accepted', body: 'Please make prepayment to confirm.');
} else {
booking.status = BookingStatus.rejectedByWorker;
_notify(userId: booking.customerId, title: 'Booking rejected', body: 'Worker rejected your request.');
}
notifyListeners();
}

bool customerPrepay(String bookingId) {
final booking = _bookings.firstWhere((b) => b.id == bookingId);
final customer = _users.firstWhere((u) => u.id == booking.customerId);
if (customer.walletBalance < booking.amount) return false;
customer.walletBalance -= booking.amount;
booking.escrowAmount = booking.amount;
booking.status = BookingStatus.confirmed;
_addWalletTx(userId: customer.id, type: WalletTransactionType.escrowDeposit, amount: -booking.amount, description: 'Escrow deposit');
_notify(userId: booking.workerId, title: 'Booking confirmed', body: 'Prepayment received.');
notifyListeners();
return true;
}

void workerMarkCompleted(String bookingId) {
final booking = _bookings.firstWhere((b) => b.id == bookingId);
booking.status = BookingStatus.awaitingCustomerApproval;
_notify(userId: booking.customerId, title: 'Service completed', body: 'Please approve or request refund.');
notifyListeners();
}

void customerApprove({required String bookingId, int? rating, String? review}) {
final booking = _bookings.firstWhere((b) => b.id == bookingId);
booking.status = BookingStatus.released;
booking.rating = rating;
booking.review = review;

final workerUser = _users.firstWhere((u) => u.id == booking.workerId);
workerUser.walletBalance += booking.escrowAmount;
_addWalletTx(userId: workerUser.id, type: WalletTransactionType.escrowRelease, amount: booking.escrowAmount, description: 'Escrow released');
booking.escrowAmount = 0;
_notify(userId: booking.workerId, title: 'Payment released', body: 'Customer approved and funds released.');
notifyListeners();
}

void customerRequestRefund(String bookingId) {
final booking = _bookings.firstWhere((b) => b.id == bookingId);
booking.status = BookingStatus.refundRequested;
_notify(userId: booking.workerId, title: 'Refund requested', body: 'Customer requested a refund.');
notifyListeners();
}

void adminApproveRefund(String bookingId) {
final booking = _bookings.firstWhere((b) => b.id == bookingId);
final customer = _users.firstWhere((u) => u.id == booking.customerId);
customer.walletBalance += booking.escrowAmount;
_addWalletTx(userId: customer.id, type: WalletTransactionType.refundToCustomer, amount: booking.escrowAmount, description: 'Refund');
booking.escrowAmount = 0;
booking.status = BookingStatus.refunded;
_notify(userId: booking.customerId, title: 'Refunded', body: 'Admin approved your refund.');
_notify(userId: booking.workerId, title: 'Refund processed', body: 'Booking amount refunded to customer.');
notifyListeners();
}

// Subscriptions
bool subscribeWorker(String workerUserId, SubscriptionTier tier) {
final profile = _workers.firstWhere((w) => w.userId == workerUserId);
final plan = _plans.firstWhere((p) => p.tier == tier);
final user = _users.firstWhere((u) => u.id == workerUserId);
if (user.walletBalance < plan.monthlyPrice) return false;
user.walletBalance -= plan.monthlyPrice;
if (plan.premiumFlag) {
_premiumApprovals.add(PremiumApprovalRequest(
id: _uuid.v4(),
userId: workerUserId,
role: UserRole.worker,
tier: plan.tier,
requestedAt: DateTime.now(),
));
} else {
profile.activeSubscription = plan.tier;
}
_addWalletTx(userId: user.id, type: WalletTransactionType.subscriptionFee, amount: -plan.monthlyPrice, description: 'Subscription ${plan.name}');
notifyListeners();
return true;
}

bool subscribeStore(String storeUserId, SubscriptionTier tier) {
final profile = _stores.firstWhere((s) => s.userId == storeUserId);
final plan = _plans.firstWhere((p) => p.tier == tier);
final user = _users.firstWhere((u) => u.id == storeUserId);
if (user.walletBalance < plan.monthlyPrice) return false;
user.walletBalance -= plan.monthlyPrice;
if (plan.premiumFlag) {
_premiumApprovals.add(PremiumApprovalRequest(
id: _uuid.v4(),
userId: storeUserId,
role: UserRole.store,
tier: plan.tier,
requestedAt: DateTime.now(),
));
} else {
profile.activeSubscription = plan.tier;
}
_addWalletTx(userId: user.id, type: WalletTransactionType.subscriptionFee, amount: -plan.monthlyPrice, description: 'Subscription ${plan.name}');
notifyListeners();
return true;
}

// Admin actions
void adminApprovePremium(String requestId, {required bool approve}) {
final request = _premiumApprovals.firstWhere((r) => r.id == requestId);
request.status = approve ? ApprovalStatus.approved : ApprovalStatus.rejected;
if (approve) {
if (request.role == UserRole.worker) {
final profile = _workers.firstWhere((w) => w.userId == request.userId);
profile.activeSubscription = request.tier;
} else if (request.role == UserRole.store) {
final profile = _stores.firstWhere((s) => s.userId == request.userId);
profile.activeSubscription = request.tier;
}
_notify(userId: request.userId, title: 'Premium approved', body: 'Your premium plan was approved.');
} else {
_notify(userId: request.userId, title: 'Premium rejected', body: 'Your premium plan was rejected.');
}
notifyListeners();
}

// Ads
bool purchaseAd({required String storeUserId, required AdPackageType type}) {
final user = _users.firstWhere((u) => u.id == storeUserId);
final double price = switch (type) { AdPackageType.bronze => 5, AdPackageType.silver => 10, AdPackageType.gold => 20 };
if (user.walletBalance < price) return false;
user.walletBalance -= price;
_ads.add(AdPackage(
id: _uuid.v4(),
storeUserId: storeUserId,
type: type,
purchasedAt: DateTime.now(),
expiresAt: DateTime.now().add(const Duration(days: 30)),
));
_addWalletTx(userId: user.id, type: WalletTransactionType.adPurchase, amount: -price, description: 'Ad ${type.name}');
notifyListeners();
return true;
}

// Products (Store)
void addProduct({required String storeUserId, required String name, required double price, int stock = 0}) {
final store = _stores.firstWhere((s) => s.userId == storeUserId);
store.products.add(Product(id: _uuid.v4(), name: name, price: price, stock: stock));
notifyListeners();
}

// Chat (simple)
ChatThread openThread(String customerId, String workerId) {
final existing = _chats.where((t) => t.customerId == customerId && t.workerId == workerId).toList();
if (existing.isNotEmpty) return existing.first;
final thread = ChatThread(id: _uuid.v4(), customerId: customerId, workerId: workerId);
_chats.add(thread);
notifyListeners();
return thread;
}

void sendMessage({required String threadId, required String senderId, required String text}) {
final thread = _chats.firstWhere((t) => t.id == threadId);
thread.messages.add(ChatMessage(id: _uuid.v4(), senderId: senderId, sentAt: DateTime.now(), text: text));
notifyListeners();
}

// Notifications
void _notify({required String userId, required String title, required String body}) {
_notifications.add(NotificationItem(id: _uuid.v4(), userId: userId, createdAt: DateTime.now(), title: title, body: body));
}
}
21 changes: 21 additions & 0 deletions lib/core/models/admin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:khadamati/core/models/enums.dart';

enum ApprovalStatus { pending, approved, rejected }

class PremiumApprovalRequest {
final String id;
final String userId;
final UserRole role; // worker or store
final SubscriptionTier tier; // should be premium
final DateTime requestedAt;
ApprovalStatus status;

PremiumApprovalRequest({
required this.id,
required this.userId,
required this.role,
required this.tier,
required this.requestedAt,
this.status = ApprovalStatus.pending,
});
}
19 changes: 19 additions & 0 deletions lib/core/models/ads.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:khadamati/core/models/enums.dart';

class AdPackage {
final String id;
final String storeUserId;
final AdPackageType type;
final DateTime purchasedAt;
final DateTime expiresAt;

AdPackage({
required this.id,
required this.storeUserId,
required this.type,
required this.purchasedAt,
required this.expiresAt,
});

bool get isActive => DateTime.now().isBefore(expiresAt);
}
Loading