diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh index 3f2ebb32..bf33d1da 100755 --- a/ios/Flutter/flutter_export_environment.sh +++ b/ios/Flutter/flutter_export_environment.sh @@ -2,9 +2,10 @@ # This is a generated file; do not edit or check into version control. export "FLUTTER_ROOT=/Users/rainvisitor/development/flutter" export "FLUTTER_APPLICATION_PATH=/Users/rainvisitor/Documents/GitHub-NKUST-ITC/NKUST-AP-Flutter" -export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_TARGET=/Users/rainvisitor/Documents/GitHub-NKUST-ITC/NKUST-AP-Flutter/lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "SYMROOT=${SOURCE_ROOT}/../build/ios" -export "FLUTTER_FRAMEWORK_DIR=/Users/rainvisitor/development/flutter/bin/cache/artifacts/engine/ios-release" +export "FLUTTER_FRAMEWORK_DIR=/Users/rainvisitor/development/flutter/bin/cache/artifacts/engine/ios" export "FLUTTER_BUILD_NAME=3.2.9" export "FLUTTER_BUILD_NUMBER=30209" +export "TRACK_WIDGET_CREATION=true" diff --git a/lib/api/helper.dart b/lib/api/helper.dart index 6997b641..15adcb95 100644 --- a/lib/api/helper.dart +++ b/lib/api/helper.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:encrypt/encrypt.dart'; +import 'package:http_parser/http_parser.dart'; import 'package:intl/intl.dart'; import 'package:nkust_ap/config/constants.dart'; import 'package:nkust_ap/models/announcements_data.dart'; @@ -21,10 +21,10 @@ import 'package:nkust_ap/models/reward_and_penalty_data.dart'; import 'package:nkust_ap/models/room_data.dart'; import 'package:nkust_ap/models/server_info_data.dart'; import 'package:nkust_ap/utils/preferences.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:nkust_ap/utils/utils.dart'; class Helper { - static const HOST = 'nkust.taki.dog'; + static const HOST = 'nkus-ap-staging.rainvisitor.me'; static const VERSION = 'v3'; @@ -56,7 +56,6 @@ class Helper { Helper() { var host = Preferences.getString(Constants.API_HOST, HOST); - print(host); options = BaseOptions( baseUrl: 'https://$host/$VERSION', connectTimeout: 10000, @@ -65,6 +64,12 @@ class Helper { dio = Dio(options); } + static resetInstance() { + _instance = Helper(); + jsonCodec = JsonCodec(); + cancelToken = CancelToken(); + } + handleDioError(DioError dioError) { switch (dioError.type) { case DioErrorType.DEFAULT: @@ -144,8 +149,13 @@ class Helper { var response = await dio.get("/news/announcements/all"); if (response.statusCode == 204) return AnnouncementsData(data: []); - else - return AnnouncementsData.fromJson(response.data); + else { + var announcementsData = AnnouncementsData.fromJson(response.data); + announcementsData.data.sort((a, b) { + return b.weight.compareTo(a.weight); + }); + return announcementsData; + } } on DioError catch (dioError) { print(dioError); throw dioError; @@ -418,15 +428,24 @@ class Helper { Future sendLeavesSubmit(LeaveSubmitData data, File image) async { if (isExpire()) await login(username, password); try { + MultipartFile file; + if (image != null) { + file = MultipartFile.fromFileSync( + image.path, + filename: image.path.split('/').last, + contentType: MediaType( + 'image', Utils.parserImageFileType(image.path.split('.').last)), + ); + } + print(data.toRawJson()); var response = await dio.post( '/leave/submit', - data: { - 'leavesData': data.toJson(), - 'proofImage': image == null - ? null - : MultipartFile.fromFile(image.path, - filename: image.path.split('/').last), - }, + data: FormData.fromMap( + { + 'leavesData': data.toRawJson(), + 'proofImage': file, + }, + ), cancelToken: cancelToken, ); return response; diff --git a/lib/pages/home/bus_page.dart b/lib/pages/home/bus_page.dart index 21bc1b8b..be397477 100644 --- a/lib/pages/home/bus_page.dart +++ b/lib/pages/home/bus_page.dart @@ -27,6 +27,7 @@ class BusPageState extends State with SingleTickerProviderStateMixin { @override void initState() { + _currentIndex = widget.initIndex; controller = TabController(length: 2, initialIndex: widget.initIndex, vsync: this); super.initState(); diff --git a/lib/pages/home/leave/leave_apply_page.dart b/lib/pages/home/leave/leave_apply_page.dart index 414c2249..1287c32d 100644 --- a/lib/pages/home/leave/leave_apply_page.dart +++ b/lib/pages/home/leave/leave_apply_page.dart @@ -121,359 +121,367 @@ class LeaveApplyPageState extends State ), ); default: - return Form( - key: _formKey, - child: ListView( - padding: EdgeInsets.symmetric(vertical: 24), - children: [ - SizedBox(height: 8.0), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: Text(app.leaveType), - ), - SizedBox(height: 8.0), - Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: double.infinity), - child: CupertinoSegmentedControl( - selectedColor: Resource.Colors.blueAccent, - borderColor: Resource.Colors.blueAccent, - unselectedColor: Resource.Colors.segmentControlUnSelect, - groupValue: typeIndex, - children: this - .leaveSubmitInfo - .type - .map((leaveType) { - return Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Text(leaveType.title), - ); - }) - .toList() - .asMap(), - onValueChanged: (index) { - if (mounted) { - setState(() { - print(index); - typeIndex = index; - }); - } - FA.logAction('segment', 'click'); - }, - ), + return GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + }, + child: Form( + key: _formKey, + child: ListView( + padding: EdgeInsets.symmetric(vertical: 24), + children: [ + SizedBox(height: 8.0), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Text(app.leaveType), ), - ), - SizedBox(height: 16), - Divider(color: Resource.Colors.grey, height: 1), - SizedBox(height: 16), - FractionallySizedBox( - widthFactor: 0.3, - child: RaisedButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(30.0), + SizedBox(height: 8.0), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: double.infinity), + child: CupertinoSegmentedControl( + selectedColor: Resource.Colors.blueAccent, + borderColor: Resource.Colors.blueAccent, + unselectedColor: Resource.Colors.segmentControlUnSelect, + groupValue: typeIndex, + children: this + .leaveSubmitInfo + .type + .map((leaveType) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Text(leaveType.title), + ); + }) + .toList() + .asMap(), + onValueChanged: (index) { + if (mounted) { + setState(() { + print(index); + typeIndex = index; + }); + } + FA.logAction('segment', 'click'); + }, ), ), - padding: EdgeInsets.all(4.0), - color: Resource.Colors.blueAccent, - onPressed: () async { - final List picked = - await DateRagePicker.showDatePicker( - context: context, - initialFirstDate: DateTime.now(), - initialLastDate: (DateTime.now()).add(Duration(days: 7)), - firstDate: DateTime(2015), - lastDate: DateTime(2020), - ); - if (picked != null && picked.length == 2) { - DateTime dateTime = picked[0], - end = picked[1].add(Duration(days: 1)); - while (dateTime.isBefore(end)) { - bool hasRepeat = false; - for (var i = 0; i < leaveModels.length; i++) { - if (leaveModels[i].isSameDay(dateTime)) - hasRepeat = true; - } - if (!hasRepeat) { - leaveModels.add( - LeaveModel( - dateTime, - leaveSubmitInfo.timeCodes.length, - ), - ); + ), + SizedBox(height: 16), + Divider(color: Resource.Colors.grey, height: 1), + SizedBox(height: 16), + FractionallySizedBox( + widthFactor: 0.3, + child: RaisedButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(30.0), + ), + ), + padding: EdgeInsets.all(4.0), + color: Resource.Colors.blueAccent, + onPressed: () async { + final List picked = + await DateRagePicker.showDatePicker( + context: context, + initialFirstDate: DateTime.now(), + initialLastDate: + (DateTime.now()).add(Duration(days: 7)), + firstDate: DateTime(2015), + lastDate: DateTime(2020), + ); + if (picked != null && picked.length == 2) { + DateTime dateTime = picked[0], + end = picked[1].add(Duration(days: 1)); + while (dateTime.isBefore(end)) { + bool hasRepeat = false; + for (var i = 0; i < leaveModels.length; i++) { + if (leaveModels[i].isSameDay(dateTime)) + hasRepeat = true; + } + if (!hasRepeat) { + leaveModels.add( + LeaveModel( + dateTime, + leaveSubmitInfo.timeCodes.length, + ), + ); + } + dateTime = dateTime.add(Duration(days: 1)); } - dateTime = dateTime.add(Duration(days: 1)); + checkIsDelay(); + setState(() {}); } - checkIsDelay(); - setState(() {}); - } - }, - child: Text( - app.addDate, - style: TextStyle(color: Colors.white), + }, + child: Text( + app.addDate, + style: TextStyle(color: Colors.white), + ), ), ), - ), - SizedBox(height: 16), - Container( - height: leaveModels.length == 0 ? 0 : 280, - child: ListView.builder( - itemCount: leaveModels.length, - scrollDirection: Axis.horizontal, - itemBuilder: (_, index) { - return Card( - elevation: 4.0, - margin: - EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - child: Padding( - padding: EdgeInsets.all(4.0), - child: Column( - children: [ - SizedBox(height: 4.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('${leaveModels[index].dateTime.year}/' - '${leaveModels[index].dateTime.month}/' - '${leaveModels[index].dateTime.day}'), - IconButton( - padding: EdgeInsets.all(0.0), - icon: Icon( - AppIcon.cancel, - size: 20.0, - color: Resource.Colors.red, - ), - onPressed: () { - setState(() { - leaveModels.removeAt(index); - checkIsDelay(); - }); - }, - ), - ], - ), - Container( - height: 200, - width: 200, - child: GridView.builder( - physics: NeverScrollableScrollPhysics(), - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - childAspectRatio: 1.0, - ), - itemBuilder: (context, sectionIndex) { - return FlatButton( + SizedBox(height: 16), + Container( + height: leaveModels.length == 0 ? 0 : 280, + child: ListView.builder( + itemCount: leaveModels.length, + scrollDirection: Axis.horizontal, + itemBuilder: (_, index) { + return Card( + elevation: 4.0, + margin: EdgeInsets.symmetric( + vertical: 8.0, horizontal: 8.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: EdgeInsets.all(4.0), + child: Column( + children: [ + SizedBox(height: 4.0), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + Text('${leaveModels[index].dateTime.year}/' + '${leaveModels[index].dateTime.month}/' + '${leaveModels[index].dateTime.day}'), + IconButton( padding: EdgeInsets.all(0.0), - child: Container( - margin: EdgeInsets.all(4.0), - padding: EdgeInsets.all(2.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(4.0), - ), - border: Border.all( - color: Resource.Colors.blueAccent), - color: leaveModels[index] - .selected[sectionIndex] - ? Resource.Colors.blueAccent - : null, - ), - alignment: Alignment.center, - child: Text( - '${leaveSubmitInfo.timeCodes[sectionIndex]}', - style: TextStyle( - color: leaveModels[index] - .selected[sectionIndex] - ? Colors.white - : Resource.Colors.blueAccent, - ), - ), + icon: Icon( + AppIcon.cancel, + size: 20.0, + color: Resource.Colors.red, ), onPressed: () { setState(() { - leaveModels[index] - .selected[sectionIndex] = - !leaveModels[index] - .selected[sectionIndex]; + leaveModels.removeAt(index); + checkIsDelay(); }); }, - ); - }, - itemCount: leaveSubmitInfo.timeCodes.length, + ), + ], + ), + Container( + height: 200, + width: 200, + child: GridView.builder( + physics: NeverScrollableScrollPhysics(), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 1.0, + ), + itemBuilder: (context, sectionIndex) { + return FlatButton( + padding: EdgeInsets.all(0.0), + child: Container( + margin: EdgeInsets.all(4.0), + padding: EdgeInsets.all(2.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(4.0), + ), + border: Border.all( + color: + Resource.Colors.blueAccent), + color: leaveModels[index] + .selected[sectionIndex] + ? Resource.Colors.blueAccent + : null, + ), + alignment: Alignment.center, + child: Text( + '${leaveSubmitInfo.timeCodes[sectionIndex]}', + style: TextStyle( + color: leaveModels[index] + .selected[sectionIndex] + ? Colors.white + : Resource.Colors.blueAccent, + ), + ), + ), + onPressed: () { + setState(() { + leaveModels[index] + .selected[sectionIndex] = + !leaveModels[index] + .selected[sectionIndex]; + }); + }, + ); + }, + itemCount: leaveSubmitInfo.timeCodes.length, + ), ), - ), - ], + ], + ), ), - ), - ); - }, + ); + }, + ), ), - ), - SizedBox(height: leaveModels.length == 0 ? 0 : 16.0), - Divider(color: Resource.Colors.grey, height: 1), - ListTile( - enabled: leaveSubmitInfo.tutor == null, - onTap: leaveSubmitInfo.tutor == null - ? () async { - var teacher = await Navigator.of(context).push( - CupertinoPageRoute(builder: (BuildContext context) { - return PickTutorPage(); - }), - ); - if (teacher != null) { - setState(() { - this.teacher = teacher; - }); + SizedBox(height: leaveModels.length == 0 ? 0 : 16.0), + Divider(color: Resource.Colors.grey, height: 1), + ListTile( + enabled: leaveSubmitInfo.tutor == null, + onTap: leaveSubmitInfo.tutor == null + ? () async { + var teacher = await Navigator.of(context).push( + CupertinoPageRoute(builder: (BuildContext context) { + return PickTutorPage(); + }), + ); + if (teacher != null) { + setState(() { + this.teacher = teacher; + }); + } } - } - : null, - contentPadding: EdgeInsets.symmetric( - horizontal: 24, - vertical: 8, - ), - leading: Icon( - AppIcon.person, - size: 30, - color: Resource.Colors.grey, - ), - trailing: Icon( - AppIcon.keyboardArrowDown, - size: 30, - color: Resource.Colors.grey, - ), - title: Text( - app.tutor, - style: TextStyle(fontSize: 20), - ), - subtitle: Text( - leaveSubmitInfo.tutor == null - ? (teacher?.name ?? app.pleasePick) - : (leaveSubmitInfo.tutor?.name ?? ''), - style: TextStyle(fontSize: 20), + : null, + contentPadding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 8, + ), + leading: Icon( + AppIcon.person, + size: 30, + color: Resource.Colors.grey, + ), + trailing: Icon( + AppIcon.keyboardArrowDown, + size: 30, + color: Resource.Colors.grey, + ), + title: Text( + app.tutor, + style: TextStyle(fontSize: 20), + ), + subtitle: Text( + leaveSubmitInfo.tutor == null + ? (teacher?.name ?? app.pleasePick) + : (leaveSubmitInfo.tutor?.name ?? ''), + style: TextStyle(fontSize: 20), + ), ), - ), - Divider(color: Resource.Colors.grey, height: 1), - ListTile( - onTap: () { - ImagePicker.pickImage(source: ImageSource.gallery).then( - (image) async { - if (image != null) { - FA.logLeavesImageSize(image); - if ((image.lengthSync() / 1024 / 1024) >= - Constants.MAX_IMAGE_SIZE) { - resizeImage(image); - } else { - setState(() { - this.image = image; - }); + Divider(color: Resource.Colors.grey, height: 1), + ListTile( + onTap: () { + ImagePicker.pickImage(source: ImageSource.gallery).then( + (image) async { + if (image != null) { + FA.logLeavesImageSize(image); + if ((image.lengthSync() / 1024 / 1024) >= + Constants.MAX_IMAGE_SIZE) { + resizeImage(image); + } else { + setState(() { + this.image = image; + }); + } } - } - }, - ); - }, - contentPadding: EdgeInsets.symmetric( - horizontal: 24, - vertical: 8, - ), - leading: Icon( - Icons.insert_drive_file, - size: 30, - color: Resource.Colors.grey, - ), - trailing: Icon( - AppIcon.keyboardArrowDown, - size: 30, - color: Resource.Colors.grey, - ), - title: Text( - app.leaveProof, - style: TextStyle(fontSize: 20), - ), - subtitle: Text( - image?.path?.split('/')?.last ?? app.leaveProofHint, - style: TextStyle(fontSize: 20), - ), - ), - Divider(color: Resource.Colors.grey, height: 1), - SizedBox(height: 24), - Padding( - padding: EdgeInsets.symmetric(horizontal: 20), - child: TextFormField( - maxLines: 2, - controller: _reason, - validator: (value) { - if (value.isEmpty) { - return app.doNotEmpty; - } - return null; + }, + ); }, - decoration: InputDecoration( - border: OutlineInputBorder(), - fillColor: Resource.Colors.blueAccent, - labelStyle: TextStyle( - color: Resource.Colors.grey, - ), - labelText: app.reason, + contentPadding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 8, + ), + leading: Icon( + AppIcon.insertDriveFile, + size: 30, + color: Resource.Colors.grey, + ), + trailing: Icon( + AppIcon.keyboardArrowDown, + size: 30, + color: Resource.Colors.grey, + ), + title: Text( + app.leaveProof, + style: TextStyle(fontSize: 20), + ), + subtitle: Text( + image?.path?.split('/')?.last ?? app.leaveProofHint, + style: TextStyle(fontSize: 20), ), ), - ), - if (isDelay) ...[ - SizedBox(height: 24), Divider(color: Resource.Colors.grey, height: 1), - SizedBox(height: 24), + SizedBox(height: 36), Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: TextFormField( maxLines: 2, + controller: _reason, validator: (value) { - if (isDelay && value.isEmpty) { + if (value.isEmpty) { return app.doNotEmpty; } return null; }, - controller: _delayReason, decoration: InputDecoration( border: OutlineInputBorder(), fillColor: Resource.Colors.blueAccent, labelStyle: TextStyle( color: Resource.Colors.grey, ), - labelText: app.delayReason, + labelText: app.reason, ), ), ), - ], - SizedBox(height: 24), - FractionallySizedBox( - widthFactor: 0.8, - child: RaisedButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(30.0), + if (isDelay) ...[ + SizedBox(height: 36), + Divider(color: Resource.Colors.grey, height: 1), + SizedBox(height: 36), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: TextFormField( + maxLines: 2, + validator: (value) { + if (isDelay && value.isEmpty) { + return app.doNotEmpty; + } + return null; + }, + controller: _delayReason, + decoration: InputDecoration( + border: OutlineInputBorder(), + fillColor: Resource.Colors.blueAccent, + labelStyle: TextStyle( + color: Resource.Colors.grey, + ), + labelText: app.delayReason, + ), ), ), - padding: EdgeInsets.all(14.0), - onPressed: () { - _leaveSubmit(); - FA.logAction('leave_submit', 'click'); - }, - color: Resource.Colors.blueAccent, - child: Text( - app.confirm, - style: TextStyle( - color: Colors.white, - fontSize: 18.0, + ], + SizedBox(height: 36), + FractionallySizedBox( + widthFactor: 0.8, + child: RaisedButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(30.0), + ), + ), + padding: EdgeInsets.all(14.0), + onPressed: () { + _leaveSubmit(); + FA.logAction('leave_submit', 'click'); + }, + color: Resource.Colors.blueAccent, + child: Text( + app.confirm, + style: TextStyle( + color: Colors.white, + fontSize: 18.0, + ), ), ), ), - ), - SizedBox(height: 24), - ], + SizedBox(height: 24), + ], + ), ), ); } @@ -531,7 +539,11 @@ class LeaveApplyPageState extends State void checkIsDelay() { isDelay = false; for (var i = 0; i < leaveModels.length; i++) { - if (leaveModels[i].dateTime.isBefore(DateTime.now())) isDelay = true; + if (leaveModels[i].dateTime.isBefore( + DateTime.now().add( + Duration(days: 7), + ), + )) isDelay = true; } if (isDelay) { Utils.showToast(context, app.leaveDelayHint); @@ -612,8 +624,7 @@ class LeaveApplyPageState extends State style: TextStyle(fontWeight: FontWeight.bold), ), TextSpan( - text: - '${leaveSubmitInfo.type[typeIndex].title}\n'), + text: '${leaveSubmitInfo.type[typeIndex].title}\n'), TextSpan( text: '${app.tutor}:', style: TextStyle(fontWeight: FontWeight.bold), @@ -687,13 +698,16 @@ class LeaveApplyPageState extends State context: context, builder: (BuildContext context) => DefaultDialog( title: response.statusCode == 200 - ? app.leaveSubmitSuccess + ? app.leaveSubmit : '${response.statusCode}', contentWidget: RichText( - textAlign: TextAlign.left, + textAlign: TextAlign.center, text: TextSpan( style: TextStyle( - color: Resource.Colors.grey, height: 1.3, fontSize: 16.0), + color: Resource.Colors.grey, + height: 1.3, + fontSize: 16.0, + ), children: [ TextSpan( text: response.statusCode == 200 @@ -714,16 +728,23 @@ class LeaveApplyPageState extends State if (e is DioError) { switch (e.type) { case DioErrorType.RESPONSE: - ErrorResponse errorResponse = - ErrorResponse.fromJson(e.response.data); + String text = app.somethingError; + if (e.response.data is Map) { + ErrorResponse errorResponse = + ErrorResponse.fromJson(e.response.data); + text = errorResponse.description; + } showDialog( context: context, builder: (BuildContext context) => DefaultDialog( - title: app.busReserveFailTitle, + title: app.leaveSubmitFail, contentWidget: Text( - errorResponse.description, + text, style: TextStyle( - color: Resource.Colors.grey, height: 1.3, fontSize: 16.0), + color: Resource.Colors.grey, + height: 1.3, + fontSize: 16.0, + ), ), actionText: app.iKnow, actionFunction: () { diff --git a/lib/pages/home/leave_page.dart b/lib/pages/home/leave_page.dart index 87c0797b..b3460014 100644 --- a/lib/pages/home/leave_page.dart +++ b/lib/pages/home/leave_page.dart @@ -30,6 +30,7 @@ class LeavePageState extends State @override void initState() { super.initState(); + _currentIndex = widget.initIndex; controller = TabController(length: 2, initialIndex: widget.initIndex, vsync: this); } diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 1c464913..2900c620 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -2,9 +2,7 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dio/dio.dart'; -import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:nkust_ap/models/announcements_data.dart'; @@ -17,7 +15,6 @@ import 'package:nkust_ap/utils/global.dart'; import 'package:nkust_ap/utils/preferences.dart'; import 'package:nkust_ap/widgets/drawer_body.dart'; import 'package:nkust_ap/widgets/hint_content.dart'; -import 'package:nkust_ap/widgets/progress_dialog.dart'; import 'package:nkust_ap/widgets/share_data_widget.dart'; import 'package:nkust_ap/widgets/yes_no_dialog.dart'; @@ -49,7 +46,12 @@ class HomePageState extends State { } else { checkLogin(); } - Utils.checkUpdate(context); + Utils.checkRemoteConfig(context, () { + _getNewsAll(); + if (Preferences.getBool(Constants.PREF_AUTO_LOGIN, false)) { + _login(); + } + }); super.initState(); } @@ -61,7 +63,6 @@ class HomePageState extends State { @override Widget build(BuildContext context) { app = AppLocalizations.of(context); - if (state != _State.offline) _setupBusNotify(context); return WillPopScope( child: Scaffold( key: _scaffoldKey, @@ -154,7 +155,7 @@ class HomePageState extends State { child: (Platform.isIOS || Platform.isAndroid) ? CachedNetworkImage( imageUrl: announcement.imgUrl, - errorWidget: (context, url, error) => Icon(Icons.error), + errorWidget: (context, url, error) => Icon(AppIcon.error), ) : Image.network(announcement.imgUrl), ), @@ -213,8 +214,11 @@ class HomePageState extends State { itemCount: announcementsResponse.data.length, itemBuilder: (context, int currentIndex) { bool active = (currentIndex == _currentNewsIndex); - return _newsImage(announcementsResponse.data[currentIndex], - orientation, active); + return _newsImage( + announcementsResponse.data[currentIndex], + orientation, + active, + ); }, ), ), @@ -238,9 +242,14 @@ class HomePageState extends State { ); case _State.offline: return HintContent( - icon: Icons.offline_bolt, + icon: AppIcon.offlineBolt, content: app.offlineMode, ); + case _State.error: + return HintContent( + icon: AppIcon.offlineBolt, + content: app.somethingError, + ); default: return Container(); } @@ -249,10 +258,7 @@ class HomePageState extends State { void onTabTapped(int index) async { switch (index) { case 0: -// if (bus) Utils.pushCupertinoStyle(context, BusPage()); -// else -// Utils.showToast(context, app.canNotUseFeature); break; case 1: Utils.pushCupertinoStyle(context, CoursePage()); @@ -271,30 +277,15 @@ class HomePageState extends State { } else Helper.instance.getAllAnnouncements().then((announcementsResponse) { this.announcementsResponse = announcementsResponse; - this.announcementsResponse.data.sort((a, b) { - return b.weight.compareTo(a.weight); - }); setState(() { state = announcementsResponse.data.length == 0 ? _State.empty : _State.finish; }); }).catchError((e) { - if (e is DioError) { - switch (e.type) { - case DioErrorType.RESPONSE: - Utils.handleResponseError(context, 'getAllNews', mounted, e); - break; - case DioErrorType.CANCEL: - break; - default: - state = _State.error; - Utils.handleDioError(context, e); - break; - } - } else { - throw e; - } + setState(() { + state = _State.error; + }); }); } @@ -425,41 +416,54 @@ class HomePageState extends State { await Future.delayed(Duration(microseconds: 30)); var username = Preferences.getString(Constants.PREF_USERNAME, ''); var password = Preferences.getStringSecurity(Constants.PREF_PASSWORD, ''); - showDialog( - context: context, - builder: (BuildContext context) => WillPopScope( - child: ProgressDialog(app.logining), - onWillPop: () async { - return false; - }), - barrierDismissible: false, - ); Helper.instance .login(username, password) .then((LoginResponse response) async { - if (Navigator.canPop(context)) - Navigator.of(context, rootNavigator: true).pop(); ShareDataWidget.of(context).data.loginResponse = response; ShareDataWidget.of(context).data.isLogin = true; + Preferences.setBool(Constants.PREF_IS_OFFLINE_LOGIN, false); _getUserInfo(); + _setupBusNotify(context); + if (state != _State.finish) { + _getNewsAll(); + } + _scaffoldKey.currentState.showSnackBar( + SnackBar( + content: Text(app.loginSuccess), + duration: Duration(seconds: 2), + ), + ); }).catchError((e) { - if (Navigator.canPop(context)) - Navigator.of(context, rootNavigator: true).pop(); + String text = app.loginFail; if (e is DioError) { switch (e.type) { - case DioErrorType.RESPONSE: - Utils.showToast(context, app.loginFail); - Utils.handleResponseError(context, 'login', mounted, e); + case DioErrorType.DEFAULT: + text = app.noInternet; break; - case DioErrorType.CANCEL: + case DioErrorType.CONNECT_TIMEOUT: + case DioErrorType.RECEIVE_TIMEOUT: + case DioErrorType.SEND_TIMEOUT: + text = app.timeoutMessage; break; default: - Utils.handleDioError(context, e); break; } - } else { - throw e; + Preferences.setBool(Constants.PREF_IS_OFFLINE_LOGIN, true); + Utils.showToast(context, app.loadOfflineData); + ShareDataWidget.of(context).data.isLogin = true; } + _scaffoldKey.currentState.showSnackBar( + SnackBar( + content: Text(text), + duration: Duration(days: 1), + action: SnackBarAction( + onPressed: _login, + label: app.retry, + textColor: Resource.Colors.snackBarActionTextColor, + ), + ), + ); + if (!(e is DioError)) throw e; }); } @@ -469,10 +473,13 @@ class HomePageState extends State { builder: (_) => LoginPage(), ), ); - print((result)); checkLogin(); if (result ?? false) { _getUserInfo(); + _setupBusNotify(context); + if (state != _State.finish) { + _getNewsAll(); + } setState(() { ShareDataWidget.of(context).data.isLogin = true; }); @@ -481,20 +488,26 @@ class HomePageState extends State { void checkLogin() async { await Future.delayed(Duration(microseconds: 30)); - print(ShareDataWidget.of(context).data.isLogin); if (ShareDataWidget.of(context).data.isLogin) { _scaffoldKey.currentState.hideCurrentSnackBar(); } else { - _scaffoldKey.currentState.showSnackBar( - SnackBar( - content: Text(app.notLogin), - duration: Duration(days: 1), - action: SnackBarAction( - onPressed: openLoginPage, - label: app.login, - textColor: Resource.Colors.snackBarActionTextColor, - ), - ), + _scaffoldKey.currentState + .showSnackBar( + SnackBar( + content: Text(app.notLogin), + duration: Duration(days: 1), + action: SnackBarAction( + onPressed: openLoginPage, + label: app.login, + textColor: Resource.Colors.snackBarActionTextColor, + ), + ), + ) + .closed + .then( + (SnackBarClosedReason reason) { + checkLogin(); + }, ); } } diff --git a/lib/res/app_icon.dart b/lib/res/app_icon.dart index 9c9e71fa..50f6e79b 100644 --- a/lib/res/app_icon.dart +++ b/lib/res/app_icon.dart @@ -316,4 +316,14 @@ class AppIcon { return OMIcons.folder; } } + + static IconData get insertDriveFile { + switch (AppIcon.code) { + case AppIcon.FILLED: + return Icons.insert_drive_file; + case AppIcon.OUTLINED: + default: + return OMIcons.insertDriveFile; + } + } } diff --git a/lib/utils/app_localizations.dart b/lib/utils/app_localizations.dart index 47d4f247..f18f05ea 100644 --- a/lib/utils/app_localizations.dart +++ b/lib/utils/app_localizations.dart @@ -404,6 +404,9 @@ class AppLocalizations { 'leaveAppHint': 'Do you want to leave App?', 'closeAppTitle': 'Close App', 'closeAppHint': 'Do you want to close App?', + 'leaveSubmitFail': 'Oops Leaves Submit Fail!', + 'loginSuccess': 'Login Success', + 'retry': 'Retry', }, 'zh': { 'app_name': '高科校務通', @@ -722,6 +725,9 @@ class AppLocalizations { 'leaveSubmit': '假單送出', 'closeAppTitle': '關閉App', 'closeAppHint': '是否關閉App?', + 'leaveSubmitFail': 'Oops 請假送出失敗', + 'loginSuccess': '登入成功', + 'retry': '重試', }, }; @@ -1293,6 +1299,12 @@ class AppLocalizations { String get closeAppTitle => _vocabularies['closeAppTitle']; String get closeAppHint => _vocabularies['closeAppHint']; + + String get leaveSubmitFail => _vocabularies['leaveSubmitFail']; + + String get loginSuccess => _vocabularies['loginSuccess']; + + String get retry => _vocabularies['retry']; } class AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 4572b61f..34b82f34 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_native_image/flutter_native_image.dart'; import 'package:image/image.dart' as ImageUtils; +import 'package:nkust_ap/api/helper.dart'; import 'package:nkust_ap/config/constants.dart'; import 'package:nkust_ap/models/bus_reservations_data.dart'; import 'package:nkust_ap/models/course_data.dart'; @@ -384,7 +385,7 @@ class Utils { } } - static checkUpdate(BuildContext context) async { + static checkRemoteConfig(BuildContext context, Function apiHostUpdate) async { await Future.delayed( Duration(milliseconds: 50), ); @@ -413,7 +414,7 @@ class Utils { Preferences.setString( Constants.PREF_CURRENT_VERSION, packageInfo.buildNumber); } - if (!Constants.isInDebugMode) { + if (Constants.isInDebugMode) { final RemoteConfig remoteConfig = await RemoteConfig.instance; try { await remoteConfig.fetch( @@ -421,8 +422,14 @@ class Utils { ); await remoteConfig.activateFetched(); } on FetchThrottledException catch (exception) {} catch (exception) {} - Preferences.setString( - Constants.API_HOST, remoteConfig.getString(Constants.API_HOST)); + String apiHostLocal = + Preferences.getString(Constants.API_HOST, Helper.HOST); + String apiHostRemote = remoteConfig.getString(Constants.API_HOST); + await Preferences.setString(Constants.API_HOST, apiHostRemote); + if (apiHostLocal != apiHostRemote) { + Helper.resetInstance(); + apiHostUpdate(); + } String url = ""; int versionDiff = 0, newVersion; if (Platform.isAndroid) { @@ -554,4 +561,25 @@ class Utils { ); return result; } + + static String parserImageFileType(String last) { + if (last.contains('jpg') || last.contains('jpeg')) + return 'jpeg'; + else if (last.contains('png')) + return 'png'; + else if (last.contains('bmp')) + return 'bmp'; + else if (last.contains('gif')) + return 'gif'; + else if (last.contains('ico')) + return 'vnd.microsoft.icon'; + else if (last.contains('svg')) + return 'svg+xml'; + else if (last.contains('tif') || last.contains('tiff')) + return 'tiff'; + else if (last.contains('webp')) + return 'webp'; + else + return 'unkwon'; + } } diff --git a/pubspec.yaml b/pubspec.yaml index 37be48b8..7338c2c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: nkust_ap description: A new Flutter application. -version: 3.2.9+30209 +version: 3.2.10+30210 environment: sdk: ">=2.2.2 <3.0.0" @@ -26,6 +26,7 @@ dependencies: firebase_crashlytics: 0.1.0+3 http: 0.12.0+2 html: 0.14.0+2 + http_parser: 3.1.3 #third party plugin dio: 3.0.3 toast: 0.1.4