From 33c15b1a5ffa98b20a467cf653cffe476998b845 Mon Sep 17 00:00:00 2001 From: bala Date: Tue, 25 Nov 2025 00:36:35 +0530 Subject: [PATCH] s --- lib/core/constants/api_endpoints.dart | 4 + lib/core/routing/app_router.dart | 12 + lib/core/routing/route_paths.dart | 3 + lib/core/widgets/side_menu.dart | 4 +- lib/data/models/turn14_model.dart | 98 ++++++++ .../repositories/turn14_repository_impl.dart | 77 +++++++ lib/domain/entities/turn14.dart | 31 +++ .../providers/turn14_provider.dart | 91 ++++++++ .../screens/ebay/ebay_screen.dart | 124 ++++++++++ .../screens/store/create_location_screen.dart | 215 ++++++++++++++++++ lib/presentation/screens/store/store.dart | 131 +++++++++++ .../screens/turn14_screen/turn14_screen.dart | 183 +++++++++------ pubspec.lock | 72 ++++++ pubspec.yaml | 10 +- 14 files changed, 988 insertions(+), 67 deletions(-) create mode 100644 lib/data/models/turn14_model.dart create mode 100644 lib/data/repositories/turn14_repository_impl.dart create mode 100644 lib/domain/entities/turn14.dart create mode 100644 lib/presentation/providers/turn14_provider.dart create mode 100644 lib/presentation/screens/ebay/ebay_screen.dart create mode 100644 lib/presentation/screens/store/create_location_screen.dart create mode 100644 lib/presentation/screens/store/store.dart diff --git a/lib/core/constants/api_endpoints.dart b/lib/core/constants/api_endpoints.dart index 3f2dff2..93b2e02 100644 --- a/lib/core/constants/api_endpoints.dart +++ b/lib/core/constants/api_endpoints.dart @@ -13,4 +13,8 @@ class ApiEndpoints { static const signup = '/auth/signup'; static const forgotPassword = '/auth/forgot-password'; static const userDetails = '/auth/users/'; + + ///Turn14 + static const turn14Save = '/auth/turn14/save'; + static const turn14Status = '/auth/turn14/status'; } diff --git a/lib/core/routing/app_router.dart b/lib/core/routing/app_router.dart index 53343ab..8cc9694 100644 --- a/lib/core/routing/app_router.dart +++ b/lib/core/routing/app_router.dart @@ -3,6 +3,9 @@ import 'package:autos/presentation/screens/auth/forgot_password_screen.dart'; import 'package:autos/presentation/screens/auth/login_screen.dart'; import 'package:autos/presentation/screens/auth/sign_up_screen.dart'; import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart'; +import 'package:autos/presentation/screens/ebay/ebay_screen.dart'; +import 'package:autos/presentation/screens/store/create_location_screen.dart'; +import 'package:autos/presentation/screens/store/store.dart'; import 'package:autos/presentation/screens/turn14_screen/turn14_screen.dart'; import 'package:flutter/material.dart'; @@ -45,6 +48,15 @@ class AppRouter { case AppRoutePaths.turn14: return slideRoute(const Turn14Screen()); + case AppRoutePaths.ebay: + return slideRoute(EbayScreen()); + + case AppRoutePaths.store: + return slideRoute(StoreScreen()); + + case AppRoutePaths.createStoreLocation: + return slideRoute(CreateLocationScreen()); + default: return _defaultFallback(settings); } diff --git a/lib/core/routing/route_paths.dart b/lib/core/routing/route_paths.dart index 1056a09..31d37c7 100644 --- a/lib/core/routing/route_paths.dart +++ b/lib/core/routing/route_paths.dart @@ -4,4 +4,7 @@ class AppRoutePaths { static const forgotPassword = '/forgotPassword'; static const dashboard = '/dashboard'; static const turn14 = '/turn14'; + static const ebay = '/ebay'; + static const store = '/store'; + static const createStoreLocation = '/createStoreLocation'; } diff --git a/lib/core/widgets/side_menu.dart b/lib/core/widgets/side_menu.dart index e622a90..2a2c1bc 100644 --- a/lib/core/widgets/side_menu.dart +++ b/lib/core/widgets/side_menu.dart @@ -59,8 +59,8 @@ class SideMenu extends ConsumerWidget { // --- INTEGRATIONS --- _sectionHeader("INTEGRATIONS"), _menuItem(context, "⚡", "Turn14", AppRoutePaths.turn14), - _menuItem(context, "🛍️", "eBay", 'ebay'), - _menuItem(context, "🛒", "Store", 'store'), + _menuItem(context, "🛍️", "eBay", AppRoutePaths.ebay), + _menuItem(context, "🛒", "Store", AppRoutePaths.store), // --- MANAGE --- _sectionHeader("MANAGE"), diff --git a/lib/data/models/turn14_model.dart b/lib/data/models/turn14_model.dart new file mode 100644 index 0000000..6dc69f6 --- /dev/null +++ b/lib/data/models/turn14_model.dart @@ -0,0 +1,98 @@ +import 'dart:convert'; +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 json) { + return Turn14Response( + code: json['code'] ?? '', + message: json['message'] ?? '', + userId: json['userid'] ?? '', + accessToken: json['access_token'] ?? '', + ); + } + + Map 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 { + final String userId; + final bool hasCredentials; + final String? clientId; + final String? clientSecret; + final String? accessToken; + final String? expiresIn; + + Turn14StatusModel({ + required this.userId, + required this.hasCredentials, + this.clientId, + this.clientSecret, + this.accessToken, + this.expiresIn, + }); + + factory Turn14StatusModel.fromJson(Map json) { + return Turn14StatusModel( + userId: json["userid"], + hasCredentials: json["hasCredentials"] ?? false, + clientId: json["credentials"]?["turn14clientid"], + clientSecret: json["credentials"]?["turn14clientsecret"], + accessToken: json["tokenInfo"]?["access_token"], + expiresIn: json["tokenInfo"]?["expires_in"], + ); + } + + Turn14StatusEntity toEntity() { + return Turn14StatusEntity( + userId: userId, + hasCredentials: hasCredentials, + clientId: clientId, + clientSecret: clientSecret, + accessToken: accessToken, + expiresIn: expiresIn, + ); + } +} diff --git a/lib/data/repositories/turn14_repository_impl.dart b/lib/data/repositories/turn14_repository_impl.dart new file mode 100644 index 0000000..ee5d645 --- /dev/null +++ b/lib/data/repositories/turn14_repository_impl.dart @@ -0,0 +1,77 @@ +import 'package:autos/core/constants/api_endpoints.dart'; +import 'package:autos/data/models/turn14_model.dart'; +import 'package:autos/data/sources/remote/api_service.dart'; +import 'package:autos/domain/entities/turn14.dart'; +import 'package:dio/dio.dart'; + +abstract class Turn14Repository { + /// Save Turn14 credentials + Future save({ + required String userId, + required String clientId, + required String clientSecret, + }); +} + +class Turn14RepositoryImpl implements Turn14Repository { + final ApiService _apiService; + + Turn14RepositoryImpl(this._apiService); + + @override + Future save({ + required String userId, + required String clientId, + required String clientSecret, + }) async { + try { + final response = await _apiService.post(ApiEndpoints.turn14Save, { + "userid": userId, + "turn14clientid": clientId, + "turn14clientsecret": clientSecret, + }); + + // Check status + if (response.statusCode == 200) { + final data = response.data; + + if (data['code'] == "TURN14_SAVED") { + /// Convert Response → Entity + return Turn14Response.fromJson(data).toEntity(); + } else { + throw Exception( + data['message'] ?? "Failed to save Turn14 credentials", + ); + } + } else { + throw Exception("Server error: ${response.statusCode}"); + } + } on DioException catch (e) { + throw Exception("Network error: ${e.message}"); + } catch (e) { + throw Exception("Unexpected error: $e"); + } + } + + Future status(String userId) async { + try { + final response = await _apiService.post(ApiEndpoints.turn14Status, { + "userid": userId, + }); + + if (response.statusCode == 200) { + final data = response.data; + + if (data["code"] == "TURN14_STATUS") { + return Turn14StatusModel.fromJson(data).toEntity(); + } else { + throw Exception(data["message"] ?? "Failed to fetch Turn14 status"); + } + } else { + throw Exception("Server error: ${response.statusCode}"); + } + } catch (e) { + throw Exception("Network/Unexpected error: $e"); + } + } +} diff --git a/lib/domain/entities/turn14.dart b/lib/domain/entities/turn14.dart new file mode 100644 index 0000000..536940b --- /dev/null +++ b/lib/domain/entities/turn14.dart @@ -0,0 +1,31 @@ +class Turn14Entity { + final String code; + final String message; + final String userId; + final String accessToken; + + const Turn14Entity({ + required this.code, + required this.message, + required this.userId, + required this.accessToken, + }); +} + +class Turn14StatusEntity { + final String userId; + final bool hasCredentials; + final String? clientId; + final String? clientSecret; + final String? accessToken; + final String? expiresIn; + + Turn14StatusEntity({ + required this.userId, + required this.hasCredentials, + this.clientId, + this.clientSecret, + this.accessToken, + this.expiresIn, + }); +} diff --git a/lib/presentation/providers/turn14_provider.dart b/lib/presentation/providers/turn14_provider.dart new file mode 100644 index 0000000..d98567d --- /dev/null +++ b/lib/presentation/providers/turn14_provider.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:autos/data/models/turn14_model.dart'; +import 'package:autos/data/repositories/turn14_repository_impl.dart'; +import 'package:autos/data/sources/remote/api_service.dart'; +import 'package:autos/domain/entities/turn14.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + + +/// ------------------------------------------------------------ +/// Service Providers +/// ------------------------------------------------------------ + +final turn14ApiServiceProvider = + Provider((ref) => ApiService()); + +final turn14RepositoryProvider = Provider((ref) { + return Turn14RepositoryImpl(ref.read(turn14ApiServiceProvider)); +}); + + +/// ------------------------------------------------------------ +/// Turn14 Notifier +/// ------------------------------------------------------------ + +class Turn14Notifier extends StateNotifier> { + final Turn14RepositoryImpl repository; + final _storage = const FlutterSecureStorage(); + + static const _turn14StorageKey = "turn14_credentials"; + + Turn14Notifier(this.repository) : super(const AsyncValue.data(null)); + + /// Save Turn14 credentials + Future saveCredentials({ + required String userId, + required String clientId, + required String clientSecret, + }) async { + state = const AsyncValue.loading(); + + try { + final response = await repository.save( + userId: userId, + clientId: clientId, + clientSecret: clientSecret, + ); + + // if (response is Turn14Response) { + // await _storage.write( + // key: _turn14StorageKey, + // value: response.toRawJson(), + // ); + // } + + state = AsyncValue.data(response); + } catch (e, st) { + state = AsyncValue.error(e, st); + } + } + + /// Load saved Turn14 credentials + Future loadSavedCredentials() async { + final saved = await _storage.read(key: _turn14StorageKey); + if (saved == null) return; + + final decoded = jsonDecode(saved); + final model = Turn14Response.fromJson(decoded); + + state = AsyncValue.data(model as Turn14Entity?); + } + + /// Clear saved Turn14 data + Future clear() async { + await _storage.delete(key: _turn14StorageKey); + state = const AsyncValue.data(null); + } +} + + +/// ------------------------------------------------------------ +/// Riverpod Provider +/// ------------------------------------------------------------ + +final turn14Provider = + StateNotifierProvider>((ref) { + final repository = ref.read(turn14RepositoryProvider); + return Turn14Notifier(repository); +}); diff --git a/lib/presentation/screens/ebay/ebay_screen.dart b/lib/presentation/screens/ebay/ebay_screen.dart new file mode 100644 index 0000000..8f5807d --- /dev/null +++ b/lib/presentation/screens/ebay/ebay_screen.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:autos/core/widgets/hamburger_button.dart'; +import 'package:autos/core/widgets/side_menu.dart'; + +class EbayScreen extends StatefulWidget { + const EbayScreen({super.key}); + + @override + State createState() => _EbayScreenState(); +} + +class _EbayScreenState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + String selected = "ebay"; + + @override + Widget build(BuildContext context) { + final double topPadding = MediaQuery.of(context).padding.top + 16; + + return Scaffold( + key: _scaffoldKey, + drawer: SideMenu( + selected: selected, + onItemSelected: (key) { + setState(() => selected = key); + }, + ), + + // backgroundColor: const Color(0xFFEFFAFF), + body: Stack( + children: [ + /// TITLE + Positioned( + top: topPadding, + left: 0, + right: 0, + child: Center( + child: Text( + "eBay Settings", + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + + /// MAIN BOX UI + Center( + child: Container( + width: MediaQuery.of(context).size.width * 0.90, + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40), + margin: EdgeInsets.only(top: topPadding + 60), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 25, + offset: const Offset(0, 12), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + /// Description + Text( + "Connect your eBay store to enable product sync, inventory updates, and order flow.", + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 15, + 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 + HamburgerButton(scaffoldKey: _scaffoldKey), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/store/create_location_screen.dart b/lib/presentation/screens/store/create_location_screen.dart new file mode 100644 index 0000000..19c6232 --- /dev/null +++ b/lib/presentation/screens/store/create_location_screen.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; + +class CreateLocationScreen extends StatefulWidget { + const CreateLocationScreen({super.key}); + + @override + State createState() => _CreateLocationScreenState(); +} + +class _CreateLocationScreenState extends State { + // Controllers + final TextEditingController storeName = TextEditingController(); + final TextEditingController phone = TextEditingController(); + final TextEditingController address1 = TextEditingController(); + final TextEditingController city = TextEditingController(); + final TextEditingController stateCtrl = TextEditingController(); + final TextEditingController postalCode = TextEditingController(); + final TextEditingController country = TextEditingController(); + final TextEditingController timeZone = TextEditingController(text: "America/New_York"); + TimeOfDay openTime = const TimeOfDay(hour: 9, minute: 0); + TimeOfDay closeTime = const TimeOfDay(hour: 18, minute: 0); + + Future pickTime({required bool isOpen}) async { + final TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: isOpen ? openTime : closeTime, + ); + + if (picked != null) { + setState(() { + if (isOpen) openTime = picked; + else closeTime = picked; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFEFFAFF), + appBar: AppBar( + title: const Text("Create New Location"), + backgroundColor: Colors.white, + elevation: 0, + foregroundColor: Colors.black, + ), + + body: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Container( + padding: const EdgeInsets.all(30), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 20, + offset: const Offset(0, 10), + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _input("Store Name *", "Enter store name", storeName), + _input("Phone *", "Enter phone number", phone), + _input("Address Line 1 *", "Enter address", address1), + + Row( + children: [ + Expanded(child: _input("City *", "City", city)), + const SizedBox(width: 12), + Expanded(child: _input("State *", "State", stateCtrl)), + ], + ), + + Row( + children: [ + Expanded(child: _input("Postal Code *", "Postal Code", postalCode)), + const SizedBox(width: 12), + Expanded(child: _input("Country *", "Country Code (e.g. US)", country)), + ], + ), + + _input("Time Zone *", "America/New_York", timeZone), + + const SizedBox(height: 15), + + Row( + children: [ + Expanded( + child: _timePicker( + label: "Open Time *", + time: openTime, + onTap: () => pickTime(isOpen: true), + ), + ), + const SizedBox(width: 12), + Expanded( + child: _timePicker( + label: "Close Time *", + time: closeTime, + onTap: () => pickTime(isOpen: false), + ), + ), + ], + ), + + const SizedBox(height: 25), + + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + final json = { + "store_name": storeName.text, + "phone": phone.text, + "address1": address1.text, + "city": city.text, + "state": stateCtrl.text, + "postal_code": postalCode.text, + "country": country.text, + "timezone": timeZone.text, + "open_time": openTime.format(context), + "close_time": closeTime.format(context), + }; + + print("📦 LOCATION JSON → $json"); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF00CFFF), + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text( + "Save & Console JSON", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } + + // Reusable TextField + Widget _input(String label, String hint, TextEditingController controller) { + return Padding( + padding: const EdgeInsets.only(bottom: 18), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)), + const SizedBox(height: 8), + TextField( + controller: controller, + decoration: InputDecoration( + hintText: hint, + filled: true, + fillColor: const Color(0xFFF0F6FF), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + ), + ], + ), + ); + } + + // Time Picker Widget + Widget _timePicker({ + required String label, + required TimeOfDay time, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 18), + decoration: BoxDecoration( + color: const Color(0xFFF0F6FF), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Text(time.format(context), + style: const TextStyle(fontSize: 16)), + const Spacer(), + const Icon(Icons.access_time, size: 20), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/presentation/screens/store/store.dart b/lib/presentation/screens/store/store.dart new file mode 100644 index 0000000..0b1c75e --- /dev/null +++ b/lib/presentation/screens/store/store.dart @@ -0,0 +1,131 @@ +import 'package:autos/core/routing/route_paths.dart'; +import 'package:flutter/material.dart'; +import 'package:autos/core/widgets/hamburger_button.dart'; +import 'package:autos/core/widgets/side_menu.dart'; + +class StoreScreen extends StatefulWidget { + const StoreScreen({super.key}); + + @override + State createState() => _StoreScreenState(); +} + +class _StoreScreenState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + String selected = "ebay"; + + @override + Widget build(BuildContext context) { + final double topPadding = MediaQuery.of(context).padding.top + 16; + + return Scaffold( + key: _scaffoldKey, + drawer: SideMenu( + selected: selected, + onItemSelected: (key) { + setState(() => selected = key); + }, + ), + + body: Stack( + children: [ + /// TITLE + Positioned( + top: topPadding, + left: 0, + right: 0, + child: const Center( + child: Text( + "eBay Locations", + style: TextStyle(fontSize: 26, fontWeight: FontWeight.w700), + ), + ), + ), + + /// MAIN WITH BUTTONS + Center( + child: Container( + width: MediaQuery.of(context).size.width * 0.90, + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40), + margin: EdgeInsets.only(top: topPadding + 60), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 25, + offset: const Offset(0, 12), + ), + ], + ), + + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + /// SAVE SELECTED BUTTON + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + // TODO: Save Selected Flow + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF00CFFF), + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text( + "Save Selected", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ), + ), + ), + + const SizedBox(height: 20), + + /// CREATE NEW LOCATION BUTTON + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed( + context, + AppRoutePaths.createStoreLocation, + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF00CFFF), + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text( + "Create New Location", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ), + ), + ), + ], + ), + ), + ), + + /// HAMBURGER BUTTON + HamburgerButton(scaffoldKey: _scaffoldKey), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/turn14_screen/turn14_screen.dart b/lib/presentation/screens/turn14_screen/turn14_screen.dart index 5c3fc9f..a013373 100644 --- a/lib/presentation/screens/turn14_screen/turn14_screen.dart +++ b/lib/presentation/screens/turn14_screen/turn14_screen.dart @@ -2,8 +2,10 @@ import 'package:autos/core/theme/app_typography.dart'; import 'package:autos/core/widgets/hamburger_button.dart'; import 'package:autos/core/widgets/side_menu.dart'; import 'package:autos/presentation/providers/user_provider.dart'; +import 'package:autos/presentation/providers/turn14_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fluttertoast/fluttertoast.dart'; class Turn14Screen extends ConsumerStatefulWidget { const Turn14Screen({super.key}); @@ -16,9 +18,16 @@ class _Turn14ScreenState extends ConsumerState { final GlobalKey _scaffoldKey = GlobalKey(); String selected = 'turn14'; + // controllers + final TextEditingController clientIdController = TextEditingController(); + final TextEditingController clientSecretController = TextEditingController(); + @override Widget build(BuildContext context) { - final user = ref.watch(userDetailsProvider); + final asyncUser = ref.watch(userDetailsProvider); + final turn14State = ref.watch(turn14Provider); + + final user = asyncUser.value; final double topPadding = MediaQuery.of(context).padding.top + 16; return Scaffold( @@ -46,13 +55,13 @@ class _Turn14ScreenState extends ConsumerState { ), ), + /// Main Scrollable UI SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Description Row Row( children: [ Text("⚡", style: const TextStyle(fontSize: 28)), @@ -70,21 +79,16 @@ class _Turn14ScreenState extends ConsumerState { ), const SizedBox(height: 20), - // Client ID Input - _inputField( - label: "Client ID", - controller: TextEditingController(), - ), + _inputField(label: "Client ID", controller: clientIdController), const SizedBox(height: 20), - // Secret Key Input With Eye Icon _passwordField( label: "Secret Key", - controller: TextEditingController(), + controller: clientSecretController, ), const SizedBox(height: 25), - // Save Button + /// SAVE BUTTON SizedBox( width: double.infinity, child: ElevatedButton( @@ -95,65 +99,70 @@ class _Turn14ScreenState extends ConsumerState { ), backgroundColor: const Color(0xFF00C9FF), ), - onPressed: () {}, - child: const Text( - "Save Credentials", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), + onPressed: turn14State.isLoading + ? null + : () async { + 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 || + clientSecretController.text.trim().isEmpty) { + Fluttertoast.showToast( + msg: "⚠️ Please fill all fields", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.orange, + textColor: Colors.white, + fontSize: 16.0, + ); + return; + } + + await ref + .read(turn14Provider.notifier) + .saveCredentials( + userId: user.id, + clientId: clientIdController.text.trim(), + clientSecret: + clientSecretController.text.trim(), + ); + + Fluttertoast.showToast( + msg: "✅ Turn14 Credentials Saved Successfully", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.green, + textColor: Colors.white, + fontSize: 16.0, + ); + }, + child: turn14State.isLoading + ? const CircularProgressIndicator(color: Colors.white) + : const Text( + "Save Credentials", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), ), ), + const SizedBox(height: 20), - // Info Box - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color(0xFFE8F1FF), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: const [ - Icon(Icons.info, color: Colors.blue), - SizedBox(width: 10), - Text( - "No credentials saved yet.", - style: TextStyle(color: Colors.black87), - ), - ], - ), - ), + _infoBox(), const SizedBox(height: 20), - - // Tips Box - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color(0xFFE8FCFF), - borderRadius: BorderRadius.circular(10), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Text( - "💡 Connection Tips", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - SizedBox(height: 10), - Text("• Ensure your credentials are valid and active."), - Text("• Credentials are encrypted before saving."), - Text("• Contact Turn14 support for API setup help."), - ], - ), - ), + _tipsBox(), ], ), ), @@ -164,6 +173,7 @@ class _Turn14ScreenState extends ConsumerState { ); } + /// UI COMPONENTS Widget _inputField({ required String label, required TextEditingController controller, @@ -224,4 +234,49 @@ class _Turn14ScreenState extends ConsumerState { }, ); } + + Widget _infoBox() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFE8F1FF), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: const [ + Icon(Icons.info, color: Colors.blue), + SizedBox(width: 10), + Text( + "No credentials saved yet.", + style: TextStyle(color: Colors.black87), + ), + ], + ), + ); + } + + Widget _tipsBox() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFE8FCFF), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + "💡 Connection Tips", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + SizedBox(height: 10), + Text("• Ensure your credentials are valid and active."), + Text("• Credentials are encrypted before saving."), + Text("• Contact Turn14 support for API setup help."), + ], + ), + ); + } } diff --git a/pubspec.lock b/pubspec.lock index e8a25bc..0376281 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.7.1" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" args: dependency: transitive description: @@ -49,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" cli_config: dependency: transitive description: @@ -57,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -214,6 +238,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + flutter_launcher_icons: + dependency: "direct main" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -328,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" io: dependency: transitive description: @@ -344,6 +384,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -488,6 +536,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" platform: dependency: transitive description: @@ -512,6 +568,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" pub_semver: dependency: transitive description: @@ -733,6 +797,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 57f996d..fbd93a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: fluttertoast: ^9.0.0 flutter_secure_storage: ^9.2.4 youtube_player_flutter: ^9.1.3 + flutter_launcher_icons: ^0.14.4 dev_dependencies: flutter_test: @@ -48,4 +49,11 @@ flutter: - asset: assets/fonts/Nunito-SemiBold.ttf weight: 600 - asset: assets/fonts/Nunito-Bold.ttf - weight: 700 \ No newline at end of file + weight: 700 + +flutter_launcher_icons: + android: true + ios: true + image_path: "assets/auth/autos_transp.png" + adaptive_icon_background: "#FFFFFF" + adaptive_icon_foreground: "assets/auth/autos_transp.png" \ No newline at end of file