본문 바로가기
개발일지/GoodWishes

[굿위시 제작기] 15. 프로필 페이지 제작하기

by 박기린 2024. 11. 8.

프로필 페이지

화면 우측 상단에는 작게 프로필 이미지가 있습니다.

사실 굿위시 자체에는 커뮤니티&계정 기능이 있다보니 프로필 이미지를 따로 넣을 필요가 있을까 싶지만, 각자의 개성을 표현하는 수단으로 좋아보여서 넣었습니다.

 

 

 


프로필 페이지의 코드 전문

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:goodwishes/Models/goods_model.dart';
import 'package:goodwishes/Models/profile_model.dart';
import 'package:goodwishes/Models/wish_model.dart';
import 'package:goodwishes/constants/ui_numbers.dart';
import 'package:goodwishes/pages/backup_restore_page.dart';
import 'package:goodwishes/widgets/amount_text.dart';
import 'package:goodwishes/widgets/license_button.dart';
import 'package:goodwishes/widgets/profile_icon_big.dart';
import 'package:goodwishes/widgets/section_title.dart';
import 'package:image_picker/image_picker.dart';

import 'package:provider/provider.dart';

class ProfilePage extends StatelessWidget {
  const ProfilePage({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final profileProvider = Provider.of<ProfiletProvider>(context);
    final goodsAmount = Provider.of<GoodsListProvider>(context).goodsAmount;
    final wishAmount = Provider.of<WishListProvider>(context).wishAmount;

    final ImagePicker picker = ImagePicker();

    // 이미지를 가져오는 함수
    Future getImage(ImageSource imageSource) async {
      // pickedFile에 ImagePicker로 가져온 이미지가 담긴다.
      final XFile? pickedFile = await picker.pickImage(source: imageSource);

      if (pickedFile != null) {
        List<int> imageBytes = await pickedFile.readAsBytes();
        profileProvider.changeProfile(Uint8List.fromList(imageBytes));
      }
    }

    return Scaffold(
      appBar: AppBar(
        title: const SectionTitle(
          titleText: 'Profile',
        ),
      ),
      body: SafeArea(
        child: SizedBox(
          width: MediaQuery.of(context).size.width,
          child: Padding(
            padding: const EdgeInsets.symmetric(vertical: 20.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Column(
                  children: [
                    ProfileIconBig(
                        profile: profileProvider.profile, onUpload: getImage),
                    const SizedBox(
                      height: UIDefault.sizedBoxHeight * 2,
                    ),
                    AmountText(
                      text: '가지고 있는 굿즈의 수',
                      amount: goodsAmount,
                    ),
                    AmountText(
                      text: '원하는 위시의 수',
                      amount: wishAmount,
                    ),
                    GestureDetector(
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => BackupRestorePage(),
                          ),
                        );
                      },
                      child: const Text('백업 & 복원'),
                    ),
                  ],
                ),
                const Column(
                  children: [
                    LicenseButton(),
                    Text('made by ParkGiraffe'),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

 

 

 

 

 


상단 : 프로필 이미지 확인/삽입/수정

프로필 페이지답게, 맨 위에는 프로필 이미지가 크게 있습니다.

이 동그란 이미지를 터치하면

 

 

 

 

 

 

원하는 이미지를 선택할 수 있고, 선택하면 프로필 이미지가 바뀝니다.

 

 

 

 

profile_page.dart

굿즈 추가 페이지의 사진추가 기능을 구현할 때 사용한 함수를 그대로 가져왔습니다.

( 굿즈 추가 페이지의 사진추가 기능 되돌아보기 : 링크 )

 

함수 맨 아래를 보면, profileProvider의 changeProfile 함수가 등장합니다.

 

 

 

 

profile_model.dart

사진 저장 방식인 Uint8List 값을 저장받는 메소드로, profile을 담당하는 HIveBox에 새로 입력받은 이미지를 put 합니다.

 

 

 

profile_model.dart

이때 Profile 역시 HiveType으로 지정해서, 해당 클래스 모델이 HiveBox에 온전히 저장되게 해놨습니다.

 

 

 

 

 

 

 


중단 : 굿즈와 위시 갯수 표시

지금까지 모인 굿즈와 위시 갯수도 출력합니다.

 

 

 

profile_page.dart

goods와 wish Provider에서 갯수를 출력하는 값을 가져옵니다.

 

 

 

 

 

goods_model.dart

참고로, goodsAmount와 wishAmount의 원본값은, HiveBox의 values 값의 length입니다.

 

 

 

 

그리고 AmountText라는 커스텀 위젯을 이용해서 출력합니다.

 

 

 

AmountText 위젯 코드 전문

import 'package:flutter/material.dart';
import 'package:goodwishes/constants/ui_numbers.dart';

class AmountText extends StatelessWidget {
  const AmountText({
    super.key,
    required this.amount,
    required this.text,
  });

  final int amount;
  final String text;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          '$text : $amount',
          style: const TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(
          height: UIDefault.sizedBoxHeight,
        ),
      ],
    );
  }
}

 

 

 

 

 


하단 : 라이센스 페이지 버튼

최하단에는 제작자 설명과 라이센스가 있습니다.

단순히 글자가 아니라,

 

 

 

 

누르면 위와 같이 라이센스 페이지가 뜹니다.

오픈소스 라이선스를 지키기 위해 제작한 페이지입니다.

 

 

 

 

 

 

 


Flutter OSS Licenses

공식 링크 : https://pub.dev/packages/flutter_oss_licenses

 

flutter_oss_licenses | Dart package

A tool to generate detail and better OSS license list using pubspec.yaml/lock files.

pub.dev

 

위 패키지는, 플러터 프로젝트에 사용된 외부 라이브러리 패키지들을 한 번에 모아서, dart의 리스트 데이터로 만들어줍니다.

 

 

 

 

 

flutter pub run flutter_oss_licenses:generate.dart

패키지 설치 후, 터미널에 위 명령어를 입력하면, oss_licenses.dart라는 파일이 자동으로 생성됩니다.

 

 

 

oss_licenses.dart는 Package라는 클래스 모델을 생성합니다. Package는 모든 패키지의 세부사항들을 담는 모델입니다.

그리고 자동으로 현재 플러터 프로젝트에 적용된 패키지들을 읽은 후, Package 타입으로 변환합니다.

 

 

 

그리고, dependencies, devDependencies 두 개의 Package List를 반환합니다.

이걸 이용해서 쉽게 라이센스 페이지를 제작합니다.

 

 

 


라이센스 페이지 코드 전문

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:goodwishes/widgets/section_title.dart';
import '../../oss_licenses.dart';

class LicensesPage extends StatelessWidget {
  const LicensesPage({super.key});

  @override
  Widget build(BuildContext context) {
    var ossLicenses = dependencies;
    return Scaffold(
      appBar: AppBar(
        title: const SectionTitle(
          titleText: 'Licenses',
        ),
      ),
      body: ListView.builder(
        physics: const BouncingScrollPhysics(),
        itemCount: ossLicenses.length,
        itemBuilder: (_, index) {
          return Padding(
            padding: const EdgeInsets.all(8.0),
            child: Container(
              decoration: BoxDecoration(
                color: Theme.of(context).cardColor,
                borderRadius: BorderRadius.circular(8),
              ),
              child: ListTile(
                onTap: () {
                  Navigator.push(
                    context,
                    CupertinoPageRoute(
                      builder: (_) => LicenceDetailPage(
                        title: ossLicenses[index].name[0].toUpperCase() +
                            ossLicenses[index].name.substring(1),
                        licence: ossLicenses[index].license!,
                      ),
                    ),
                  );
                },
                //capitalize the first letter of the string
                title: Text(
                  ossLicenses[index].name[0].toUpperCase() +
                      ossLicenses[index].name.substring(1),
                ),
                subtitle: Text(ossLicenses[index].description),
              ),
            ),
          );
        },
      ),
    );
  }
}

//detail page for the licence
class LicenceDetailPage extends StatelessWidget {
  final String title, licence;
  const LicenceDetailPage(
      {super.key, required this.title, required this.licence});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: SectionTitle(
          titleText: 'Licenses',
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          padding: const EdgeInsets.all(5),
          decoration: BoxDecoration(
              color: Theme.of(context).cardColor,
              borderRadius: BorderRadius.circular(8)),
          child: SingleChildScrollView(
            physics: const BouncingScrollPhysics(),
            child: Column(
              children: [
                Text(
                  licence,
                  style: const TextStyle(fontSize: 15),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

 

licenses_page.dart

oss_licenses.dart의 dependencies를 ossLicneses 변수에 담은 후,

 

 

 

ListView.Builder를 통해 형식에 맞게 자동으로 출력하게 만들었습니다.

 

 

 

 

끝.

 

반응형