From 445e286b45546319bc32567afb960ae408ccb1cf Mon Sep 17 00:00:00 2001 From: sparcscasio <162997498+sparcscasio@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:44:03 +0900 Subject: [PATCH] feat: add promotion consent --- assets/translations/en.json | 15 +++++- assets/translations/ko.json | 16 +++++- devtools_options.yaml | 3 ++ lib/main.dart | 1 + lib/pages/settings_page.dart | 20 ++++++++ lib/providers/settings_model.dart | 17 +++++++ lib/widgets/otl_dialog.dart | 83 +++++++++++++++++++++++++++++-- 7 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 devtools_options.yaml diff --git a/assets/translations/en.json b/assets/translations/en.json index 9d9fb3d7..2eb7462f 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -19,7 +19,8 @@ "all_selected": "All Selected", "num_selected": " Selected", "close": "Close", - "coming": "Coming Soon" + "coming": "Coming Soon", + "confirm": "Confirm" }, "semester": { "spring": "Spring", @@ -165,11 +166,21 @@ "send_error_log_desc": "Automatically collect logs without user reporting", "send_anonymously": "Send anonymously", "send_anonymously_desc": "Not including user ID in error logs", + "receive_promotion": "Promotion consent", + "receive_promotion_desc": "Receive promotional notifications", "reset_all": "Reset all settings", "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." + "reset_settings_desc": "Are you really want to reset all data? However, it will be reset except for login data.", + "disable_promotion" : "Refusal to Receive Promotional Information", + "promotion_desc" : "You can change your consent to receive promotional information in Settings > [Promoiton Consent].", + "enable_promotion" : "Consent to Receive Promotional Information", + "date" : "Date", + "user_name" : "Name", + "detail" : "Detail", + "disable" : "decline", + "enable" : "agree" }, "throw_test": "Throw Test Exception", "throw_test_desc": "for testing firebase crashlytics", diff --git a/assets/translations/ko.json b/assets/translations/ko.json index 5837b0f6..51177b8f 100644 --- a/assets/translations/ko.json +++ b/assets/translations/ko.json @@ -19,7 +19,8 @@ "all_selected": "전체 선택됨", "num_selected": "개 선택됨", "close": "닫기", - "coming": "준비 중입니다." + "coming": "준비 중입니다.", + "confirm": "확인" }, "semester": { "spring": "봄", @@ -164,12 +165,23 @@ "send_error_log": "오류 로그 전송", "send_error_log_desc": "사용자의 제보 없이 자동으로 오류를 수집합니다.", "send_anonymously": "익명으로 전송", + "receive_promotion": "광고성 정보 수신 동의", + "receive_promotion_desc": "광고성 정보에 대한 푸시 알림 수신에 동의합니다.", "send_anonymously_desc": "오류 로그에 사용자 ID를 포함하지 않고 익명으로 전송합니다.", "reset_all": "모든 설정 데이터 초기화", "dialog": { "reset": "초기화", + "yes": "확인", "reset_settings": "설정 초기화", - "reset_settings_desc": "정말 모든 설정 데이터를 초기화하시겠습니까? 단, 로그인 정보는 제외하고 초기화됩니다." + "reset_settings_desc": "정말 모든 설정 데이터를 초기화하시겠습니까? 단, 로그인 정보는 제외하고 초기화됩니다.", + "disable_promotion": "광고성 정보 수신 거부 처리 결과", + "enable_promotion" : "광고성 정보 수신 동의 처리 결과", + "promotion_desc" : "광고성 정보 수신동의는 설정 > [광고성 정보 수신 동의] 에서 변경 가능합니다.", + "date" : "일시", + "user_name" : "전송자", + "detail" : "내용", + "disable" : "거부", + "enable" : "동의" }, "throw_test": "테스트 오류 발생", "throw_test_desc": "Firebase crashlytics 테스트용", diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/main.dart b/lib/main.dart index c74694c1..59abd491 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -49,6 +49,7 @@ void main() { ); final token = await FirebaseMessaging.instance.getToken(); + debugPrint('firebase token : ${token}'); FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 1481fb54..d6c2ddf9 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -3,6 +3,7 @@ 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/info_model.dart'; import 'package:otlplus/providers/settings_model.dart'; import 'package:otlplus/widgets/dropdown.dart'; import 'package:otlplus/widgets/otl_dialog.dart'; @@ -18,6 +19,7 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { final isEn = EasyLocalization.of(context)?.currentLocale == Locale('en'); + final user = context.watch().user; return OTLScaffold( child: OTLLayout( @@ -84,6 +86,24 @@ class SettingsPage extends StatelessWidget { ), ], ), + _buildListTile( + title: "settings.receive_promotion".tr(), + subtitle: "settings.receive_promotion_desc".tr(), + trailing: CupertinoSwitch( + value: context.watch().getReceivePromotion(), + onChanged: (value) { + context.read().setReceivePromotion(value); + OTLNavigator.pushDialog( + context: context, + builder: (_) => OTLDialog( + type: value + ? OTLDialogType.enablePromotion + : OTLDialogType.disablePromotion, + ), + ); + }, + ), + ), _buildListTile( title: "settings.send_error_log".tr(), subtitle: "settings.send_error_log_desc".tr(), diff --git a/lib/providers/settings_model.dart b/lib/providers/settings_model.dart index bf8bc354..8807b50d 100644 --- a/lib/providers/settings_model.dart +++ b/lib/providers/settings_model.dart @@ -1,14 +1,17 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; final _kSendCrashlytics = 'sendCrashlytics'; final _kSendCrashlyticsAnonymously = 'sendCrashlyticsAnonymously'; final _kShowsChannelTalkButton = 'showsChannelTalkButton'; +final _kReceivePromotion = 'receivePromoiton'; class SettingsModel extends ChangeNotifier { late bool _sendCrashlytics; late bool _sendCrashlyticsAnonymously; late bool _showsChannelTalkButton; + late bool _receivePromotion; bool getSendCrashlytics() => _sendCrashlytics; void setSendCrashlytics(bool newValue) { @@ -34,6 +37,17 @@ class SettingsModel extends ChangeNotifier { (instance) => instance.setBool(_kShowsChannelTalkButton, newValue)); } + bool getReceivePromotion() => _receivePromotion; + Future setReceivePromotion(bool newValue) async { + _receivePromotion = newValue; + notifyListeners(); + SharedPreferences.getInstance() + .then((instance) => instance.setBool(_kReceivePromotion, newValue)); + newValue + ? await FirebaseMessaging.instance.subscribeToTopic("promotion") + : await FirebaseMessaging.instance.unsubscribeFromTopic("promotion"); + } + SettingsModel({bool forTest = false}) { SharedPreferences.getInstance().then((instance) { getAllValues(instance); @@ -43,6 +57,7 @@ class SettingsModel extends ChangeNotifier { _sendCrashlytics = true; _sendCrashlyticsAnonymously = false; _showsChannelTalkButton = true; + _receivePromotion = true; } } @@ -52,6 +67,8 @@ class SettingsModel extends ChangeNotifier { instance.getBool(_kSendCrashlyticsAnonymously) ?? false; _showsChannelTalkButton = instance.getBool(_kShowsChannelTalkButton) ?? true; + _receivePromotion = instance.getBool(_kReceivePromotion) ?? false; + notifyListeners(); } diff --git a/lib/widgets/otl_dialog.dart b/lib/widgets/otl_dialog.dart index 8f167985..40df018d 100644 --- a/lib/widgets/otl_dialog.dart +++ b/lib/widgets/otl_dialog.dart @@ -4,8 +4,11 @@ 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/models/user.dart'; +import 'package:otlplus/providers/info_model.dart'; import 'package:otlplus/utils/navigator.dart'; import 'package:otlplus/widgets/responsive_button.dart'; +import 'package:provider/provider.dart'; enum OTLDialogType { /// namedArgs: 'lecture' @@ -37,6 +40,10 @@ enum OTLDialogType { resetSettings, + disablePromotion, + + enablePromotion, + about, } @@ -133,7 +140,21 @@ extension OTLDialogTypeExt on OTLDialogType { title: 'settings.dialog.reset_settings', content: 'settings.dialog.reset_settings_desc', icon: 'alert', - posText: 'settings.dialog.reset', + posText: '.dialog.reset', + ), + OTLDialogType.disablePromotion: _OTLDialogData( + title: 'settings.dialog.disable_promotion', + content: 'settings.dialog.reset_settings_desc', + icon: 'OTL', + negText: 'common.close', + btnStyle: BtnStyle.one, + ), + OTLDialogType.enablePromotion: _OTLDialogData( + title: 'settings.dialog.enable_promotion', + content: 'settings.dialog.reset_settings_desc', + icon: 'OTL', + negText: 'common.close', + btnStyle: BtnStyle.one, ), OTLDialogType.about: _OTLDialogData( title: 'Online Timeplanner with Lectures Plus @ KAIST', @@ -169,6 +190,8 @@ class OTLDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final user = context.watch().user; + return Center( child: Container( width: 256, @@ -200,7 +223,7 @@ class OTLDialog extends StatelessWidget { children: [ Text(type.title.tr(), style: titleBold), const SizedBox(height: 8), - _buildContent(), + _buildContent(user: user), ], ), ), @@ -259,8 +282,11 @@ class OTLDialog extends StatelessWidget { ); } - Widget _buildContent() { + Widget _buildContent({User? user}) { final content = type.content.tr(namedArgs: namedArgs); + + user ?? null; + switch (type) { case OTLDialogType.addLecture: case OTLDialogType.addLectureWithTab: @@ -275,6 +301,57 @@ class OTLDialog extends StatelessWidget { content, style: bodyRegular, ); + + case OTLDialogType.disablePromotion: + return Column( + children: [ + Container( + width: 200, + child: Column( + children: [ + Text( + '${'settings.dialog.date'.tr(namedArgs: namedArgs)} : ${DateFormat('yyyy-MM-dd').format(DateTime.now())}'), + Text( + '${'settings.dialog.user_name'.tr(namedArgs: namedArgs)} : ${user!.firstName} ${user!.lastName}'), + Text( + '${'settings.dialog.detail'.tr(namedArgs: namedArgs)} : ${'settings.dialog.disable'.tr(namedArgs: namedArgs)}'), + ], + ), + color: OTLColor.grayE, + ), + SizedBox( + height: 5, + ), + Text( + '${'settings.dialog.promotion_desc'.tr(namedArgs: namedArgs)}'), + ], + ); + + case OTLDialogType.enablePromotion: + return Column( + children: [ + Container( + width: 200, + child: Column( + children: [ + Text( + '${'settings.dialog.date'.tr(namedArgs: namedArgs)} : ${DateFormat('yyyy-MM-dd').format(DateTime.now())}'), + Text( + '${'settings.dialog.user_name'.tr(namedArgs: namedArgs)} : ${user!.firstName} ${user!.lastName}'), + Text( + '${'settings.dialog.detail'.tr(namedArgs: namedArgs)} : ${'settings.dialog.enable'.tr(namedArgs: namedArgs)}'), + ], + ), + color: OTLColor.grayE, + ), + SizedBox( + height: 5, + ), + Text( + '${'settings.dialog.promotion_desc'.tr(namedArgs: namedArgs)}'), + ], + ); + case OTLDialogType.addOverlappingLecture: case OTLDialogType.addOverlappingLectureWithTab: return Text.rich(TextSpan(