DAY 29: 개인 프로젝트 — view (3)

Pt J·2026년 9월 13일
post-thumbnail

DAY 29: 개인 프로젝트 — view (3)

작업 일정

  • #7 [view] 설정 화면 뼈대 잡기
  • #8 [view] 로그인 및 회원가입 화면 뼈대 잡기

#7 [view] 설정 화면 뼈대 잡기

설정 화면 (/me/settings)

설정 화면은 불변 목록이라 쉽게 구현할 수 있다. 하단에 앱 버전을 표기하기로 하였는데 이는 package_info_plus package를 사용하여 작성할 수 있다. 패키지 정보를 비동기로 읽어오기 때문에 FutureBuilder로 감싸 주어야 한다.

pubspec.yaml

# 앞 부분 생략
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.8
  go_router: ^18.0.1
  intl: ^0.20.2
  animated_toggle_switch: ^0.8.7
  uuid: ^4.6.0
  flutter_map: ^8.3.2
  latlong2: ^0.10.1
  table_calendar: ^3.2.1
  month_picker_dialog: ^6.7.2
  image_picker: ^1.2.3
  package_info_plus: ^10.2.1
# 뒷 부분 생략

lib/pages/settings_page.dart

// 앞 부분 생략
FutureBuilder<PackageInfo>(
  future: _getPackageInfo(),
  builder: (context, snapshot) {
    final PackageInfo info;
    if (snapshot.hasError || !snapshot.hasData) {
      return Container();
    }
    info = snapshot.data!;
    return Text('v${info.version}', style: textTheme.labelSmall,);
  },
),
// 뒷 부분 생략

계정 설정 화면 (/me/settings/account)

계정 설정 화면도 당장은 할 게 많지 않다. 로그아웃 경고 모달 및 계정 삭제 경고 모달을 만들다 보니 modal dialog도 너무 많아지는 것 같아 별도 디렉토리로 빼기로 했다.

비밀번호 변경 화면 (/me/settings/account/password)

비밀번호 변경 화면은 비밀번호 보이기/가리기까지만 작성해 놓고 검증은 로직 구현 단계에서 진행하기로 했다.

캐릭터 변경 화면 (/me/settings/character)

캐릭터 변경 화면은 프로필 화면에 기록을 띄우는 것처럼 GridView를 사용하였다. 처음에는 내용이 잘려서 나와서 잠깐 헤맸는데, GridView는 기본적으로 항목을 정방형으로 출력하기 때문에 발생한 이슈였다. gridDelegate property를 통해 항목의 비율을 변경하니 문제 없이 출력되었다.

캐릭터 변경이 완료되면 하단에 SnackBar를 띄워주기로 했다. 이전에 구현한 부분에도 SnackBar가 있으면 좋을 만한 곳이 좀 있긴 한데 그건 로직을 구현하면서 적당히 추가하도록 하자.

알림 설정 화면 (/me/settings/notification)

친구, 반응 알림은 켜져 있는 것을 기본값으로 하고 리마인더 알림은 꺼져 있는 것을 기본값으로 한다. 그리고 전체 알림을 끄거나 켤 수 있는 버튼도 존재한다.

요일 선택은 실습 때 했던 대로 할까 하다가 찾아보니 Material 3에서는 ToggleButtons보다 SegmentedButton이 더 선호된다고 한다. 적은 코드로 버튼 제스처에 대한 기능을 구현할 수 있고, 다중 선택이 보다 간결하며, 타입 안정성과 웹/앱 접근성 표준 측면에서도 이득이라나.

ToggleButtons는 Material 2 스타일이었다는 모양이다.

오픈소스 라이선스 고지 화면 (/license)

오픈소스 라이선스 고지 화면은 built-in functionshowLicensePage(context: context); 를 통해 쉽게 만들 수 있지만 레이아웃에 제약이 있다. 이를 보완한 외부 package도 존재하지만 연습삼아 직접 작성해 보도록 하겠다.

각 패키지 항목은 Card로 작성하고 클릭하면 ModalBottomSheet을 통해 라이선스 정보가 출력되도록 하였다.

설정 화면 구성할 때 context.go() 로 연결해 놓았는데 오픈소스 라인선스 고지화면과 개인정보 처리방침 화면, 이용약관 화면은 어차피 거기서 이동할 곳도 없으니 context.push() 로 하는 편이 나을 듯하여 수정했다. 오픈소스 라이선스 고지는 그렇다 쳐도 나머지 두 녀석은 회원가입 화면에서도 띄워야 하는데 context.go() 로 하면 코드가 더 복잡해질 테니 말이다.

