Initialize project and update portal port configuration
Set default portal port to 8081, fix Dart build issue in cart screen, and update setup documentation. Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
47
lib/services/app_config_service.dart
Normal file
47
lib/services/app_config_service.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class AppConfigService {
|
||||
AppConfigService._();
|
||||
|
||||
static final AppConfigService instance = AppConfigService._();
|
||||
|
||||
bool _initialized = false;
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (_initialized) return;
|
||||
|
||||
final configRaw =
|
||||
await rootBundle.loadString('assets/config/runtime_config.json');
|
||||
final Map<String, dynamic> config = json.decode(configRaw);
|
||||
|
||||
await Firebase.initializeApp(
|
||||
options: FirebaseOptions(
|
||||
apiKey: _required(config, 'firebase_api_key'),
|
||||
appId: _required(config, 'firebase_app_id'),
|
||||
messagingSenderId: _required(config, 'firebase_messaging_sender_id'),
|
||||
projectId: _required(config, 'firebase_project_id'),
|
||||
authDomain: _required(config, 'firebase_auth_domain'),
|
||||
storageBucket: _required(config, 'firebase_storage_bucket'),
|
||||
measurementId: _optional(config, 'firebase_measurement_id'),
|
||||
),
|
||||
);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
String _required(Map<String, dynamic> config, String key) {
|
||||
final value = (config[key] ?? '').toString().trim();
|
||||
if (value.isEmpty || value.startsWith('replace-with-')) {
|
||||
throw StateError('Missing required runtime configuration: $key');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
String? _optional(Map<String, dynamic> config, String key) {
|
||||
final value = (config[key] ?? '').toString().trim();
|
||||
return value.isEmpty ? null : value;
|
||||
}
|
||||
}
|
||||
48
lib/services/auth_service.dart
Normal file
48
lib/services/auth_service.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
|
||||
class AuthService {
|
||||
AuthService._();
|
||||
|
||||
static final AuthService instance = AuthService._();
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
||||
Stream<User?> authStateChanges() => _auth.authStateChanges();
|
||||
|
||||
User? get currentUser => _auth.currentUser;
|
||||
|
||||
Future<UserCredential> signInWithEmailPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
}) {
|
||||
return _auth.signInWithEmailAndPassword(email: email, password: password);
|
||||
}
|
||||
|
||||
Future<UserCredential> registerWithEmailPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
}) {
|
||||
return _auth.createUserWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendPasswordResetEmail(String email) {
|
||||
return _auth.sendPasswordResetEmail(email: email);
|
||||
}
|
||||
|
||||
Future<UserCredential> signInWithGoogle() {
|
||||
final provider = GoogleAuthProvider();
|
||||
provider.setCustomParameters({'prompt': 'select_account'});
|
||||
return _auth.signInWithPopup(provider);
|
||||
}
|
||||
|
||||
Future<UserCredential> signInWithGithub() {
|
||||
final provider = GithubAuthProvider();
|
||||
return _auth.signInWithPopup(provider);
|
||||
}
|
||||
|
||||
Future<void> signOut() {
|
||||
return _auth.signOut();
|
||||
}
|
||||
}
|
||||
43
lib/services/cart_service.dart
Normal file
43
lib/services/cart_service.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../models/cart_item.dart';
|
||||
import '../models/product.dart';
|
||||
|
||||
class CartService extends ChangeNotifier {
|
||||
final List<CartItem> _items = [];
|
||||
|
||||
List<CartItem> get items => List.unmodifiable(_items);
|
||||
|
||||
int get totalItems => _items.fold(0, (sum, item) => sum + item.quantity);
|
||||
|
||||
double get totalPrice =>
|
||||
_items.fold(0.0, (sum, item) => sum + item.lineTotal);
|
||||
|
||||
void addProduct(Product product) {
|
||||
final index = _items.indexWhere((item) => item.product.id == product.id);
|
||||
if (index == -1) {
|
||||
_items.add(CartItem(product: product));
|
||||
} else {
|
||||
_items[index].quantity += 1;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void removeProduct(String productId) {
|
||||
_items.removeWhere((item) => item.product.id == productId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void updateQuantity(String productId, int quantity) {
|
||||
if (quantity < 1) return;
|
||||
final index = _items.indexWhere((item) => item.product.id == productId);
|
||||
if (index == -1) return;
|
||||
_items[index].quantity = quantity;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_items.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
55
lib/services/product_repository.dart
Normal file
55
lib/services/product_repository.dart
Normal file
@ -0,0 +1,55 @@
|
||||
import '../models/product.dart';
|
||||
|
||||
class ProductRepository {
|
||||
static const List<Product> products = [
|
||||
Product(
|
||||
id: 'p1',
|
||||
name: 'Minimalist Chair',
|
||||
description: 'Ergonomic chair with breathable fabric and oak finish.',
|
||||
imageUrl: 'https://picsum.photos/seed/chair/900/600',
|
||||
price: 129.99,
|
||||
),
|
||||
Product(
|
||||
id: 'p2',
|
||||
name: 'Modern Table Lamp',
|
||||
description: 'Warm ambient lighting with dimmable touch controls.',
|
||||
imageUrl: 'https://picsum.photos/seed/lamp/900/600',
|
||||
price: 59.50,
|
||||
),
|
||||
Product(
|
||||
id: 'p3',
|
||||
name: 'Noise-Cancel Headphones',
|
||||
description: 'Wireless over-ear headphones with premium audio.',
|
||||
imageUrl: 'https://picsum.photos/seed/headphones/900/600',
|
||||
price: 219.00,
|
||||
),
|
||||
Product(
|
||||
id: 'p4',
|
||||
name: 'Smart Watch',
|
||||
description: 'Fitness tracking, sleep monitoring, and message alerts.',
|
||||
imageUrl: 'https://picsum.photos/seed/watch/900/600',
|
||||
price: 179.90,
|
||||
),
|
||||
Product(
|
||||
id: 'p5',
|
||||
name: 'Travel Backpack',
|
||||
description: 'Water-resistant backpack with dedicated laptop sleeve.',
|
||||
imageUrl: 'https://picsum.photos/seed/backpack/900/600',
|
||||
price: 89.00,
|
||||
),
|
||||
Product(
|
||||
id: 'p6',
|
||||
name: 'Mechanical Keyboard',
|
||||
description: 'Hot-swappable switches and customizable RGB lighting.',
|
||||
imageUrl: 'https://picsum.photos/seed/keyboard/900/600',
|
||||
price: 149.00,
|
||||
),
|
||||
];
|
||||
|
||||
static Product? byId(String id) {
|
||||
for (final product in products) {
|
||||
if (product.id == id) return product;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user