25.12.08
회원 가입 페이지(Register Screen), 설정 페이지(Settings_Screen) 약관 로직 개선 및 위치기반 서비스 동의 추가
This commit is contained in:
@@ -1,6 +1,44 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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 {
|
class RegisterScreen extends StatefulWidget {
|
||||||
const RegisterScreen({super.key});
|
const RegisterScreen({super.key});
|
||||||
|
|
||||||
@@ -18,6 +56,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
|
|
||||||
bool _isServiceAgreed = false;
|
bool _isServiceAgreed = false;
|
||||||
bool _isPrivacyAgreed = false;
|
bool _isPrivacyAgreed = false;
|
||||||
|
bool _isLocationAgreed = false;
|
||||||
bool _isMarketingAgreed = false;
|
bool _isMarketingAgreed = false;
|
||||||
|
|
||||||
final Color mainBlueColor = const Color(0xFF0033CC);
|
final Color mainBlueColor = const Color(0xFF0033CC);
|
||||||
@@ -43,7 +82,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
if (_phoneController.text.isEmpty) return _showError('전화번호를 입력해주세요.');
|
if (_phoneController.text.isEmpty) return _showError('전화번호를 입력해주세요.');
|
||||||
if (_phoneController.text.length < 12) return _showError('올바른 전화번호를 입력해주세요.');
|
if (_phoneController.text.length < 12) return _showError('올바른 전화번호를 입력해주세요.');
|
||||||
if (_nicknameController.text.isEmpty) return _showError('닉네임을 입력해주세요.');
|
if (_nicknameController.text.isEmpty) return _showError('닉네임을 입력해주세요.');
|
||||||
if (!_isServiceAgreed || !_isPrivacyAgreed) {
|
|
||||||
|
if (!_isServiceAgreed || !_isPrivacyAgreed || !_isLocationAgreed) {
|
||||||
return _showError('(필수) 약관에 모두 동의해주세요.');
|
return _showError('(필수) 약관에 모두 동의해주세요.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,15 +112,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showTermDetails(String title, String content) {
|
void _showTermDetails(String title, String content) {
|
||||||
String fullContent = content.length < 50
|
String fullContent = content;
|
||||||
? '$content\n\n'
|
|
||||||
'제1조 (목적)\n본 약관은 메타큐랩(이하 "회사")이 제공하는 모든 서비스의 이용조건 및 절차, 이용자와 회사의 권리, 의무, 책임사항과 기타 필요한 사항을 규정함을 목적으로 합니다.\n\n'
|
|
||||||
'제2조 (용어의 정의)\n1. "서비스"라 함은 회원이 이용할 수 있는 관련 제반 서비스를 의미합니다.\n'
|
|
||||||
'2. "회원"이라 함은 회사의 "서비스"에 접속하여 본 약관에 따라 회사와 이용계약을 체결하고 회사가 제공하는 "서비스"를 이용하는 고객을 말합니다.\n\n'
|
|
||||||
'제3조 (약관의 게시와 개정)\n1. 회사는 이 약관의 내용을 회원이 쉽게 알 수 있도록 서비스 초기 화면에 게시합니다.\n'
|
|
||||||
'2. 회사는 "약관의 규제에 관한 법률" 등 관련 법령을 위배하지 않는 범위에서 이 약관을 개정할 수 있습니다.\n\n'
|
|
||||||
'(이하 생략)'
|
|
||||||
: content;
|
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -281,21 +313,28 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
title: '(필수) 서비스 이용약관 동의',
|
title: '(필수) 서비스 이용약관 동의',
|
||||||
value: _isServiceAgreed,
|
value: _isServiceAgreed,
|
||||||
onChanged: (v) => setState(() => _isServiceAgreed = v!),
|
onChanged: (v) => setState(() => _isServiceAgreed = v!),
|
||||||
details: '서비스 이용약관 내용입니다...',
|
details: _serviceTermContent,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildAgreementRow(
|
_buildAgreementRow(
|
||||||
title: '(필수) 개인정보 수집 및 이용 동의',
|
title: '(필수) 개인정보 수집 및 이용 동의',
|
||||||
value: _isPrivacyAgreed,
|
value: _isPrivacyAgreed,
|
||||||
onChanged: (v) => setState(() => _isPrivacyAgreed = v!),
|
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),
|
const SizedBox(height: 12),
|
||||||
_buildAgreementRow(
|
_buildAgreementRow(
|
||||||
title: '(선택) 이벤트 및 마케팅 수신 동의',
|
title: '(선택) 이벤트 및 마케팅 수신 동의',
|
||||||
value: _isMarketingAgreed,
|
value: _isMarketingAgreed,
|
||||||
onChanged: (v) => setState(() => _isMarketingAgreed = v!),
|
onChanged: (v) => setState(() => _isMarketingAgreed = v!),
|
||||||
details: '마케팅 정보 수신 동의 내용입니다...',
|
details: _marketingTermContent,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,6 +15,44 @@ const BoxShadow _cleanShadow = BoxShadow(
|
|||||||
spreadRadius: 0,
|
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 {
|
class SettingsScreen extends StatefulWidget {
|
||||||
const SettingsScreen({super.key});
|
const SettingsScreen({super.key});
|
||||||
|
|
||||||
@@ -27,6 +65,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
bool _isRentalAlertEnabled = true;
|
bool _isRentalAlertEnabled = true;
|
||||||
bool _isStorageStatusAlert = true;
|
bool _isStorageStatusAlert = true;
|
||||||
bool _isEnvSensorAlert = true;
|
bool _isEnvSensorAlert = true;
|
||||||
|
bool _isMarketingAlertEnabled = true;
|
||||||
|
|
||||||
bool _isBiometricEnabled = false;
|
bool _isBiometricEnabled = false;
|
||||||
bool _isAutoLogoutEnabled = true;
|
bool _isAutoLogoutEnabled = true;
|
||||||
@@ -331,17 +370,21 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showTermsSheet(BuildContext context) {
|
void _showTermContentSheet(BuildContext context, String title, String content) {
|
||||||
_showCommonModal(
|
_showCommonModal(
|
||||||
context,
|
context,
|
||||||
'이용 약관',
|
title,
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
child: Text(
|
child: Text(
|
||||||
'제 1 조 (목적)\n이 약관은 스마트 헬멧 서비스(이하 "서비스")의 이용 조건 및 절차, 이용자와 회사의 권리, 의무, 책임 사항을 규정함을 목적으로 합니다.\n\n제 2 조 (용어의 정의)\n1. "이용자"란 앱에 접속하여 본 약관에 따라 서비스를 이용하는 회원을 말합니다.\n2. "헬멧"이란 회사가 대여하는 스마트 IoT 안전모를 말합니다.\n\n(이하 생략... 더미 데이터입니다.)\n\n제 3 조 (약관의 효력)\n본 약관은 서비스를 신청한 때부터 효력이 발생합니다.',
|
content,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
style: TextStyle(color: _subTextColor, height: 1.6),
|
style: TextStyle(color: _subTextColor, height: 1.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,6 +766,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
(val) => setState(() => _isEnvSensorAlert = val),
|
(val) => setState(() => _isEnvSensorAlert = val),
|
||||||
showDivider: true,
|
showDivider: true,
|
||||||
),
|
),
|
||||||
|
_buildToggleItem(
|
||||||
|
'이벤트 및 마케팅 알림',
|
||||||
|
'혜택 및 소식 받기',
|
||||||
|
_isMarketingAlertEnabled,
|
||||||
|
(val) => setState(() => _isMarketingAlertEnabled = val),
|
||||||
|
showDivider: true,
|
||||||
|
onTapLabel: () => _showTermContentSheet(context, '마케팅 수신 동의', _marketingTermContent),
|
||||||
|
),
|
||||||
_buildDivider(16),
|
_buildDivider(16),
|
||||||
_buildInfoLink('알림 방식', Icons.notifications_active_outlined, value: '배너 + 진동', onTap: () => _showNotificationStyleSheet(context)),
|
_buildInfoLink('알림 방식', Icons.notifications_active_outlined, value: '배너 + 진동', onTap: () => _showNotificationStyleSheet(context)),
|
||||||
],
|
],
|
||||||
@@ -769,7 +820,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_buildInfoLink('버전 정보', null, value: _currentAppVersion, showArrow: false),
|
_buildInfoLink('버전 정보', null, value: _currentAppVersion, showArrow: false),
|
||||||
_buildDivider(16),
|
_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),
|
_buildDivider(16),
|
||||||
_buildInfoLink('오픈소스 라이선스', Icons.code, onTap: () => _showLicenseSheet(context)),
|
_buildInfoLink('오픈소스 라이선스', Icons.code, onTap: () => _showLicenseSheet(context)),
|
||||||
_buildDivider(16),
|
_buildDivider(16),
|
||||||
@@ -838,7 +893,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
|
|
||||||
Widget _buildToggleItem(
|
Widget _buildToggleItem(
|
||||||
String title, String subtitle, bool value, ValueChanged<bool> onChanged,
|
String title, String subtitle, bool value, ValueChanged<bool> onChanged,
|
||||||
{required bool showDivider}) {
|
{required bool showDivider, VoidCallback? onTapLabel}) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@@ -847,15 +902,27 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onTapLabel,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(title, style: TextStyle(color: _mainTextColor, fontSize: 14, fontWeight: FontWeight.w500)),
|
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),
|
const SizedBox(height: 2),
|
||||||
Text(subtitle, style: TextStyle(color: _subTextColor, fontSize: 11)),
|
Text(subtitle, style: TextStyle(color: _subTextColor, fontSize: 11)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Transform.scale(
|
Transform.scale(
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
child: Switch(
|
child: Switch(
|
||||||
|
|||||||
Reference in New Issue
Block a user