코딩하는 제리

[Flutter/Project](Instagram Clone) 포스트 이미지 다운로드 링크 저장 본문

Flutter/Project_InstaClone(완)

[Flutter/Project](Instagram Clone) 포스트 이미지 다운로드 링크 저장

JerryCho 2021. 3. 2. 11:31

screens/share_post_screen.dart
repo/post_network_repository.dart
post_img 필드에 링크가 들어있다.
Firebase Storage에 업로드되어 있다.


소스코드 및 pubspec.yaml

// screens/share_post_screen.dart

import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_project_IJ/constants/common_size.dart';
import 'package:flutter_project_IJ/constants/screen_size.dart';
import 'package:flutter_project_IJ/models/firestore/post_model.dart';
import 'package:flutter_project_IJ/models/firestore/user_model.dart';
import 'package:flutter_project_IJ/models/user_model_state.dart';
import 'package:flutter_project_IJ/repo/image_network_repository.dart';
import 'package:flutter_project_IJ/repo/post_network_repository.dart';
import 'package:flutter_project_IJ/widgets/my_progress_indicator.dart';
import 'package:flutter_tags/flutter_tags.dart';
import 'package:provider/provider.dart';

// TextEditingController를 위해 StatefulWidget로 변경
class SharePostScreen extends StatefulWidget {
  final File imageFile;
  final String postKey;

  SharePostScreen(this.imageFile, {Key key, @required this.postKey})
      : super(key: key);

  @override
  _SharePostScreenState createState() => _SharePostScreenState();
}

class _SharePostScreenState extends State<SharePostScreen> {
  // 캡션 텍스트박스 컨트롤러
  TextEditingController _textEditingController = TextEditingController();

  List<String> _tagItems = [
    "approval",
    "pigeon",
    "brown",
    "expenditure",
    "compromise",
    "citizen",
    "inspire",
    "relieve",
    "grave",
    "incredible",
    "voucher",
    "girl",
    "relax",
  ];

  @override
  void dispose() {
    // 컨트롤러 종료
    _textEditingController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('New Post'),
        actions: [
          TextButton(
            onPressed: sharePostProcedure,
            child: Text(
              'Share',
              textScaleFactor: 1.4,
              style: TextStyle(color: Colors.blue),
            ),
          )
        ],
      ),
      body: ListView(
        children: [
          _captionWithImage(),
          _divider,
          _sectionButton('Tag People'),
          _divider,
          _sectionButton('Add Location'),
          _tags(),
          SizedBox(height: common_s_gap),
          _divider,
          SectionSwitch('Facebook'),
          SectionSwitch('Instagram'),
          SectionSwitch('Tumblr'),
          _divider,
        ],
      ),
    );
  }

  // 포스트 업로드/업데이트/다운로드
  void sharePostProcedure() async {
    showModalBottomSheet(
      context: context,
      builder: (_) => MyProgressIndicator(),
      isDismissible: false /* 닫을 수 없음 */,
      enableDrag: false /* 드래그 불가 */,
    );
    // 이미지 업로드
    await imageNetworkRepository.uploadImage(widget.imageFile,
        postKey: widget.postKey);

    UserModel userModel =
        Provider
            .of<UserModelState>(context, listen: false)
            .userModel;

    // 포스트 생성
    await postNetworkRepository.createNewPost(
        widget.postKey,
        PostModel.getMapForeCreatePost(
            userKey: userModel.userKey,
            username: userModel.username,
            caption: _textEditingController.text));

    // 이미지 다운로드 링크
    String postImgLink = await imageNetworkRepository.getPostImageUrl(
        widget.postKey);

    // 위에서 포스트를 생성한 뒤에 postKey를 가져와서 업데이트
    await postNetworkRepository.updatePostImageUrl(
        postKey: widget.postKey, postImg: postImgLink);

    // 팝업 창을 닫음
    Navigator.of(context).pop();
    // share_post_screen을 닫음
    Navigator.of(context).pop();
  }

  Tags _tags() {
    return Tags(
      horizontalScroll: true,
      itemCount: _tagItems.length,
      heightHorizontalScroll: 30 /* 높이 */,
      itemBuilder: (index) =>
          ItemTags(
            index: index,
            title: _tagItems[index],
            activeColor: Colors.grey[200],
            textActiveColor: Colors.black87,
            borderRadius: BorderRadius.circular(4) /* 테두리 둥글기 */,
            elevation: 2 /* 그림자 */,
            splashColor: Colors.grey[800] /* 선택효과 색상 */,
            color: Colors.red /* 선택 색상 */,
          ),
    );
  }

  Divider get _divider =>
      Divider(
        color: Colors.grey[300],
        height: 1,
        thickness: 1,
      );

  ListTile _sectionButton(String title) {
    return ListTile(
      title: Text(
        title,
        style: TextStyle(fontWeight: FontWeight.w400),
      ),
      trailing: Icon(Icons.navigate_next),
      dense: true,
      /* 작게 만듦. */
      contentPadding: EdgeInsets.symmetric(horizontal: common_gap),
    );
  }

  ListTile _captionWithImage() {
    return ListTile(
      contentPadding: EdgeInsets.symmetric(
        vertical: common_gap,
        horizontal: common_gap,
      ),
      leading: Image.file(
        widget.imageFile,
        width: size.width / 6,
        height: size.width / 6,
        fit: BoxFit.cover,
      ),
      title: TextField(
        controller: _textEditingController,
        autofocus: true,
        decoration: InputDecoration(
          hintText: 'Wirte a caption...',
          border: InputBorder.none,
        ),
      ),
    );
  }
}

