ebay screen connected to backend.
This commit is contained in:
parent
c6f2b15453
commit
a7d57dfa72
@ -7,8 +7,8 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.autos"
|
namespace = "com.example.autos"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = 36
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
@ -20,25 +20,21 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "com.example.autos"
|
applicationId = "com.example.autos"
|
||||||
// You can update the following values to match your application needs.
|
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = 36
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
|
||||||
|
|||||||
@ -21,6 +21,9 @@ class ApiEndpoints {
|
|||||||
static const turn14Save = '/api/auth/turn14/save';
|
static const turn14Save = '/api/auth/turn14/save';
|
||||||
static const turn14Status = '/api/auth/turn14/status';
|
static const turn14Status = '/api/auth/turn14/status';
|
||||||
|
|
||||||
|
///Ebay
|
||||||
|
static const checkstorestatus = '/api/auth/ebay/store/checkstorestatus';
|
||||||
|
|
||||||
///Brands
|
///Brands
|
||||||
static const String brands = "/v1/brands";
|
static const String brands = "/v1/brands";
|
||||||
|
|
||||||
|
|||||||
26
lib/core/utils/date_time_utils.dart
Normal file
26
lib/core/utils/date_time_utils.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
String formatLastOpenedFromString(String? raw) {
|
||||||
|
if (raw == null || raw.isEmpty) return "—";
|
||||||
|
|
||||||
|
DateTime? date;
|
||||||
|
|
||||||
|
try {
|
||||||
|
date = DateTime.parse(raw).toLocal();
|
||||||
|
} catch (_) {
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
final diff = now.difference(date);
|
||||||
|
|
||||||
|
if (diff.inMinutes < 1) {
|
||||||
|
return "just now";
|
||||||
|
} else if (diff.inMinutes < 60) {
|
||||||
|
return "${diff.inMinutes} mins ago";
|
||||||
|
} else if (diff.inHours < 24) {
|
||||||
|
return "${diff.inHours} hrs ago";
|
||||||
|
} else if (diff.inDays < 7) {
|
||||||
|
return "${diff.inDays} days ago";
|
||||||
|
} else {
|
||||||
|
return "${date.day}/${date.month}/${date.year}";
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/core/utils/ebay_webview_screen.dart
Normal file
56
lib/core/utils/ebay_webview_screen.dart
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
class EbayWebViewScreen extends StatefulWidget {
|
||||||
|
final String url;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
const EbayWebViewScreen({
|
||||||
|
super.key,
|
||||||
|
required this.url,
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EbayWebViewScreen> createState() => _EbayWebViewScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EbayWebViewScreenState extends State<EbayWebViewScreen> {
|
||||||
|
late final WebViewController _controller;
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_controller = WebViewController()
|
||||||
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
|
..setNavigationDelegate(
|
||||||
|
NavigationDelegate(
|
||||||
|
onPageFinished: (_) {
|
||||||
|
setState(() => _isLoading = false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..loadRequest(Uri.parse(widget.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(widget.title),
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
WebViewWidget(controller: _controller),
|
||||||
|
|
||||||
|
if (_isLoading)
|
||||||
|
const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,71 +1,13 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:autos/domain/entities/turn14.dart';
|
import 'package:autos/domain/entities/turn14.dart';
|
||||||
|
|
||||||
class Turn14Response {
|
|
||||||
final String code;
|
|
||||||
final String message;
|
|
||||||
final String userId;
|
|
||||||
final String accessToken;
|
|
||||||
|
|
||||||
const Turn14Response({
|
|
||||||
required this.code,
|
|
||||||
required this.message,
|
|
||||||
required this.userId,
|
|
||||||
required this.accessToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Turn14Response.fromJson(Map<String, dynamic> json) {
|
|
||||||
return Turn14Response(
|
|
||||||
code: json['code'] ?? '',
|
|
||||||
message: json['message'] ?? '',
|
|
||||||
userId: json['userid'] ?? '',
|
|
||||||
accessToken: json['access_token'] ?? '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'code': code,
|
|
||||||
'message': message,
|
|
||||||
'userid': userId,
|
|
||||||
'access_token': accessToken,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert model → raw JSON string
|
|
||||||
String toRawJson() => jsonEncode(toJson());
|
|
||||||
|
|
||||||
/// Convert raw JSON string → model
|
|
||||||
factory Turn14Response.fromRawJson(String raw) =>
|
|
||||||
Turn14Response.fromJson(jsonDecode(raw));
|
|
||||||
|
|
||||||
/// Convert Response → Domain Entity
|
|
||||||
Turn14Entity toEntity() {
|
|
||||||
return Turn14Entity(
|
|
||||||
code: code,
|
|
||||||
message: message,
|
|
||||||
userId: userId,
|
|
||||||
accessToken: accessToken,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'Turn14Response(code: $code, message: $message, userId: $userId, accessToken: $accessToken)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Turn14StatusModel {
|
class Turn14StatusModel {
|
||||||
final String userId;
|
final String userId;
|
||||||
final bool hasCredentials;
|
final bool hasCredentials;
|
||||||
final String? clientId;
|
final String? clientId;
|
||||||
final String? clientSecret;
|
final String? clientSecret;
|
||||||
final String? accessToken;
|
final String? accessToken;
|
||||||
final int? expiresIn; // ✅ FIXED: should be int
|
final String? expiresIn;
|
||||||
final String? code; // ✅ ADDED
|
final String? code;
|
||||||
final String? message; // ✅ ADDED
|
final String? message;
|
||||||
|
|
||||||
Turn14StatusModel({
|
Turn14StatusModel({
|
||||||
required this.userId,
|
required this.userId,
|
||||||
@ -78,7 +20,6 @@ class Turn14StatusModel {
|
|||||||
this.message,
|
this.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// ✅ SAFE FROM JSON
|
|
||||||
factory Turn14StatusModel.fromJson(Map<String, dynamic> json) {
|
factory Turn14StatusModel.fromJson(Map<String, dynamic> json) {
|
||||||
final credentials = json["credentials"];
|
final credentials = json["credentials"];
|
||||||
final tokenInfo = json["tokenInfo"];
|
final tokenInfo = json["tokenInfo"];
|
||||||
@ -86,21 +27,34 @@ class Turn14StatusModel {
|
|||||||
return Turn14StatusModel(
|
return Turn14StatusModel(
|
||||||
userId: json["userid"]?.toString() ?? "",
|
userId: json["userid"]?.toString() ?? "",
|
||||||
hasCredentials: json["hasCredentials"] ?? false,
|
hasCredentials: json["hasCredentials"] ?? false,
|
||||||
|
|
||||||
clientId: credentials?["turn14clientid"],
|
clientId: credentials?["turn14clientid"],
|
||||||
clientSecret: credentials?["turn14clientsecret"],
|
clientSecret: credentials?["turn14clientsecret"],
|
||||||
|
|
||||||
accessToken: tokenInfo?["access_token"],
|
accessToken: tokenInfo?["access_token"],
|
||||||
expiresIn: tokenInfo?["expires_in"],
|
expiresIn: tokenInfo?["expires_in"]?.toString(),
|
||||||
|
|
||||||
code: json["code"],
|
code: json["code"],
|
||||||
message: json["message"],
|
message: json["message"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✅ MODEL → ENTITY (CLEAN ARCH)
|
Map<String, dynamic> toJson() => {
|
||||||
Turn14StatusEntity toEntity() {
|
"userid": userId,
|
||||||
return Turn14StatusEntity(
|
"hasCredentials": hasCredentials,
|
||||||
|
"credentials": {
|
||||||
|
"turn14clientid": clientId,
|
||||||
|
"turn14clientsecret": clientSecret,
|
||||||
|
},
|
||||||
|
"tokenInfo": {
|
||||||
|
"access_token": accessToken,
|
||||||
|
"expires_in": expiresIn,
|
||||||
|
},
|
||||||
|
"code": code,
|
||||||
|
"message": message,
|
||||||
|
};
|
||||||
|
|
||||||
|
Turn14Entity toEntity() {
|
||||||
|
return Turn14Entity(
|
||||||
|
code: code ?? '',
|
||||||
|
message: message ?? '',
|
||||||
userId: userId,
|
userId: userId,
|
||||||
hasCredentials: hasCredentials,
|
hasCredentials: hasCredentials,
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
|
|||||||
54
lib/data/repositories/ebay_repository_impl.dart
Normal file
54
lib/data/repositories/ebay_repository_impl.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:autos/core/constants/api_endpoints.dart';
|
||||||
|
import 'package:autos/data/models/user_model.dart';
|
||||||
|
import 'package:autos/data/sources/remote/api_service.dart';
|
||||||
|
import 'package:autos/domain/entities/ebay.dart';
|
||||||
|
import 'package:autos/data/repositories/token_repository.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
|
abstract class EbayRepository {
|
||||||
|
/// Return a single store or null if none exists
|
||||||
|
Future<EbayEntity?> getEbayStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
class EbayRepositoryImpl implements EbayRepository {
|
||||||
|
final ApiService _apiService;
|
||||||
|
final TokenRepository _tokenRepository;
|
||||||
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||||
|
static const _userKey = 'logged_in_user';
|
||||||
|
|
||||||
|
EbayRepositoryImpl(this._apiService, this._tokenRepository);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<EbayEntity?> getEbayStatus() async {
|
||||||
|
// 1️⃣ Read logged-in user
|
||||||
|
final jsonString = await _storage.read(key: _userKey);
|
||||||
|
if (jsonString == null) return null;
|
||||||
|
|
||||||
|
final user = UserModel.fromJson(jsonDecode(jsonString));
|
||||||
|
|
||||||
|
// 2️⃣ Get valid token
|
||||||
|
final token = await _tokenRepository.getValidToken(user.id);
|
||||||
|
|
||||||
|
// 3️⃣ Call eBay API
|
||||||
|
final response = await _apiService.postWithOptions(
|
||||||
|
ApiEndpoints.checkstorestatus,
|
||||||
|
data: {"userid": user.id},
|
||||||
|
headers: {"Authorization": "Bearer $token"},
|
||||||
|
);
|
||||||
|
|
||||||
|
debugPrint('🟢 Ebay API Raw Response: ${response.data}');
|
||||||
|
|
||||||
|
// 4️⃣ Extract 'store' from response
|
||||||
|
final data = response.data;
|
||||||
|
if (data == null || data is! Map<String, dynamic>) return null;
|
||||||
|
|
||||||
|
final storeJson = data['store'];
|
||||||
|
if (storeJson == null || storeJson is! Map<String, dynamic>) return null;
|
||||||
|
|
||||||
|
// 5️⃣ Convert to entity
|
||||||
|
final storeEntity = EbayEntity.fromJson(storeJson);
|
||||||
|
return storeEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,26 +32,26 @@ class TokenRepository {
|
|||||||
|
|
||||||
/// 🔄 Refresh Token API
|
/// 🔄 Refresh Token API
|
||||||
Future<String> _refreshToken(String userId) async {
|
Future<String> _refreshToken(String userId) async {
|
||||||
final response = await apiService.post(
|
final response = await apiService.post(ApiEndpoints.getToken, {
|
||||||
ApiEndpoints.getToken,
|
"userid": userId,
|
||||||
{"userid": userId},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
final data = response.data;
|
final data = response.data;
|
||||||
|
final code = data["code"];
|
||||||
if (data["code"] != "TOKEN_UPDATED") {
|
|
||||||
throw Exception(data["message"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
final token = data["access_token"];
|
final token = data["access_token"];
|
||||||
|
|
||||||
/// ✅ Save New Token + Timestamp
|
if (code == "TOKEN_VALID" || code == "TOKEN_UPDATED") {
|
||||||
await _storage.write(key: _tokenKey, value: token);
|
// ✅ Save token and timestamp if updated, otherwise just use existing token
|
||||||
await _storage.write(
|
if (code == "TOKEN_UPDATED") {
|
||||||
key: _tokenTimeKey,
|
await _storage.write(key: _tokenKey, value: token);
|
||||||
value: DateTime.now().toIso8601String(),
|
await _storage.write(
|
||||||
);
|
key: _tokenTimeKey,
|
||||||
|
value: DateTime.now().toIso8601String(),
|
||||||
return token;
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
} else {
|
||||||
|
throw Exception("Failed to get Turn14 token: ${data["message"]}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ class Turn14RepositoryImpl implements Turn14Repository {
|
|||||||
|
|
||||||
if (data['code'] == "TURN14_SAVED") {
|
if (data['code'] == "TURN14_SAVED") {
|
||||||
/// Convert Response → Entity
|
/// Convert Response → Entity
|
||||||
return Turn14Response.fromJson(data).toEntity();
|
return Turn14StatusModel.fromJson(data).toEntity();
|
||||||
} else {
|
} else {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
data['message'] ?? "Failed to save Turn14 credentials",
|
data['message'] ?? "Failed to save Turn14 credentials",
|
||||||
@ -53,7 +53,7 @@ class Turn14RepositoryImpl implements Turn14Repository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Turn14StatusEntity> status(String userId) async {
|
Future<Turn14Entity> status(String userId) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiService.post(ApiEndpoints.turn14Status, {
|
final response = await _apiService.post(ApiEndpoints.turn14Status, {
|
||||||
"userid": userId,
|
"userid": userId,
|
||||||
|
|||||||
45
lib/domain/entities/ebay.dart
Normal file
45
lib/domain/entities/ebay.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
class EbayEntity {
|
||||||
|
final String userId;
|
||||||
|
final String storeName;
|
||||||
|
final String storeDescription;
|
||||||
|
final String storeUrl;
|
||||||
|
final String storeUrlPath;
|
||||||
|
final String? storeLastOpenedTimeRaw;
|
||||||
|
final String storeLogoUrl;
|
||||||
|
|
||||||
|
EbayEntity({
|
||||||
|
required this.userId,
|
||||||
|
required this.storeName,
|
||||||
|
required this.storeDescription,
|
||||||
|
required this.storeUrl,
|
||||||
|
required this.storeUrlPath,
|
||||||
|
required this.storeLastOpenedTimeRaw,
|
||||||
|
required this.storeLogoUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Create an instance from JSON map
|
||||||
|
factory EbayEntity.fromJson(Map<String, dynamic> json) {
|
||||||
|
return EbayEntity(
|
||||||
|
userId: json['userid'] as String,
|
||||||
|
storeName: json['store_name'] as String,
|
||||||
|
storeDescription: json['store_description'] as String,
|
||||||
|
storeUrl: json['store_url'] as String,
|
||||||
|
storeUrlPath: json['store_url_path'] as String,
|
||||||
|
storeLastOpenedTimeRaw: json['store_last_opened_time_raw'] as String?,
|
||||||
|
storeLogoUrl: json['store_logo_url'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert instance to JSON map
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'userid': userId,
|
||||||
|
'store_name': storeName,
|
||||||
|
'store_description': storeDescription,
|
||||||
|
'store_url': storeUrl,
|
||||||
|
'store_url_path': storeUrlPath,
|
||||||
|
'store_last_opened_time_raw': storeLastOpenedTimeRaw,
|
||||||
|
'store_logo_url': storeLogoUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,16 +2,32 @@ class Turn14Entity {
|
|||||||
final String code;
|
final String code;
|
||||||
final String message;
|
final String message;
|
||||||
final String userId;
|
final String userId;
|
||||||
final String accessToken;
|
|
||||||
|
/// Credentials
|
||||||
|
final bool hasCredentials;
|
||||||
|
final String? clientId;
|
||||||
|
final String? clientSecret;
|
||||||
|
|
||||||
|
/// Token Info
|
||||||
|
final String? accessToken;
|
||||||
|
final String? expiresIn;
|
||||||
|
|
||||||
const Turn14Entity({
|
const Turn14Entity({
|
||||||
required this.code,
|
required this.code,
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.accessToken,
|
required this.hasCredentials,
|
||||||
|
this.clientId,
|
||||||
|
this.clientSecret,
|
||||||
|
this.accessToken,
|
||||||
|
this.expiresIn,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bool get isConnected =>
|
||||||
|
hasCredentials == true && accessToken != null && accessToken!.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Turn14StatusEntity {
|
class Turn14StatusEntity {
|
||||||
final String userId;
|
final String userId;
|
||||||
final bool hasCredentials;
|
final bool hasCredentials;
|
||||||
|
|||||||
@ -6,8 +6,6 @@ import 'package:autos/presentation/screens/auth/login_screen.dart';
|
|||||||
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
|
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
// ✅ ADD THIS IMPORT
|
|
||||||
import 'package:onesignal_flutter/onesignal_flutter.dart';
|
import 'package:onesignal_flutter/onesignal_flutter.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -18,7 +16,6 @@ void main() {
|
|||||||
OneSignal.initialize("271c3931-07ee-46b6-8629-7f0d63f58085");
|
OneSignal.initialize("271c3931-07ee-46b6-8629-7f0d63f58085");
|
||||||
OneSignal.Notifications.requestPermission(false);
|
OneSignal.Notifications.requestPermission(false);
|
||||||
|
|
||||||
// KEEP YOUR ORIGINAL LOGIC
|
|
||||||
runApp(const ProviderScope(child: MyApp()));
|
runApp(const ProviderScope(child: MyApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
76
lib/presentation/providers/ebay_provider.dart
Normal file
76
lib/presentation/providers/ebay_provider.dart
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:autos/data/models/turn14_model.dart';
|
||||||
|
import 'package:autos/data/repositories/ebay_repository_impl.dart';
|
||||||
|
import 'package:autos/data/repositories/token_repository.dart';
|
||||||
|
import 'package:autos/data/sources/remote/api_service.dart';
|
||||||
|
import 'package:autos/domain/entities/ebay.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_riverpod/legacy.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
|
/// ✅ API SERVICE PROVIDER
|
||||||
|
final ebayApiServiceProvider = Provider<ApiService>((ref) => ApiService());
|
||||||
|
|
||||||
|
/// ✅ TOKEN REPOSITORY PROVIDER
|
||||||
|
final _tokenRepositoryProvider = Provider<TokenRepository>((ref) {
|
||||||
|
final api = ref.read(ebayApiServiceProvider);
|
||||||
|
return TokenRepository(api);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// ✅ REPOSITORY PROVIDER
|
||||||
|
final ebayRepositoryProvider = Provider<EbayRepositoryImpl>((ref) {
|
||||||
|
final api = ref.read(ebayApiServiceProvider);
|
||||||
|
final tokenRepo = ref.read(_tokenRepositoryProvider);
|
||||||
|
return EbayRepositoryImpl(api, tokenRepo);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// ✅ STATE NOTIFIER PROVIDER
|
||||||
|
final ebayProvider =
|
||||||
|
StateNotifierProvider<EbayNotifier, AsyncValue<EbayEntity?>>((ref) {
|
||||||
|
final repo = ref.read(ebayRepositoryProvider);
|
||||||
|
return EbayNotifier(repo);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// ✅ NOTIFIER
|
||||||
|
class EbayNotifier extends StateNotifier<AsyncValue<EbayEntity?>> {
|
||||||
|
final EbayRepositoryImpl repository;
|
||||||
|
|
||||||
|
EbayNotifier(this.repository) : super(const AsyncValue.loading()) {
|
||||||
|
fetchStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ✅ FETCH STORE
|
||||||
|
Future<void> fetchStore() async {
|
||||||
|
try {
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
|
// 1️⃣ Read stored credentials
|
||||||
|
const storage = FlutterSecureStorage();
|
||||||
|
final saved = await storage.read(key: "turn14_credentials");
|
||||||
|
if (saved == null) {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final decoded = jsonDecode(saved);
|
||||||
|
final turn14 = Turn14StatusModel.fromJson(decoded);
|
||||||
|
if (turn14.hasCredentials != true) {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Fetch store from backend
|
||||||
|
final EbayEntity? store = await repository.getEbayStatus();
|
||||||
|
// repository should now return a single EbayEntity? instead of List<EbayEntity>
|
||||||
|
|
||||||
|
// 3️⃣ Update state
|
||||||
|
state = AsyncValue.data(store);
|
||||||
|
} catch (e, st) {
|
||||||
|
debugPrint("❌ EBAY FETCH ERROR: $e");
|
||||||
|
state = AsyncValue.error(e, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -8,18 +8,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:flutter_riverpod/legacy.dart';
|
import 'package:flutter_riverpod/legacy.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
|
|
||||||
/// ------------------------------------------------------------
|
/// ------------------------------------------------------------
|
||||||
/// Service Providers
|
/// Service Providers
|
||||||
/// ------------------------------------------------------------
|
/// ------------------------------------------------------------
|
||||||
|
|
||||||
final turn14ApiServiceProvider =
|
final turn14ApiServiceProvider = Provider<ApiService>(
|
||||||
Provider<ApiService>((ref) => ApiService());
|
(ref) => ApiService(),
|
||||||
|
);
|
||||||
final turn14RepositoryProvider = Provider<Turn14RepositoryImpl>((ref) {
|
|
||||||
return Turn14RepositoryImpl(ref.read(turn14ApiServiceProvider));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
final turn14RepositoryProvider = Provider<Turn14RepositoryImpl>(
|
||||||
|
(ref) => Turn14RepositoryImpl(ref.read(turn14ApiServiceProvider)),
|
||||||
|
);
|
||||||
|
|
||||||
/// ------------------------------------------------------------
|
/// ------------------------------------------------------------
|
||||||
/// Turn14 Notifier
|
/// Turn14 Notifier
|
||||||
@ -27,14 +26,15 @@ final turn14RepositoryProvider = Provider<Turn14RepositoryImpl>((ref) {
|
|||||||
|
|
||||||
class Turn14Notifier extends StateNotifier<AsyncValue<Turn14Entity?>> {
|
class Turn14Notifier extends StateNotifier<AsyncValue<Turn14Entity?>> {
|
||||||
final Turn14RepositoryImpl repository;
|
final Turn14RepositoryImpl repository;
|
||||||
final _storage = const FlutterSecureStorage();
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
static const _turn14StorageKey = "turn14_credentials";
|
static const String _turn14StorageKey = "turn14_credentials";
|
||||||
|
|
||||||
Turn14Notifier(this.repository) : super(const AsyncValue.data(null));
|
Turn14Notifier(this.repository) : super(const AsyncValue.data(null));
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
/// Save Turn14 credentials
|
// Save Turn14 credentials (API + local)
|
||||||
|
// ------------------------------------------------------------
|
||||||
Future<void> saveCredentials({
|
Future<void> saveCredentials({
|
||||||
required String userId,
|
required String userId,
|
||||||
required String clientId,
|
required String clientId,
|
||||||
@ -43,50 +43,127 @@ class Turn14Notifier extends StateNotifier<AsyncValue<Turn14Entity?>> {
|
|||||||
state = const AsyncValue.loading();
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await repository.save(
|
final Turn14Entity entity = await repository.save(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
clientSecret: clientSecret,
|
clientSecret: clientSecret,
|
||||||
);
|
);
|
||||||
|
|
||||||
// if (response is Turn14Response) {
|
// Convert entity → model (for persistence)
|
||||||
// await _storage.write(
|
final model = Turn14StatusModel(
|
||||||
// key: _turn14StorageKey,
|
userId: entity.userId,
|
||||||
// value: response.toRawJson(),
|
hasCredentials: entity.hasCredentials,
|
||||||
// );
|
clientId: entity.clientId,
|
||||||
// }
|
clientSecret: entity.clientSecret,
|
||||||
|
accessToken: entity.accessToken,
|
||||||
|
expiresIn: entity.expiresIn,
|
||||||
|
code: entity.code,
|
||||||
|
message: entity.message,
|
||||||
|
);
|
||||||
|
|
||||||
state = AsyncValue.data(response);
|
await _storage.write(
|
||||||
|
key: _turn14StorageKey,
|
||||||
|
value: jsonEncode(model.toJson()),
|
||||||
|
);
|
||||||
|
|
||||||
|
state = AsyncValue.data(entity);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
state = AsyncValue.error(e, st);
|
state = AsyncValue.error(e, st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load saved Turn14 credentials
|
// ------------------------------------------------------------
|
||||||
|
// Load Turn14 from LOCAL storage
|
||||||
|
// ------------------------------------------------------------
|
||||||
Future<void> loadSavedCredentials() async {
|
Future<void> loadSavedCredentials() async {
|
||||||
final saved = await _storage.read(key: _turn14StorageKey);
|
try {
|
||||||
if (saved == null) return;
|
final saved = await _storage.read(key: _turn14StorageKey);
|
||||||
|
if (saved == null) {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final decoded = jsonDecode(saved);
|
final decoded = jsonDecode(saved);
|
||||||
final model = Turn14Response.fromJson(decoded);
|
final model = Turn14StatusModel.fromJson(decoded);
|
||||||
|
|
||||||
state = AsyncValue.data(model as Turn14Entity?);
|
final entity = Turn14Entity(
|
||||||
|
code: model.code ?? '',
|
||||||
|
message: model.message ?? '',
|
||||||
|
userId: model.userId,
|
||||||
|
hasCredentials: model.hasCredentials,
|
||||||
|
clientId: model.clientId,
|
||||||
|
clientSecret: model.clientSecret,
|
||||||
|
accessToken: model.accessToken,
|
||||||
|
expiresIn: model.expiresIn,
|
||||||
|
);
|
||||||
|
|
||||||
|
state = AsyncValue.data(entity);
|
||||||
|
} catch (e, st) {
|
||||||
|
state = AsyncValue.error(e, st);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear saved Turn14 data
|
// ------------------------------------------------------------
|
||||||
|
// Load Turn14 status from API + persist locally
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
Future<void> loadTurn14Status(String userId) async {
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final status = await repository.status(userId);
|
||||||
|
// Turn14StatusEntity
|
||||||
|
|
||||||
|
if (status.hasCredentials == true) {
|
||||||
|
final entity = Turn14Entity(
|
||||||
|
code: '',
|
||||||
|
message: '',
|
||||||
|
userId: status.userId,
|
||||||
|
hasCredentials: status.hasCredentials,
|
||||||
|
clientId: status.clientId,
|
||||||
|
clientSecret: status.clientSecret,
|
||||||
|
accessToken: status.accessToken,
|
||||||
|
expiresIn: status.expiresIn?.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final model = Turn14StatusModel(
|
||||||
|
userId: entity.userId,
|
||||||
|
hasCredentials: entity.hasCredentials,
|
||||||
|
clientId: entity.clientId,
|
||||||
|
clientSecret: entity.clientSecret,
|
||||||
|
accessToken: entity.accessToken,
|
||||||
|
expiresIn: entity.expiresIn,
|
||||||
|
code: entity.code,
|
||||||
|
message: entity.message,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _storage.write(
|
||||||
|
key: _turn14StorageKey,
|
||||||
|
value: jsonEncode(model.toJson()),
|
||||||
|
);
|
||||||
|
|
||||||
|
state = AsyncValue.data(entity);
|
||||||
|
} else {
|
||||||
|
await clear();
|
||||||
|
}
|
||||||
|
} catch (e, st) {
|
||||||
|
state = AsyncValue.error(e, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Clear Turn14 data (logout / user switch)
|
||||||
|
// ------------------------------------------------------------
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
await _storage.delete(key: _turn14StorageKey);
|
await _storage.delete(key: _turn14StorageKey);
|
||||||
state = const AsyncValue.data(null);
|
state = const AsyncValue.data(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// ------------------------------------------------------------
|
/// ------------------------------------------------------------
|
||||||
/// Riverpod Provider
|
/// Riverpod Provider
|
||||||
/// ------------------------------------------------------------
|
/// ------------------------------------------------------------
|
||||||
|
|
||||||
final turn14Provider =
|
final turn14Provider =
|
||||||
StateNotifierProvider<Turn14Notifier, AsyncValue<Turn14Entity?>>((ref) {
|
StateNotifierProvider<Turn14Notifier, AsyncValue<Turn14Entity?>>(
|
||||||
final repository = ref.read(turn14RepositoryProvider);
|
(ref) => Turn14Notifier(ref.read(turn14RepositoryProvider)),
|
||||||
return Turn14Notifier(repository);
|
);
|
||||||
});
|
|
||||||
|
|||||||
@ -17,8 +17,9 @@ final userRepositoryProvider = Provider<UserRepositoryImpl>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// SINGLE GLOBAL USER PROVIDER
|
/// SINGLE GLOBAL USER PROVIDER
|
||||||
final userProvider =
|
final userProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((
|
||||||
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
|
ref,
|
||||||
|
) {
|
||||||
final repo = ref.read(userRepositoryProvider);
|
final repo = ref.read(userRepositoryProvider);
|
||||||
return UserNotifier(ref, repo);
|
return UserNotifier(ref, repo);
|
||||||
});
|
});
|
||||||
@ -34,8 +35,7 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
|
|||||||
|
|
||||||
AuthAction lastAction = AuthAction.idle;
|
AuthAction lastAction = AuthAction.idle;
|
||||||
|
|
||||||
UserNotifier(this.ref, this.repository)
|
UserNotifier(this.ref, this.repository) : super(const AsyncValue.data(null));
|
||||||
: super(const AsyncValue.data(null));
|
|
||||||
|
|
||||||
// AUTO LOGIN ON APP START
|
// AUTO LOGIN ON APP START
|
||||||
Future<void> loadUserFromStorage() async {
|
Future<void> loadUserFromStorage() async {
|
||||||
@ -75,41 +75,41 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SIGNUP
|
// SIGNUP
|
||||||
Future<void> signup(
|
Future<void> signup(
|
||||||
String name,
|
String name,
|
||||||
String email,
|
String email,
|
||||||
String password,
|
String password,
|
||||||
String phone,
|
String phone,
|
||||||
) async {
|
) async {
|
||||||
lastAction = AuthAction.signup;
|
lastAction = AuthAction.signup;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state = const AsyncValue.loading();
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
// ✅ 1️⃣ Signup → returns USER ENTITY
|
// ✅ 1️⃣ Signup → returns USER ENTITY
|
||||||
final user = await repository.signup(name, email, password, phone);
|
final user = await repository.signup(name, email, password, phone);
|
||||||
|
|
||||||
// ✅ 2️⃣ Convert User → UserModel for storage
|
// ✅ 2️⃣ Convert User → UserModel for storage
|
||||||
final userModel = UserModel.fromEntity(user);
|
final userModel = UserModel.fromEntity(user);
|
||||||
|
|
||||||
// ✅ 3️⃣ Store safely
|
// ✅ 3️⃣ Store safely
|
||||||
await _storage.write(
|
await _storage.write(key: _userKey, value: userModel.toRawJson());
|
||||||
key: _userKey,
|
|
||||||
value: userModel.toRawJson(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ✅ 4️⃣ Update provider state
|
// ✅ 4️⃣ Update provider state
|
||||||
state = AsyncValue.data(user);
|
state = AsyncValue.data(user);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
state = AsyncValue.error(e, st);
|
state = AsyncValue.error(e, st);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ✅ ✅ LOGOUT
|
// ✅ ✅ LOGOUT
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
await _storage.delete(key: _userKey);
|
// await _storage.delete(key: _userKey);
|
||||||
|
// await _storage.delete(key: "turn14_credentials");
|
||||||
|
// state = const AsyncValue.data(null);
|
||||||
|
|
||||||
|
await _storage.deleteAll();
|
||||||
state = const AsyncValue.data(null);
|
state = const AsyncValue.data(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +1,42 @@
|
|||||||
|
import 'package:autos/core/routing/route_paths.dart';
|
||||||
import 'package:autos/core/theme/app_typography.dart';
|
import 'package:autos/core/theme/app_typography.dart';
|
||||||
|
import 'package:autos/core/utils/date_time_utils.dart';
|
||||||
|
import 'package:autos/core/utils/ebay_webview_screen.dart';
|
||||||
|
import 'package:autos/domain/entities/ebay.dart';
|
||||||
|
import 'package:autos/presentation/providers/ebay_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:autos/core/widgets/hamburger_button.dart';
|
import 'package:autos/core/widgets/hamburger_button.dart';
|
||||||
import 'package:autos/core/widgets/side_menu.dart';
|
import 'package:autos/core/widgets/side_menu.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
class EbayScreen extends StatefulWidget {
|
class EbayScreen extends ConsumerStatefulWidget {
|
||||||
const EbayScreen({super.key});
|
const EbayScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EbayScreen> createState() => _EbayScreenState();
|
ConsumerState<EbayScreen> createState() => _EbayScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EbayScreenState extends State<EbayScreen> {
|
class _EbayScreenState extends ConsumerState<EbayScreen> {
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
String selected = "ebay";
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
/// ✅ Fetch ONCE
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ref.read(ebayProvider.notifier).fetchStore();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final double topPadding = MediaQuery.of(context).padding.top + 16;
|
final storeAsync = ref.watch(ebayProvider);
|
||||||
|
final topPadding = MediaQuery.of(context).padding.top + 16;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _scaffoldKey,
|
key: scaffoldKey,
|
||||||
drawer: SideMenu(
|
drawer: SideMenu(selected: "ebay", onItemSelected: (_) {}),
|
||||||
selected: selected,
|
|
||||||
onItemSelected: (key) {
|
|
||||||
setState(() => selected = key);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
// backgroundColor: const Color(0xFFEFFAFF),
|
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
/// TITLE
|
/// TITLE
|
||||||
@ -43,80 +52,312 @@ class _EbayScreenState extends State<EbayScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
/// MAIN BOX UI
|
/// CONTENT
|
||||||
|
Positioned.fill(
|
||||||
|
top: topPadding + 60,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||||
|
child: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 720),
|
||||||
|
child: storeAsync.when(
|
||||||
|
loading: () =>
|
||||||
|
const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (e, _) => _errorCard(e.toString()),
|
||||||
|
data: (store) => store == null
|
||||||
|
? _connectCard(context)
|
||||||
|
: _connectedCard(context, store),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
HamburgerButton(scaffoldKey: scaffoldKey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// CONNECT (NOT CONNECTED)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
Widget _connectCard(BuildContext context) {
|
||||||
|
return _cardWrapper(
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"Connect your eBay store to enable product sync, inventory updates, and order flow.",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 15, color: Colors.black54, height: 1.5),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF00CFFF),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
"Connect your eBay store",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// CONNECTED UI (RESPONSIVE)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
Widget _connectedCard(BuildContext context, EbayEntity store) {
|
||||||
|
final isSmall = MediaQuery.of(context).size.width < 400;
|
||||||
|
|
||||||
|
return _cardWrapper(
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
/// CONNECTED BADGE
|
||||||
Center(
|
Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: MediaQuery.of(context).size.width * 0.90,
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40),
|
|
||||||
margin: EdgeInsets.only(top: topPadding + 60),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.green.shade50,
|
||||||
borderRadius: BorderRadius.circular(25),
|
borderRadius: BorderRadius.circular(20),
|
||||||
boxShadow: [
|
border: Border.all(color: Colors.green.shade300),
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black12,
|
|
||||||
blurRadius: 25,
|
|
||||||
offset: const Offset(0, 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: const Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
/// Description
|
Icon(Icons.check_circle, color: Colors.green, size: 18),
|
||||||
|
SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
"Connect your eBay store to enable product sync, inventory updates, and order flow.",
|
"Connected",
|
||||||
textAlign: TextAlign.center,
|
style: TextStyle(
|
||||||
style: const TextStyle(
|
color: Colors.green,
|
||||||
fontSize: 15,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.black54,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
|
|
||||||
/// BUTTON
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Add eBay authorization flow
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF00CFFF),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
"Connect your eBay store",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
"You'll be redirected to eBay to authorize access, then returned here.",
|
|
||||||
style: const TextStyle(fontSize: 13, color: Colors.black45),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
/// HAMBURGER BUTTON
|
const SizedBox(height: 16),
|
||||||
HamburgerButton(scaffoldKey: _scaffoldKey),
|
|
||||||
|
/// TITLE
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
"eBay connected successfully 🎉",
|
||||||
|
style: AppTypo.h3.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFF00BFFF),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
/// STORE CARD
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: Colors.grey.shade200),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
/// HEADER
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
store.storeLogoUrl.isNotEmpty
|
||||||
|
? Image.network(
|
||||||
|
store.storeLogoUrl,
|
||||||
|
height: 60,
|
||||||
|
width: 60,
|
||||||
|
)
|
||||||
|
: const Icon(Icons.store, size: 60),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
store.storeName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
"Last opened: ${formatLastOpenedFromString(store.storeLastOpenedTimeRaw)}",
|
||||||
|
style: const TextStyle(color: Colors.black54),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
/// DESCRIPTION
|
||||||
|
Text(
|
||||||
|
store.storeDescription,
|
||||||
|
textAlign: TextAlign.justify,
|
||||||
|
style: const TextStyle(fontSize: 14, height: 1.5),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
/// ACTION BUTTONS (RESPONSIVE)
|
||||||
|
isSmall
|
||||||
|
? Column(
|
||||||
|
children: [
|
||||||
|
_primaryButton("Visit eBay Store", () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => EbayWebViewScreen(
|
||||||
|
url: store.storeUrl,
|
||||||
|
title: store.storeName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_outlineButton("Go to Dashboard", () {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
AppRoutePaths.dashboard,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: [
|
||||||
|
_primaryButton("Visit eBay Store", () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => EbayWebViewScreen(
|
||||||
|
url: store.storeUrl,
|
||||||
|
title: store.storeName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
_outlineButton("Go to Dashboard", () {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
AppRoutePaths.dashboard,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
/// CONNECT ANOTHER
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF00CFFF),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text("Connect another eBay store"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
"Use this to link an additional eBay store.",
|
||||||
|
style: TextStyle(color: Colors.black54, fontSize: 13),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
Widget _primaryButton(String text, VoidCallback onTap) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: onTap,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF00CFFF),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
),
|
||||||
|
child: Text(text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _outlineButton(String text, VoidCallback onTap) {
|
||||||
|
return OutlinedButton(
|
||||||
|
onPressed: onTap,
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
),
|
||||||
|
child: Text(text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _errorCard(String message) {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
message,
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _cardWrapper(Widget child) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black12,
|
||||||
|
blurRadius: 25,
|
||||||
|
offset: Offset(0, 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,18 +18,56 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
|||||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
String selected = 'turn14';
|
String selected = 'turn14';
|
||||||
|
|
||||||
// controllers
|
|
||||||
final TextEditingController clientIdController = TextEditingController();
|
final TextEditingController clientIdController = TextEditingController();
|
||||||
final TextEditingController clientSecretController = TextEditingController();
|
final TextEditingController clientSecretController = TextEditingController();
|
||||||
|
|
||||||
|
bool _obscure = true;
|
||||||
|
bool _autoFilledOnce = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
Future.microtask(() {
|
||||||
|
final user = ref.read(userProvider).value;
|
||||||
|
if (user != null) {
|
||||||
|
ref.read(turn14Provider.notifier).loadTurn14Status(user.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final asyncUser = ref.watch(userProvider);
|
final asyncUser = ref.watch(userProvider);
|
||||||
final turn14State = ref.watch(turn14Provider);
|
final turn14State = ref.watch(turn14Provider);
|
||||||
|
|
||||||
final user = asyncUser.value;
|
final user = asyncUser.value;
|
||||||
final double topPadding = MediaQuery.of(context).padding.top + 16;
|
final double topPadding = MediaQuery.of(context).padding.top + 16;
|
||||||
|
|
||||||
|
/// ✅ SAFE AUTO-FILL (only once)
|
||||||
|
turn14State.when(
|
||||||
|
data: (data) {
|
||||||
|
final connected = data?.isConnected == true;
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
clientIdController.text = data?.clientId ?? '';
|
||||||
|
clientSecretController.text = data?.clientSecret ?? '';
|
||||||
|
} else {
|
||||||
|
clientIdController.clear();
|
||||||
|
clientSecretController.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loading: () {},
|
||||||
|
error: (_, __) {
|
||||||
|
clientIdController.clear();
|
||||||
|
clientSecretController.clear();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final bool isConnected = turn14State.maybeWhen(
|
||||||
|
data: (data) => data?.isConnected == true,
|
||||||
|
orElse: () => false,
|
||||||
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
drawer: SideMenu(
|
drawer: SideMenu(
|
||||||
@ -44,27 +82,41 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
|||||||
top: topPadding,
|
top: topPadding,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Row(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Turn14 Settings",
|
"Turn14 Settings",
|
||||||
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
|
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
|
||||||
|
// /// ✅ STATUS BADGE
|
||||||
|
// if (turn14State.isLoading)
|
||||||
|
// const CircularProgressIndicator(strokeWidth: 2)
|
||||||
|
// else if (isConnected)
|
||||||
|
// const Text(
|
||||||
|
// "✅ Connected",
|
||||||
|
// style: TextStyle(color: Colors.green),
|
||||||
|
// )
|
||||||
|
// else
|
||||||
|
// const Text(
|
||||||
|
// "⚠️ Not Connected",
|
||||||
|
// style: TextStyle(color: Colors.orange),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
/// Main Scrollable UI
|
/// MAIN UI
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20),
|
padding: EdgeInsets.fromLTRB(16, topPadding + 80, 16, 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text("⚡", style: const TextStyle(fontSize: 28)),
|
const Text("⚡", style: TextStyle(fontSize: 28)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -82,13 +134,10 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
|||||||
_inputField(label: "Client ID", controller: clientIdController),
|
_inputField(label: "Client ID", controller: clientIdController),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
_passwordField(
|
_passwordField(label: "Secret Key"),
|
||||||
label: "Secret Key",
|
|
||||||
controller: clientSecretController,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
|
|
||||||
/// SAVE BUTTON
|
/// ✅ SAVE BUTTON
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
@ -99,68 +148,59 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
|||||||
),
|
),
|
||||||
backgroundColor: const Color(0xFF00C9FF),
|
backgroundColor: const Color(0xFF00C9FF),
|
||||||
),
|
),
|
||||||
onPressed: turn14State.isLoading
|
onPressed: () async {
|
||||||
? null
|
if (turn14State.isLoading) {
|
||||||
: () async {
|
return; // prevent double click
|
||||||
if (user == null) {
|
}
|
||||||
Fluttertoast.showToast(
|
|
||||||
msg: "⚠️ User not found",
|
|
||||||
toastLength: Toast.LENGTH_LONG,
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 16.0,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientIdController.text.trim().isEmpty ||
|
if (isConnected) {
|
||||||
clientSecretController.text.trim().isEmpty) {
|
Fluttertoast.showToast(msg: "✅ Already connected");
|
||||||
Fluttertoast.showToast(
|
return;
|
||||||
msg: "⚠️ Please fill all fields",
|
}
|
||||||
toastLength: Toast.LENGTH_LONG,
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
backgroundColor: Colors.orange,
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 16.0,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ref
|
if (user == null) {
|
||||||
.read(turn14Provider.notifier)
|
Fluttertoast.showToast(msg: "⚠️ User not found");
|
||||||
.saveCredentials(
|
return;
|
||||||
userId: user.id,
|
}
|
||||||
clientId: clientIdController.text.trim(),
|
|
||||||
clientSecret:
|
|
||||||
clientSecretController.text.trim(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Fluttertoast.showToast(
|
if (clientIdController.text.trim().isEmpty ||
|
||||||
msg: "✅ Turn14 Credentials Saved Successfully",
|
clientSecretController.text.trim().isEmpty) {
|
||||||
toastLength: Toast.LENGTH_LONG,
|
Fluttertoast.showToast(
|
||||||
gravity: ToastGravity.BOTTOM,
|
msg: "⚠️ Please fill all fields",
|
||||||
backgroundColor: Colors.green,
|
);
|
||||||
textColor: Colors.white,
|
return;
|
||||||
fontSize: 16.0,
|
}
|
||||||
);
|
|
||||||
},
|
await ref
|
||||||
|
.read(turn14Provider.notifier)
|
||||||
|
.saveCredentials(
|
||||||
|
userId: user.id,
|
||||||
|
clientId: clientIdController.text.trim(),
|
||||||
|
clientSecret: clientSecretController.text.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: "✅ Turn14 Connected Successfully",
|
||||||
|
);
|
||||||
|
},
|
||||||
child: turn14State.isLoading
|
child: turn14State.isLoading
|
||||||
? const CircularProgressIndicator(color: Colors.white)
|
? const SizedBox(
|
||||||
|
height: 22,
|
||||||
|
width: 22,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
: const Text(
|
: const Text(
|
||||||
"Save Credentials",
|
"Save Credentials",
|
||||||
style: TextStyle(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
_infoBox(isConnected),
|
||||||
_infoBox(),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
_tipsBox(),
|
_tipsBox(),
|
||||||
],
|
],
|
||||||
@ -173,7 +213,8 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UI COMPONENTS
|
/// ---------------- UI COMPONENTS ----------------
|
||||||
|
|
||||||
Widget _inputField({
|
Widget _inputField({
|
||||||
required String label,
|
required String label,
|
||||||
required TextEditingController controller,
|
required TextEditingController controller,
|
||||||
@ -198,58 +239,49 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _passwordField({
|
Widget _passwordField({required String label}) {
|
||||||
required String label,
|
return Column(
|
||||||
required TextEditingController controller,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
}) {
|
children: [
|
||||||
bool _obscure = true;
|
Text(label, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
return StatefulBuilder(
|
TextField(
|
||||||
builder: (context, setStateSB) {
|
controller: clientSecretController,
|
||||||
return Column(
|
obscureText: _obscure,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
decoration: InputDecoration(
|
||||||
children: [
|
filled: true,
|
||||||
Text(label, style: const TextStyle(fontWeight: FontWeight.w600)),
|
fillColor: const Color(0xFFF0F6FF),
|
||||||
const SizedBox(height: 8),
|
border: OutlineInputBorder(
|
||||||
TextField(
|
borderRadius: BorderRadius.circular(10),
|
||||||
controller: controller,
|
borderSide: BorderSide.none,
|
||||||
obscureText: _obscure,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
filled: true,
|
|
||||||
fillColor: const Color(0xFFF0F6FF),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_obscure ? Icons.visibility_off : Icons.visibility,
|
|
||||||
),
|
|
||||||
onPressed: () => setStateSB(() => _obscure = !_obscure),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
suffixIcon: IconButton(
|
||||||
);
|
icon: Icon(_obscure ? Icons.visibility_off : Icons.visibility),
|
||||||
},
|
onPressed: () => setState(() => _obscure = !_obscure),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _infoBox() {
|
Widget _infoBox(bool isConnected) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFE8F1FF),
|
color: isConnected ? Colors.green.shade50 : const Color(0xFFE8F1FF),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: const [
|
children: [
|
||||||
Icon(Icons.info, color: Colors.blue),
|
Icon(Icons.info, color: isConnected ? Colors.green : Colors.blue),
|
||||||
SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
"No credentials saved yet.",
|
isConnected
|
||||||
style: TextStyle(color: Colors.black87),
|
? "Existing Turn14 credentials loaded."
|
||||||
|
: "No credentials saved yet.",
|
||||||
|
style: const TextStyle(color: Colors.black87),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -264,9 +296,9 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
|||||||
color: const Color(0xFFE8FCFF),
|
color: const Color(0xFFE8FCFF),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: const Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: const [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"💡 Connection Tips",
|
"💡 Connection Tips",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
|
|||||||
@ -8,9 +8,11 @@ import Foundation
|
|||||||
import flutter_inappwebview_macos
|
import flutter_inappwebview_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import webview_flutter_wkwebview
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
32
pubspec.lock
32
pubspec.lock
@ -789,6 +789,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
|
webview_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: webview_flutter
|
||||||
|
sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.13.0"
|
||||||
|
webview_flutter_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_android
|
||||||
|
sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.10.1"
|
||||||
|
webview_flutter_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_platform_interface
|
||||||
|
sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.14.0"
|
||||||
|
webview_flutter_wkwebview:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_wkwebview
|
||||||
|
sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.23.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -22,6 +22,7 @@ dependencies:
|
|||||||
youtube_player_flutter: ^9.1.3
|
youtube_player_flutter: ^9.1.3
|
||||||
flutter_launcher_icons: ^0.14.4
|
flutter_launcher_icons: ^0.14.4
|
||||||
onesignal_flutter: ^5.3.4
|
onesignal_flutter: ^5.3.4
|
||||||
|
webview_flutter: ^4.13.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user