feat: add splash, onboarding, and navigationbar, and home initiate, and depedency needed
This commit is contained in:
parent
a747c98331
commit
fbde7c0ba6
|
@ -1,89 +1,162 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
final String baseUrl;
|
final String baseUrl = dotenv.get('BASE_URL');
|
||||||
|
final String apiKey = dotenv.get('API_KEY');
|
||||||
|
|
||||||
ApiService({this.baseUrl = ''});
|
static const Map<String, String> _headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
static String get apiUrl => dotenv.env['BASE_URL'] ?? '';
|
};
|
||||||
|
|
||||||
Future<Map<String, dynamic>> get(String endpoint) async {
|
Future<Map<String, dynamic>> get(String endpoint) async {
|
||||||
final url = Uri.parse('$apiUrl/$endpoint');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await http.get(url);
|
final url = Uri.parse('$baseUrl$endpoint');
|
||||||
|
final response = await http.get(
|
||||||
|
url,
|
||||||
|
headers: {..._headers, 'API_KEY': apiKey},
|
||||||
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
return _processResponse(response);
|
||||||
return json.decode(response.body);
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to load data');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
throw NetworkException(
|
||||||
|
'Failed to connect to the server. Please check your internet connection.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> post(
|
Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> body) async {
|
||||||
String endpoint,
|
|
||||||
Map<String, dynamic> data,
|
|
||||||
) async {
|
|
||||||
final url = Uri.parse('$apiUrl/$endpoint');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final url = Uri.parse('$baseUrl$endpoint');
|
||||||
|
|
||||||
|
// Debugging URL dan Body Request
|
||||||
|
debugPrint('Request URL: $url');
|
||||||
|
debugPrint('Request Body: ${jsonEncode(body)}');
|
||||||
|
debugPrint('API_KEY: $apiKey');
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
url,
|
url,
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {
|
||||||
body: json.encode(data),
|
..._headers, // Menggunakan _headers untuk Content-Type
|
||||||
|
'x-api-key': apiKey, // Pastikan API_KEY dimasukkan dengan benar di sini
|
||||||
|
},
|
||||||
|
body: jsonEncode(body),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
debugPrint('Response: ${response.body}'); // Debugging Response
|
||||||
return json.decode(response.body);
|
|
||||||
} else {
|
return _processResponse(response);
|
||||||
throw Exception('Failed to submit data');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
debugPrint('Error during API request: $e');
|
||||||
|
throw NetworkException(
|
||||||
|
'Failed to connect to the server. Please check your internet connection.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> put(
|
Future<Map<String, dynamic>> put(
|
||||||
String endpoint,
|
String endpoint,
|
||||||
Map<String, dynamic> data,
|
Map<String, dynamic> body,
|
||||||
) async {
|
) async {
|
||||||
final url = Uri.parse('$apiUrl/$endpoint');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final url = Uri.parse('$baseUrl$endpoint');
|
||||||
final response = await http.put(
|
final response = await http.put(
|
||||||
url,
|
url,
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {..._headers, 'API_KEY': apiKey},
|
||||||
body: json.encode(data),
|
body: jsonEncode(body),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
return _processResponse(response);
|
||||||
return json.decode(response.body);
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to update data');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
throw NetworkException(
|
||||||
|
'Failed to connect to the server. Please check your internet connection.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> patch(
|
||||||
|
String endpoint,
|
||||||
|
Map<String, dynamic> body,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final url = Uri.parse('$baseUrl$endpoint');
|
||||||
|
final response = await http.patch(
|
||||||
|
url,
|
||||||
|
headers: {..._headers, 'API_KEY': apiKey},
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
|
||||||
|
return _processResponse(response);
|
||||||
|
} catch (e) {
|
||||||
|
throw NetworkException(
|
||||||
|
'Failed to connect to the server. Please check your internet connection.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> delete(String endpoint) async {
|
Future<Map<String, dynamic>> delete(String endpoint) async {
|
||||||
final url = Uri.parse('$apiUrl/$endpoint');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await http.delete(url);
|
final url = Uri.parse('$baseUrl$endpoint');
|
||||||
|
final response = await http.delete(
|
||||||
|
url,
|
||||||
|
headers: {..._headers, 'API_KEY': apiKey},
|
||||||
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
return _processResponse(response);
|
||||||
return json.decode(response.body);
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to delete data');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
throw NetworkException(
|
||||||
|
'Failed to connect to the server. Please check your internet connection.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _processResponse(http.Response response) {
|
||||||
|
switch (response.statusCode) {
|
||||||
|
case 200:
|
||||||
|
return jsonDecode(response.body);
|
||||||
|
case 400:
|
||||||
|
throw BadRequestException(
|
||||||
|
'Bad request. The server could not process your request.',
|
||||||
|
);
|
||||||
|
case 401:
|
||||||
|
throw UnauthorizedException(
|
||||||
|
'Unauthorized. Please check your credentials.',
|
||||||
|
);
|
||||||
|
case 404:
|
||||||
|
throw NotFoundException(
|
||||||
|
'Not found. The requested resource could not be found.',
|
||||||
|
);
|
||||||
|
case 500:
|
||||||
|
throw ServerException('Internal server error. Please try again later.');
|
||||||
|
default:
|
||||||
|
throw Exception('Failed with status code: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NetworkException implements Exception {
|
||||||
|
final String message;
|
||||||
|
NetworkException(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BadRequestException implements Exception {
|
||||||
|
final String message;
|
||||||
|
BadRequestException(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnauthorizedException implements Exception {
|
||||||
|
final String message;
|
||||||
|
UnauthorizedException(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotFoundException implements Exception {
|
||||||
|
final String message;
|
||||||
|
NotFoundException(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerException implements Exception {
|
||||||
|
final String message;
|
||||||
|
ServerException(this.message);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:iconsax_flutter/iconsax_flutter.dart';
|
||||||
|
import 'package:rijig_mobile/core/guide.dart';
|
||||||
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/activity/activity_screen.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/cart/cart_screen.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/home_screen.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/profil/profil_screen.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class NavigationPage extends StatefulWidget {
|
||||||
|
final dynamic data;
|
||||||
|
const NavigationPage({super.key, this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NavigationPage> createState() => _NavigationPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavigationPageState extends State<NavigationPage> {
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
|
_loadSelectedIndex() async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
setState(() {
|
||||||
|
_selectedIndex = prefs.getInt('last_selected_index') ?? 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveSelectedIndex(int index) async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
prefs.setInt('last_selected_index', index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadSelectedIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onItemTapped(int index) {
|
||||||
|
if (index == 2) {
|
||||||
|
router.push("/requestpickup");
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_selectedIndex = index;
|
||||||
|
});
|
||||||
|
_saveSelectedIndex(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
extendBody: true,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: IndexedStack(
|
||||||
|
index: _selectedIndex,
|
||||||
|
children: const [
|
||||||
|
HomeScreen(),
|
||||||
|
ActivityScreen(),
|
||||||
|
Text(""),
|
||||||
|
CartScreen(),
|
||||||
|
ProfilScreen(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomNavigationBar: Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Visibility(
|
||||||
|
visible: _selectedIndex != 2,
|
||||||
|
child: BottomAppBar(
|
||||||
|
shape: const CircularNotchedRectangle(),
|
||||||
|
padding: PaddingCustom().paddingHorizontal(2),
|
||||||
|
elevation: 0,
|
||||||
|
height: 67,
|
||||||
|
color: Colors.white,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
notchMargin: 3.0,
|
||||||
|
child: BottomNavigationBar(
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
showSelectedLabels: true,
|
||||||
|
showUnselectedLabels: true,
|
||||||
|
selectedItemColor: Colors.blue,
|
||||||
|
unselectedItemColor: Colors.grey,
|
||||||
|
currentIndex: _selectedIndex,
|
||||||
|
onTap: _onItemTapped,
|
||||||
|
items: [
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Iconsax.home_1, color: Colors.grey),
|
||||||
|
activeIcon: Icon(Iconsax.home_1, color: Colors.blue),
|
||||||
|
label: 'Beranda',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Iconsax.home, color: Colors.grey),
|
||||||
|
activeIcon: Icon(Iconsax.home, color: Colors.blue),
|
||||||
|
label: 'Pesan',
|
||||||
|
),
|
||||||
|
const BottomNavigationBarItem(
|
||||||
|
icon: SizedBox.shrink(),
|
||||||
|
label: '',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Iconsax.document, color: Colors.grey),
|
||||||
|
activeIcon: Icon(Iconsax.document, color: Colors.blue),
|
||||||
|
label: 'Tutorial',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Iconsax.home, color: Colors.grey),
|
||||||
|
activeIcon: Icon(Iconsax.home, color: Colors.blue),
|
||||||
|
label: 'Profil',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selectedLabelStyle: const TextStyle(fontSize: 14),
|
||||||
|
unselectedLabelStyle: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
|
floatingActionButton: SizedBox(
|
||||||
|
width: 78,
|
||||||
|
height: 78,
|
||||||
|
child: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
router.push("/requestpickup");
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
shape: const CircleBorder(
|
||||||
|
side: BorderSide(color: Colors.white, width: 4),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
highlightElevation: 0,
|
||||||
|
hoverColor: Colors.blue,
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Icon(Iconsax.home, color: primaryColor, size: 30),
|
||||||
|
Text("data", style: TextStyle(color: blackNavyColor)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,37 @@
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:rijig_mobile/screen/auth/login_screen.dart';
|
import 'package:rijig_mobile/core/navigation.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/home_screen.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/requestpick/requestpickup_screen.dart';
|
||||||
|
// import 'package:rijig_mobile/screen/auth/login_screen.dart';
|
||||||
import 'package:rijig_mobile/screen/auth/otp_screen.dart';
|
import 'package:rijig_mobile/screen/auth/otp_screen.dart';
|
||||||
|
import 'package:rijig_mobile/screen/launch/onboardingpage_screen.dart';
|
||||||
|
|
||||||
final router = GoRouter(
|
final router = GoRouter(
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(path: '/', builder: (context, state) => LoginScreen()),
|
// GoRoute(path: '/', builder: (context, state) => SplashScreen()),
|
||||||
GoRoute(path: '/verif-otp', builder: (context, state) => VerifotpScreen()),
|
GoRoute(path: '/', builder: (context, state) => NavigationPage()),
|
||||||
|
GoRoute(
|
||||||
|
path: '/onboarding',
|
||||||
|
builder: (context, state) => OnboardongPageScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/navigasi',
|
||||||
|
builder: (context, state) {
|
||||||
|
dynamic data = state.extra;
|
||||||
|
return NavigationPage(data: data);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/verif-otp',
|
||||||
|
builder: (context, state) {
|
||||||
|
final phone = state.extra as String?;
|
||||||
|
return VerifotpScreen(phone: phone!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
|
||||||
|
GoRoute(
|
||||||
|
path: '/requestpickup',
|
||||||
|
builder: (context, state) => RequestPickScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:rijig_mobile/core/router.dart';
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
// import 'screen/auth/login_screen.dart';
|
import 'package:rijig_mobile/viewmodel/auth_vmod.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await dotenv.load(fileName: "server/.env.dev");
|
await dotenv.load(fileName: "server/.env.dev");
|
||||||
runApp(MyApp());
|
HttpOverrides.global = MyHttpOverrides();
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await initializeDateFormatting(
|
||||||
|
'id_ID',
|
||||||
|
null,
|
||||||
|
).then((_) => runApp(const MyApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
@ -16,11 +25,23 @@ class MyApp extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScreenUtilInit(
|
return ScreenUtilInit(
|
||||||
designSize: const Size(375, 812),
|
designSize: const Size(375, 812),
|
||||||
builder: (_, child) => MaterialApp.router(
|
builder:
|
||||||
// theme: ThemeData(textTheme: textTheme),
|
(_, child) => ChangeNotifierProvider(
|
||||||
debugShowCheckedModeBanner: false,
|
create: (_) => UserViewModel(),
|
||||||
routerConfig: router,
|
child: MaterialApp.router(
|
||||||
),
|
debugShowCheckedModeBanner: false,
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyHttpOverrides extends HttpOverrides {
|
||||||
|
@override
|
||||||
|
HttpClient createHttpClient(SecurityContext? context) {
|
||||||
|
return super.createHttpClient(context)
|
||||||
|
..badCertificateCallback =
|
||||||
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
class AuthModel {
|
||||||
|
final int status;
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
AuthModel({required this.status, required this.message});
|
||||||
|
|
||||||
|
factory AuthModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AuthModel(
|
||||||
|
status: json['meta']['status'],
|
||||||
|
message: json['meta']['message'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Product {
|
||||||
|
final int id;
|
||||||
|
final String title, description;
|
||||||
|
final List<String> images;
|
||||||
|
final List<Color> colors;
|
||||||
|
final double rating, price;
|
||||||
|
final bool isFavourite, isPopular;
|
||||||
|
|
||||||
|
Product({
|
||||||
|
required this.id,
|
||||||
|
required this.images,
|
||||||
|
required this.colors,
|
||||||
|
this.rating = 0.0,
|
||||||
|
this.isFavourite = false,
|
||||||
|
this.isPopular = false,
|
||||||
|
required this.title,
|
||||||
|
required this.price,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our demo Products
|
||||||
|
|
||||||
|
List<Product> demoProducts = [
|
||||||
|
Product(
|
||||||
|
id: 1,
|
||||||
|
images: ["assets/image/Image Popular Product 1.png"],
|
||||||
|
colors: [
|
||||||
|
const Color(0xFFF6625E),
|
||||||
|
const Color(0xFF836DB8),
|
||||||
|
const Color(0xFFDECB9C),
|
||||||
|
Colors.white,
|
||||||
|
],
|
||||||
|
title: "Wireless Controller for PS4™",
|
||||||
|
price: 64.99,
|
||||||
|
description: description,
|
||||||
|
rating: 4.8,
|
||||||
|
isFavourite: true,
|
||||||
|
isPopular: true,
|
||||||
|
),
|
||||||
|
Product(
|
||||||
|
id: 2,
|
||||||
|
images: ["assets/image/Image Popular Product 1.png"],
|
||||||
|
colors: [
|
||||||
|
const Color(0xFFF6625E),
|
||||||
|
const Color(0xFF836DB8),
|
||||||
|
const Color(0xFFDECB9C),
|
||||||
|
Colors.white,
|
||||||
|
],
|
||||||
|
title: "Nike Sport White - Man Pant",
|
||||||
|
price: 50.5,
|
||||||
|
description: description,
|
||||||
|
rating: 4.1,
|
||||||
|
isPopular: true,
|
||||||
|
),
|
||||||
|
Product(
|
||||||
|
id: 3,
|
||||||
|
images: ["assets/image/Image Popular Product 1.png"],
|
||||||
|
colors: [
|
||||||
|
const Color(0xFFF6625E),
|
||||||
|
const Color(0xFF836DB8),
|
||||||
|
const Color(0xFFDECB9C),
|
||||||
|
Colors.white,
|
||||||
|
],
|
||||||
|
title: "Gloves XC Omega - Polygon",
|
||||||
|
price: 36.55,
|
||||||
|
description: description,
|
||||||
|
rating: 4.1,
|
||||||
|
isFavourite: true,
|
||||||
|
isPopular: true,
|
||||||
|
),
|
||||||
|
Product(
|
||||||
|
id: 4,
|
||||||
|
images: ["assets/image/Image Popular Product 1.png"],
|
||||||
|
colors: [
|
||||||
|
const Color(0xFFF6625E),
|
||||||
|
const Color(0xFF836DB8),
|
||||||
|
const Color(0xFFDECB9C),
|
||||||
|
Colors.white,
|
||||||
|
],
|
||||||
|
title: "Logitech Head",
|
||||||
|
price: 20.20,
|
||||||
|
description: description,
|
||||||
|
rating: 4.1,
|
||||||
|
isFavourite: true,
|
||||||
|
),
|
||||||
|
// Product(
|
||||||
|
// id: 1,
|
||||||
|
// images: [
|
||||||
|
// "assets/images/ps4_console_white_1.png",
|
||||||
|
// "assets/images/ps4_console_white_2.png",
|
||||||
|
// "assets/images/ps4_console_white_3.png",
|
||||||
|
// "assets/images/ps4_console_white_4.png",
|
||||||
|
// ],
|
||||||
|
// colors: [
|
||||||
|
// const Color(0xFFF6625E),
|
||||||
|
// const Color(0xFF836DB8),
|
||||||
|
// const Color(0xFFDECB9C),
|
||||||
|
// Colors.white,
|
||||||
|
// ],
|
||||||
|
// title: "Wireless Controller for PS4™",
|
||||||
|
// price: 64.99,
|
||||||
|
// description: description,
|
||||||
|
// rating: 4.8,
|
||||||
|
// isFavourite: true,
|
||||||
|
// isPopular: true,
|
||||||
|
// ),
|
||||||
|
// Product(
|
||||||
|
// id: 2,
|
||||||
|
// images: ["assets/images/Image Popular Product 2.png"],
|
||||||
|
// colors: [
|
||||||
|
// const Color(0xFFF6625E),
|
||||||
|
// const Color(0xFF836DB8),
|
||||||
|
// const Color(0xFFDECB9C),
|
||||||
|
// Colors.white,
|
||||||
|
// ],
|
||||||
|
// title: "Nike Sport White - Man Pant",
|
||||||
|
// price: 50.5,
|
||||||
|
// description: description,
|
||||||
|
// rating: 4.1,
|
||||||
|
// isPopular: true,
|
||||||
|
// ),
|
||||||
|
// Product(
|
||||||
|
// id: 3,
|
||||||
|
// images: ["assets/images/glap.png"],
|
||||||
|
// colors: [
|
||||||
|
// const Color(0xFFF6625E),
|
||||||
|
// const Color(0xFF836DB8),
|
||||||
|
// const Color(0xFFDECB9C),
|
||||||
|
// Colors.white,
|
||||||
|
// ],
|
||||||
|
// title: "Gloves XC Omega - Polygon",
|
||||||
|
// price: 36.55,
|
||||||
|
// description: description,
|
||||||
|
// rating: 4.1,
|
||||||
|
// isFavourite: true,
|
||||||
|
// isPopular: true,
|
||||||
|
// ),
|
||||||
|
// Product(
|
||||||
|
// id: 4,
|
||||||
|
// images: ["assets/images/wireless headset.png"],
|
||||||
|
// colors: [
|
||||||
|
// const Color(0xFFF6625E),
|
||||||
|
// const Color(0xFF836DB8),
|
||||||
|
// const Color(0xFFDECB9C),
|
||||||
|
// Colors.white,
|
||||||
|
// ],
|
||||||
|
// title: "Logitech Head",
|
||||||
|
// price: 20.20,
|
||||||
|
// description: description,
|
||||||
|
// rating: 4.1,
|
||||||
|
// isFavourite: true,
|
||||||
|
// ),
|
||||||
|
];
|
||||||
|
|
||||||
|
const String description =
|
||||||
|
"Wireless Controller for PS4™ gives you what you want in your gaming from over precision control your games to sharing …";
|
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ActivityScreen extends StatefulWidget {
|
||||||
|
const ActivityScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ActivityScreen> createState() => _ActivityScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ActivityScreenState extends State<ActivityScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final titleofscreen = "Aktivitas";
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(child: Text("ini adalah halaman $titleofscreen")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CartScreen extends StatefulWidget {
|
||||||
|
const CartScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CartScreen> createState() => _CartScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CartScreenState extends State<CartScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final titleofscreen = "Cart";
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(child: Text("ini adalah halaman $titleofscreen")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
class Categories extends StatelessWidget {
|
||||||
|
const Categories({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Map<String, dynamic>> categories = [
|
||||||
|
{"icon": "assets/icons/Flash Icon.svg", "text": "Flash Deal"},
|
||||||
|
{"icon": "assets/icons/Bill Icon.svg", "text": "Bill"},
|
||||||
|
{"icon": "assets/icons/Game Icon.svg", "text": "Game"},
|
||||||
|
{"icon": "assets/icons/Gift Icon.svg", "text": "Daily Gift"},
|
||||||
|
{"icon": "assets/icons/Discover.svg", "text": "More"},
|
||||||
|
];
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: List.generate(
|
||||||
|
categories.length,
|
||||||
|
(index) => CategoryCard(
|
||||||
|
icon: categories[index]["icon"],
|
||||||
|
text: categories[index]["text"],
|
||||||
|
press: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoryCard extends StatelessWidget {
|
||||||
|
const CategoryCard({
|
||||||
|
super.key,
|
||||||
|
required this.icon,
|
||||||
|
required this.text,
|
||||||
|
required this.press,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String icon, text;
|
||||||
|
final GestureTapCallback press;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: press,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
height: 56,
|
||||||
|
width: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFFECDF),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(icon),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(text, textAlign: TextAlign.center)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DiscountBanner extends StatelessWidget {
|
||||||
|
const DiscountBanner({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
_buildCard(
|
||||||
|
'Pendapatan',
|
||||||
|
'Rp 35.000',
|
||||||
|
Icons.account_balance_wallet,
|
||||||
|
),
|
||||||
|
_buildCard('Sampah', '10 Kg', Icons.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCard(String title, String value, IconData icon) {
|
||||||
|
return Expanded(
|
||||||
|
child: Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Icon on the left
|
||||||
|
Icon(icon, color: Colors.blue, size: 40),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
// Column for title and value on the right
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(fontSize: 14, color: Colors.black54),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:iconsax_flutter/iconsax_flutter.dart';
|
||||||
|
|
||||||
|
class HomeHeader extends StatelessWidget {
|
||||||
|
const HomeHeader({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("Rijig", style: TextStyle(fontSize: 24)),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Icon(Iconsax.notification),
|
||||||
|
Gap(10),
|
||||||
|
Icon(Iconsax.message),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// IconBtnWithCounter(
|
||||||
|
// svgSrc: "assets/icons/Cart Icon.svg",
|
||||||
|
// press: () => Navigator.pushNamed(context, CartScreen.routeName),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 8),
|
||||||
|
// IconBtnWithCounter(
|
||||||
|
// svgSrc: "assets/icons/Bell.svg",
|
||||||
|
// numOfitem: 3,
|
||||||
|
// press: () {},
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
// import '../../../constants.dart';
|
||||||
|
|
||||||
|
class IconBtnWithCounter extends StatelessWidget {
|
||||||
|
const IconBtnWithCounter({
|
||||||
|
super.key,
|
||||||
|
required this.svgSrc,
|
||||||
|
this.numOfitem = 0,
|
||||||
|
required this.press,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String svgSrc;
|
||||||
|
final int numOfitem;
|
||||||
|
final GestureTapCallback press;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
onTap: press,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
height: 46,
|
||||||
|
width: 46,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
// color: kSecondaryColor.withOpacity(0.1),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(svgSrc),
|
||||||
|
),
|
||||||
|
if (numOfitem != 0)
|
||||||
|
Positioned(
|
||||||
|
top: -3,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFF4848),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(width: 1.5, color: Colors.white),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"$numOfitem",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rijig_mobile/model/product.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/components/product_card.dart';
|
||||||
|
|
||||||
|
import 'section_title.dart';
|
||||||
|
|
||||||
|
class PopularProducts extends StatelessWidget {
|
||||||
|
const PopularProducts({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: SectionTitle(
|
||||||
|
title: "Popular Products",
|
||||||
|
// press: () {
|
||||||
|
// Navigator.pushNamed(context, ProductsScreen.routeName);
|
||||||
|
// },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
...List.generate(
|
||||||
|
demoProducts.length,
|
||||||
|
(index) {
|
||||||
|
if (demoProducts[index].isPopular) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 20),
|
||||||
|
child: ProductCard(
|
||||||
|
product: demoProducts[index],
|
||||||
|
onPress: (){},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const SizedBox
|
||||||
|
.shrink(); // here by default width and height is 0
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:rijig_mobile/core/guide.dart';
|
||||||
|
import 'package:rijig_mobile/model/product.dart';
|
||||||
|
|
||||||
|
class ProductCard extends StatelessWidget {
|
||||||
|
const ProductCard({
|
||||||
|
super.key,
|
||||||
|
this.width = 140,
|
||||||
|
this.aspectRetio = 1.02,
|
||||||
|
required this.product,
|
||||||
|
required this.onPress,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double width, aspectRetio;
|
||||||
|
final Product product;
|
||||||
|
final VoidCallback onPress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onPress,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 1.02,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Image.asset(product.images[0]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
product.title,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"\$${product.price}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
onTap: () {},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
product.isFavourite
|
||||||
|
? primaryColor.withValues(alpha: 0.1)
|
||||||
|
: secondaryColor.withValues(alpha: 0.1),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
"assets/icons/Heart Icon_2.svg",
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
product.isFavourite
|
||||||
|
? const Color(0xFFFF4848)
|
||||||
|
: const Color(0xFFDBDEE4),
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// import '../../../constants.dart';
|
||||||
|
|
||||||
|
// class SearchField extends StatelessWidget {
|
||||||
|
// const SearchField({
|
||||||
|
// Key? key,
|
||||||
|
// }) : super(key: key);
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context) {
|
||||||
|
// return Form(
|
||||||
|
// child: TextFormField(
|
||||||
|
// onChanged: (value) {},
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// filled: true,
|
||||||
|
// fillColor: kSecondaryColor.withOpacity(0.1),
|
||||||
|
// contentPadding:
|
||||||
|
// const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
// border: searchOutlineInputBorder,
|
||||||
|
// focusedBorder: searchOutlineInputBorder,
|
||||||
|
// enabledBorder: searchOutlineInputBorder,
|
||||||
|
// hintText: "Search product",
|
||||||
|
// prefixIcon: const Icon(Icons.search),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const searchOutlineInputBorder = OutlineInputBorder(
|
||||||
|
// borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
// borderSide: BorderSide.none,
|
||||||
|
// );
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SectionTitle extends StatelessWidget {
|
||||||
|
const SectionTitle({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
// this.press,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
// GestureTapCallback press;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: (){},
|
||||||
|
style: TextButton.styleFrom(foregroundColor: Colors.grey),
|
||||||
|
child: const Text("See more"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'section_title.dart';
|
||||||
|
|
||||||
|
class SpecialOffers extends StatelessWidget {
|
||||||
|
const SpecialOffers({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: SectionTitle(
|
||||||
|
title: "Special for you",
|
||||||
|
// press: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SpecialOfferCard(
|
||||||
|
image: "assets/image/Image Banner 2.png",
|
||||||
|
category: "Smartphone",
|
||||||
|
numOfBrands: 18,
|
||||||
|
press: () {
|
||||||
|
// Navigator.pushNamed(context, ProductsScreen.routeName);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SpecialOfferCard(
|
||||||
|
image: "assets/image/Image Banner 3.png",
|
||||||
|
category: "Fashion",
|
||||||
|
numOfBrands: 24,
|
||||||
|
press: () {
|
||||||
|
// Navigator.pushNamed(context, ProductsScreen.routeName);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SpecialOfferCard extends StatelessWidget {
|
||||||
|
const SpecialOfferCard({
|
||||||
|
super.key,
|
||||||
|
required this.category,
|
||||||
|
required this.image,
|
||||||
|
required this.numOfBrands,
|
||||||
|
required this.press,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String category, image;
|
||||||
|
final int numOfBrands;
|
||||||
|
final GestureTapCallback press;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 20),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: press,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 242,
|
||||||
|
height: 100,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Image.asset(image, fit: BoxFit.cover),
|
||||||
|
Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.black54,
|
||||||
|
Colors.black38,
|
||||||
|
Colors.black26,
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "$category\n",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: "$numOfBrands Brands"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/components/categories.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/components/discount_banner.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/components/home_header.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/components/popular_product.dart';
|
||||||
|
import 'package:rijig_mobile/screen/app/home/components/special_offers.dart';
|
||||||
|
|
||||||
|
class HomeScreen extends StatefulWidget {
|
||||||
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// final titleofscreen = "Home";
|
||||||
|
return const Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
HomeHeader(),
|
||||||
|
DiscountBanner(),
|
||||||
|
Categories(),
|
||||||
|
SpecialOffers(),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
PopularProducts(),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ProfilScreen extends StatefulWidget {
|
||||||
|
const ProfilScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProfilScreen> createState() => _ProfilScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProfilScreenState extends State<ProfilScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final titleofscreen = "Profil";
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(child: Text("ini adalah halaman $titleofscreen")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RequestPickScreen extends StatelessWidget {
|
||||||
|
const RequestPickScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text("Request Pickup")),
|
||||||
|
body: Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text("Back to Home"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,81 +1,60 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:rijig_mobile/core/router.dart';
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
|
import 'package:rijig_mobile/viewmodel/auth_vmod.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatelessWidget {
|
class LoginScreen extends StatelessWidget {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _phoneController = TextEditingController();
|
||||||
|
|
||||||
LoginScreen({super.key});
|
LoginScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
appBar: AppBar(title: Text('Login')),
|
||||||
body: SafeArea(
|
body: Padding(
|
||||||
child: LayoutBuilder(
|
padding: const EdgeInsets.all(16.0),
|
||||||
builder: (context, constraints) {
|
child: Consumer<UserViewModel>(
|
||||||
return SingleChildScrollView(
|
builder: (context, userVM, child) {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
if (userVM.authModel?.status == 200) {
|
||||||
child: Column(
|
Future.delayed(Duration.zero, () {
|
||||||
children: [
|
router.go('/verif-otp', extra: _phoneController.text);
|
||||||
SizedBox(height: constraints.maxHeight * 0.1),
|
});
|
||||||
Text("Halo, Rijig"),
|
}
|
||||||
SizedBox(height: constraints.maxHeight * 0.1),
|
|
||||||
Text(
|
|
||||||
"Masukkan Nomor Whatsapp",
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: constraints.maxHeight * 0.05),
|
|
||||||
Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'Phone',
|
|
||||||
filled: true,
|
|
||||||
fillColor: Color(0xFFF5FCF9),
|
|
||||||
contentPadding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0 * 1.5,
|
|
||||||
vertical: 16.0,
|
|
||||||
),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(50),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
onSaved: (phone) {},
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
|
||||||
),
|
|
||||||
|
|
||||||
ElevatedButton(
|
return Column(
|
||||||
onPressed: () {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
debugPrint("klik send otp");
|
children: [
|
||||||
router.push("/verif-otp");
|
TextField(
|
||||||
},
|
controller: _phoneController,
|
||||||
style: ElevatedButton.styleFrom(
|
keyboardType: TextInputType.phone,
|
||||||
elevation: 0,
|
decoration: InputDecoration(
|
||||||
backgroundColor: const Color(0xFF00BF6D),
|
labelText: 'Phone Number',
|
||||||
foregroundColor: Colors.white,
|
errorText: userVM.errorMessage,
|
||||||
minimumSize: const Size(double.infinity, 48),
|
),
|
||||||
shape: const RoundedRectangleBorder(
|
),
|
||||||
borderRadius: BorderRadius.all(
|
SizedBox(height: 20),
|
||||||
Radius.circular(16),
|
userVM.isLoading
|
||||||
),
|
? CircularProgressIndicator()
|
||||||
),
|
: ElevatedButton(
|
||||||
),
|
onPressed: () {
|
||||||
child: const Text("send otp"),
|
if (_phoneController.text.isNotEmpty) {
|
||||||
),
|
userVM.login(_phoneController.text);
|
||||||
],
|
}
|
||||||
|
},
|
||||||
|
child: Text('Send OTP'),
|
||||||
|
),
|
||||||
|
if (userVM.authModel != null)
|
||||||
|
Text(
|
||||||
|
userVM.authModel!.message,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
userVM.authModel!.status == 200
|
||||||
|
? Colors.green
|
||||||
|
: Colors.red,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,205 +1,68 @@
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:rijig_mobile/core/guide.dart';
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
|
import 'package:rijig_mobile/viewmodel/auth_vmod.dart';
|
||||||
|
|
||||||
class VerifotpScreen extends StatefulWidget {
|
class VerifotpScreen extends StatelessWidget {
|
||||||
const VerifotpScreen({super.key});
|
final String phone;
|
||||||
|
final _otpController = TextEditingController();
|
||||||
|
|
||||||
@override
|
VerifotpScreen({super.key, required this.phone});
|
||||||
State<VerifotpScreen> createState() => _VerifotpScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _VerifotpScreenState extends State<VerifotpScreen> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: whiteColor,
|
appBar: AppBar(title: Text('Verify OTP')),
|
||||||
body: SafeArea(
|
body: Padding(
|
||||||
child: SizedBox(
|
padding: const EdgeInsets.all(16.0),
|
||||||
width: double.infinity,
|
child: Consumer<UserViewModel>(
|
||||||
child: Padding(
|
builder: (context, userVM, child) {
|
||||||
padding: PaddingCustom().paddingHorizontalVertical(16, 30),
|
if (userVM.isLoading) {
|
||||||
child: SingleChildScrollView(
|
return Center(child: CircularProgressIndicator());
|
||||||
child: Column(
|
}
|
||||||
children: [
|
|
||||||
Gap(16),
|
return Column(
|
||||||
Text("OTP Verification", style: Tulisan.heading),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Gap(8),
|
children: [
|
||||||
const Text(
|
Text('Phone: $phone', style: TextStyle(fontSize: 18)),
|
||||||
"kode otp tela dikirim ke whatsapp 6287874****** \ndan akan kadaluarsa dalam 00:30",
|
SizedBox(height: 20),
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(color: Color(0xFF757575)),
|
TextField(
|
||||||
|
controller: _otpController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Enter OTP',
|
||||||
|
errorText: userVM.errorMessage,
|
||||||
),
|
),
|
||||||
// const SizedBox(height: 16),
|
),
|
||||||
GapCustom().gapValue(12, true),
|
SizedBox(height: 20),
|
||||||
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
|
ElevatedButton(
|
||||||
const OtpForm(),
|
onPressed: () async {
|
||||||
SizedBox(height: MediaQuery.of(context).size.height * 0.2),
|
String otp = _otpController.text;
|
||||||
TextButton(
|
|
||||||
onPressed: () {},
|
if (otp.isNotEmpty) {
|
||||||
child: const Text(
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
"tidak menerima kode otp?\nkirim ulang kode otp",
|
userVM.verifyOtp(phone, otp);
|
||||||
style: TextStyle(color: Color(0xFF757575)),
|
|
||||||
textAlign: TextAlign.center,
|
if (userVM.authModel?.status == 200) {
|
||||||
),
|
debugPrint("routing ke halaman home");
|
||||||
|
router.go('/home');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text('Verify OTP'),
|
||||||
|
),
|
||||||
|
|
||||||
|
if (userVM.errorMessage != null)
|
||||||
|
Text(
|
||||||
|
userVM.errorMessage!,
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const authOutlineInputBorder = OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Color(0xFF757575)),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
);
|
|
||||||
|
|
||||||
class OtpForm extends StatelessWidget {
|
|
||||||
const OtpForm({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Form(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 64,
|
|
||||||
width: 64,
|
|
||||||
child: TextFormField(
|
|
||||||
onSaved: (pin) {},
|
|
||||||
onChanged: (pin) {
|
|
||||||
if (pin.isNotEmpty) {
|
|
||||||
FocusScope.of(context).nextFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
LengthLimitingTextInputFormatter(1),
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintStyle: const TextStyle(color: Color(0xFF757575)),
|
|
||||||
border: authOutlineInputBorder,
|
|
||||||
enabledBorder: authOutlineInputBorder,
|
|
||||||
focusedBorder: authOutlineInputBorder.copyWith(
|
|
||||||
borderSide: const BorderSide(color: Color(0xFF00BF6D)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 64,
|
|
||||||
width: 64,
|
|
||||||
child: TextFormField(
|
|
||||||
onSaved: (pin) {},
|
|
||||||
onChanged: (pin) {
|
|
||||||
if (pin.isNotEmpty) {
|
|
||||||
FocusScope.of(context).nextFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
LengthLimitingTextInputFormatter(1),
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintStyle: const TextStyle(color: Color(0xFF757575)),
|
|
||||||
border: authOutlineInputBorder,
|
|
||||||
enabledBorder: authOutlineInputBorder,
|
|
||||||
focusedBorder: authOutlineInputBorder.copyWith(
|
|
||||||
borderSide: const BorderSide(color: Color(0xFF00BF6D)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 64,
|
|
||||||
width: 64,
|
|
||||||
child: TextFormField(
|
|
||||||
onSaved: (pin) {},
|
|
||||||
onChanged: (pin) {
|
|
||||||
if (pin.isNotEmpty) {
|
|
||||||
FocusScope.of(context).nextFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
LengthLimitingTextInputFormatter(1),
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintStyle: const TextStyle(color: Color(0xFF757575)),
|
|
||||||
border: authOutlineInputBorder,
|
|
||||||
enabledBorder: authOutlineInputBorder,
|
|
||||||
focusedBorder: authOutlineInputBorder.copyWith(
|
|
||||||
borderSide: const BorderSide(color: Color(0xFF00BF6D)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 64,
|
|
||||||
width: 64,
|
|
||||||
child: TextFormField(
|
|
||||||
onSaved: (pin) {},
|
|
||||||
onChanged: (pin) {
|
|
||||||
if (pin.isNotEmpty) {
|
|
||||||
FocusScope.of(context).nextFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
LengthLimitingTextInputFormatter(1),
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintStyle: const TextStyle(color: Color(0xFF757575)),
|
|
||||||
border: authOutlineInputBorder,
|
|
||||||
enabledBorder: authOutlineInputBorder,
|
|
||||||
focusedBorder: authOutlineInputBorder.copyWith(
|
|
||||||
borderSide: const BorderSide(color: Color(0xFF00BF6D)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
elevation: 0,
|
|
||||||
backgroundColor: const Color(0xFF00BF6D),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
minimumSize: const Size(double.infinity, 48),
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text("Continue"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import 'package:concentric_transition/concentric_transition.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final pages = [
|
||||||
|
const PageData(
|
||||||
|
icon: Icons.food_bank_outlined,
|
||||||
|
title: "Search for your favourite food",
|
||||||
|
bgColor: Color(0xff3b1791),
|
||||||
|
textColor: Colors.white,
|
||||||
|
),
|
||||||
|
const PageData(
|
||||||
|
icon: Icons.shopping_bag_outlined,
|
||||||
|
title: "Add it to cart",
|
||||||
|
bgColor: Color(0xfffab800),
|
||||||
|
textColor: Color(0xff3b1790),
|
||||||
|
),
|
||||||
|
const PageData(
|
||||||
|
icon: Icons.delivery_dining,
|
||||||
|
title: "Order and wait",
|
||||||
|
bgColor: Color(0xffffffff),
|
||||||
|
textColor: Color(0xff3b1790),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
class OnboardongPageScreen extends StatelessWidget {
|
||||||
|
const OnboardongPageScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
return Scaffold(
|
||||||
|
body: ConcentricPageView(
|
||||||
|
colors: pages.map((p) => p.bgColor).toList(),
|
||||||
|
radius: screenWidth * 0.1,
|
||||||
|
nextButtonBuilder:
|
||||||
|
(context) => Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 3), // visual center
|
||||||
|
child: Icon(Icons.navigate_next, size: screenWidth * 0.08),
|
||||||
|
),
|
||||||
|
// enable itemcount to disable infinite scroll
|
||||||
|
// itemCount: pages.length,
|
||||||
|
// opacityFactor: 2.0,
|
||||||
|
scaleFactor: 2,
|
||||||
|
duration: Duration(milliseconds: 500),
|
||||||
|
// verticalPosition: 0.7,
|
||||||
|
// direction: Axis.vertical,
|
||||||
|
// itemCount: pages.length,
|
||||||
|
// physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (index) {
|
||||||
|
final page = pages[index % pages.length];
|
||||||
|
return SafeArea(child: _Page(page: page));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageData {
|
||||||
|
final String? title;
|
||||||
|
final IconData? icon;
|
||||||
|
final Color bgColor;
|
||||||
|
final Color textColor;
|
||||||
|
|
||||||
|
const PageData({
|
||||||
|
this.title,
|
||||||
|
this.icon,
|
||||||
|
this.bgColor = Colors.white,
|
||||||
|
this.textColor = Colors.black,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Page extends StatelessWidget {
|
||||||
|
final PageData page;
|
||||||
|
|
||||||
|
const _Page({required this.page});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenHeight = MediaQuery.of(context).size.height;
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
margin: const EdgeInsets.all(16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: page.textColor,
|
||||||
|
),
|
||||||
|
child: Icon(page.icon, size: screenHeight * 0.1, color: page.bgColor),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
page.title ?? "",
|
||||||
|
style: TextStyle(
|
||||||
|
color: page.textColor,
|
||||||
|
fontSize: screenHeight * 0.035,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rijig_mobile/core/guide.dart';
|
||||||
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
|
|
||||||
|
class SplashScreen extends StatelessWidget {
|
||||||
|
const SplashScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Future.delayed(Duration(seconds: 3), () {
|
||||||
|
router.go('/onboarding');
|
||||||
|
});
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: whiteColor,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Image.asset('assets/image/Go_Ride.png', height: 200),
|
||||||
|
),
|
||||||
|
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 250.0),
|
||||||
|
child: Text(
|
||||||
|
'Rijig',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: primaryColor,
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rijig_mobile/core/api_services.dart';
|
||||||
|
import 'package:rijig_mobile/model/auth_model.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class UserViewModel extends ChangeNotifier {
|
||||||
|
final ApiService _apiService = ApiService();
|
||||||
|
bool isLoading = false;
|
||||||
|
String? errorMessage;
|
||||||
|
AuthModel? authModel;
|
||||||
|
|
||||||
|
Future<void> login(String phone) async {
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
errorMessage = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
var response = await _apiService.post('/authmasyarakat/auth', {
|
||||||
|
'phone': phone,
|
||||||
|
});
|
||||||
|
|
||||||
|
authModel = AuthModel.fromJson(response);
|
||||||
|
|
||||||
|
if (authModel?.status == 200) {
|
||||||
|
} else {
|
||||||
|
errorMessage = authModel?.message ?? 'Failed to send OTP';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is NetworkException) {
|
||||||
|
errorMessage = e.message;
|
||||||
|
} else {
|
||||||
|
errorMessage = 'Something went wrong. Please try again later.';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> verifyOtp(String phone, String otp) async {
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
errorMessage = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
var response = await _apiService.post('/authmasyarakat/verify-otp', {
|
||||||
|
'phone': phone,
|
||||||
|
'otp': otp,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response['meta']['status'] == 200) {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('token', response['data']['token']);
|
||||||
|
await prefs.setString('user_id', response['data']['user_id']);
|
||||||
|
await prefs.setString('user_role', response['data']['user_role']);
|
||||||
|
|
||||||
|
debugPrint("berhasil login");
|
||||||
|
} else {
|
||||||
|
errorMessage = response['meta']['message'] ?? 'Failed to verify OTP';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is NetworkException) {
|
||||||
|
errorMessage = e.message;
|
||||||
|
} else {
|
||||||
|
errorMessage = 'Something went wrong. Please try again later.';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
pubspec.lock
88
pubspec.lock
|
@ -1,6 +1,14 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -41,6 +49,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
concentric_transition:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: concentric_transition
|
||||||
|
sha256: "825191221e4bc6a0cfaf00adbc5cd2cc1333970f61311bce52021f1f68e0a891"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -110,6 +126,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.9.3"
|
version: "5.9.3"
|
||||||
|
flutter_svg:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_svg
|
||||||
|
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -160,6 +184,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
|
iconsax_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: iconsax_flutter
|
||||||
|
sha256: "95b65699da8ea98f87c5d232f06b0debaaf1ec1332b697e4d90969ec9a93037d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.20.2"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -240,6 +280,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_parsing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_parsing
|
||||||
|
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -288,6 +336,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -429,6 +485,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
vector_graphics:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics
|
||||||
|
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.18"
|
||||||
|
vector_graphics_codec:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_codec
|
||||||
|
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.13"
|
||||||
|
vector_graphics_compiler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_compiler
|
||||||
|
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.16"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -461,6 +541,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.2 <4.0.0"
|
dart: ">=3.7.2 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.27.0"
|
||||||
|
|
|
@ -8,15 +8,19 @@ environment:
|
||||||
sdk: ^3.7.2
|
sdk: ^3.7.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
concentric_transition: ^1.0.3
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_dotenv: ^5.2.1
|
flutter_dotenv: ^5.2.1
|
||||||
flutter_screenutil: ^5.9.3
|
flutter_screenutil: ^5.9.3
|
||||||
|
flutter_svg: ^2.1.0
|
||||||
gap: ^3.0.1
|
gap: ^3.0.1
|
||||||
go_router: ^15.1.1
|
go_router: ^15.1.1
|
||||||
google_fonts: ^6.0.0
|
google_fonts: ^6.0.0
|
||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
|
iconsax_flutter: ^1.0.0
|
||||||
|
intl: ^0.20.2
|
||||||
provider: ^6.1.4
|
provider: ^6.1.4
|
||||||
shared_preferences: ^2.3.3
|
shared_preferences: ^2.3.3
|
||||||
|
|
||||||
|
@ -29,3 +33,4 @@ flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- server/.env.dev
|
- server/.env.dev
|
||||||
|
- assets/image/
|
||||||
|
|
Loading…
Reference in New Issue