kinda done

This commit is contained in:
sewmina7@gmail.com 2023-08-09 22:48:53 +05:30
parent c9f77fb94c
commit da15e3cda6
7 changed files with 439 additions and 71 deletions

25
lib/CustomWidgets.dart Normal file
View File

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
class GradientText extends StatelessWidget {
const GradientText({
super.key,
required this.text,
required this.gradient,
this.style,
});
final String text;
final TextStyle? style;
final Gradient gradient;
@override
Widget build(BuildContext context) {
return ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => gradient.createShader(
Rect.fromLTWH(0, 0, bounds.width, bounds.height),
),
child: Text(text, style: style),
);
}
}

View File

@ -3,11 +3,13 @@ import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:queue_client/backend/DebugHelper.dart';
import 'package:shared_preferences/shared_preferences.dart';
// DateFormat dateFormat;
final String API_ENDPOINT= "https://vps.playpoolstudios.com/qms/api/";
final dateTimeFormat = DateFormat("yyyy-MM-dd hh:mm");
final dateTimeFormat = DateFormat("MM/dd hh:mm a");
final timeFormat = DateFormat("hh:mm a");
class DataManager{
static _DataManager? m_instance = null;
@ -24,10 +26,20 @@ class _DataManager{
List<dynamic> services = [];
List<Map<String, dynamic>> AvailableServices = [];
List<Map<String, dynamic>> JoinedServices = [];
String Username = "";
int userId = -1;
Future<String> AutoLogin() async{
final Prefs = await SharedPreferences.getInstance();
if(Prefs.containsKey("username") && Prefs.containsKey("password")){
return await Login(Prefs.getString("username") ?? "default",Prefs.getString("password") ?? "default");
}
return "-1";
}
Future<String> Login(String username,String password) async{
String responseTxt = "";
try{
var response = (await http.post(
@ -39,6 +51,10 @@ class _DataManager{
responseTxt = response.body.toString();
int result = int.parse(response.body.toString());
userId = result;
final Prefs = await SharedPreferences.getInstance();
Prefs.setString("username", username);
Prefs.setString("password",password);
Username = username;
return "0";
}catch(e){
Debug.LogError(e);
@ -66,6 +82,9 @@ class _DataManager{
JoinedServices=[];
for (var m_service in services) {
Map<String,dynamic> service= jsonDecode(m_service);
if(DateTime.parse(service['end_time']).isBefore(DateTime.now())){
continue;
}
List<String> members = service['members'].toString().split(',');
if(members.contains(userId.toString())){
@ -113,4 +132,85 @@ class _DataManager{
ProcessServices();
}
}
class Helpers{
static int isPhoneNumber(String number){
if(number.length != 10){
return 1;
}
if(number[0] != "0"){
return 2;
}
if(number[1] != "7"){
return 3;
}
if(number[2] == "3" || number[3] == "9"){
return 4;
}
return 0;
}
static String DateTimeToRelative(DateTime dt){
Duration diff = dt.difference(DateTime.now());
if(diff.isNegative){return "Past";}
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = DateTime(now.year, now.month, now.day - 1);
final tomorrow = DateTime(now.year, now.month, now.day + 1);
final dateToCheck = dt;
final aDate = DateTime(dateToCheck.year, dateToCheck.month, dateToCheck.day);
if(aDate == today) {
return "Today at ${timeFormat.format(dt)}";
} else if(aDate == tomorrow) {
return "Tomorrow at ${timeFormat.format(dt)}";
}
if(now.month == dt.month){
String day = dt.day.toString();
if(day == "1" || day=="21" || day=="31"){
return "${day}st at ${timeFormat.format(dt)}";
}else if(day =="2" || day=="22"){
return "${day}nd at ${timeFormat.format(dt)}";
}else if(day =="3" || day=="23"){
return "${day}rd at ${timeFormat.format(dt)}";
}else{
return "${day}th at ${timeFormat.format(dt)}";
}
}
return dateTimeFormat.format(dt);
}
static Duration ParseDuration(String input){
if(input.contains(":")){
return Duration(hours: int.parse(input.split(":")[0]), minutes: int.parse(input.split(":")[1]));
}
return Duration(minutes: 0);
}
}
extension DurationExtension on Duration{
String toCustomString(){
if(this.inMinutes > 60){
return "${this.inHours}:${this.inMinutes}";
}
return "${this.inMinutes} mins";
}
}
extension ContextExtension on BuildContext {
bool get isTablet => MediaQuery.of(this).size.shortestSide > 600;
bool get isPhone => MediaQuery.of(this).size.shortestSide < 600;
bool get isSmall => MediaQuery.of(this).size.shortestSide < 340;
}

View File

@ -1,5 +1,6 @@
// import 'package:fhub/backend/login_mgr.dart';
import 'package:duration_picker/duration_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// import 'package:flutter_spinkit/flutter_spinkit.dart';
@ -66,6 +67,36 @@ class Dialogs{
);
}
}
static showDurationPicker(BuildContext context, String title) {
// set up the button
Widget okButton = TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
backgroundColor: Color(0xFF1F1F1F),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)),
title: Text(title,textAlign: TextAlign.center,),
// content: DurationPicker(onChange: (Duration value) { return value; },),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
// static hide(){
// showing=false;
// Navigator.of(navigatorKey.currentContext!).popUntil((route){

View File

@ -12,7 +12,6 @@ class HomePage extends StatefulWidget {
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
// TODO: implement initState
@ -21,71 +20,203 @@ class _HomePageState extends State<HomePage> {
refresh();
}
void refresh()async{
void refresh() async {
await DataManager.instance().GetData();
setState(() {
});
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Queue Helper"),
),
body: Container(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
title: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Joined"),
ListView.builder(
shrinkWrap: true,
itemCount: DataManager.instance().JoinedServices.length,
itemBuilder: (context, index){
// Map<String,dynamic> service= jsonDecode(DataManager.instance().services[index]);
Map<String,dynamic> service= DataManager.instance().JoinedServices[index];
return ServiceCard(tokenId:service['tokenId'],id: int.parse(service['id']),sTime: DateTime.parse(service['start_time'] ?? DateTime.now().toString()), name: service['name']!, memberCount: service['members']!.split(',').length-1);
}),
SizedBox(height:50),
Text("Available"),
ListView.builder(
shrinkWrap: true,
itemCount: DataManager.instance().AvailableServices.length,
itemBuilder: (context, index){
// Map<String,dynamic> service= jsonDecode(DataManager.instance().services[index]);
Map<String,dynamic> service= DataManager.instance().AvailableServices[index];
return ServiceCard(tokenId: -1,id: int.parse(service['id']),sTime: DateTime.parse(service['start_time'] ?? DateTime.now().toString()), name: service['name']!, memberCount: service['members']!.split(',').length-1);
}),
Text(
"${DataManager.instance().JoinedServices.length} Pending Queues",style: TextStyle(fontSize: 18),),
Row(
children: [
Icon(Icons.person_2_rounded),
Text(" ${DataManager.instance().Username} ",style: TextStyle(fontSize: 18)),
],
),
],
),
),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.token),
Text(" Your Token ID")
],
),
Text(DataManager.instance().userId.toString(),style: TextStyle(fontSize: 45),)
],
),
),
),
Text("Joined"),
SizedBox(height: 10,),
GridView.count(
shrinkWrap: true,
crossAxisCount: context.isSmall ? 1 : ( context.isTablet ? 4: 2),
children: List.generate(
DataManager.instance().JoinedServices.length, (index) {
Map<String, dynamic> service =
DataManager.instance().JoinedServices[index];
return ServiceCard(
tokenId: service['tokenId'],
id: int.parse(service['id']),
sTime: DateTime.parse(
service['start_time'] ?? DateTime.now().toString()),
eTime: DateTime.parse(
service['end_time'] ?? DateTime.now().toString()),
name: service['name']!,
memberCount: service['members']!.split(',').length - 1,
duration: Helpers.ParseDuration(service['duration'])
);
}),
),
// ListView.builder(
// shrinkWrap: true,
// itemCount: DataManager.instance().JoinedServices.length,
// itemBuilder: (context, index){
// // Map<String,dynamic> service= jsonDecode(DataManager.instance().services[index]);
// Map<String,dynamic> service= DataManager.instance().JoinedServices[index];
// return ServiceCard(tokenId:service['tokenId'],id: int.parse(service['id']),sTime: DateTime.parse(service['start_time'] ?? DateTime.now().toString()), name: service['name']!, memberCount: service['members']!.split(',').length-1);
// }),
SizedBox(height: 50),
Text("Available"),
SizedBox(height: 10,),
ListView.builder(
shrinkWrap: true,
itemCount: DataManager.instance().AvailableServices.length,
itemBuilder: (context, index) {
// Map<String,dynamic> service= jsonDecode(DataManager.instance().services[index]);
Map<String, dynamic> service =
DataManager.instance().AvailableServices[index];
return ServiceCard(
tokenId: -1,
id: int.parse(service['id']),
sTime: DateTime.parse(
service['start_time'] ?? DateTime.now().toString()),
eTime: DateTime.parse(
service['end_time'] ?? DateTime.now().toString()),
name: service['name']!,
memberCount: service['members']!.split(',').length - 1,
duration: Helpers.ParseDuration(service['duration']));
}),
],
),
),
),
);
}
Widget ServiceCard(
{required int id, required DateTime sTime,
{required int id,
required DateTime sTime,
required DateTime eTime,
required String name,
required int memberCount, required int tokenId}) {
required int memberCount,
required int tokenId,
required Duration duration}) {
DateTime eta = sTime.add(duration * tokenId);
return InkWell(
onTap: () async{
await Navigator.of(context).push(MaterialPageRoute(builder: (context)=> ServiceInfoPage(id: id,tokenId: tokenId,)));
setState(() {
onTap: () async {
});
if(tokenId > 0){return;}
await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ServiceInfoPage(
id: id,
tokenId: tokenId,
)));
setState(() {});
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${sTime.year}-${sTime.month}-${sTime.day}"),
Text(name),
Container(width: 25,height: 25,decoration: BoxDecoration(borderRadius: BorderRadius.circular(50),color: Colors.deepPurple),child: Center(child: Text(memberCount.toString()),),)
]),
children: [
Center(
child: Text(
name,
style: TextStyle(fontSize: 20),
)),
SizedBox(height: tokenId > 0 ? 0 : 10,),
(tokenId > 0) ? Column(
children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.people_alt_rounded,size: 25,),
Text(
" $tokenId",
style: TextStyle(fontSize: 40),
),
]),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(flex:1,child: Icon(Icons.people)),
Expanded(flex: 4,child: Text("$memberCount in queue",maxLines: 3,overflow: TextOverflow.ellipsis,))
]),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(flex:1,child: Icon(Icons.access_time)),
Expanded(flex: 4,child: Text("Be there around ${Helpers.DateTimeToRelative(eta)}",maxLines: 3,overflow: TextOverflow.ellipsis,))
]),
// (sTime.isAfter(DateTime.now()))
// ? Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// Expanded(flex:1,child: Icon(Icons.access_time)),
// Expanded(flex: 4,child: Text("Starts ${Helpers.DateTimeToRelative(sTime)}",maxLines: 3,overflow: TextOverflow.ellipsis,style: TextStyle(fontSize: 13),))
// ])
// : Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// Expanded(flex:1,child: Icon(Icons.directions_run_outlined)),
// Expanded(flex: 4,
// child: Text(
// "Ongoing until ${Helpers.DateTimeToRelative(eTime)}"),
// )
// ]),
(tokenId <=0) ?
Column(
children: [
SizedBox(height: 10,),
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text("Tap to join the queue",style: TextStyle(fontSize: 12,color: Colors.grey),),
),
),
],
) : Container()
],
),
),
),
);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:queue_client/CustomWidgets.dart';
import 'package:queue_client/backend/DataManager.dart';
import 'package:queue_client/backend/Dialogs.dart';
import 'package:queue_client/home.dart';
@ -13,36 +14,107 @@ class LoginPage extends StatefulWidget {
class _LoginPageState extends State<LoginPage> {
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool logging = false;
@override
void initState() {
// TODO: implement initState
super.initState();
AutoLogin();
}
void AutoLogin()async{
String results = await DataManager.instance().AutoLogin();
isLogging = true;
setState(() {
});
if(results == "0"){
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context)=>HomePage()));
}else if(results == "-1"){
}else{
Dialogs.showAlertDialog(context, "Failed login", results);
}
isLogging=false;
setState(() {
});
}
bool isLogging = true;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Card(
child: Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text("Login"),
SizedBox(height: 30,),
ListTile(title: Text("Phone Number"),
subtitle: TextField(controller: usernameController),),
body: SafeArea(
child: Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
SizedBox(height: 40,),
// Text("Queue Helper",style:TextStyle(fontSize: 45)),
GradientText(text: "Digital Queue", gradient: LinearGradient(colors: [Colors.white.withOpacity(0.5), Colors.white.withOpacity(0.3)]),style: TextStyle(fontSize: 45,fontWeight: FontWeight.bold),),
Text("Stop waiting at queues!")
],
),
(isLogging) ? Container() : Center(
child: Card(
child: Container(
width: 350,
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
// Text("Login"),
ListTile(title: Text("Enter your Phone Number"),
subtitle: TextField(controller: usernameController,decoration: InputDecoration(hintText: "ex: 0701234567"),),),
SizedBox(height: 30,),
ElevatedButton(onPressed: () async{
setState(() {
logging=true;
});
String results = await DataManager.instance().Login(usernameController.text, passwordController.text);
if(results == "0"){
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context)=>HomePage()));
}else{
Dialogs.showAlertDialog(context, "Failed login", results);
}
}, child: Text("Continue"))
],
),
ElevatedButton(autofocus: true,onPressed: () async{
int isPhoneNumber = Helpers.isPhoneNumber(usernameController.text);
if(isPhoneNumber==1){
Dialogs.showAlertDialog(context, "Invalid Number", "Phone number must be 10 digits");
return;
}
if(isPhoneNumber==2 || isPhoneNumber==4){
Dialogs.showAlertDialog(context, "Invalid Number", "Please enter a valid phone number");
return;
}
if(isPhoneNumber==3){
Dialogs.showAlertDialog(context, "Invalid Number", "Please Enter your Personal Phone number");
return;
}
setState(() {
isLogging=true;
});
String results = await DataManager.instance().Login(usernameController.text, usernameController.text);
if(results == "0"){
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context)=>HomePage()));
}else{
Dialogs.showAlertDialog(context, "Failed login", results);
}
setState(() {
isLogging=false;
});
}, child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Login"),
))
],
),
),
),
),
Text("Developed by Xperience Technologies",style: TextStyle(fontSize: 12,color: Colors.grey))
],
),
),
),

View File

@ -49,6 +49,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
duration_picker:
dependency: "direct main"
description:
name: duration_picker
sha256: "052b34dac04c29f3849bb3817a26c5aebe9e5f0697c3a374be87db2b84d75753"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
fake_async:
dependency: transitive
description:

View File

@ -35,6 +35,7 @@ dependencies:
intl: ^0.18.1
shared_preferences: ^2.2.0
cupertino_icons: ^1.0.2
duration_picker: ^1.1.1
dev_dependencies:
flutter_test: