169 lines
4.7 KiB
Dart
169 lines
4.7 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class ApiService with ChangeNotifier {
|
|
String? _baseUrl;
|
|
String? _consumerKey;
|
|
String? _consumerSecret;
|
|
bool _isLoading = false;
|
|
String? _error;
|
|
|
|
bool get isLoading => _isLoading;
|
|
String? get error => _error;
|
|
bool get isLoggedIn => _baseUrl != null && _consumerKey != null && _consumerSecret != null;
|
|
|
|
ApiService();
|
|
|
|
void connect(String url, String key, String secret) {
|
|
_baseUrl = url;
|
|
_consumerKey = key;
|
|
_consumerSecret = secret;
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> verifyCredentials(String url, String key, String secret) async {
|
|
_isLoading = true;
|
|
_error = null;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// Normalize URL
|
|
String baseUrl = url.trim();
|
|
if (!baseUrl.startsWith('http')) baseUrl = 'https://$baseUrl';
|
|
if (baseUrl.endsWith('/')) baseUrl = baseUrl.substring(0, baseUrl.length - 1);
|
|
|
|
// Construct WC API URL
|
|
final apiUri = Uri.parse('$baseUrl/wp-json/wc/v3/system_status');
|
|
|
|
final response = await http.get(
|
|
apiUri,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Basic ${base64Encode(utf8.encode('$key:$secret'))}',
|
|
},
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
String msg = 'Connection failed: ${response.statusCode}';
|
|
try {
|
|
final body = jsonDecode(response.body);
|
|
msg = body['message'] ?? msg;
|
|
} catch (_) {}
|
|
throw Exception(msg);
|
|
}
|
|
} catch (e) {
|
|
_error = e.toString().contains('FormatException')
|
|
? 'Invalid Response. Is this a WooCommerce site?'
|
|
: e.toString().replaceAll('Exception: ', '');
|
|
rethrow;
|
|
} finally {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void disconnect() {
|
|
_baseUrl = null;
|
|
_consumerKey = null;
|
|
_consumerSecret = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
disconnect();
|
|
}
|
|
|
|
// --- Products ---
|
|
|
|
Map<String, String> get _headers => {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Basic ${base64Encode(utf8.encode('$_consumerKey:$_consumerSecret'))}',
|
|
};
|
|
|
|
// Helper to build WC API URIs
|
|
Uri _getUri(String path, [Map<String, String>? queryParams]) {
|
|
// Ensuring path starts with /wp-json/wc/v3
|
|
// We assume _baseUrl is just the domain e.g. https://site.com
|
|
// We append the internal implementation path
|
|
return Uri.parse('$_baseUrl/wp-json/wc/v3$path').replace(queryParameters: queryParams);
|
|
}
|
|
|
|
Future<List<dynamic>> getProducts({int page = 1, String search = ''}) async {
|
|
if (!isLoggedIn) throw Exception('Not logged in');
|
|
|
|
final uri = _getUri('/products', {
|
|
'page': page.toString(),
|
|
'per_page': '20',
|
|
'search': search,
|
|
});
|
|
|
|
final response = await http.get(uri, headers: _headers);
|
|
|
|
if (response.statusCode == 200) {
|
|
try {
|
|
return jsonDecode(response.body);
|
|
} on FormatException {
|
|
throw Exception('Invalid JSON response');
|
|
}
|
|
} else {
|
|
throw Exception('Failed to load products: ${response.statusCode}');
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>> getProduct(dynamic id) async {
|
|
if (!isLoggedIn) throw Exception('Not logged in');
|
|
|
|
final uri = _getUri('/products/$id');
|
|
final response = await http.get(uri, headers: _headers);
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
throw Exception('Failed to load product');
|
|
}
|
|
}
|
|
|
|
Future<void> createProduct(Map<String, dynamic> productData) async {
|
|
if (!isLoggedIn) throw Exception('Not logged in');
|
|
|
|
final uri = _getUri('/products');
|
|
final response = await http.post(
|
|
uri,
|
|
headers: _headers,
|
|
body: jsonEncode(productData),
|
|
);
|
|
|
|
if (response.statusCode != 200 && response.statusCode != 201) {
|
|
throw Exception('Failed to create product: ${response.body}');
|
|
}
|
|
}
|
|
|
|
Future<void> updateProduct(int id, Map<String, dynamic> productData) async {
|
|
if (!isLoggedIn) throw Exception('Not logged in');
|
|
|
|
final uri = _getUri('/products/$id');
|
|
final response = await http.put(
|
|
uri,
|
|
headers: _headers,
|
|
body: jsonEncode(productData),
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
throw Exception('Failed to update product: ${response.body}');
|
|
}
|
|
}
|
|
|
|
Future<void> deleteProduct(int id) async {
|
|
if (!isLoggedIn) throw Exception('Not logged in');
|
|
|
|
final uri = _getUri('/products/$id', {'force': 'true'});
|
|
final response = await http.delete(uri, headers: _headers);
|
|
|
|
if (response.statusCode != 200) {
|
|
throw Exception('Failed to delete product');
|
|
}
|
|
}
|
|
}
|