본문 바로가기

Flutter/12 Clone 'Used Goods app'

[Flutter] Clone - 당근마켓43(Item detail & PageView) - 5

이번에는 게시글 작성자의 다른 게시글을 하단에 추가해보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

 

 

 

구현 전/후 화면은 아래와 같습니다.

 

 

 

 

./src/repo/item_service.dart - 유저 정보 하위에 저장된 글정보 가져오는 함수 추가

 

Future<List<ItemModel2>> getUserItems({required String userKey, String? itemKey}) async {
  CollectionReference<Map<String, dynamic>> collectionReference =
      FirebaseFirestore.instance.collection(COL_USERS).doc(userKey).collection(COL_USER_ITEMS);
  QuerySnapshot<Map<String, dynamic>> snapshots = await collectionReference.get();
  List<ItemModel2> items = [];

  for (var snapshot in snapshots.docs) {
    // ItemModel2 itemModel = ItemModel2.fromQuerySnapshot(snapshot);
    ItemModel2 itemModel = ItemModel2.fromJson(snapshot.data());
    itemModel.reference = snapshot.reference;
    itemModel.itemKey = snapshot.id;

    if (itemKey == null || itemModel.itemKey != itemKey) {
      items.add(itemModel);
    }
  }

  // logger.d(items[0].toJson());
  return items;
}

 

 

 

./src/screens/home/item_detail_page.dart - SliverToBoxAdapter 을 이용하여 해당 영역의 제목과 GridView 구현

1. Future 는 Sliver 타입 함수가 없어서 SliverToBoxAdapter 로 wrapping 하여 사용하였다.

2. 여기서 GridView 사용시 주의 사항은 자체 영역 스크롤과 화면 영역 제한에 대해서 주의해야 한다.

physics: GridView 자체의 스크롤을 off 시키는 옵션,

shrinkWrap: 기본은 false 인데, false 면 전체화면을 차지하려하여 다른 위젯과 사용시 에러발생, ture 면, 실제 데이터를 개수만큼만 화면을 차지한다,

 

SliverToBoxAdapter(
  child: Padding(
    padding: const EdgeInsets.symmetric(
        horizontal: padding_16),
    // 판매자의 다른 상품 보기
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(
          '판매자의 다른 상품',
          style: Theme.of(context).textTheme.bodyText1,
        ),
        SizedBox(
          width: _size!.width / 4,
          child: MaterialButton(
            // padding: EdgeInsets.zero,
            onPressed: () {},
            child: Align(
              alignment: Alignment.centerRight,
              child: Text(
                '더보기',
                style: Theme.of(context)
                    .textTheme
                    .button!
                    .copyWith(color: Colors.grey),
              ),
            ),
          ),
        ),
      ],
    ),
  ),
),
// 일반위젯을 sliver 안에 넣으러면 SliverToBoxAdapter 로 wrapping 해야 함,
SliverToBoxAdapter(
  child: FutureBuilder<List<ItemModel2>>(
    future: ItemService().getUserItems(
      userKey: itemModel.userKey,
      itemKey: itemModel.itemKey,
    ),
    builder: (context, snapshot) {
      if (snapshot.hasData) {
        return Padding(
          padding: const EdgeInsets.all(padding_08),
          child: GridView.count(
            padding: const EdgeInsets.symmetric(horizontal: padding_08),
            //EdgeInsets.zero,
            // GridView 자체의 스크롤을 off 시키는 옵션,
            physics: const NeverScrollableScrollPhysics(),
            // 기본은 false 인데, false 면 전체화면을 차지하려하여 에러발생,
            // ture 면, 가져온 정보를 기초로 화면을 차지함,
            shrinkWrap: true,
            crossAxisCount: 2,
            mainAxisSpacing: padding_08,
            crossAxisSpacing: padding_16,
            childAspectRatio: 6 / 7,
            children: List.generate(snapshot.data!.length,
                (index) => SimilarItem(snapshot.data![index])),
          ),
        );
      }
      return Container();
    },
  ),
),

 

 

 

./src/screens/home/similar_item.dart - GridView 상세 부분을 별도의 위젯으로 구현, GridView 에서 보이는 게시글을 클릭하면 해당 게시물로 다시 이동한다.

 

import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../../constants/common_size.dart';
import '../../constants/data_keys.dart';
import '../../models/item_model.dart';

class SimilarItem extends StatelessWidget {
  final ItemModel2 _itemModel;

  const SimilarItem(this._itemModel, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () {
        Get.toNamed(
            ROUTE_ITEM_DETAIL,
            arguments: {'itemKey': _itemModel.itemKey},
            // 같은 페이지는 호출시, 중복방지가 기본설정인, false 하면 중복 호출 가능,
            preventDuplicates: false
            );
        debugPrint('itemKey: ${_itemModel.itemKey}');
      },
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          AspectRatio(
            aspectRatio: 5 / 4,
            child: ExtendedImage.network(
              _itemModel.imageDownloadUrls[0],
              fit: BoxFit.cover,
              borderRadius: BorderRadius.circular(8),
              shape: BoxShape.rectangle,
            ),
          ),
          Text(
            _itemModel.title,
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
            style: Theme.of(context).textTheme.subtitle1,
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: padding_08),
            child: Text(
              '${_itemModel.price.toString()}원',
              style: Theme.of(context).textTheme.subtitle2,
            ),
          ),
        ],
      ),
    );
  }
}

 

 

 

./src/utils/time_calculation.dart - 게시글 조회 시점기준으로 해당 게시물 작성시간 계산하는 함수

 

class TimeCalculation {
  static String getTimeDiff(DateTime createdDate) {
    DateTime _now = DateTime.now();
    Duration timeDiff = _now.difference(createdDate);
    if (timeDiff.inHours <= 1) {
      return '방금 전';
    } else if (timeDiff.inHours <= 24) {
      return '${timeDiff.inHours}시간 전';
    } else {
      return '${timeDiff.inDays}일 전';
    }
  }
}

 

 

 

./src/models/item_model.dart - null 인경우, 처리 로직 추가

 

factory ItemModel2.fromJson(Map<String, dynamic> json) => ItemModel2(
      itemKey: json["itemKey"] ?? '',
      userKey: json["userKey"] ?? '',
      userPhone: json["userPhone"] ?? '',
      imageDownloadUrls: List<String>.from(json["imageDownloadUrls"].map((x) => x)),
      title: json["title"] ?? '',
      category: json["category"] ?? 'none',
      price: json["price"] ?? 0,
      negotiable: json["negotiable"] ?? false,
      detail: json["detail"] ?? '',
      address: json["address"] ?? '',
      geoFirePoint: json[DOC_GEOFIREPOINT] == null
          ? GeoFirePoint(0, 0)
          : GeoFirePoint((json[DOC_GEOFIREPOINT][DOC_GEOPOINT]).latitude,
              (json[DOC_GEOFIREPOINT][DOC_GEOPOINT]).longitude),
      createdDate: json[DOC_CREATEDDATE] == null
          ? DateTime.now().toUtc()
          : (json[DOC_CREATEDDATE] as Timestamp).toDate(),
      // reference: json["reference"],
    );

 

 

 

./src/screens/home/item_screen.dart -  글 게시간 표시 부분 수정

 

Text(
  TimeCalculation.getTimeDiff(_item.createdDate),
  // '50일전',
  style: Theme.of(context).textTheme.subtitle2,
),