// 버튼에 변화를 줘야함 = StatefulWidget
class SectionSwitch extends StatefulWidget {
  final String _title;

  const SectionSwitch(this._title, {
    Key key,
  }) : super(key: key);

  @override
  _SectionSwitchState createState() => _SectionSwitchState();
}

class _SectionSwitchState extends State<SectionSwitch> {
  bool checked = false;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        widget._title,
        style: TextStyle(fontWeight: FontWeight.w400),
      ),
      trailing: CupertinoSwitch(
        value: checked,
        onChanged: (onValue) {
          setState(() {
            checked = onValue;
          });
        },
      ),
      dense: true,
      /* 작게 만듦. */
      contentPadding: EdgeInsets.symmetric(horizontal: common_gap),
    );
  }
}
// repo/pst_network_repository.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_project_IJ/constants/firestore_keys.dart';

/*
포스트 데이터 업로드 과정
1. post reference를 가져옴(생성)
2. 어떤 포스트가 해당 유저에 포함되어 있는지 user reference를 가져옴
3. post collection과 user collection 두가지를 업데이트 해야하기에 transaction 사용.
4. user data에 postKey를 업데이트
5. post reference를 통해 post data를 업로드
*/

class PostNetworkRepository {
  Future<void> createNewPost(
      String postKey, Map<String, dynamic> postData) async {
    // 1. post reference를 가져옴(생성)
    final DocumentReference postRef =
        FirebaseFirestore.instance.collection(COLLECTION_POSTS).doc(postKey);
    // post reference를 통해 해당 document의 snapshot을 가져옴.
    final DocumentSnapshot postSnapshot = await postRef.get();
    // 2. 어떤 포스트가 해당 유저에 포함되어 있는지 user reference를 가져옴
    final DocumentReference userRef = FirebaseFirestore.instance
        .collection(COLLECTION_USERS)
        .doc(postData[KEY_USERKEY]);

    // 3. transaction -> 데이터 업로드에 하나라도 실패하면 이전의 데이터로 바꿔줌.
    return FirebaseFirestore.instance.runTransaction((Transaction tx) async {
      // 해당 postSnapshot이 존재하지 않으면
      if (!postSnapshot.exists) {
        // 4. 포스트 데이터 업로드
        await tx.set(postRef, postData);
        // 5. user의 mypost를 업데이트
        // user reference에 postKey 추가
        await tx.update(userRef, {
          KEY_MYPOSTS: FieldValue.arrayUnion([postKey])
        });
      }
    });
  }

  // postLink를 가져와서 저장
  Future<void> updatePostImageUrl({String postImg, String postKey}) async {
    // 1. post reference를 가져옴(생성)
    final DocumentReference postRef =
        FirebaseFirestore.instance.collection(COLLECTION_POSTS).doc(postKey);

    // post reference를 통해 해당 document의 snapshot을 가져옴.
    final DocumentSnapshot postSnapshot = await postRef.get();

    // postSnapshot이 존재하면 업데이트
    if (postSnapshot.exists) {
      // KEY_POSTIMG에 받아온 postImg를 저장.
      await postRef.update({KEY_POSTIMG: postImg});
    }
  }
}

PostNetworkRepository postNetworkRepository = PostNetworkRepository();
Comments