login ui
This commit is contained in:
92
lib/app/constant.dart
Normal file
92
lib/app/constant.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class Constant {
|
||||
static String tokenName = "westone-kurir";
|
||||
static String version = "1";
|
||||
static String baseUrl = "http://kd-kurir.aplikasi.web.id/one-api/";
|
||||
static String baseBirtUrl = "http://kd-kurir.aplikasi.web.id/";
|
||||
|
||||
static double designHeight = 844;
|
||||
static double designWidth = 390;
|
||||
|
||||
//size convertion
|
||||
static double getActualX({
|
||||
required BuildContext context,
|
||||
required double x,
|
||||
}) {
|
||||
return x / designWidth * MediaQuery.of(context).size.width;
|
||||
}
|
||||
|
||||
static double getActualY({
|
||||
required BuildContext context,
|
||||
required double y,
|
||||
}) {
|
||||
return y / designHeight * MediaQuery.of(context).size.height;
|
||||
}
|
||||
|
||||
static Color primary = const Color(0xff2196F3);
|
||||
static Color primaryLighten = const Color(0xffE3F2FD);
|
||||
static Color primaryDarken = const Color(0xff0D47A1);
|
||||
|
||||
static Color secondary = const Color(0xffFFC107);
|
||||
static Color secondaryLighten = const Color(0xffFFF8E1);
|
||||
static Color secondaryDarken = const Color(0xffFF8F00);
|
||||
|
||||
static Color error = const Color(0xffF44336);
|
||||
static Color errorLighten = const Color(0xffFFEBEE);
|
||||
static Color errorDarken = const Color(0xffB71C1C);
|
||||
|
||||
static Color info = const Color(0xff00BCD4);
|
||||
static Color infoLighten = const Color(0xffE0F7FA);
|
||||
static Color infoDarken = const Color(0xff006064);
|
||||
|
||||
static Color success = const Color(0xff4CAF50);
|
||||
static Color successLighten = const Color(0xffE8F5E9);
|
||||
static Color successDarken = const Color(0xff1B5E20);
|
||||
|
||||
static Color warning = const Color(0xffFF9800);
|
||||
static Color warningLighten = const Color(0xffFFF3E0);
|
||||
static Color warningDarken = const Color(0xffEF6C00);
|
||||
|
||||
// typography
|
||||
static TextStyle body1({required BuildContext context}) {
|
||||
return GoogleFonts.roboto(
|
||||
textStyle: TextStyle(
|
||||
fontSize: Constant.getActualY(context: context, y: 14),
|
||||
height: 1.5,
|
||||
));
|
||||
}
|
||||
|
||||
static TextStyle subtitle1({required BuildContext context}) {
|
||||
return GoogleFonts.roboto(
|
||||
textStyle: TextStyle(
|
||||
fontSize: Constant.getActualY(context: context, y: 18),
|
||||
height: 1.5,
|
||||
));
|
||||
}
|
||||
|
||||
static TextStyle subtitle2({required BuildContext context}) {
|
||||
return GoogleFonts.roboto(
|
||||
textStyle: TextStyle(
|
||||
fontSize: Constant.getActualY(context: context, y: 16),
|
||||
height: 1.5,
|
||||
));
|
||||
}
|
||||
|
||||
static TextStyle body2({required BuildContext context}) {
|
||||
return GoogleFonts.roboto(
|
||||
textStyle: TextStyle(
|
||||
fontSize: Constant.getActualY(context: context, y: 12),
|
||||
height: 1.5,
|
||||
));
|
||||
}
|
||||
|
||||
static TextStyle h2({required BuildContext context}) {
|
||||
return GoogleFonts.roboto(
|
||||
textStyle: TextStyle(
|
||||
fontSize: Constant.getActualY(context: context, y: 28),
|
||||
height: 1.5,
|
||||
));
|
||||
}
|
||||
}
|
||||
24
lib/app/route.dart
Normal file
24
lib/app/route.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:westone_kurirapp/screen/login_screen/login.dart';
|
||||
|
||||
const loginRoute = "/loginRoute";
|
||||
|
||||
class AppRoute {
|
||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||
// input pekerjaan pengambilan bahan
|
||||
if (settings.name == loginRoute) {
|
||||
return MaterialPageRoute(builder: (context) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaleFactor: 1.0, padding: const EdgeInsets.all(0)),
|
||||
child: const LoginScreen());
|
||||
});
|
||||
}
|
||||
return MaterialPageRoute(builder: (context) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context)
|
||||
.copyWith(textScaleFactor: 1.0, padding: const EdgeInsets.all(0)),
|
||||
child: const LoginScreen());
|
||||
});
|
||||
}
|
||||
}
|
||||
BIN
lib/images/home_card.png
Normal file
BIN
lib/images/home_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
BIN
lib/images/login.png
Normal file
BIN
lib/images/login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
BIN
lib/images/logo.png
Normal file
BIN
lib/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
137
lib/main.dart
Normal file
137
lib/main.dart
Normal file
@@ -0,0 +1,137 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:westone_kurirapp/app/constant.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:westone_kurirapp/app/route.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitUp,
|
||||
]).then((value) => runApp(const ProviderScope(child: MyApp())));
|
||||
// runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
debugShowCheckedModeBanner: false,
|
||||
initialRoute: loginRoute,
|
||||
onGenerateRoute: AppRoute.generateRoute,
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// TRY THIS: Try running your application with "flutter run". You'll see
|
||||
// the application has a purple toolbar. Then, without quitting the app,
|
||||
// try changing the seedColor in the colorScheme below to Colors.green
|
||||
// and then invoke "hot reload" (save your changes or press the "hot
|
||||
// reload" button in a Flutter-supported IDE, or press "r" if you used
|
||||
// the command line to start the app).
|
||||
//
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// state is not lost during the reload. To reset the state, use hot
|
||||
// restart instead.
|
||||
//
|
||||
// This works for code too, not just values: Most code changes can be
|
||||
// tested with just a hot reload.
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Constant.primary),
|
||||
useMaterial3: true,
|
||||
),
|
||||
// home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// TRY THIS: Try changing the color here to a specific color (to
|
||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
||||
// change color while the other colors stay the same.
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
//
|
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
|
||||
// action in the IDE, or press "p" in the console), to see the
|
||||
// wireframe for each widget.
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
||||
50
lib/screen/login_screen/login.dart
Normal file
50
lib/screen/login_screen/login.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:westone_kurirapp/app/constant.dart';
|
||||
import 'package:westone_kurirapp/screen/login_screen/login_box.dart';
|
||||
|
||||
class LoginScreen extends HookConsumerWidget {
|
||||
const LoginScreen({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
"lib/images/login.png",
|
||||
),
|
||||
scale: 10,
|
||||
fit: BoxFit.fill)),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: Constant.getActualY(context: context, y: 21),
|
||||
left: Constant.getActualX(context: context, x: 22),
|
||||
),
|
||||
alignment: Alignment.topLeft,
|
||||
child: Image(
|
||||
image: const AssetImage('lib/images/logo.png'),
|
||||
width: Constant.getActualX(context: context, x: 132),
|
||||
))),
|
||||
),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Constant.primaryLighten, Colors.white],
|
||||
begin: Alignment.center,
|
||||
end: Alignment.topCenter)),
|
||||
child: const LoginBox(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
85
lib/screen/login_screen/login_box.dart
Normal file
85
lib/screen/login_screen/login_box.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:westone_kurirapp/app/constant.dart';
|
||||
import 'package:loading_animation_widget/loading_animation_widget.dart';
|
||||
import 'package:westone_kurirapp/widget/custom_button.dart';
|
||||
|
||||
class LoginBox extends HookConsumerWidget {
|
||||
const LoginBox({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final loading = useState(false);
|
||||
final obsecure = useState(true);
|
||||
final usernameController = useTextEditingController(text: "");
|
||||
final passwordController = useTextEditingController(text: "");
|
||||
return Container(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Constant.getActualX(context: context, x: 36),
|
||||
Constant.getActualY(context: context, y: 50),
|
||||
Constant.getActualX(context: context, x: 36),
|
||||
Constant.getActualY(context: context, y: 10)),
|
||||
child: ListView(
|
||||
children: [
|
||||
Text(
|
||||
"Selamat Datang!",
|
||||
style: Constant.h2(context: context)
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
"Silahkan login untuk mulai bertugas",
|
||||
style: Constant.subtitle2(context: context)
|
||||
.copyWith(fontWeight: FontWeight.w400),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualY(context: context, y: 44),
|
||||
),
|
||||
TextField(
|
||||
controller: usernameController,
|
||||
decoration: InputDecoration(
|
||||
label: Text(
|
||||
"Username",
|
||||
style: Constant.subtitle2(context: context),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: InputBorder.none),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualY(context: context, y: 20),
|
||||
),
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
obscureText: obsecure.value,
|
||||
decoration: InputDecoration(
|
||||
label: Text("Password",
|
||||
style: Constant.subtitle2(context: context)),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: InputBorder.none),
|
||||
),
|
||||
SizedBox(
|
||||
height: Constant.getActualY(context: context, y: 30),
|
||||
),
|
||||
CustomButton(
|
||||
text: "LOGIN",
|
||||
type: ButtonType.primary,
|
||||
onPressed: () {
|
||||
// print("aldksld");
|
||||
loading.value = true;
|
||||
Timer(Duration(seconds: 3), () {
|
||||
loading.value = false;
|
||||
});
|
||||
},
|
||||
loading: loading.value,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
83
lib/widget/custom_button.dart
Normal file
83
lib/widget/custom_button.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:westone_kurirapp/app/constant.dart';
|
||||
import 'package:loading_animation_widget/loading_animation_widget.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
enum ButtonType { primary, secondary, error, warning, custom, success }
|
||||
|
||||
class CustomButton extends HookConsumerWidget {
|
||||
const CustomButton(
|
||||
{super.key,
|
||||
required this.onPressed,
|
||||
this.text = '',
|
||||
this.loading = false,
|
||||
this.disabled = false,
|
||||
this.type = ButtonType.primary,
|
||||
this.color = Colors.black,
|
||||
this.overlayColor = Colors.white54});
|
||||
final void Function()? onPressed;
|
||||
final bool loading;
|
||||
final bool disabled;
|
||||
final String text;
|
||||
final ButtonType type;
|
||||
final Color color;
|
||||
final Color overlayColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final colorState = useState(Constant.primary);
|
||||
final overlayColorState = useState(Constant.primaryDarken);
|
||||
switch (type) {
|
||||
case ButtonType.primary:
|
||||
colorState.value = Constant.primary;
|
||||
overlayColorState.value = Constant.primaryDarken;
|
||||
break;
|
||||
case ButtonType.secondary:
|
||||
colorState.value = Constant.secondary;
|
||||
overlayColorState.value = Constant.secondaryDarken;
|
||||
break;
|
||||
case ButtonType.error:
|
||||
colorState.value = Constant.error;
|
||||
overlayColorState.value = Constant.errorDarken;
|
||||
break;
|
||||
case ButtonType.warning:
|
||||
colorState.value = Constant.warning;
|
||||
overlayColorState.value = Constant.warningDarken;
|
||||
break;
|
||||
case ButtonType.success:
|
||||
colorState.value = Constant.success;
|
||||
overlayColorState.value = Constant.successDarken;
|
||||
break;
|
||||
case ButtonType.custom:
|
||||
colorState.value = color;
|
||||
overlayColorState.value = overlayColor;
|
||||
break;
|
||||
default:
|
||||
colorState.value = Constant.primary;
|
||||
overlayColorState.value = Constant.primaryDarken;
|
||||
}
|
||||
|
||||
return ElevatedButton(
|
||||
onPressed: loading || disabled ? null : onPressed,
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.symmetric(
|
||||
vertical: Constant.getActualY(context: context, y: 12))),
|
||||
// surfaceTintColor: MaterialStateProperty.all(Constant.secondary),
|
||||
// shadowColor: MaterialStateProperty.all(Constant.secondary),
|
||||
overlayColor: WidgetStateProperty.all(overlayColorState.value),
|
||||
// foregroundColor: MaterialStateProperty.all(Constant.secondary),
|
||||
backgroundColor: WidgetStateProperty.all(colorState.value),
|
||||
shape: WidgetStateProperty.all(const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4))))),
|
||||
child: loading
|
||||
? LoadingAnimationWidget.staggeredDotsWave(
|
||||
color: Colors.white, size: 25)
|
||||
: Text(
|
||||
text,
|
||||
style: Constant.subtitle1(context: context)
|
||||
.copyWith(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user