diff --git a/assets/icons/OTL.svg b/assets/icons/OTL.svg
new file mode 100644
index 00000000..da4a0ab3
--- /dev/null
+++ b/assets/icons/OTL.svg
@@ -0,0 +1,13 @@
+
diff --git a/assets/icons/addLecture.svg b/assets/icons/addLecture.svg
new file mode 100644
index 00000000..fb450b3d
--- /dev/null
+++ b/assets/icons/addLecture.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/alert.svg b/assets/icons/alert.svg
new file mode 100644
index 00000000..e7cb8914
--- /dev/null
+++ b/assets/icons/alert.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/icons/deleteLecture.svg b/assets/icons/deleteLecture.svg
new file mode 100644
index 00000000..22e4ef49
--- /dev/null
+++ b/assets/icons/deleteLecture.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/overlap.svg b/assets/icons/overlap.svg
new file mode 100644
index 00000000..19a8ae50
--- /dev/null
+++ b/assets/icons/overlap.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/timetable.svg b/assets/icons/timetable.svg
new file mode 100644
index 00000000..6b936aee
--- /dev/null
+++ b/assets/icons/timetable.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/translations/en.json b/assets/translations/en.json
index 83cab03d..8d1efffa 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -65,15 +65,20 @@
}
},
"timetable": {
- "ask_delete_lecture": "Do you want to delete class '{}'?",
- "ask_delete_tab": "Do you really want to delete '{}'?",
"my_tab": "My Table",
"tab": "Table {}",
- "add_lecture": "Add Lecture",
- "remove_lecture": "Remove Lecture",
"dialog": {
"add_lecture": "Add Lecture",
- "ask_add_lecture": "There is a lecture with overlapping hours. If added, the previous lecture will be deleted.\nDo you want to add new lecture to the timetable?"
+ "add_overlapping_lecture": "Add Overlapping Lecture",
+ "delete_lecture": "Delete Lecture",
+ "delete_tab": "Delete Table",
+ "ask_add_lecture": "Do you want to add lecture '{lecture}'?",
+ "ask_add_lecture_with_tab": "Do you want to add lecture '{lecture}' to {timetable}?",
+ "ask_add_overlapping_lecture": "Time overlaps with {lectures}. If you add '{lecture}', the lecture(s) will be deleted. Do you want to add it to the timetable?",
+ "ask_add_overlapping_lecture_with_tab": "Time overlaps with {lectures} in {timetable}. If you add '{lecture}', the lecture(s) will be deleted. Do you want to add it to the timetable?",
+ "ask_delete_lecture": "Do you want to delete lecture '{lecture}'?",
+ "ask_delete_lecture_with_tab": "Do you want to delete lecture '{lecture}' from {timetable}?",
+ "ask_delete_tab": "Do you really want to delete {timetable}?"
},
"tab_menu": {
"copy": "Copy Timetable",
@@ -130,7 +135,12 @@
"speech": "Speech",
"like": "Like",
"report": "Report",
- "expand": "more"
+ "expand": "more",
+ "mailto": {
+ "subject": "[Report Review]",
+ "body_reason": "[Reason for Reporting]\n*Please describe a reason for reporting.\n---------------------------------\n\n\n\n---------------------------------\n\n",
+ "body_info": "[Review Information]\n*This information has been filled automatically. Please do NOT edit.\nLecture: {title} ({oldCode})\nSemester: {semesterTitle}\nProf.: {professors}\nContent:\n{content}\n"
+ }
},
"user": {
"name": "Name",
@@ -154,10 +164,15 @@
"send_anonymously": "Send anonymously",
"send_anonymously_desc": "Not including user ID in error logs",
"reset_all": "Reset all settings",
- "reset_all_desc": "Are you really want to reset all data except login data?",
+ "dialog": {
+ "reset": "Reset",
+ "reset_settings": "Reset settings",
+ "reset_settings_desc": "Are you really want to reset all data? However, it will be reset except for login data."
+ },
"throw_test": "Throw Test Exception",
"throw_test_desc": "for testing firebase crashlytics",
- "about": "About"
+ "about": "About",
+ "view_licenses": "View Licenses"
},
"department": {
"department": "Dept.",
diff --git a/assets/translations/ko.json b/assets/translations/ko.json
index 97c5a78f..98bc1c3d 100644
--- a/assets/translations/ko.json
+++ b/assets/translations/ko.json
@@ -4,7 +4,7 @@
"alert": "경고",
"cancel": "취소",
"delete": "삭제",
- "add": "추가하기",
+ "add": "추가",
"reset": "초기화",
"reset_all": "전체 초기화",
"search": "검색",
@@ -65,15 +65,20 @@
}
},
"timetable": {
- "ask_delete_lecture": "'{}' 수업을 삭제하시겠습니까?",
- "ask_delete_tab": "'{}'을(를) 정말 삭제하시겠습니까?",
"my_tab": "내 시간표",
"tab": "시간표 {}",
- "add_lecture": "시간표에 추가",
- "remove_lecture": "시간표에서 제거",
"dialog": {
"add_lecture": "수업 추가",
- "ask_add_lecture": "시간이 겹치는 수업이 있습니다. 추가하시면 해당 수업은 삭제됩니다.\n시간표에 추가하시겠습니까?"
+ "add_overlapping_lecture": "겹치는 수업 추가",
+ "delete_lecture": "수업 삭제",
+ "delete_tab": "시간표 삭제",
+ "ask_add_lecture": "'{lecture}' 수업을 추가하시겠습니까?",
+ "ask_add_lecture_with_tab": "'{lecture}' 수업을 {timetable}에 추가하시겠습니까?",
+ "ask_add_overlapping_lecture": "{lectures}와(과) 시간이 겹칩니다. '{lecture}'을(를) 추가하시면 해당 수업은 삭제됩니다. 시간표에 추가하시겠습니까?",
+ "ask_add_overlapping_lecture_with_tab": "{timetable}의 {lectures}와(과) 시간이 겹칩니다. '{lecture}'을(를) 추가하시면 해당 수업은 삭제됩니다. 시간표에 추가하시겠습니까?",
+ "ask_delete_lecture": "'{lecture}' 수업을 삭제하시겠습니까?",
+ "ask_delete_lecture_with_tab": "'{lecture}' 수업을 {timetable}에서 삭제하시겠습니까?",
+ "ask_delete_tab": "{timetable}을(를) 정말 삭제하시겠습니까?"
},
"tab_menu": {
"copy": "시간표 복제하기",
@@ -130,7 +135,12 @@
"speech": "강의",
"like": "추천",
"report": "신고하기",
- "expand": "더 보기"
+ "expand": "더 보기",
+ "mailto": {
+ "subject": "[후기 신고]",
+ "body_reason": "[신고 사유]\n*후기를 신고한 이유를 서술해 주세요.\n---------------------------------\n\n\n\n---------------------------------\n\n",
+ "body_info": "[신고할 후기 정보]\n*자동으로 작성된 후기 정보입니다. 원활한 신고를 위해서 수정하지 말아주세요.\n과목: {title} ({oldCode})\n학기: {semesterTitle}\n교수: {professors}\n내용:\n{content}\n"
+ }
},
"user": {
"name": "이름",
@@ -142,7 +152,7 @@
"logout": "로그아웃",
"delete_account": "계정 삭제",
"ask_delete_account": "계정을 정말 삭제하시겠습니까?",
- "account_deleted": "계정 삭제됨",
+ "account_deleted": "삭제된 계정",
"deleted_account": "삭제된 계정입니다."
},
"settings": {
@@ -154,10 +164,15 @@
"send_anonymously": "익명으로 전송",
"send_anonymously_desc": "오류 로그에 사용자 ID를 포함하지 않고 익명으로 전송합니다.",
"reset_all": "모든 설정 데이터 초기화",
- "reset_all_desc": "정말 모든 설정 데이터를 초기화하시겠습니까? 로그인 정보는 초기화되지 않습니다.",
+ "dialog": {
+ "reset": "초기화",
+ "reset_settings": "설정 초기화",
+ "reset_settings_desc": "정말 모든 설정 데이터를 초기화하시겠습니까? 단, 로그인 정보는 제외하고 초기화됩니다."
+ },
"throw_test": "테스트 오류 발생",
"throw_test_desc": "Firebase crashlytics 테스트용",
- "about": "정보"
+ "about": "정보",
+ "view_licenses": "라이선스 보기"
},
"department": {
"department": "학과",
diff --git a/lib/constants/color.dart b/lib/constants/color.dart
index 21768701..fe208309 100644
--- a/lib/constants/color.dart
+++ b/lib/constants/color.dart
@@ -10,6 +10,7 @@ class OTLColor {
static const grayD = Color(0xFFDDDDDD);
static const grayE = Color(0xFFEEEEEE);
static const grayF = Color(0xFFFFFFFF);
+ static const barrier = Color(0x40000000);
static const pinksLight = Color(0xFFF9F0F0);
static const pinksSub = Color(0xFFF6C5CD);
diff --git a/lib/constants/text_styles.dart b/lib/constants/text_styles.dart
index c7d0fe41..b4f29c88 100644
--- a/lib/constants/text_styles.dart
+++ b/lib/constants/text_styles.dart
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
-const regularBase = TextStyle(letterSpacing: 0.15);
+const regularBase = TextStyle(
+ letterSpacing: 0.15,
+ leadingDistribution: TextLeadingDistribution.even,
+);
final labelRegular = regularBase.copyWith(
fontSize: 12.0,
@@ -31,16 +34,3 @@ final displayRegular = regularBase.copyWith(
height: 1.6,
);
final displayBold = displayRegular.copyWith(fontWeight: FontWeight.bold);
-
-final evenLabelRegular = labelRegular.copyWith(
- leadingDistribution: TextLeadingDistribution.even,
-);
-final evenBodyRegular = bodyRegular.copyWith(
- leadingDistribution: TextLeadingDistribution.even,
-);
-final evenBodyBold = bodyBold.copyWith(
- leadingDistribution: TextLeadingDistribution.even,
-);
-final evenTitleBold = titleBold.copyWith(
- leadingDistribution: TextLeadingDistribution.even,
-);
diff --git a/lib/constants/url.dart b/lib/constants/url.dart
index e5f8763e..9cfb2780 100644
--- a/lib/constants/url.dart
+++ b/lib/constants/url.dart
@@ -22,3 +22,5 @@ const API_LIKED_REVIEW_URL = API_URL + "/users/{user_id}/liked-reviews";
const API_SHARE_URL = API_URL + "share/timetable/{share_type}";
enum ShareType { image, ical }
+
+const CONTACT = "otlplus@sparcs.org";
diff --git a/lib/main.dart b/lib/main.dart
index ea4b4bb3..58365a6c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -127,7 +127,7 @@ class OTLFirebaseApp extends StatelessWidget {
final base = ThemeData(
fontFamily: 'NotoSansKR',
primarySwatch: createMaterialColor(OTLColor.pinksMain),
- canvasColor: Colors.white,
+ canvasColor: OTLColor.grayF,
iconTheme: const IconThemeData(color: OTLColor.gray3),
inputDecorationTheme: const InputDecorationTheme(
border: InputBorder.none,
diff --git a/lib/pages/course_detail_page.dart b/lib/pages/course_detail_page.dart
index a4136721..f01a68ea 100644
--- a/lib/pages/course_detail_page.dart
+++ b/lib/pages/course_detail_page.dart
@@ -134,7 +134,7 @@ class CourseDetailPage extends StatelessWidget {
: (isEn
? (professor.nameEn == '' ? professor.name : professor.nameEn)
: professor.name),
- style: evenLabelRegular,
+ style: labelRegular,
),
selected: (professor == null
? selectedFilter == "ALL"
diff --git a/lib/pages/lecture_detail_page.dart b/lib/pages/lecture_detail_page.dart
index f99dbbed..39fb2067 100644
--- a/lib/pages/lecture_detail_page.dart
+++ b/lib/pages/lecture_detail_page.dart
@@ -2,8 +2,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_web_browser/flutter_web_browser.dart';
import 'package:otlplus/constants/text_styles.dart';
+import 'package:otlplus/extensions/semester.dart';
import 'package:otlplus/models/review.dart';
import 'package:otlplus/pages/course_detail_page.dart';
+import 'package:otlplus/widgets/otl_dialog.dart';
import 'package:otlplus/widgets/responsive_button.dart';
import 'package:otlplus/utils/navigator.dart';
import 'package:otlplus/widgets/otl_scaffold.dart';
@@ -98,41 +100,55 @@ class LectureDetailPage extends StatelessWidget {
padding: const EdgeInsets.all(16),
onTap: () {
final timetableModel = context.read();
+ final isKo = context.locale == Locale('ko');
+ final lectureTitle = isKo ? lecture.title : lecture.titleEn;
+ final timetable =
+ '${timetableModel.selectedSemester.title} ${'timetable.tab'.tr(
+ args: [timetableModel.selectedIndex.toString()],
+ )}';
if (isAdded) {
- timetableModel.removeLecture(lecture: lecture);
+ OTLNavigator.pushDialog(
+ context: context,
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.deleteLectureWithTab,
+ namedArgs: {'lecture': lectureTitle, 'timetable': timetable},
+ onTapPos: () => timetableModel.removeLecture(lecture: lecture),
+ ),
+ );
} else {
timetableModel.addLecture(
lecture: lecture,
+ noOverlap: () async {
+ bool result = false;
+
+ await OTLNavigator.pushDialog(
+ context: context,
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.addLectureWithTab,
+ namedArgs: {'lecture': lectureTitle, 'timetable': timetable},
+ onTapPos: () => result = true,
+ ),
+ );
+
+ return result;
+ },
onOverlap: (lectures) async {
bool result = false;
await OTLNavigator.pushDialog(
context: context,
- barrierDismissible: false,
- builder: (context) => AlertDialog(
- title: Text("timetable.dialog.add_lecture".tr()),
- content: Text("timetable.dialog.ask_add_lecture".tr()),
- actions: [
- IconTextButton(
- padding: EdgeInsets.all(12),
- text: 'common.cancel'.tr(),
- color: OTLColor.pinksMain,
- onTap: () {
- result = false;
- OTLNavigator.pop(context);
- },
- ),
- IconTextButton(
- padding: EdgeInsets.all(12),
- text: 'common.add'.tr(),
- color: OTLColor.pinksMain,
- onTap: () {
- result = true;
- OTLNavigator.pop(context);
- },
- ),
- ],
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.addOverlappingLectureWithTab,
+ namedArgs: {
+ 'lectures': lectures
+ .map((lecture) =>
+ "'${isKo ? lecture.title : lecture.titleEn}'")
+ .join(', '),
+ 'lecture': lectureTitle,
+ 'timetable': timetable
+ },
+ onTapPos: () => result = true,
),
);
diff --git a/lib/pages/liked_review_page.dart b/lib/pages/liked_review_page.dart
index 10309a42..a3624d3f 100644
--- a/lib/pages/liked_review_page.dart
+++ b/lib/pages/liked_review_page.dart
@@ -1,5 +1,6 @@
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/pages/course_detail_page.dart';
import 'package:otlplus/providers/course_detail_model.dart';
@@ -86,7 +87,7 @@ class LikedReviewPage extends StatelessWidget {
width: 24,
height: 24,
child: CircularProgressIndicator(
- color: Colors.black12,
+ color: OTLColor.grayE,
strokeWidth: 2,
),
),
diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart
index 0cde36ca..66b5ca9e 100644
--- a/lib/pages/login_page.dart
+++ b/lib/pages/login_page.dart
@@ -1,10 +1,9 @@
import 'dart:io';
-import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:otlplus/constants/color.dart';
-import 'package:otlplus/widgets/responsive_button.dart';
+import 'package:otlplus/utils/navigator.dart';
+import 'package:otlplus/widgets/otl_dialog.dart';
import 'package:otlplus/widgets/otl_scaffold.dart';
import 'package:provider/provider.dart';
import 'package:otlplus/providers/auth_model.dart';
@@ -29,21 +28,11 @@ class _LoginPageState extends State {
(_) async {
if (!((await SharedPreferences.getInstance()).getBool('hasAccount') ??
true)) {
- await showDialog(
+ OTLNavigator.pushDialog(
context: context,
- builder: (context) => AlertDialog(
- title: Text('user.account_deleted'.tr()),
- content: Text('user.deleted_account'.tr()),
- actions: [
- IconTextButton(
- padding: EdgeInsets.all(12),
- text: 'common.close'.tr(),
- color: OTLColor.pinksMain,
- onTap: () {
- SystemNavigator.pop();
- },
- ),
- ],
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.accountDeleted,
+ onTapNeg: () => SystemNavigator.pop(),
),
);
}
diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart
index 27573c6b..f0dbc839 100644
--- a/lib/pages/main_page.dart
+++ b/lib/pages/main_page.dart
@@ -111,8 +111,6 @@ class _MainPageState extends State {
Image.asset(
"assets/images/bg.4556cdee.jpg",
fit: BoxFit.cover,
- color: const Color(0xFF9B4810).withOpacity(0.1),
- colorBlendMode: BlendMode.srcATop,
),
Container(
constraints: const BoxConstraints.expand(),
@@ -157,7 +155,7 @@ class _MainPageState extends State {
Expanded(
child: Text(
"common.search_hint".tr(),
- style: evenBodyRegular.copyWith(
+ style: bodyRegular.copyWith(
color: OTLColor.grayA, height: 1.24),
),
),
@@ -181,7 +179,7 @@ class _MainPageState extends State {
SliverFillRemaining(
hasScrollBody: false,
child: ColoredBox(
- color: Colors.white,
+ color: OTLColor.grayF,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
diff --git a/lib/pages/review_page.dart b/lib/pages/review_page.dart
index 22d57c2d..22781a37 100644
--- a/lib/pages/review_page.dart
+++ b/lib/pages/review_page.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:otlplus/constants/color.dart';
import 'package:otlplus/pages/course_detail_page.dart';
import 'package:otlplus/providers/hall_of_fame_model.dart';
import 'package:otlplus/utils/navigator.dart';
@@ -119,7 +120,7 @@ class LatestReviewsPage extends StatelessWidget {
width: 24,
height: 24,
child: CircularProgressIndicator(
- color: Colors.black12,
+ color: OTLColor.grayE,
strokeWidth: 2,
),
),
@@ -176,7 +177,7 @@ class HallOfFamePage extends StatelessWidget {
width: 24,
height: 24,
child: CircularProgressIndicator(
- color: Colors.black12,
+ color: OTLColor.grayE,
strokeWidth: 2,
),
),
diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart
index d0d66e00..2cb41ccc 100644
--- a/lib/pages/settings_page.dart
+++ b/lib/pages/settings_page.dart
@@ -2,9 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:otlplus/constants/color.dart';
import 'package:otlplus/constants/text_styles.dart';
+import 'package:otlplus/constants/url.dart';
import 'package:otlplus/providers/settings_model.dart';
import 'package:otlplus/widgets/dropdown.dart';
-import 'package:otlplus/widgets/responsive_button.dart';
+import 'package:otlplus/widgets/otl_dialog.dart';
import 'package:otlplus/utils/navigator.dart';
import 'package:otlplus/widgets/otl_scaffold.dart';
import 'package:provider/provider.dart';
@@ -13,8 +14,6 @@ import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:easy_localization/easy_localization.dart';
class SettingsPage extends StatelessWidget {
- final contactEmail = 'otlplus@kaist.ac.kr';
-
@override
Widget build(BuildContext context) {
final isEn = EasyLocalization.of(context)?.currentLocale == Locale('en');
@@ -121,65 +120,30 @@ class SettingsPage extends StatelessWidget {
onTap: () {
OTLNavigator.pushDialog(
context: context,
- builder: (context) => AlertDialog(
- title: Text(
- 'common.alert'.tr(),
- style: titleRegular,
- ),
- content: Text(
- 'settings.reset_all_desc'.tr(),
- style: bodyRegular,
- ),
- actions: [
- TextButton(
- child: Text(
- "common.cancel".tr(),
- style: bodyRegular,
- ),
- onPressed: () {
- OTLNavigator.pop(context);
- },
- ),
- TextButton(
- child: Text(
- "common.delete".tr(),
- style: bodyRegular,
- ),
- onPressed: () {
- context
- .read()
- .clearAllValues()
- .then((_) => OTLNavigator.pop(context));
- },
- ),
- ],
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.resetSettings,
+ onTapPos: () =>
+ context.read().clearAllValues(),
),
);
},
),
_buildListTile(
title: "settings.about".tr(),
- onTap: () => showAboutDialog(
+ onTap: () => OTLNavigator.pushDialog(
context: context,
- applicationName: "",
- applicationIcon:
- Image.asset("assets/images/logo.png", height: 48.0),
- children: [
- Text(
- "Online Timeplanner with Lectures Plus @ KAIST",
- style: bodyRegular,
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.about,
+ onTapContent: () =>
+ launchUrl(Uri.parse("mailto:$CONTACT")),
+ onTapPos: () => showLicensePage(
+ context: context,
+ applicationName: "",
+ applicationIcon:
+ Image.asset("assets/images/logo.png", height: 48.0),
),
- IconTextButton(
- padding: EdgeInsets.fromLTRB(0, 4, 10, 4),
- onTap: () =>
- launchUrl(Uri.parse("mailto:$contactEmail")),
- text: contactEmail,
- textStyle:
- bodyRegular.copyWith(color: OTLColor.pinksMain),
- )
- ],
+ ),
),
- hasTopPadding: false,
),
],
),
@@ -191,7 +155,7 @@ class SettingsPage extends StatelessWidget {
Widget _buildListTile(
{required String title,
- String subtitle = '',
+ String? subtitle,
Widget? trailing,
void Function()? onTap,
bool hasTopPadding = true}) {
@@ -209,8 +173,10 @@ class SettingsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: bodyBold),
- const SizedBox(height: 4),
- Text(subtitle, style: bodyRegular),
+ if (subtitle != null) ...[
+ const SizedBox(height: 4),
+ Text(subtitle, style: bodyRegular),
+ ]
],
),
),
diff --git a/lib/pages/timetable_page.dart b/lib/pages/timetable_page.dart
index 24b7984b..87d21560 100644
--- a/lib/pages/timetable_page.dart
+++ b/lib/pages/timetable_page.dart
@@ -3,8 +3,7 @@ import 'package:otlplus/pages/lecture_detail_page.dart';
import 'package:otlplus/pages/lecture_search_page.dart';
import 'package:otlplus/utils/navigator.dart';
import 'package:otlplus/providers/lecture_search_model.dart';
-import 'package:otlplus/widgets/delete_dialog.dart';
-import 'package:otlplus/widgets/responsive_button.dart';
+import 'package:otlplus/widgets/otl_dialog.dart';
import 'package:otlplus/widgets/lecture_search.dart';
import 'package:otlplus/widgets/map_view.dart';
import 'package:otlplus/widgets/otl_scaffold.dart';
@@ -158,7 +157,7 @@ class _TimetablePageState extends State {
child: RepaintBoundary(
key: _paintKey,
child: Container(
- color: Colors.white,
+ color: OTLColor.grayF,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _buildTimetable(context, lectures, isExamTime),
),
@@ -178,7 +177,6 @@ class _TimetablePageState extends State {
bool isFirst = true;
final tempLecture =
context.select((model) => model.tempLecture);
- final isEn = EasyLocalization.of(context)?.currentLocale == Locale('en');
return Timetable(
lectures: (tempLecture == null) ? lectures : [...lectures, tempLecture],
@@ -203,47 +201,25 @@ class _TimetablePageState extends State {
context.read().loadLecture(lecture.id, true);
OTLNavigator.push(context, LectureDetailPage());
},
- onLongPress: isSelected
- ? null
- : () async {
- bool result = false;
- await OTLNavigator.pushDialog(
- context: context,
- barrierDismissible: false,
- builder: (context) => AlertDialog(
- title: Text("common.delete".tr()),
- content: Text("timetable.ask_delete_lecture").tr(
- args: [isEn ? lecture.titleEn : lecture.title],
- ),
- actions: [
- IconTextButton(
- padding: EdgeInsets.all(12),
- text: 'common.cancel'.tr(),
- color: OTLColor.pinksMain,
- onTap: () {
- result = false;
- OTLNavigator.pop(context);
+ onLongPress:
+ isSelected || context.read().selectedIndex == 0
+ ? null
+ : () {
+ OTLNavigator.pushDialog(
+ context: context,
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.deleteLecture,
+ namedArgs: {
+ 'lecture': context.locale == Locale('ko')
+ ? lecture.title
+ : lecture.titleEn
},
+ onTapPos: () => context
+ .read()
+ .removeLecture(lecture: lecture),
),
- IconTextButton(
- padding: EdgeInsets.all(12),
- text: 'common.delete'.tr(),
- color: OTLColor.pinksMain,
- onTap: () {
- result = true;
- OTLNavigator.pop(context);
- },
- ),
- ],
- ),
- );
-
- if (result) {
- context
- .read()
- .removeLecture(lecture: lecture);
- }
- },
+ );
+ },
);
},
);
@@ -274,20 +250,15 @@ class _TimetablePageState extends State {
});*/
},
onDeleteTap: () {
- showGeneralDialog(
+ OTLNavigator.pushDialog(
context: context,
- barrierColor: Colors.black.withOpacity(0.2),
- barrierDismissible: true,
- barrierLabel:
- MaterialLocalizations.of(context).modalBarrierDismissLabel,
- pageBuilder: (context, _, __) => DeleteDialog(
- text: 'timetable.ask_delete_tab'.tr(args: [
- 'timetable.tab'
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.deleteTab,
+ namedArgs: {
+ 'timetable': 'timetable.tab'
.tr(args: [timetableModel.selectedIndex.toString()])
- ]),
- onDelete: () {
- context.read().deleteTimetable();
},
+ onTapPos: () => context.read().deleteTimetable(),
),
);
},
diff --git a/lib/pages/user_page.dart b/lib/pages/user_page.dart
index e2819ff9..3949cfdf 100644
--- a/lib/pages/user_page.dart
+++ b/lib/pages/user_page.dart
@@ -7,7 +7,7 @@ import 'package:otlplus/pages/liked_review_page.dart';
import 'package:otlplus/pages/my_review_page.dart';
import 'package:otlplus/providers/auth_model.dart';
import 'package:otlplus/utils/navigator.dart';
-import 'package:otlplus/widgets/delete_dialog.dart';
+import 'package:otlplus/widgets/otl_dialog.dart';
import 'package:otlplus/widgets/responsive_button.dart';
import 'package:otlplus/widgets/otl_scaffold.dart';
import 'package:provider/provider.dart';
@@ -24,7 +24,7 @@ class UserPage extends StatelessWidget {
child: OTLLayout(
middle: Text('title.my_information'.tr(), style: titleBold),
body: ColoredBox(
- color: Colors.white,
+ color: OTLColor.grayF,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
@@ -77,19 +77,15 @@ class UserPage extends StatelessWidget {
if (Platform.isIOS)
_buildAccount(
Icons.highlight_off,
- () async {
- showGeneralDialog(
+ () {
+ OTLNavigator.pushDialog(
context: context,
- barrierColor: Colors.black.withOpacity(0.2),
- barrierDismissible: true,
- barrierLabel: MaterialLocalizations.of(context)
- .modalBarrierDismissLabel,
- pageBuilder: (context, _, __) => DeleteDialog(
- text: 'user.ask_delete_account'.tr(),
- onDelete: () {
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.deleteAccount,
+ onTapPos: () {
context.read().logout();
context.read().deleteAccount();
- Navigator.pop(context);
+ OTLNavigator.pop(context);
},
),
);
diff --git a/lib/providers/course_search_model.dart b/lib/providers/course_search_model.dart
index ad9795f6..cea6f67b 100644
--- a/lib/providers/course_search_model.dart
+++ b/lib/providers/course_search_model.dart
@@ -148,7 +148,7 @@ class CourseSearchModel extends ChangeNotifier {
Text get courseSearchquery => (_courseSearchquery == null)
? Text(
"common.search_hint".tr(),
- style: evenBodyRegular.copyWith(color: OTLColor.grayA),
+ style: bodyRegular.copyWith(color: OTLColor.grayA),
)
: _courseSearchquery!;
void updateCourseSearchquery() {
@@ -166,7 +166,7 @@ class CourseSearchModel extends ChangeNotifier {
} else {
_courseSearchquery = Text.rich(
TextSpan(
- style: evenBodyRegular.copyWith(color: OTLColor.grayA),
+ style: bodyRegular.copyWith(color: OTLColor.grayA),
children: [
TextSpan(
text: _courseSearchText.isEmpty ? '' : '"$_courseSearchText"',
diff --git a/lib/providers/lecture_search_model.dart b/lib/providers/lecture_search_model.dart
index c9e23f3a..4e5b65aa 100644
--- a/lib/providers/lecture_search_model.dart
+++ b/lib/providers/lecture_search_model.dart
@@ -133,7 +133,7 @@ class LectureSearchModel extends ChangeNotifier {
.map((i) => i.label)))).values.expand((i) => i).toList();
_lectureSearchquery = Text.rich(
TextSpan(
- style: evenBodyRegular.copyWith(color: OTLColor.grayA),
+ style: bodyRegular.copyWith(color: OTLColor.grayA),
children: [
TextSpan(
text: _lectureSearchText.isEmpty ? '' : '"$_lectureSearchText"',
@@ -184,10 +184,10 @@ class LectureSearchModel extends ChangeNotifier {
children: [
TextSpan(
text: keyword,
- style:
- TextStyle(fontWeight: FontWeight.bold, color: Colors.black)),
+ style: TextStyle(
+ fontWeight: FontWeight.bold, color: OTLColor.gray0)),
if (filterOptions.length > 0)
- TextSpan(style: TextStyle(color: Color(0xFFAAAAAA)), children: [
+ TextSpan(style: TextStyle(color: OTLColor.grayA), children: [
if ((keyword ?? '').length > 0) TextSpan(text: ", "),
TextSpan(text: (filterOptions).join(", ")),
])
diff --git a/lib/providers/timetable_model.dart b/lib/providers/timetable_model.dart
index 5ed3a542..42997abc 100644
--- a/lib/providers/timetable_model.dart
+++ b/lib/providers/timetable_model.dart
@@ -168,6 +168,7 @@ class TimetableModel extends ChangeNotifier {
Future addLecture(
{required Lecture lecture,
+ required FutureOr Function() noOverlap,
required FutureOr Function(Iterable) onOverlap}) async {
try {
final overlappedLectures = currentTimetable.lectures.where(
@@ -189,7 +190,7 @@ class TimetableModel extends ChangeNotifier {
data: {"lecture": lecture.id});
}
currentTimetable.lectures.removeWhere(overlappedLectures.contains);
- }
+ } else if (!await noOverlap()) return false;
final response = await DioProvider().dio.post(
API_TIMETABLE_ADD_LECTURE_URL
diff --git a/lib/utils/navigator.dart b/lib/utils/navigator.dart
index 6940caed..ac8da534 100644
--- a/lib/utils/navigator.dart
+++ b/lib/utils/navigator.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:otlplus/constants/color.dart';
enum OTLNavigatorTransition { rightLeft, downUp, immediate }
@@ -108,9 +109,9 @@ class OTLNavigator {
required BuildContext context,
required WidgetBuilder builder,
bool barrierDismissible = true,
- Color? barrierColor = Colors.black54,
+ Color? barrierColor = OTLColor.barrier,
String? barrierLabel,
- bool useSafeArea = true,
+ bool useSafeArea = false,
bool useRootNavigator = true,
RouteSettings? routeSettings,
Offset? anchorPoint,
diff --git a/lib/widgets/custom_header_delegate.dart b/lib/widgets/custom_header_delegate.dart
index 9a0100b1..1938867e 100644
--- a/lib/widgets/custom_header_delegate.dart
+++ b/lib/widgets/custom_header_delegate.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:otlplus/constants/color.dart';
class CustomHeaderDelegate extends SliverPersistentHeaderDelegate {
final Widget Function(double) builder;
@@ -12,7 +13,7 @@ class CustomHeaderDelegate extends SliverPersistentHeaderDelegate {
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
- color: Colors.white,
+ color: OTLColor.grayF,
padding: padding,
transform: Matrix4.translationValues(0, -1, 0),
child: Material(
diff --git a/lib/widgets/delete_dialog.dart b/lib/widgets/delete_dialog.dart
deleted file mode 100644
index b5fd85db..00000000
--- a/lib/widgets/delete_dialog.dart
+++ /dev/null
@@ -1,83 +0,0 @@
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
-import 'package:otlplus/constants/color.dart';
-
-class DeleteDialog extends StatelessWidget {
- const DeleteDialog({Key? key, required this.text, this.onDelete})
- : super(key: key);
- final String text;
- final void Function()? onDelete;
-
- @override
- Widget build(BuildContext context) {
- return Center(
- child: ClipRRect(
- borderRadius: BorderRadius.circular(10),
- child: Material(
- child: IntrinsicWidth(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Container(
- padding: const EdgeInsets.fromLTRB(16, 19, 16, 20),
- alignment: Alignment.center,
- color: Colors.white,
- child: Text(
- text,
- style: TextStyle(
- fontSize: 12,
- ),
- ),
- ),
- Row(
- children: [
- _buildButton(
- () => Navigator.pop(context),
- text: 'common.cancel'.tr(),
- buttonColor: OTLColor.grayE,
- ),
- _buildButton(
- () {
- if (onDelete != null) onDelete!();
- Navigator.pop(context);
- },
- text: 'common.delete'.tr(),
- buttonColor: OTLColor.pinksMain,
- textColor: Colors.white,
- ),
- ],
- )
- ],
- ),
- ),
- ),
- ),
- );
- }
-
- Widget _buildButton(
- void Function()? onTap, {
- required String text,
- required Color buttonColor,
- Color? textColor,
- }) {
- return Expanded(
- child: GestureDetector(
- onTap: onTap,
- child: Container(
- height: 40,
- alignment: Alignment.center,
- color: buttonColor,
- child: Text(
- text,
- style: TextStyle(
- color: textColor,
- fontSize: 12,
- fontWeight: FontWeight.w700,
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/widgets/expandable_text.dart b/lib/widgets/expandable_text.dart
index 5df05919..7efde15d 100644
--- a/lib/widgets/expandable_text.dart
+++ b/lib/widgets/expandable_text.dart
@@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart' as _;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
+import 'package:otlplus/constants/color.dart';
class ExpandableText extends StatefulWidget {
const ExpandableText(
@@ -32,7 +33,7 @@ class ExpandableTextState extends State {
TextSpan(
text: "review.expand".tr(),
style:
- (widget.style ?? TextStyle()).copyWith(color: Colors.black45),
+ (widget.style ?? TextStyle()).copyWith(color: OTLColor.gray75),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() => _expanded = true);
diff --git a/lib/widgets/hall_of_fame_control.dart b/lib/widgets/hall_of_fame_control.dart
index db4c66af..1a8ca82d 100644
--- a/lib/widgets/hall_of_fame_control.dart
+++ b/lib/widgets/hall_of_fame_control.dart
@@ -49,7 +49,7 @@ class _HallOfFameControlState extends State {
_currentSemester == null
? "common.all".tr()
: "${_currentSemester?.year} ${_currentSemester?.semester == 1 ? 'semester.spring'.tr() : 'semester.fall'.tr()}",
- style: evenBodyBold.copyWith(
+ style: bodyBold.copyWith(
color: OTLColor.pinksMain,
),
),
diff --git a/lib/widgets/lecture_group_block.dart b/lib/widgets/lecture_group_block.dart
index eecf62e9..619e2213 100644
--- a/lib/widgets/lecture_group_block.dart
+++ b/lib/widgets/lecture_group_block.dart
@@ -75,7 +75,7 @@ class LectureGroupBlock extends StatelessWidget {
child: SizedBox(
height: 1,
child: ColoredBox(
- color: Color(0xFFDADADA),
+ color: OTLColor.grayA,
),
),
),
diff --git a/lib/widgets/lecture_group_block_row.dart b/lib/widgets/lecture_group_block_row.dart
index 4e54cc16..bc0dad2d 100644
--- a/lib/widgets/lecture_group_block_row.dart
+++ b/lib/widgets/lecture_group_block_row.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/widgets/otl_dialog.dart';
import 'package:otlplus/widgets/responsive_button.dart';
import 'package:otlplus/utils/navigator.dart';
import 'package:provider/provider.dart';
@@ -100,7 +101,7 @@ class _LectureGroupBlockRowState extends State {
icon: 'assets/icons/info.svg',
iconSize: 20.0,
onTap: widget.onLongPress,
- color: Color(0xFF000000),
+ color: OTLColor.gray0,
),
SizedBox(
width: 6.0,
@@ -119,7 +120,7 @@ class _LectureGroupBlockRowState extends State {
iconSize: 24,
color: alreadyAdded
? OTLColor.pinksMain
- : Color(0xFF000000),
+ : OTLColor.gray0,
)
],
),
@@ -135,38 +136,43 @@ class _LectureGroupBlockRowState extends State {
}
Future _addLecture(Lecture lec) async {
+ final isKo = context.locale == Locale('ko');
+ final lectureTitle = isKo ? lec.title : lec.titleEn;
+
bool result = await context.read().addLecture(
lecture: lec,
+ noOverlap: () async {
+ bool result = false;
+
+ await OTLNavigator.pushDialog(
+ context: context,
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.addLecture,
+ namedArgs: {'lecture': lectureTitle},
+ onTapPos: () => result = true,
+ ),
+ );
+
+ return result;
+ },
onOverlap: (lectures) async {
bool result = false;
+
await OTLNavigator.pushDialog(
context: context,
- barrierDismissible: false,
- builder: (context) => AlertDialog(
- title: Text("timetable.dialog.add_lecture".tr()),
- content: Text("timetable.dialog.ask_add_lecture".tr()),
- actions: [
- IconTextButton(
- padding: EdgeInsets.all(12),
- text: 'common.cancel'.tr(),
- color: OTLColor.pinksMain,
- onTap: () {
- result = false;
- OTLNavigator.pop(context);
- },
- ),
- IconTextButton(
- padding: EdgeInsets.all(12),
- text: 'common.add'.tr(),
- color: OTLColor.pinksMain,
- onTap: () {
- result = true;
- OTLNavigator.pop(context);
- },
- ),
- ],
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.addOverlappingLecture,
+ namedArgs: {
+ 'lectures': lectures
+ .map((lecture) =>
+ "'${isKo ? lecture.title : lecture.titleEn}'")
+ .join(', '),
+ 'lecture': lectureTitle
+ },
+ onTapPos: () => result = true,
),
);
+
return result;
},
);
@@ -176,9 +182,17 @@ class _LectureGroupBlockRowState extends State {
}
Future _removeLecture(Lecture lec) async {
- await context.read().removeLecture(
- lecture: lec,
- );
+ await OTLNavigator.pushDialog(
+ context: context,
+ builder: (_) => OTLDialog(
+ type: OTLDialogType.deleteLecture,
+ namedArgs: {
+ 'lecture': context.locale == Locale('ko') ? lec.title : lec.titleEn
+ },
+ onTapPos: () =>
+ context.read().removeLecture(lecture: lec),
+ ),
+ );
context.read().setTempLecture(null);
}
}
diff --git a/lib/widgets/lecture_group_simple_block.dart b/lib/widgets/lecture_group_simple_block.dart
index 2165efa9..3ed9cd16 100644
--- a/lib/widgets/lecture_group_simple_block.dart
+++ b/lib/widgets/lecture_group_simple_block.dart
@@ -71,11 +71,11 @@ class LectureGroupSimpleBlock extends StatelessWidget {
),
child: Text.rich(
TextSpan(
- style: evenBodyRegular,
+ style: bodyRegular,
children: [
TextSpan(
text: lecture.classTitle,
- style: evenBodyBold,
+ style: bodyBold,
),
TextSpan(text: ' '),
TextSpan(
diff --git a/lib/widgets/otl_dialog.dart b/lib/widgets/otl_dialog.dart
new file mode 100644
index 00000000..e41f5a38
--- /dev/null
+++ b/lib/widgets/otl_dialog.dart
@@ -0,0 +1,325 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:otlplus/constants/color.dart';
+import 'package:otlplus/constants/text_styles.dart';
+import 'package:otlplus/constants/url.dart';
+import 'package:otlplus/utils/navigator.dart';
+import 'package:otlplus/widgets/responsive_button.dart';
+
+enum OTLDialogType {
+ /// namedArgs: 'lecture'
+ addLecture,
+
+ /// namedArgs: 'lecture', 'timetable'
+ addLectureWithTab,
+
+ /// namedArgs: 'lectures', 'lecture'
+ addOverlappingLecture,
+
+ /// namedArgs: 'lectures', 'lecture', 'timetable'
+ addOverlappingLectureWithTab,
+
+ /// namedArgs: 'lecture'
+ deleteLecture,
+
+ /// namedArgs: 'lecture', 'timetable'
+ deleteLectureWithTab,
+
+ /// namedArgs: 'timetable'
+ deleteTab,
+
+ deleteAccount,
+
+ accountDeleted,
+
+ resetSettings,
+
+ about,
+}
+
+enum BtnStyle { one, even, uneven }
+
+class _OTLDialogData {
+ /// Without tr()
+ ///
+ /// Example: 'timetable.dialog.add_lecture', 'timetable.dialog.ask_add_lecture'
+ final String title, content;
+
+ /// Without 'assets/icons/'
+ ///
+ /// Example: 'addLecture'
+ final String icon;
+
+ /// Without tr()
+ ///
+ /// Example: 'common.cancel', 'common.add'
+ final String negText, posText;
+ final BtnStyle btnStyle;
+
+ _OTLDialogData({
+ required this.title,
+ required this.content,
+ required this.icon,
+ this.negText = 'common.cancel',
+ this.posText = 'common.add',
+ this.btnStyle = BtnStyle.even,
+ });
+}
+
+extension OTLDialogTypeExt on OTLDialogType {
+ static final _data = {
+ OTLDialogType.addLecture: _OTLDialogData(
+ title: 'timetable.dialog.add_lecture',
+ content: 'timetable.dialog.ask_add_lecture',
+ icon: 'addLecture',
+ ),
+ OTLDialogType.addLectureWithTab: _OTLDialogData(
+ title: 'timetable.dialog.add_lecture',
+ content: 'timetable.dialog.ask_add_lecture_with_tab',
+ icon: 'addLecture',
+ ),
+ OTLDialogType.addOverlappingLecture: _OTLDialogData(
+ title: 'timetable.dialog.add_overlapping_lecture',
+ content: 'timetable.dialog.ask_add_overlapping_lecture',
+ icon: 'overlap',
+ ),
+ OTLDialogType.addOverlappingLectureWithTab: _OTLDialogData(
+ title: 'timetable.dialog.add_overlapping_lecture',
+ content: 'timetable.dialog.ask_add_overlapping_lecture_with_tab',
+ icon: 'overlap',
+ ),
+ OTLDialogType.deleteLecture: _OTLDialogData(
+ title: 'timetable.dialog.delete_lecture',
+ content: 'timetable.dialog.ask_delete_lecture',
+ icon: 'deleteLecture',
+ posText: 'common.delete',
+ ),
+ OTLDialogType.deleteLectureWithTab: _OTLDialogData(
+ title: 'timetable.dialog.delete_lecture',
+ content: 'timetable.dialog.ask_delete_lecture_with_tab',
+ icon: 'deleteLecture',
+ posText: 'common.delete',
+ ),
+ OTLDialogType.deleteTab: _OTLDialogData(
+ title: 'timetable.dialog.delete_tab',
+ content: 'timetable.dialog.ask_delete_tab',
+ icon: 'timetable',
+ posText: 'common.delete',
+ ),
+ OTLDialogType.deleteAccount: _OTLDialogData(
+ title: 'user.delete_account',
+ content: 'user.ask_delete_account',
+ icon: 'alert',
+ posText: 'common.delete',
+ ),
+ OTLDialogType.accountDeleted: _OTLDialogData(
+ title: 'user.account_deleted',
+ content: 'user.deleted_account',
+ icon: 'alert',
+ negText: 'common.close',
+ btnStyle: BtnStyle.one,
+ ),
+ OTLDialogType.resetSettings: _OTLDialogData(
+ title: 'settings.dialog.reset_settings',
+ content: 'settings.dialog.reset_settings_desc',
+ icon: 'alert',
+ posText: 'settings.dialog.reset',
+ ),
+ OTLDialogType.about: _OTLDialogData(
+ title: 'Online Timeplanner with Lectures Plus @ KAIST',
+ content: CONTACT,
+ icon: 'OTL',
+ negText: 'common.close',
+ posText: 'settings.view_licenses',
+ btnStyle: BtnStyle.uneven,
+ ),
+ };
+
+ String get title => _data[this]!.title;
+ String get content => _data[this]!.content;
+ String get icon => _data[this]!.icon;
+ String get negText => _data[this]!.negText;
+ String get posText => _data[this]!.posText;
+ BtnStyle get btnStyle => _data[this]!.btnStyle;
+}
+
+class OTLDialog extends StatelessWidget {
+ const OTLDialog({
+ Key? key,
+ required this.type,
+ this.namedArgs,
+ this.onTapContent,
+ this.onTapNeg,
+ this.onTapPos,
+ }) : super(key: key);
+
+ final OTLDialogType type;
+ final Map? namedArgs;
+ final void Function()? onTapContent, onTapNeg, onTapPos;
+
+ @override
+ Widget build(BuildContext context) {
+ return Center(
+ child: Container(
+ width: 256,
+ padding: const EdgeInsets.all(20),
+ decoration: BoxDecoration(
+ color: OTLColor.grayF,
+ borderRadius: BorderRadius.circular(10),
+ boxShadow: [
+ BoxShadow(
+ color: OTLColor.gray0.withOpacity(0.15),
+ offset: const Offset(2, 2),
+ blurRadius: 16,
+ ),
+ ],
+ ),
+ child: Material(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SvgPicture.asset(
+ 'assets/icons/${type.icon}.svg',
+ height: 80,
+ ),
+ Container(
+ padding: const EdgeInsets.symmetric(vertical: 24),
+ alignment: Alignment.centerLeft,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(type.title.tr(), style: titleBold),
+ const SizedBox(height: 8),
+ _buildContent(),
+ ],
+ ),
+ ),
+ () {
+ // Map
+ final btnData = {
+ true: {
+ 'btnColor': OTLColor.pinksMain,
+ 'onTap': () {
+ if (onTapPos != null) onTapPos!();
+ if (type != OTLDialogType.about)
+ OTLNavigator.pop(context);
+ },
+ 'btnText': type.posText.tr(),
+ 'textColor': OTLColor.grayF,
+ },
+ false: {
+ 'btnColor': OTLColor.grayE,
+ 'onTap': () {
+ if (onTapNeg != null) onTapNeg!();
+ OTLNavigator.pop(context);
+ },
+ 'btnText': type.negText.tr(),
+ 'textColor': OTLColor.gray0,
+ }
+ };
+
+ switch (type.btnStyle) {
+ case BtnStyle.one:
+ return _buildButton(btnData[false]!);
+ case BtnStyle.even:
+ return Row(
+ children: [
+ Expanded(child: _buildButton(btnData[false]!)),
+ const SizedBox(width: 10),
+ Expanded(child: _buildButton(btnData[true]!)),
+ ],
+ );
+ case BtnStyle.uneven:
+ return Row(
+ children: [
+ Expanded(child: _buildButton(btnData[true]!)),
+ const SizedBox(width: 10),
+ SizedBox(
+ width: 64,
+ child: _buildButton(btnData[false]!),
+ ),
+ ],
+ );
+ }
+ }(),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildContent() {
+ final content = type.content.tr(namedArgs: namedArgs);
+ switch (type) {
+ case OTLDialogType.addLecture:
+ case OTLDialogType.addLectureWithTab:
+ case OTLDialogType.deleteLecture:
+ case OTLDialogType.deleteLectureWithTab:
+ case OTLDialogType.deleteTab:
+ case OTLDialogType.deleteAccount:
+ case OTLDialogType.accountDeleted:
+ case OTLDialogType.resetSettings:
+ return Text(
+ content,
+ style: bodyRegular,
+ );
+ case OTLDialogType.addOverlappingLecture:
+ case OTLDialogType.addOverlappingLectureWithTab:
+ return Text.rich(TextSpan(
+ children: () {
+ final reg = RegExp(r"'.*?'");
+ final lectures = reg
+ .allMatches(content)
+ .map((e) => TextSpan(text: e[0], style: bodyBold))
+ .toList();
+ final nonLectures = content
+ .split(reg)
+ .map((e) => TextSpan(text: e, style: bodyRegular))
+ .toList();
+ final children = [];
+
+ children.add(nonLectures.first);
+
+ for (int i = 0; i < lectures.length; i++) {
+ children.add(lectures[i]);
+ children.add(nonLectures[i + 1]);
+ }
+
+ return children;
+ }(),
+ ));
+ case OTLDialogType.about:
+ return RawResponsiveButton(
+ data: {
+ 'Text': {
+ 'arg': content,
+ 'style': bodyRegular.copyWith(color: OTLColor.pinksMain),
+ }
+ },
+ onTap: onTapContent,
+ );
+ default:
+ return SizedBox();
+ }
+ }
+
+ Widget _buildButton(Map data) {
+ return ClipRRect(
+ borderRadius: BorderRadius.circular(24),
+ child: BackgroundButton(
+ color: data['btnColor'],
+ onTap: data['onTap'],
+ child: Container(
+ height: 30,
+ alignment: Alignment.center,
+ child: Text(
+ data['btnText'],
+ style: bodyBold.copyWith(color: data['textColor']),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/pop_up.dart b/lib/widgets/pop_up.dart
index 8380ff7d..a30337f1 100644
--- a/lib/widgets/pop_up.dart
+++ b/lib/widgets/pop_up.dart
@@ -100,13 +100,13 @@ Widget _build23fRecruiting() {
children: [
Text(
'지원하러 가기',
- style: evenBodyBold.copyWith(color: Colors.black),
+ style: bodyBold.copyWith(color: OTLColor.gray0),
textAlign: TextAlign.center,
),
const SizedBox(width: 8.0),
Icon(
Icons.arrow_forward,
- color: Colors.black,
+ color: OTLColor.gray0,
)
],
),
diff --git a/lib/widgets/responsive_button.dart b/lib/widgets/responsive_button.dart
index 77800cac..bbee5226 100644
--- a/lib/widgets/responsive_button.dart
+++ b/lib/widgets/responsive_button.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
+import 'package:otlplus/constants/color.dart';
enum ButtonTapEffect { none, darken, lighten }
@@ -8,7 +9,7 @@ enum ButtonDirection { row, column, rowReversed, columnReversed }
class IconTextButton extends StatelessWidget {
const IconTextButton({
Key? key,
- this.color = const Color(0xFF000000),
+ this.color = OTLColor.gray0,
this.icon,
this.iconSize = 24,
this.spaceBetween = 0,
@@ -108,7 +109,7 @@ class IconTextButton extends StatelessWidget {
class BackgroundButton extends StatelessWidget {
const BackgroundButton(
{Key? key,
- this.color = const Color(0x00000000),
+ this.color = Colors.transparent,
this.onTap,
this.onLongPress,
this.tapEffect = ButtonTapEffect.darken,
@@ -183,13 +184,13 @@ class RawResponsiveWidget extends StatelessWidget {
.toList(),
);
case 'Icon':
- final Color unpressedColor = args['color'] ?? const Color(0xFF000000);
+ final Color unpressedColor = args['color'] ?? OTLColor.gray0;
final Color pressedColor = Color.lerp(
unpressedColor,
tapEffect == ButtonTapEffect.darken
- ? const Color(0xFF000000)
+ ? OTLColor.gray0
: tapEffect == ButtonTapEffect.lighten
- ? const Color(0xFFFFFFFF)
+ ? OTLColor.grayF
: unpressedColor,
tapEffectColorRatio)!;
return ValueListenableBuilder(
@@ -200,16 +201,16 @@ class RawResponsiveWidget extends StatelessWidget {
color: effect ? pressedColor : unpressedColor);
});
case 'SvgPicture.asset':
- final ColorFilter unpressedColorFilter = ColorFilter.mode(
- args['color'] ?? const Color(0xFF000000), BlendMode.srcIn);
+ final ColorFilter unpressedColorFilter =
+ ColorFilter.mode(args['color'] ?? OTLColor.gray0, BlendMode.srcIn);
final ColorFilter pressedColorFilter = ColorFilter.mode(
Color.lerp(
- args['color'] ?? const Color(0xFF000000),
+ args['color'] ?? OTLColor.gray0,
tapEffect == ButtonTapEffect.darken
- ? const Color(0xFF000000)
+ ? OTLColor.gray0
: tapEffect == ButtonTapEffect.lighten
- ? const Color(0xFFFFFFFF)
- : args['color'] ?? const Color(0xFF000000),
+ ? OTLColor.grayF
+ : args['color'] ?? OTLColor.gray0,
tapEffectColorRatio)!,
BlendMode.srcIn);
return ValueListenableBuilder(
@@ -244,12 +245,12 @@ class RawResponsiveWidget extends StatelessWidget {
}
final TextStyle? pressedTextStyle = unpressedTextStyle.copyWith(
color: Color.lerp(
- unpressedTextStyle.color ?? const Color(0xFF000000),
+ unpressedTextStyle.color ?? OTLColor.gray0,
tapEffect == ButtonTapEffect.darken
- ? const Color(0xFF000000)
+ ? OTLColor.gray0
: tapEffect == ButtonTapEffect.lighten
- ? const Color(0xFFFFFFFF)
- : unpressedTextStyle.color ?? const Color(0xFF000000),
+ ? OTLColor.grayF
+ : unpressedTextStyle.color ?? OTLColor.gray0,
tapEffectColorRatio));
return ValueListenableBuilder(
valueListenable: pressedEffect,
@@ -258,13 +259,13 @@ class RawResponsiveWidget extends StatelessWidget {
style: effect ? pressedTextStyle : unpressedTextStyle);
});
case 'ColoredBox':
- final Color unpressedColor = args['color'] ?? const Color(0x00000000);
+ final Color unpressedColor = args['color'] ?? Colors.transparent;
final Color pressedColor = Color.lerp(
unpressedColor,
tapEffect == ButtonTapEffect.darken
- ? const Color(0xFF000000)
+ ? OTLColor.gray0
: tapEffect == ButtonTapEffect.lighten
- ? const Color(0xFFFFFFFF)
+ ? OTLColor.grayF
: unpressedColor,
tapEffectColorRatio)!;
return ValueListenableBuilder(
diff --git a/lib/widgets/review_block.dart b/lib/widgets/review_block.dart
index 01694436..d8c61bd0 100644
--- a/lib/widgets/review_block.dart
+++ b/lib/widgets/review_block.dart
@@ -5,10 +5,13 @@ import 'package:otlplus/constants/text_styles.dart';
import 'package:otlplus/constants/url.dart';
import 'package:otlplus/dio_provider.dart';
import 'package:otlplus/extensions/review.dart';
+import 'package:otlplus/extensions/semester.dart';
import 'package:otlplus/models/review.dart';
+import 'package:otlplus/models/semester.dart';
import 'package:otlplus/widgets/responsive_button.dart';
-import 'package:otlplus/utils/navigator.dart';
import 'package:otlplus/widgets/expandable_text.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:mailto/mailto.dart';
class ReviewBlock extends StatefulWidget {
final Review review;
@@ -186,20 +189,32 @@ class _ReviewBlockState extends State {
}
void _report() {
- OTLNavigator.pushDialog(
- context: context,
- builder: (_) => AlertDialog(
- title: Text('안내'),
- content: Text(
- '이 기능은 현재 개발중입니다. 부적절한 후기는 otlplus@sparcs.org로 신고해 주세요.'),
- actions: [
- new TextButton(
- child: new Text("확인"),
- onPressed: () {
- OTLNavigator.pop(context);
- },
- ),
- ],
- ));
+ final lecture = widget.review.lecture;
+ final isKo = context.locale == Locale('ko');
+ launchUrl(
+ Uri.parse(
+ '${Mailto(
+ to: [CONTACT],
+ subject: 'review.mailto.subject'.tr(),
+ body: 'review.mailto.body_reason'.tr() +
+ 'review.mailto.body_info'.tr(
+ namedArgs: {
+ 'title': isKo ? lecture.title : lecture.titleEn,
+ 'oldCode': lecture.oldCode,
+ 'semesterTitle': Semester(
+ year: lecture.year,
+ semester: lecture.semester,
+ beginning: DateTime(0),
+ end: DateTime(0))
+ .title,
+ 'professors': lecture.professors
+ .map((e) => isKo ? e.name : e.nameEn)
+ .join(', '),
+ 'content': widget.review.content
+ },
+ ),
+ )}',
+ ),
+ );
}
}
diff --git a/lib/widgets/review_mode_control.dart b/lib/widgets/review_mode_control.dart
index ffe95697..4ab80b96 100644
--- a/lib/widgets/review_mode_control.dart
+++ b/lib/widgets/review_mode_control.dart
@@ -109,7 +109,7 @@ class _ReviewModeControlState extends State {
widget._selectedMode == 0
? "title.hall_of_fame".tr()
: "title.latest_reviews".tr(),
- style: evenTitleBold.copyWith(color: OTLColor.grayF),
+ style: titleBold.copyWith(color: OTLColor.grayF),
),
],
),
diff --git a/lib/widgets/search_filter_panel.dart b/lib/widgets/search_filter_panel.dart
index 21754b6a..5d14939c 100644
--- a/lib/widgets/search_filter_panel.dart
+++ b/lib/widgets/search_filter_panel.dart
@@ -338,8 +338,8 @@ class _SilderSelectionState extends State {
thumbShape: CustomSliderThumbShape(
outerThumbRadius: 10,
innerThumbRadius: 7,
- outerThumbColor: Color(0xFFF6C5CD),
- innerThumbColor: Colors.white),
+ outerThumbColor: OTLColor.pinksSub,
+ innerThumbColor: OTLColor.grayF),
trackHeight: 5.0,
trackShape: RoundRectangularSliderTrackShape(),
tickMarkShape: SliderTickMarkShape.noTickMark,
@@ -349,8 +349,8 @@ class _SilderSelectionState extends State {
min: 0.0,
max: divisions.toDouble(),
divisions: divisions,
- activeColor: Color(0xFFF6C5CD),
- inactiveColor: Color(0xFFEEEEEE),
+ activeColor: OTLColor.pinksSub,
+ inactiveColor: OTLColor.grayE,
onChanged: (double value) {
setState(() {
_value = value;
@@ -488,8 +488,8 @@ class CustomSliderThumbShape extends SliderComponentShape {
const CustomSliderThumbShape({
this.outerThumbRadius = 10.0,
this.innerThumbRadius = 10.0,
- this.outerThumbColor = Colors.white,
- this.innerThumbColor = Colors.white,
+ this.outerThumbColor = OTLColor.grayF,
+ this.innerThumbColor = OTLColor.grayF,
this.elevation = 0.0,
this.pressedElevation = 0.0,
});
@@ -541,7 +541,7 @@ class CustomSliderThumbShape extends SliderComponentShape {
bool paintShadows = true;
if (paintShadows) {
- canvas.drawShadow(path, Colors.black, evaluatedElevation, true);
+ canvas.drawShadow(path, OTLColor.gray0, evaluatedElevation, true);
}
canvas
diff --git a/lib/widgets/search_textfield.dart b/lib/widgets/search_textfield.dart
index cfc46d98..39e30106 100644
--- a/lib/widgets/search_textfield.dart
+++ b/lib/widgets/search_textfield.dart
@@ -49,7 +49,7 @@ class _SearchTextfieldState extends State {
style: bodyRegular,
decoration: InputDecoration(
hintText: "common.search_hint".tr(),
- hintStyle: evenBodyRegular.copyWith(color: OTLColor.grayA),
+ hintStyle: bodyRegular.copyWith(color: OTLColor.grayA),
),
),
),
diff --git a/lib/widgets/timetable_mode_control.dart b/lib/widgets/timetable_mode_control.dart
index 4886083e..4fb1b2bf 100644
--- a/lib/widgets/timetable_mode_control.dart
+++ b/lib/widgets/timetable_mode_control.dart
@@ -27,7 +27,7 @@ class _TimetableModeControlState extends State {
height: 40,
padding: const EdgeInsets.fromLTRB(4, 4, 16, 4),
decoration: BoxDecoration(
- color: Colors.white,
+ color: OTLColor.grayF,
borderRadius: BorderRadius.horizontal(left: Radius.circular(20)),
),
child: Stack(
@@ -59,7 +59,7 @@ class _TimetableModeControlState extends State {
child: Icon(
_iconList[index],
color: (index == widget.dropdownIndex)
- ? Colors.white
+ ? OTLColor.grayF
: OTLColor.pinksMain,
),
),
diff --git a/lib/widgets/timetable_tabs.dart b/lib/widgets/timetable_tabs.dart
index f3798c32..31b50bf8 100644
--- a/lib/widgets/timetable_tabs.dart
+++ b/lib/widgets/timetable_tabs.dart
@@ -72,7 +72,7 @@ class _TimetableTabsState extends State {
i == 0
? 'timetable.my_tab'.tr()
: 'timetable.tab'.tr(args: [i.toString()]),
- style: evenBodyBold.copyWith(
+ style: bodyBold.copyWith(
color: i == _index ? OTLColor.grayF : OTLColor.gray0),
textAlign: TextAlign.center,
);
diff --git a/pubspec.lock b/pubspec.lock
index 4265e5ba..02a0ee0e 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -416,6 +416,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
+ mailto:
+ dependency: "direct main"
+ description:
+ name: mailto
+ sha256: f8c5ce39e0eaa94a856795b2855af7f66aac37f7c3b70ac5c26ab00b94685445
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
matcher:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index d0973986..05884dbf 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -38,6 +38,7 @@ dependencies:
flutter_svg: ^2.0.7
open_app_file: ^4.0.2
flutter_native_splash: ^2.3.2
+ mailto: ^2.0.0
# Firebase
firebase_core: ^2.8.0
firebase_analytics: ^10.1.6