diff --git a/lib/register_screen.dart b/lib/register_screen.dart index b3a7b22..f83fb57 100644 --- a/lib/register_screen.dart +++ b/lib/register_screen.dart @@ -1,6 +1,44 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +const String _serviceTermContent = """ +제1조 (목적) +본 약관은 메타큐랩 서비스 이용과 관련하여 회사와 회원의 권리, 의무 및 책임 사항, 기타 필요한 사항을 규정함을 목적으로 합니다. + +제2조 (약관의 효력 및 변경) +1. 본 약관은 서비스를 이용하고자 하는 모든 회원에 대하여 그 효력을 발생합니다. +2. 회사는 관련 법령을 위배하지 않는 범위에서 본 약관을 개정할 수 있습니다. +3. 개정된 약관은 적용일자 및 개정 사유를 명시하여 현행 약관과 함께 서비스 화면에 게시합니다. +"""; + +const String _privacyTermContent = """ +1. 수집하는 개인정보의 항목 +회사는 회원가입, 상담, 서비스 신청 등을 위해 아래와 같은 개인정보를 수집하고 있습니다. +- 필수 항목: 아이디, 비밀번호, 이름, 전화번호, 이메일 주소 +- 선택 항목: 닉네임, 마케팅 정보 수신 동의 여부 + +2. 개인정보의 수집 및 이용 목적 +회사는 다음의 목적을 위해 개인정보를 수집 및 이용합니다. +- 서비스 제공에 관한 계약 이행 및 요금 정산 +- 회원 관리 및 본인 확인 +"""; + +const String _locationTermContent = """ +1. 위치정보의 수집 및 이용 목적 +회사는 이용자의 현재 위치를 확인하여 긴급 구조 요청, 주행 경로 기록, 주변 시설 검색 등 위치 기반 서비스를 제공하기 위해 위치정보를 수집 및 이용합니다. + +2. 위치정보의 보유 및 이용 기간 +회사는 위치정보의 수집 및 이용 목적이 달성된 후에는 해당 정보를 지체 없이 파기합니다. 단, 관련 법령의 규정에 의하여 보존할 필요가 있는 경우 법령에서 정한 기간 동안 보관합니다. +"""; + +const String _marketingTermContent = """ +1. 수집 및 이용 목적 +이벤트 정보 및 참여 기회 제공, 광고성 정보 제공 등 마케팅 활동을 위해 사용됩니다. + +2. 수신 동의 철회 +회원은 언제든지 이메일 또는 고객센터를 통해 마케팅 정보 수신 동의를 철회할 수 있습니다. 수신 동의를 철회하더라도 기본 서비스 이용에는 제한이 없습니다. +"""; + class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); @@ -18,6 +56,7 @@ class _RegisterScreenState extends State { bool _isServiceAgreed = false; bool _isPrivacyAgreed = false; + bool _isLocationAgreed = false; bool _isMarketingAgreed = false; final Color mainBlueColor = const Color(0xFF0033CC); @@ -43,7 +82,8 @@ class _RegisterScreenState extends State { if (_phoneController.text.isEmpty) return _showError('전화번호를 입력해주세요.'); if (_phoneController.text.length < 12) return _showError('올바른 전화번호를 입력해주세요.'); if (_nicknameController.text.isEmpty) return _showError('닉네임을 입력해주세요.'); - if (!_isServiceAgreed || !_isPrivacyAgreed) { + + if (!_isServiceAgreed || !_isPrivacyAgreed || !_isLocationAgreed) { return _showError('(필수) 약관에 모두 동의해주세요.'); } @@ -72,15 +112,7 @@ class _RegisterScreenState extends State { } void _showTermDetails(String title, String content) { - String fullContent = content.length < 50 - ? '$content\n\n' - '제1조 (목적)\n본 약관은 메타큐랩(이하 "회사")이 제공하는 모든 서비스의 이용조건 및 절차, 이용자와 회사의 권리, 의무, 책임사항과 기타 필요한 사항을 규정함을 목적으로 합니다.\n\n' - '제2조 (용어의 정의)\n1. "서비스"라 함은 회원이 이용할 수 있는 관련 제반 서비스를 의미합니다.\n' - '2. "회원"이라 함은 회사의 "서비스"에 접속하여 본 약관에 따라 회사와 이용계약을 체결하고 회사가 제공하는 "서비스"를 이용하는 고객을 말합니다.\n\n' - '제3조 (약관의 게시와 개정)\n1. 회사는 이 약관의 내용을 회원이 쉽게 알 수 있도록 서비스 초기 화면에 게시합니다.\n' - '2. 회사는 "약관의 규제에 관한 법률" 등 관련 법령을 위배하지 않는 범위에서 이 약관을 개정할 수 있습니다.\n\n' - '(이하 생략)' - : content; + String fullContent = content; showDialog( context: context, @@ -281,21 +313,28 @@ class _RegisterScreenState extends State { title: '(필수) 서비스 이용약관 동의', value: _isServiceAgreed, onChanged: (v) => setState(() => _isServiceAgreed = v!), - details: '서비스 이용약관 내용입니다...', + details: _serviceTermContent, ), const SizedBox(height: 12), _buildAgreementRow( title: '(필수) 개인정보 수집 및 이용 동의', value: _isPrivacyAgreed, onChanged: (v) => setState(() => _isPrivacyAgreed = v!), - details: '개인정보 처리방침 내용입니다...', + details: _privacyTermContent, + ), + const SizedBox(height: 12), + _buildAgreementRow( + title: '(필수) 위치기반 서비스 이용약관 동의', + value: _isLocationAgreed, + onChanged: (v) => setState(() => _isLocationAgreed = v!), + details: _locationTermContent, ), const SizedBox(height: 12), _buildAgreementRow( title: '(선택) 이벤트 및 마케팅 수신 동의', value: _isMarketingAgreed, onChanged: (v) => setState(() => _isMarketingAgreed = v!), - details: '마케팅 정보 수신 동의 내용입니다...', + details: _marketingTermContent, ), ], ), diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index 2e86a5b..b60392e 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -15,6 +15,44 @@ const BoxShadow _cleanShadow = BoxShadow( spreadRadius: 0, ); +const String _serviceTermContent = """ +제1조 (목적) +본 약관은 메타큐랩 서비스 이용과 관련하여 회사와 회원의 권리, 의무 및 책임 사항, 기타 필요한 사항을 규정함을 목적으로 합니다. + +제2조 (약관의 효력 및 변경) +1. 본 약관은 서비스를 이용하고자 하는 모든 회원에 대하여 그 효력을 발생합니다. +2. 회사는 관련 법령을 위배하지 않는 범위에서 본 약관을 개정할 수 있습니다. +3. 개정된 약관은 적용일자 및 개정 사유를 명시하여 현행 약관과 함께 서비스 화면에 게시합니다. +"""; + +const String _privacyTermContent = """ +1. 수집하는 개인정보의 항목 +회사는 회원가입, 상담, 서비스 신청 등을 위해 아래와 같은 개인정보를 수집하고 있습니다. +- 필수 항목: 아이디, 비밀번호, 이름, 전화번호, 이메일 주소 +- 선택 항목: 닉네임, 마케팅 정보 수신 동의 여부 + +2. 개인정보의 수집 및 이용 목적 +회사는 다음의 목적을 위해 개인정보를 수집 및 이용합니다. +- 서비스 제공에 관한 계약 이행 및 요금 정산 +- 회원 관리 및 본인 확인 +"""; + +const String _locationTermContent = """ +1. 위치정보의 수집 및 이용 목적 +회사는 이용자의 현재 위치를 확인하여 긴급 구조 요청, 주행 경로 기록, 주변 시설 검색 등 위치 기반 서비스를 제공하기 위해 위치정보를 수집 및 이용합니다. + +2. 위치정보의 보유 및 이용 기간 +회사는 위치정보의 수집 및 이용 목적이 달성된 후에는 해당 정보를 지체 없이 파기합니다. 단, 관련 법령의 규정에 의하여 보존할 필요가 있는 경우 법령에서 정한 기간 동안 보관합니다. +"""; + +const String _marketingTermContent = """ +1. 수집 및 이용 목적 +이벤트 정보 및 참여 기회 제공, 광고성 정보 제공 등 마케팅 활동을 위해 사용됩니다. + +2. 수신 동의 철회 +회원은 언제든지 설정 메뉴 또는 고객센터를 통해 마케팅 정보 수신 동의를 철회할 수 있습니다. 수신 동의를 철회하더라도 기본 서비스 이용에는 제한이 없습니다. +"""; + class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -27,6 +65,7 @@ class _SettingsScreenState extends State { bool _isRentalAlertEnabled = true; bool _isStorageStatusAlert = true; bool _isEnvSensorAlert = true; + bool _isMarketingAlertEnabled = true; bool _isBiometricEnabled = false; bool _isAutoLogoutEnabled = true; @@ -331,15 +370,19 @@ class _SettingsScreenState extends State { ); } - void _showTermsSheet(BuildContext context) { + void _showTermContentSheet(BuildContext context, String title, String content) { _showCommonModal( context, - '이용 약관', + title, SingleChildScrollView( padding: const EdgeInsets.all(20), - child: Text( - '제 1 조 (목적)\n이 약관은 스마트 헬멧 서비스(이하 "서비스")의 이용 조건 및 절차, 이용자와 회사의 권리, 의무, 책임 사항을 규정함을 목적으로 합니다.\n\n제 2 조 (용어의 정의)\n1. "이용자"란 앱에 접속하여 본 약관에 따라 서비스를 이용하는 회원을 말합니다.\n2. "헬멧"이란 회사가 대여하는 스마트 IoT 안전모를 말합니다.\n\n(이하 생략... 더미 데이터입니다.)\n\n제 3 조 (약관의 효력)\n본 약관은 서비스를 신청한 때부터 효력이 발생합니다.', - style: TextStyle(color: _subTextColor, height: 1.6), + child: SizedBox( + width: double.infinity, + child: Text( + content, + textAlign: TextAlign.start, + style: TextStyle(color: _subTextColor, height: 1.6), + ), ), ), ); @@ -723,6 +766,14 @@ class _SettingsScreenState extends State { (val) => setState(() => _isEnvSensorAlert = val), showDivider: true, ), + _buildToggleItem( + '이벤트 및 마케팅 알림', + '혜택 및 소식 받기', + _isMarketingAlertEnabled, + (val) => setState(() => _isMarketingAlertEnabled = val), + showDivider: true, + onTapLabel: () => _showTermContentSheet(context, '마케팅 수신 동의', _marketingTermContent), + ), _buildDivider(16), _buildInfoLink('알림 방식', Icons.notifications_active_outlined, value: '배너 + 진동', onTap: () => _showNotificationStyleSheet(context)), ], @@ -769,7 +820,11 @@ class _SettingsScreenState extends State { children: [ _buildInfoLink('버전 정보', null, value: _currentAppVersion, showArrow: false), _buildDivider(16), - _buildInfoLink('이용 약관 및 개인정보 처리방침', Icons.article_outlined, onTap: () => _showTermsSheet(context)), + _buildInfoLink('서비스 이용약관', Icons.description_outlined, onTap: () => _showTermContentSheet(context, '서비스 이용약관', _serviceTermContent)), + _buildDivider(16), + _buildInfoLink('개인정보 처리방침', Icons.privacy_tip_outlined, onTap: () => _showTermContentSheet(context, '개인정보 처리방침', _privacyTermContent)), + _buildDivider(16), + _buildInfoLink('위치기반 서비스 이용약관', Icons.location_on_outlined, onTap: () => _showTermContentSheet(context, '위치기반 서비스 이용약관', _locationTermContent)), _buildDivider(16), _buildInfoLink('오픈소스 라이선스', Icons.code, onTap: () => _showLicenseSheet(context)), _buildDivider(16), @@ -838,7 +893,7 @@ class _SettingsScreenState extends State { Widget _buildToggleItem( String title, String subtitle, bool value, ValueChanged onChanged, - {required bool showDivider}) { + {required bool showDivider, VoidCallback? onTapLabel}) { return Column( children: [ Padding( @@ -847,13 +902,25 @@ class _SettingsScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, style: TextStyle(color: _mainTextColor, fontSize: 14, fontWeight: FontWeight.w500)), - const SizedBox(height: 2), - Text(subtitle, style: TextStyle(color: _subTextColor, fontSize: 11)), - ], + child: GestureDetector( + onTap: onTapLabel, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text(title, style: TextStyle(color: _mainTextColor, fontSize: 14, fontWeight: FontWeight.w500)), + if (onTapLabel != null) + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Icon(Icons.info_outline, size: 14, color: _subTextColor), + ), + ], + ), + const SizedBox(height: 2), + Text(subtitle, style: TextStyle(color: _subTextColor, fontSize: 11)), + ], + ), ), ), Transform.scale(