From 47ca986268ac097c465049999f561d0cd796a24b Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Sun, 30 Jul 2023 22:07:27 +0900 Subject: [PATCH 01/12] feat: modify the day of the week translation --- assets/translations/en.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 9f0c5c9b..66a939b8 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -74,13 +74,13 @@ "speech": "Speech" }, "days": { - "mon": "Monday", - "tue": "Tuesday", - "wed": "Wednesday", - "thu": "Thursday", - "fri": "Friday", - "sat": "Saturday", - "sun": "Sunday" + "mon": "Mon", + "tue": "Tue", + "wed": "Wed", + "thu": "Thu", + "fri": "Fri", + "sat": "Sat", + "sun": "Sun" } }, "dictionary": { From 02c1012a6e6f37ddd13c930d84497ff9db7dc784 Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Sun, 30 Jul 2023 23:46:46 +0900 Subject: [PATCH 02/12] feat: open search page --- lib/home.dart | 1 + lib/pages/timetable_page.dart | 53 ++++++++++++++++++++++++--------- lib/widgets/timetable_tabs.dart | 2 +- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/lib/home.dart b/lib/home.dart index a8dd03bf..7a42cbab 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -105,6 +105,7 @@ class _OTLHomeState extends State with SingleTickerProviderStateMixin { padding: const EdgeInsets.only(left: 16), child: SemesterPicker( onSemesterChanged: () { + context.read().setSelectedLecture(null); context.read().lectureClear(); }, ), diff --git a/lib/pages/timetable_page.dart b/lib/pages/timetable_page.dart index 22e6c624..e2beef2d 100644 --- a/lib/pages/timetable_page.dart +++ b/lib/pages/timetable_page.dart @@ -25,8 +25,6 @@ class _TimetablePageState extends State { final _selectedKey = GlobalKey(); final _paintKey = GlobalKey(); - Lecture? _selectedLecture; - @override Widget build(BuildContext context) { if (context.select((model) => model.isLoaded)) @@ -53,16 +51,43 @@ class _TimetablePageState extends State { color: OTLColor.grayF, child: Column( children: [ - Container( - color: OTLColor.pinksLight, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: OTLColor.grayF, - borderRadius: - BorderRadius.only(topLeft: Radius.circular(16)), - ), - child: _buildTimetableTabs(context), + SizedBox( + height: 60, + child: Row( + children: [ + Expanded( + child: Container( + color: OTLColor.pinksLight, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: OTLColor.grayF, + borderRadius: + BorderRadius.only(topLeft: Radius.circular(16)), + ), + child: _buildTimetableTabs(context), + ), + ), + ), + if (mode == 0) + GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + context.read().resetLectureFilter(); + Navigator.push(context, buildLectureSearchPageRoute()); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 18, 16, 18), + child: Icon( + Icons.search, + size: 24, + color: OTLColor.pinksMain, + ), + ), + ) + else + const SizedBox(width: 16), + ], ), ), Expanded( @@ -115,7 +140,7 @@ class _TimetablePageState extends State { if (!isExamTime) TimetableSummary( lectures: lectures, - tempLecture: _selectedLecture, + tempLecture: context.watch().selectedLecture, ), ], ); @@ -129,7 +154,7 @@ class _TimetablePageState extends State { return Timetable( lectures: (lectureSearchModel.selectedLecture == null) ? lectures - : [...lectures, _selectedLecture!], + : [...lectures, lectureSearchModel.selectedLecture!], isExamTime: isExamTime, builder: (lecture, classTimeIndex, blockHeight) { final isSelected = lectureSearchModel.selectedLecture == lecture; diff --git a/lib/widgets/timetable_tabs.dart b/lib/widgets/timetable_tabs.dart index 92af4215..a91fdd03 100644 --- a/lib/widgets/timetable_tabs.dart +++ b/lib/widgets/timetable_tabs.dart @@ -34,7 +34,7 @@ class _TimetableTabsState extends State { return Container( height: 28, - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.only(left: 16.0), child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, From 7a62b959b49aa90260d8d6163d4268f443660328 Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Sun, 30 Jul 2023 23:51:36 +0900 Subject: [PATCH 03/12] feat: modify color for the selected lecture --- lib/widgets/timetable_block.dart | 5 +++-- lib/widgets/timetable_summary.dart | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/widgets/timetable_block.dart b/lib/widgets/timetable_block.dart index 76f37bc4..15405cdd 100644 --- a/lib/widgets/timetable_block.dart +++ b/lib/widgets/timetable_block.dart @@ -49,7 +49,8 @@ class TimetableBlock extends StatelessWidget { constraints: BoxConstraints(maxHeight: height - 16 - singleHeight), child: Text( title, - style: labelRegular, + style: labelRegular.copyWith( + color: isTemp ? OTLColor.grayF : OTLColor.gray0), ), )); } @@ -71,7 +72,7 @@ class TimetableBlock extends StatelessWidget { child: Text( classroomShort, style: labelRegular.copyWith( - color: OTLColor.gray6, + color: isTemp ? OTLColor.grayE : OTLColor.gray6, overflow: TextOverflow.ellipsis, ), maxLines: maxLines > 1 ? maxLines : 1, diff --git a/lib/widgets/timetable_summary.dart b/lib/widgets/timetable_summary.dart index 65acfc43..c41e9f48 100644 --- a/lib/widgets/timetable_summary.dart +++ b/lib/widgets/timetable_summary.dart @@ -178,13 +178,15 @@ class TimetableSummary extends StatelessWidget { Expanded( child: Text( content, - style: titleBold, + style: titleBold.copyWith( + color: highlight ? OTLColor.pinksMain : OTLColor.gray0), textAlign: TextAlign.center, ), ), Text( title, - style: labelRegular, + style: labelRegular.copyWith( + color: highlight ? OTLColor.pinksMain : OTLColor.gray0), textAlign: TextAlign.center, maxLines: 1, ), From e5aa98da66512cba8e901dcf7cfbfad30d9358f8 Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 01:54:50 +0900 Subject: [PATCH 04/12] feat: apply textScaleFactor to get text height --- lib/utils/get_text_height.dart | 21 +++++++++++++++++++++ lib/widgets/timetable_block.dart | 18 ++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 lib/utils/get_text_height.dart diff --git a/lib/utils/get_text_height.dart b/lib/utils/get_text_height.dart new file mode 100644 index 00000000..afe401d1 --- /dev/null +++ b/lib/utils/get_text_height.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +double getTextHeight( + BuildContext context, { + required String text, + required TextStyle style, + double maxWidth = double.infinity, +}) { + final TextPainter textPainter = TextPainter( + text: TextSpan(text: text, style: style), + textDirection: TextDirection.ltr, + textScaleFactor: MediaQuery.of(context).textScaleFactor, + )..layout(maxWidth: maxWidth); + return textPainter.size.height; +} + +double singleHeight(BuildContext context, TextStyle style) { + return style.fontSize! * + style.height! * + MediaQuery.of(context).textScaleFactor; +} diff --git a/lib/widgets/timetable_block.dart b/lib/widgets/timetable_block.dart index 15405cdd..147a0403 100644 --- a/lib/widgets/timetable_block.dart +++ b/lib/widgets/timetable_block.dart @@ -4,6 +4,7 @@ import 'package:otlplus/constants/color.dart'; import 'package:otlplus/constants/text_styles.dart'; import 'package:otlplus/extensions/lecture.dart'; import 'package:otlplus/models/lecture.dart'; +import 'package:otlplus/utils/get_text_height.dart'; class TimetableBlock extends StatelessWidget { final Lecture lecture; @@ -37,7 +38,8 @@ class TimetableBlock extends StatelessWidget { @override Widget build(BuildContext context) { final contents = []; - final singleHeight = labelRegular.fontSize! * labelRegular.height!; + final validHeight = height - 16; + final lineHeight = singleHeight(context, labelRegular); final isKo = context.locale == Locale('ko'); final title = isKo ? lecture.title : lecture.titleEn; final classroomShort = isKo @@ -46,11 +48,12 @@ class TimetableBlock extends StatelessWidget { if (showTitle) { contents.add(ConstrainedBox( - constraints: BoxConstraints(maxHeight: height - 16 - singleHeight), + constraints: BoxConstraints(maxHeight: validHeight - lineHeight), child: Text( title, style: labelRegular.copyWith( - color: isTemp ? OTLColor.grayF : OTLColor.gray0), + color: isTemp ? OTLColor.grayF : OTLColor.gray0, + ), ), )); } @@ -60,11 +63,10 @@ class TimetableBlock extends StatelessWidget { } if (showClassroom) { - final textPainter = TextPainter( - text: TextSpan(text: title, style: labelRegular), - textDirection: TextDirection.ltr, - )..layout(maxWidth: 54); - final maxLines = (height - textPainter.size.height - 16) ~/ singleHeight; + final maxLines = (validHeight - + getTextHeight(context, + text: title, style: labelRegular, maxWidth: 54)) ~/ + lineHeight; contents.add(Expanded( child: Padding( From 5450fee1cdc3991e25386a13e29869c69f678900 Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 02:15:51 +0900 Subject: [PATCH 05/12] feat: modify multiLine lecture block design --- lib/widgets/map_view.dart | 41 ++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index fdd4a2e4..4a719ce1 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -5,6 +5,7 @@ import 'package:otlplus/constants/color.dart'; import 'package:otlplus/constants/text_styles.dart'; import 'package:otlplus/models/classtime.dart'; import 'package:otlplus/models/lecture.dart'; +import 'package:otlplus/utils/get_text_height.dart'; const POSITION_OF_LOCATIONS = { 'E2': {'left': 0.599, 'top': 0.802}, @@ -285,23 +286,35 @@ class _MapViewState extends State { ), if (classtime.roomName.isNotEmpty) Padding( - padding: const EdgeInsets.fromLTRB(16, 2.5, 0, 2.5), - child: Container( - height: 23, - padding: const EdgeInsets.symmetric(horizontal: 6), - alignment: Alignment.center, - decoration: BoxDecoration( - color: OTLColor.blockColors[lecture.course % 16], - borderRadius: BorderRadius.circular(100), - ), - child: Text( - (buildingCode == '기타') + padding: const EdgeInsets.fromLTRB(16, 4, 0, 0), + child: Builder( + builder: (context) { + final location = (buildingCode == '기타') ? _isKo ? classtime.classroom : classtime.classroomEn - : classtime.roomName, - style: labelRegular, - ), + : classtime.roomName; + final isMultiLine = (getTextHeight(context, + text: location, + style: labelRegular, + maxWidth: 143) ~/ + singleHeight(context, labelRegular)) > + 1; + + return Container( + width: isMultiLine ? 155 : null, + padding: const EdgeInsets.fromLTRB(6, 2, 6, 4), + alignment: Alignment.center, + decoration: BoxDecoration( + color: OTLColor.blockColors[lecture.course % 16], + borderRadius: BorderRadius.circular(isMultiLine ? 13 : 100), + ), + child: Text( + location, + style: labelRegular, + ), + ); + }, ), ), ], From 68bced6c6af77c25bc8114c84fa7ade06b3c6611 Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 02:27:21 +0900 Subject: [PATCH 06/12] feat: increase dropdown width --- lib/widgets/timetable_tabs.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/widgets/timetable_tabs.dart b/lib/widgets/timetable_tabs.dart index a91fdd03..c1554906 100644 --- a/lib/widgets/timetable_tabs.dart +++ b/lib/widgets/timetable_tabs.dart @@ -123,7 +123,7 @@ class _TimetableTabsState extends State { if (value == MenuItems.delete) widget.onDeleteTap(); }, dropdownStyleData: DropdownStyleData( - width: 180, + width: 200, elevation: 0, padding: EdgeInsets.zero, decoration: BoxDecoration( @@ -210,7 +210,10 @@ abstract class MenuItems { Expanded( child: Text( item.text, - style: bodyRegular.copyWith(color: color), + style: bodyRegular.copyWith( + color: color, + overflow: TextOverflow.ellipsis, + ), ), ), Icon( From 0a5616151004c74f3f8885d48ccba449846de7d5 Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 02:39:39 +0900 Subject: [PATCH 07/12] feat: apply ellipsis to timetable block title --- lib/widgets/timetable_block.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/widgets/timetable_block.dart b/lib/widgets/timetable_block.dart index 147a0403..016a2c65 100644 --- a/lib/widgets/timetable_block.dart +++ b/lib/widgets/timetable_block.dart @@ -47,14 +47,13 @@ class TimetableBlock extends StatelessWidget { : lecture.classtimes[classTimeIndex].classroomShortEn; if (showTitle) { - contents.add(ConstrainedBox( - constraints: BoxConstraints(maxHeight: validHeight - lineHeight), - child: Text( - title, - style: labelRegular.copyWith( - color: isTemp ? OTLColor.grayF : OTLColor.gray0, - ), + contents.add(Text( + title, + style: labelRegular.copyWith( + color: isTemp ? OTLColor.grayF : OTLColor.gray0, + overflow: TextOverflow.ellipsis, ), + maxLines: (validHeight - lineHeight) ~/ lineHeight, )); } From f29120f5e52bb78d5e47fe1a0d36fbc363d95ab4 Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 03:02:13 +0900 Subject: [PATCH 08/12] fix: negative maxLines --- lib/widgets/timetable_block.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/widgets/timetable_block.dart b/lib/widgets/timetable_block.dart index 016a2c65..d2233cd0 100644 --- a/lib/widgets/timetable_block.dart +++ b/lib/widgets/timetable_block.dart @@ -40,6 +40,7 @@ class TimetableBlock extends StatelessWidget { final contents = []; final validHeight = height - 16; final lineHeight = singleHeight(context, labelRegular); + int maxLines = (validHeight - lineHeight) ~/ lineHeight; final isKo = context.locale == Locale('ko'); final title = isKo ? lecture.title : lecture.titleEn; final classroomShort = isKo @@ -53,7 +54,7 @@ class TimetableBlock extends StatelessWidget { color: isTemp ? OTLColor.grayF : OTLColor.gray0, overflow: TextOverflow.ellipsis, ), - maxLines: (validHeight - lineHeight) ~/ lineHeight, + maxLines: maxLines > 1 ? maxLines : 1, )); } @@ -62,7 +63,7 @@ class TimetableBlock extends StatelessWidget { } if (showClassroom) { - final maxLines = (validHeight - + maxLines = (validHeight - getTextHeight(context, text: title, style: labelRegular, maxWidth: 54)) ~/ lineHeight; From 1ed78a08fbceb32733fda460fae1ff83069bdf1c Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 03:16:47 +0900 Subject: [PATCH 09/12] feat: modify timetable summary elements size --- lib/widgets/timetable_summary.dart | 53 +++++++++++++----------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/lib/widgets/timetable_summary.dart b/lib/widgets/timetable_summary.dart index c41e9f48..d05e6826 100644 --- a/lib/widgets/timetable_summary.dart +++ b/lib/widgets/timetable_summary.dart @@ -116,41 +116,35 @@ class TimetableSummary extends StatelessWidget { return Container( height: 75, - padding: const EdgeInsets.symmetric(vertical: 15), - alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 16), decoration: BoxDecoration( border: Border.symmetric( horizontal: BorderSide(color: OTLColor.pinksLight)), ), child: Row( - mainAxisSize: MainAxisSize.min, - children: [ + children: [ Container( - width: 164, - height: 38, + width: 150, + padding: const EdgeInsets.only(right: 3), child: GridView.builder( itemCount: 6, - padding: const EdgeInsets.only(right: 6), scrollDirection: Axis.horizontal, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, - mainAxisSpacing: 16, - crossAxisSpacing: 4, - mainAxisExtent: 42, + mainAxisSpacing: 6, + mainAxisExtent: 45, ), itemBuilder: (_, index) => _buildAttribute( titles[index], currentTypeCredit[index], tempType == index), ), ), - const SizedBox(width: 11), _buildScore( 'timetable.summary.credit'.tr(), allCreditCredit.toString(), tempLecture != null && tempLecture!.credit > 0), _buildScore("AU", allAuCredit.toString(), tempLecture != null && tempLecture!.creditAu > 0), - const SizedBox(width: 11), _buildScore( 'timetable.summary.grade'.tr(), targetNum > 0 ? LETTERS[(grade / targetNum).round()] : "?", @@ -169,29 +163,28 @@ class TimetableSummary extends StatelessWidget { } Widget _buildScore(String title, String content, bool highlight) { - return Padding( - padding: const EdgeInsets.only(left: 5), - child: SizedBox( - width: 28, - child: Column( - children: [ - Expanded( - child: Text( - content, - style: titleBold.copyWith( - color: highlight ? OTLColor.pinksMain : OTLColor.gray0), - textAlign: TextAlign.center, - ), + return Expanded( + child: Column( + children: [ + SizedBox( + height: 26, + child: Text( + content, + style: titleBold.copyWith( + color: highlight ? OTLColor.pinksMain : OTLColor.gray0), + textAlign: TextAlign.center, ), - Text( + ), + SizedBox( + height: 17, + child: Text( title, style: labelRegular.copyWith( color: highlight ? OTLColor.pinksMain : OTLColor.gray0), textAlign: TextAlign.center, - maxLines: 1, ), - ], - ), + ), + ], ), ); } @@ -208,7 +201,7 @@ class TimetableSummary extends StatelessWidget { ), ), SizedBox( - width: 14, + width: 17, child: Text( value.toString(), style: labelRegular.copyWith( From 5a81fc45054c5a67d48d8c64033d3aae2a459f5e Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 12:57:55 +0900 Subject: [PATCH 10/12] fix: export to image --- lib/constants/url.dart | 4 ++- lib/providers/timetable_model.dart | 42 +++++++++++++++--------------- lib/utils/export_image.dart | 9 +++++-- lib/widgets/timetable_tabs.dart | 7 ++--- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/lib/constants/url.dart b/lib/constants/url.dart index 0f910ee9..82b68eb0 100644 --- a/lib/constants/url.dart +++ b/lib/constants/url.dart @@ -21,4 +21,6 @@ const API_TIMETABLE_ADD_LECTURE_URL = const API_TIMETABLE_REMOVE_LECTURE_URL = API_TIMETABLE_URL + "/{timetable_id}/remove-lecture"; const API_LIKED_REVIEW_URL = API_URL + "/users/{user_id}/liked-reviews"; -const API_SHARE_URL = API_URL + "share/timetable/{type}"; +const API_SHARE_URL = API_URL + "share/timetable/{share_type}"; + +enum ShareType { image, ical } diff --git a/lib/providers/timetable_model.dart b/lib/providers/timetable_model.dart index 6f603236..8d3318b1 100644 --- a/lib/providers/timetable_model.dart +++ b/lib/providers/timetable_model.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widgetkit/flutter_widgetkit.dart'; import 'package:otlplus/constants/url.dart'; @@ -10,8 +10,7 @@ import 'package:otlplus/models/lecture.dart'; import 'package:otlplus/models/semester.dart'; import 'package:otlplus/models/timetable.dart'; import 'package:otlplus/models/user.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:permission_handler/permission_handler.dart'; +import 'package:otlplus/utils/export_image.dart'; class TimetableModel extends ChangeNotifier { late User _user; @@ -36,9 +35,6 @@ class TimetableModel extends ChangeNotifier { bool _isLoaded = false; bool get isLoaded => _isLoaded; - String get shareApiParameter => - "?timetable=${currentTimetable.id}&year=${selectedSemester.year}&semester=${selectedSemester.semester}&language="; - TimetableModel({bool forTest = false}) { if (forTest) { _user = User( @@ -248,22 +244,26 @@ class TimetableModel extends ChangeNotifier { return false; } - Future shareTimetable(String type, String language) async { + Future shareTimetable(ShareType type, String language) async { try { - final status = await [ - Permission.storage, - ].request(); - - if (status[Permission.storage]!.isGranted) { - final dir = Platform.isAndroid - ? '/storage/emulated/0/Download' - : (await getApplicationDocumentsDirectory()).path; - await DioProvider().dio.download( - API_SHARE_URL.replaceFirst('{type}', type) + - shareApiParameter + - language, - dir + '/timetable.${type == 'image' ? 'png' : 'ics'}', - ); + final response = await DioProvider().dio.get( + API_SHARE_URL.replaceFirst('{share_type}', type == ShareType.image ? 'image' : 'ical'), + queryParameters: { + 'timetable': currentTimetable.id, + 'year': selectedSemester.year, + 'semester': selectedSemester.semester, + 'language': language, + }, + options: Options(responseType: ResponseType.bytes), + ); + + switch (type) { + case ShareType.image: + writeImage(response.data); + break; + case ShareType.ical: + // TODO + break; } return true; } catch (exception) { diff --git a/lib/utils/export_image.dart b/lib/utils/export_image.dart index 8977d62c..b7e071dd 100644 --- a/lib/utils/export_image.dart +++ b/lib/utils/export_image.dart @@ -12,19 +12,24 @@ const MethodChannel _channel = const MethodChannel("org.sparcs.otlplus"); Future exportImage(RenderRepaintBoundary boundary) async { final image = await boundary.toImage(pixelRatio: 3.0); final byteData = await image.toByteData(format: ImageByteFormat.png); + + writeImage(byteData?.buffer.asUint8List()); +} + +Future writeImage(Uint8List? bytes) async { final fileName = "OTL-${DateTime.now().millisecondsSinceEpoch}.png"; if (Platform.isAndroid) { if (await Permission.storage.request().isGranted) { _channel.invokeMethod("writeImageAsBytes", { "fileName": fileName, - "bytes": byteData?.buffer.asUint8List(), + "bytes": bytes, }); } } else { final directory = await getApplicationDocumentsDirectory(); final path = "${directory.path}/$fileName"; - File(path).writeAsBytesSync(byteData?.buffer.asUint8List() as List); + File(path).writeAsBytesSync(bytes as List); OpenFile.open(path); } } diff --git a/lib/widgets/timetable_tabs.dart b/lib/widgets/timetable_tabs.dart index c1554906..d341d926 100644 --- a/lib/widgets/timetable_tabs.dart +++ b/lib/widgets/timetable_tabs.dart @@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:otlplus/constants/color.dart'; import 'package:otlplus/constants/text_styles.dart'; +import 'package:otlplus/constants/url.dart'; class TimetableTabs extends StatefulWidget { final int index; @@ -10,7 +11,7 @@ class TimetableTabs extends StatefulWidget { final Function(int) onTap; final VoidCallback onCopyTap; final VoidCallback onDeleteTap; - final Function(String) onExportTap; + final Function(ShareType) onExportTap; TimetableTabs( {this.index = 0, @@ -117,8 +118,8 @@ class _TimetableTabsState extends State { ], onChanged: (value) { if (value == MenuItems.copy) widget.onCopyTap(); - if (value == MenuItems.exportToImg) widget.onExportTap('image'); - if (value == MenuItems.exportToCal) widget.onExportTap('ical'); + if (value == MenuItems.exportToImg) widget.onExportTap(ShareType.image); + if (value == MenuItems.exportToCal) widget.onExportTap(ShareType.ical); // if (value == MenuItems.syllabus) Pass if (value == MenuItems.delete) widget.onDeleteTap(); }, From 3f082b0f879f98cd04cf2965d180a009e5404ace Mon Sep 17 00:00:00 2001 From: SungyeopJeong Date: Mon, 31 Jul 2023 23:24:55 +0900 Subject: [PATCH 11/12] fix: export to calendar --- android/app/src/main/AndroidManifest.xml | 1 + lib/providers/timetable_model.dart | 11 +----- lib/utils/export_file.dart | 48 ++++++++++++++++++++++++ lib/utils/export_image.dart | 35 ----------------- pubspec.lock | 22 +++++------ pubspec.yaml | 2 +- 6 files changed, 63 insertions(+), 56 deletions(-) create mode 100644 lib/utils/export_file.dart delete mode 100644 lib/utils/export_image.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8076ae05..3ff04bf2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + exportImage(RenderRepaintBoundary boundary) async { + final image = await boundary.toImage(pixelRatio: 3.0); + final byteData = await image.toByteData(format: ImageByteFormat.png); + + writeFile(ShareType.image, byteData?.buffer.asUint8List()); +} + +Future writeFile(ShareType type, Uint8List? bytes) async { + final fileName = + "OTL-${DateTime.now().millisecondsSinceEpoch}.${type == ShareType.image ? 'png' : 'ics'}"; + + if (Platform.isAndroid) { + if (await Permission.storage.request().isGranted) { + switch (type) { + case ShareType.image: + _channel.invokeMethod("writeImageAsBytes", { + "fileName": fileName, + "bytes": bytes, + }); + break; + case ShareType.ical: + final directory = await getExternalStorageDirectory(); + final path = + "${directory?.path ?? '/storage/emulated/0/Android/data/org.sparcs.otlplus/files'}/$fileName"; + File(path).writeAsBytesSync(bytes as List); + OpenAppFile.open(path); + break; + } + } + } else { + final directory = await getApplicationDocumentsDirectory(); + final path = "${directory.path}/$fileName"; + File(path).writeAsBytesSync(bytes as List); + OpenAppFile.open(path); + } +} diff --git a/lib/utils/export_image.dart b/lib/utils/export_image.dart deleted file mode 100644 index b7e071dd..00000000 --- a/lib/utils/export_image.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:io'; -import 'dart:ui'; - -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:open_file_safe/open_file_safe.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:permission_handler/permission_handler.dart'; - -const MethodChannel _channel = const MethodChannel("org.sparcs.otlplus"); - -Future exportImage(RenderRepaintBoundary boundary) async { - final image = await boundary.toImage(pixelRatio: 3.0); - final byteData = await image.toByteData(format: ImageByteFormat.png); - - writeImage(byteData?.buffer.asUint8List()); -} - -Future writeImage(Uint8List? bytes) async { - final fileName = "OTL-${DateTime.now().millisecondsSinceEpoch}.png"; - - if (Platform.isAndroid) { - if (await Permission.storage.request().isGranted) { - _channel.invokeMethod("writeImageAsBytes", { - "fileName": fileName, - "bytes": bytes, - }); - } - } else { - final directory = await getApplicationDocumentsDirectory(); - final path = "${directory.path}/$fileName"; - File(path).writeAsBytesSync(bytes as List); - OpenFile.open(path); - } -} diff --git a/pubspec.lock b/pubspec.lock index e8716df0..da466201 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: ffi - sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.0.2" file: dependency: transitive description: @@ -415,14 +415,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - open_file_safe: + open_app_file: dependency: "direct main" description: - name: open_file_safe - sha256: "99a43b36307bf6946bdc3b54514bc5081af53f46feaec782c840a116b681647d" + name: open_app_file + sha256: "5dd0af054364c12093947ad3e69370bb22ca535ca17f81f7b72444c7be18cc01" url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "4.0.2" package_config: dependency: transitive description: @@ -499,10 +499,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905 + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.1.7" permission_handler: dependency: "direct main" description: @@ -944,10 +944,10 @@ packages: dependency: transitive description: name: win32 - sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef + sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0 url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "5.0.6" xdg_directories: dependency: transitive description: @@ -973,5 +973,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2a17f5ed..0c928520 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,6 @@ dependencies: flutter_web_browser: ^0.17.1 webview_flutter: ^3.0.4 webview_cookie_manager: ^2.0.6 - open_file_safe: ^3.2.3 path_provider: ^2.0.12 permission_handler: ^8.3.0 provider: ^6.0.5 @@ -37,6 +36,7 @@ dependencies: flutter_widgetkit: ^1.0.3 flutter_svg: ^2.0.7 dropdown_button2: ^2.1.4 + open_app_file: ^4.0.2 # Firebase firebase_core: ^2.8.0 firebase_analytics: ^10.1.6 From fabbfd518a7b9334213ada6ef095043829f2e07b Mon Sep 17 00:00:00 2001 From: Seungbin Oh Date: Tue, 1 Aug 2023 02:45:37 +0900 Subject: [PATCH 12/12] chore: format code --- lib/home.dart | 4 +++- lib/pages/timetable_page.dart | 11 +++++++---- lib/providers/timetable_model.dart | 3 ++- lib/widgets/timetable_tabs.dart | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/home.dart b/lib/home.dart index 7a42cbab..c6dc8c95 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -105,7 +105,9 @@ class _OTLHomeState extends State with SingleTickerProviderStateMixin { padding: const EdgeInsets.only(left: 16), child: SemesterPicker( onSemesterChanged: () { - context.read().setSelectedLecture(null); + context + .read() + .setSelectedLecture(null); context.read().lectureClear(); }, ), diff --git a/lib/pages/timetable_page.dart b/lib/pages/timetable_page.dart index e2beef2d..43a84165 100644 --- a/lib/pages/timetable_page.dart +++ b/lib/pages/timetable_page.dart @@ -62,8 +62,8 @@ class _TimetablePageState extends State { padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( color: OTLColor.grayF, - borderRadius: - BorderRadius.only(topLeft: Radius.circular(16)), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16)), ), child: _buildTimetableTabs(context), ), @@ -73,8 +73,11 @@ class _TimetablePageState extends State { GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { - context.read().resetLectureFilter(); - Navigator.push(context, buildLectureSearchPageRoute()); + context + .read() + .resetLectureFilter(); + Navigator.push( + context, buildLectureSearchPageRoute()); }, child: Padding( padding: const EdgeInsets.fromLTRB(12, 18, 16, 18), diff --git a/lib/providers/timetable_model.dart b/lib/providers/timetable_model.dart index 8c8bb71a..caccfdbe 100644 --- a/lib/providers/timetable_model.dart +++ b/lib/providers/timetable_model.dart @@ -247,7 +247,8 @@ class TimetableModel extends ChangeNotifier { Future shareTimetable(ShareType type, String language) async { try { final response = await DioProvider().dio.get( - API_SHARE_URL.replaceFirst('{share_type}', type == ShareType.image ? 'image' : 'ical'), + API_SHARE_URL.replaceFirst( + '{share_type}', type == ShareType.image ? 'image' : 'ical'), queryParameters: { 'timetable': currentTimetable.id, 'year': selectedSemester.year, diff --git a/lib/widgets/timetable_tabs.dart b/lib/widgets/timetable_tabs.dart index d341d926..67438d39 100644 --- a/lib/widgets/timetable_tabs.dart +++ b/lib/widgets/timetable_tabs.dart @@ -118,8 +118,10 @@ class _TimetableTabsState extends State { ], onChanged: (value) { if (value == MenuItems.copy) widget.onCopyTap(); - if (value == MenuItems.exportToImg) widget.onExportTap(ShareType.image); - if (value == MenuItems.exportToCal) widget.onExportTap(ShareType.ical); + if (value == MenuItems.exportToImg) + widget.onExportTap(ShareType.image); + if (value == MenuItems.exportToCal) + widget.onExportTap(ShareType.ical); // if (value == MenuItems.syllabus) Pass if (value == MenuItems.delete) widget.onDeleteTap(); },