/lib/pages/oss_licenses_page.dart

// 앞 부분 생략
 void _showBottomSheet(BuildContext context, String package, List licenses) {
    TextTheme textTheme = Theme.of(context).textTheme;

    showModalBottomSheet(
      context: context,
      useSafeArea: true,
      isScrollControlled: true,
      builder: (context) {
        return Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
          ),
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(package, style: textTheme.bodyLarge),
                Text('라이선스 ${licenses.length}개', style: textTheme.labelMedium),
                Divider(),
                Expanded(
                  child: ListView.separated(
                    itemCount: licenses.length,
                    itemBuilder: (context, index) {
                      return Padding(
                        padding: EdgeInsets.symmetric(vertical: 20),
                        child: Text(
                          licenses[index],
                          style: textTheme.labelSmall,
                        ),
                      );
                    },
                    separatorBuilder: (BuildContext context, int index) {
                      return Divider();
                    },
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
 
  
  void initState() {
    super.initState();
    _loadLicenses();
  }

  
  Widget build(BuildContext context) {
    TextTheme textTheme = Theme.of(context).textTheme;
    ColorScheme colorScheme = Theme.of(context).colorScheme;

    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          onPressed: () {
            context.pop();
          },
          icon: Icon(Icons.chevron_left, color: colorScheme.tertiary),
        ),
        title: Text('오픈소스 라이선스'),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20),
        child: _isLoading
            ? Center(child: CircularProgressIndicator())
            : ListView.builder(
                itemCount: _ossLicenses.length,
                itemBuilder: (context, index) {
                  final item = _ossLicenses[index];
                  final String package = item['name'];
                  final List licenses = item['licenses'];

                  return Card(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadiusGeometry.circular(20),
                    ),
                    child: ListTile(
                      title: Text(package, style: textTheme.bodyMedium),
                      subtitle: Text(
                        '라이선스 ${licenses.length}개',
                        style: textTheme.labelMedium,
                      ),
                      trailing: Icon(
                        Icons.chevron_right,
                        color: colorScheme.tertiaryContainer,
                      ),
                      onTap: () => _showBottomSheet(context, package, licenses),
                    ),
                  );
                },
              ),
      ),
    );
// 뒷 부분 생략

개인정보 처리방침 화면 (/privacy) 및 이용약관 화면 (/tos)

개인정보 처리방침과 이용약관 페이지는 일단 AppBar만 만들어 놓고 내용은 추후에 채우도록 하겠다.

#8 [view] 로그인 및 회원가입 화면 뼈대 잡기

로그인 화면 (/auth/login)

로그인 화면 상단에 들어가는 로고 이미지는 Claude Design이 SVG로 뽑아줬으므로 flutter_svg package를 사용하도록 하겠다. PNG로 다시 뽑아달라고 해도 되겠지만 언젠가 실무에서는 내 마음대로 요청하지 못하고 주는 대로 써야 하는 상황도 있을 수 있으니 알아둬서 나쁠 게 없지.

회원가입 화면 (/auth/signup)

회원가입 화면을 만들다 보니 이것저것 옵션이 들어가 있는 TextFormField를 widget으로 따로 빼는 게 나을 것 같더라. 이거 말고도 하다 보니 조정해야 할 부분이 보이는데 전체적인 레이아웃 마치고 나서 다시 검토해 봐야지.

회원가입 완료 화면 (/auth/signup/complete)

회원가입 완료 화면에서는 가입한 이메일을 띄워주며 로그인하러 가기 버튼이 출력되고 인증 메일 다시 보내기 버튼도 함께 존재한다. UI상으로 크게 신경쓸 건 없다.

비밀번호 재설정 화면 (/auth/password)

비밀번호 재설정 화면도 크게 신경쓸 건 없다. 로직 넣을 때 검증이나 잘 해야지.

랜딩 페이지 (/)

랜딩페이지에는 샘플 이미지를 넣으려고 했는데 아직 image assets 준비가 되지 않았다. 일단 틀만 잡아놓고 image assets 작업이 완료되면 집어넣도록 하자.


>>> GitHub Repository at this point (520a372)

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다 (지금은 학생 때 하던 거 아무거나 공부하고 있고요, 취업시켜 주시면 그 분야로 공부할게요)

0개의 댓글