Notice
Recent Posts
Recent Comments
Link
250x250
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

혼자서 앱 만드는 개발자 함께하는 AI 세상

[펫버틀러] 반려 동물 고민 상담 앱 - 개발 15 일차 (공개/비공개 날씨선택버튼(라디오 타입) ) 본문

개발일지

[펫버틀러] 반려 동물 고민 상담 앱 - 개발 15 일차 (공개/비공개 날씨선택버튼(라디오 타입) )

혼앱사 2023. 1. 24. 15:39
반응형
  • 다이어리를 작성하고 작성된 일기를 공개하설정하면 앱을 설치한 다른 유저에게 공유되도록 선택버튼 기능추가하였다.
  • 그리고 다이어리의 필수 입력내용 날씨를 추가함

  • 플러터에서 기본  제공하는 ToggleButtons 기능 으로 적용가능하다.
  • 토글버튼소스를 보면 아래처럼 사용방법을 자세하게 볼수있다.
/// ToggleButtons(
///   children: <Widget>[
///     Icon(Icons.ac_unit),
///     Icon(Icons.call),
///     Icon(Icons.cake),
///   ],
///   onPressed: (int index) {
///     int count = 0;
///     isSelected.forEach((bool val) {
///       if (val) count++;
///     });
///
///     if (isSelected[index] && count < 2)
///       return;
///
///     setState(() {
///       isSelected[index] = !isSelected[index];
///     });
///   },
///   isSelected: isSelected,
/// ),
/// ```
 
 
 
  • 입력폼에 추가하기 위해서 
  • 위처럼 상단에 선언하고

  • 적용할 값을 셋팅하기 위해 해당하는 값을 list 형태로 제공한다. default 로 true 넣어도되고 모두 false 넣어서 선택하게 할 수도 있다.
  • 혹시 수정할경우 아래처럼 해당 부분을 true 바꿔 주면된다.

  • 아래는 토글버튼을 눌러을때 이벤트를 보여준다. 누를때 폼값에 넣어주어서 저장 할 수 있다.

  • 전체 소스에서 확인 할 수 있다.
import 'dart:convert';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';

import '../models/message_diary.dart';
import '../providers/diary_provider.dart';
import '../utils/database_diary.dart';
import 'diary/utils/input_validator.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:image/image.dart' as ImageProcess;
import 'package:http/http.dart' as http;

class DailyEdit extends StatefulWidget {
  final String timestamp;
  const DailyEdit({super.key, required this.timestamp});

  @override
  State<DailyEdit> createState() => _EditEntryState();
}

const List<Widget> isopen = <Widget>[Text('비공개'), Text('공개')];

const List<Widget> icons = <Widget>[
  Icon(Icons.sunny),
  Icon(Icons.cloud),
  Icon(Icons.umbrella),
  Icon(Icons.ac_unit),
];

class _EditEntryState extends State<DailyEdit>
    with SingleTickerProviderStateMixin {
  MessageDiary _formData = new MessageDiary(
      content: '',
      id: '',
      imageUrl: '',
      message: '',
      timestamp: '',
      title: '',
      yyyymmdd: '',
      isopen: '',
      weather: '');
  final _addEntryFormKey = GlobalKey<FormState>();
  late DiaryProvider dailyProvider;
  final List<bool> _selectedisopen = <bool>[false, false];
  final List<bool> _selectedWeather = <bool>[false, false, false, false];
  bool vertical = false;
  bool _isButtonEnabled = true;
  File? imageFile;
  double prograss = 0;
  final ImagePicker _picker = ImagePicker();
  bool isLoading = false;
  File? avatarImageFile;
  Image byteImage = Image(image: AssetImage('images/pet/intro/intro1.png'));
  var titleController = TextEditingController();
  var contentController = TextEditingController();
  var imageUrl;
  @override
  void initState() {
    //  entryViewModel = Provider.of<EntryViewModel>(context);
    //Check the server every 5 seconds
    // _timer = Timer.periodic(Duration(seconds: 5), (timer) => getData());
    dailyProvider = context.read<DiaryProvider>();

    DatabaseDiary().getData(widget.timestamp).then((messageList) {
      if (messageList.length > 0) {
        _formData = messageList[0];
        titleController.text = messageList[0].title;
        contentController.text = messageList[0].content;
        imageUrl = messageList[0].imageUrl;

        _selectedisopen[int.parse(messageList[0].isopen)] = true;
        _selectedWeather[int.parse(_formData.weather)] = true;

        setState(() {
          if (imageUrl != '') {
            byteImage = Image.memory(
              const Base64Decoder().convert(messageList[0].imageUrl),
              fit: BoxFit.cover,
              height: double.infinity,
              width: double.infinity,
              alignment: Alignment.center,
            );
          }
        });
      }
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    return Scaffold(
        body: SafeArea(
          child: Container(
            padding: EdgeInsets.only(left: 20, right: 20, bottom: 10),
            child: Stack(
              children: <Widget>[
                ListView(
                  children: <Widget>[
                    Theme(
                      data: ThemeData(
                        highlightColor: Colors.transparent,
                        splashColor: Colors.transparent,
                        inputDecorationTheme:
                            InputDecorationTheme(border: InputBorder.none),
                      ),
                      child: Form(
                        key: _addEntryFormKey,
                        child: Column(
                          children: <Widget>[
                            // SizedBox(height: 45),
                            profileImage(1),

                            Padding(
                              padding: EdgeInsets.only(bottom: 0.0),
                              child: TextFormField(
                                keyboardType: TextInputType.multiline,
                                controller: titleController,
                                minLines: 1,
                                maxLines: 3,
                                cursorColor: Color(0xFF3C4858),
                                style: TextStyle(fontWeight: FontWeight.bold),
                                textAlign: TextAlign.center,
                                validator: InputValidator.title2,
                                decoration: InputDecoration(
                                  hintText: '오늘은 무슨 일이 있었나요?',
                                ),
                                inputFormatters: [
                                  LengthLimitingTextInputFormatter(100),
                                ],
                                //  validator: InputValidator.title,
                                onSaved: (value) => _formData.title = value!,
                              ),
                            ),
                            TextFormField(
                              keyboardType: TextInputType.multiline,
                              minLines: 10,
                              maxLines: null,
                              controller: contentController,
                              cursorColor: Color(0xFF3C4858),
                              validator: InputValidator.contents3,

                              decoration: InputDecoration.collapsed(
                                  hintText: '나에게 말해줘요 저는 못 들었어요 🤐..'),
                              //   validator: InputValidator.content,
                              onSaved: (value) => _formData.content = value!,
                            ),
                            //   Text('공개 / 비공개', style: theme.textTheme.titleSmall),
                            const SizedBox(height: 5),
                            ToggleButtons(
                              onPressed: (int index) {
                                setState(() {
                                  // The button that is tapped is set to true, and the others to false.
                                  for (int i = 0;
                                      i < _selectedisopen.length;
                                      i++) {
                                    _selectedisopen[i] = i == index;
                                  }
                                  _formData.isopen = index.toString();
                                });
                              },
                              borderRadius:
                                  const BorderRadius.all(Radius.circular(8)),
                              selectedBorderColor: Colors.red[700],
                              selectedColor: Colors.white,
                              fillColor: Colors.red[200],
                              color: Colors.red[400],
                              constraints: const BoxConstraints(
                                minHeight: 40.0,
                                minWidth: 80.0,
                              ),
                              isSelected: _selectedisopen,
                              children: isopen,
                            ),
                            const SizedBox(height: 20),

                            //   Text('날씨', style: theme.textTheme.titleSmall),
                            const SizedBox(height: 5),
                            ToggleButtons(
                              onPressed: (int index) {
                                setState(() {
                                  // The button that is tapped is set to true, and the others to false.
                                  for (int i = 0;
                                      i < _selectedWeather.length;
                                      i++) {
                                    _selectedWeather[i] = i == index;
                                  }
                                  _formData.weather = index.toString();
                                });
                              },
                              borderRadius:
                                  const BorderRadius.all(Radius.circular(8)),
                              selectedBorderColor: Colors.blue[700],
                              selectedColor: Colors.white,
                              fillColor: Colors.blue[200],
                              color: Colors.blue[400],
                              isSelected: _selectedWeather,
                              children: icons,
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
                Row(
                  children: <Widget>[
                    InkResponse(
                        onTap: () {
                          Navigator.of(context).pop();
                        },
                        child: Container(
                          padding: EdgeInsets.all(10),
                          decoration: BoxDecoration(
                            color: Colors.white,
                            border: Border.all(color: Colors.black12),
                            borderRadius: BorderRadius.circular(100),
                            boxShadow: [
                              BoxShadow(
                                  color: Color(0xFF3C4858).withOpacity(.5),
                                  offset: Offset(1.0, 10.0),
                                  blurRadius: 10.0),
                            ],
                          ),
                          child: Icon(
                            Icons.arrow_downward,
                            semanticLabel: 'Back',
                            size: 22,
                          ),
                        )),
                  ],
                ),
              ],
            ),
          ),
        ),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            const SizedBox(
              height: 8,
            ),
            FloatingActionButton(
              heroTag: const Text("btn2"),
              onPressed: () {
                if (_addEntryFormKey.currentState!.validate() &&
                    _isButtonEnabled) {
                  _isButtonEnabled = false;
                  _addEntryFormKey.currentState?.save();
                  _handleAddEntry();
                }
              },
              child: const Icon(Icons.save),
            ),
            SizedBox(
              height: 10,
            ),
            FloatingActionButton(
              heroTag: const Text("btn2"),
              onPressed: () {
                _handleADeleteEntry(widget.timestamp);
              },
              child: const Icon(Icons.delete),
            ),
          ],
        ));
  }

  _handleAddEntry() async {
    if (imageFile != null) {
      final _imageFile = ImageProcess.decodeImage(imageFile!.readAsBytesSync());
      _byteImage = base64Encode(ImageProcess.encodePng(_imageFile!));
      _formData.imageUrl = _byteImage;
    } else {
      _formData.imageUrl = '';
    }

    dailyProvider.updateData(_formData).then((value) {
      //  final _imageFile = ImageProcess.decodeImage(imageFile!.readAsBytesSync());
      //  _byteImage = base64Encode(ImageProcess.encodePng(_imageFile!));

      setState(() {
        Fluttertoast.showToast(msg: "등록되었습니다.");
        Navigator.of(context).pop();

        print('======================>ok');

        //   memoList = getAllDatas();
      });
    });
  }

  _handleADeleteEntry(timestamp) async {
    showDialog(
        context: context,
        builder: (_) => AlertDialog(
                title: Text('삭제하시겠습니까?'),
                content: Text(''),
                actions: <Widget>[
                  TextButton(
                    child: Text('취소'),
                    onPressed: () {
                      setState(() async {
                        Navigator.of(context).pop();
                      });
                    },
                  ),
                  TextButton(
                      child: Text(
                        '삭제',
                        style: TextStyle(color: Colors.red),
                      ),
                      onPressed: () {
                        dailyProvider.deleteData(timestamp).then((value) {
                          setState(() {
                            Fluttertoast.showToast(msg: "삭제되었습니다.");
                            Navigator.of(context).pop();

                            //   memoList = getAllDatas();
                          });
                        });
                      })
                ])).then((value) => {Navigator.of(context).pop()});
  }

  var _byteImage;
  Future getImage() async {
    var height = MediaQuery.of(context).size.height;
    var width = MediaQuery.of(context).size.width;
    prograss = 0;
    showDialog(
        context: context,
        builder: (_) => AlertDialog(
              shape: const RoundedRectangleBorder(
                  borderRadius: BorderRadius.all(Radius.circular(0.0))),
              content: Builder(
                builder: (context) {
                  // Get available height and width of the build area of this widget. Make a choice depending on the size.
                  var height = MediaQuery.of(context).size.height;
                  var width = MediaQuery.of(context).size.width;

                  return SizedBox(
                    height: 250,
                    width: width,
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            InkWell(
                              onTap: () async {
                                ImagePicker imagePicker = ImagePicker();

                                XFile? pickedFile;

                                pickedFile = await imagePicker.pickImage(
                                    maxHeight: 400, source: ImageSource.camera);
                                if (pickedFile != null) {
                                  imageFile = File(pickedFile.path);

                                  //  onSendMessage(_byteImage.toString(),
                                  //   TypeMessage.imageByte, '');

                                  if (imageFile != null) {
                                    setState(() {
                                      byteImage = Image.file(imageFile!);
                                      isLoading = true;
                                      Navigator.pop(context);
                                    });
                                  }
                                }
                              }, // Handle your callback.
                              splashColor: Colors.brown.withOpacity(0.5),
                              child: Ink(
                                height: 80,
                                width: 80,
                                decoration: const BoxDecoration(
                                  borderRadius:
                                      BorderRadius.all(Radius.circular(8.0)),
                                  image: DecorationImage(
                                    image: AssetImage('assets/icon/camera.jpg'),
                                    fit: BoxFit.cover,
                                  ),
                                ),
                              ),
                            ),
                            //const Text(' 카메라'),
                            InkWell(
                              onTap: () async {
                                ImagePicker imagePicker = ImagePicker();
                                XFile? pickedFile;

                                pickedFile = await imagePicker.pickImage(
                                    maxHeight: 400,
                                    source: ImageSource.gallery);
                                if (pickedFile != null) {
                                  imageFile = File(pickedFile.path);

                                  final _imageFile = ImageProcess.decodeImage(
                                      imageFile!.readAsBytesSync());
                                  _byteImage = base64Encode(
                                      ImageProcess.encodePng(_imageFile!));

                                  if (imageFile != null) {
                                    setState(() {
                                      byteImage = Image.file(imageFile!);
                                      isLoading = true;
                                      Navigator.pop(context);
                                    });
                                  }
                                }
                              }, // Handle your callback.
                              splashColor: Colors.brown.withOpacity(0.5),
                              child: Ink(
                                height: 80,
                                width: 80,
                                decoration: const BoxDecoration(
                                  borderRadius:
                                      BorderRadius.all(Radius.circular(8.0)),
                                  image: DecorationImage(
                                    image:
                                        AssetImage('assets/icon/gallery.jpg'),
                                    fit: BoxFit.cover,
                                  ),
                                ),
                              ),
                            ),
                            //const Text(' 갤러리')
                          ],
                        ),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            InkWell(
                              onTap: () async {
                                ImagePicker imagePicker = ImagePicker();

                                XFile? pickedFile;

                                pickedFile = await imagePicker.pickVideo(
                                    source: ImageSource.camera);

                                if (pickedFile != null) {
                                  imageFile = File(pickedFile.path);
                                  if (imageFile != null) {
                                    setState(() {
                                      //  isLoading = true;
                                    });
                                    Navigator.pop(context);
                                    //uploadFile(imageFile);
                                    //  uploadFileServer(imageFile!)
                                    //     .then((value) => {});
                                  }
                                }
                              }, // Handle your callback.
                              splashColor: Colors.brown.withOpacity(0.5),
                              child: Ink(
                                height: 80,
                                width: 80,
                                decoration: const BoxDecoration(
                                  borderRadius:
                                      BorderRadius.all(Radius.circular(8.0)),
                                  image: DecorationImage(
                                    image: AssetImage('assets/icon/movie.jpg'),
                                    fit: BoxFit.cover,
                                  ),
                                ),
                              ),
                            ),
                            //const Text(' 카메라'),
                            InkWell(
                              onTap: () async {
                                ImagePicker imagePicker = ImagePicker();

                                XFile? pickedFile;

                                pickedFile = await imagePicker.pickVideo(
                                    source: ImageSource.gallery);

                                if (pickedFile != null) {
                                  imageFile = File(pickedFile.path);
                                  if (imageFile != null) {
                                    setState(() {
                                      // isLoading = true;
                                    });
                                    //uploadFile(imageFile);
                                    Navigator.pop(context);
                                    //  uploadFileServer(imageFile!)
                                    //     .then((value) => {});
                                  }
                                }
                              }, // Handle your callback.
                              splashColor: Colors.brown.withOpacity(0.5),
                              child: Ink(
                                height: 80,
                                width: 80,
                                decoration: const BoxDecoration(
                                  borderRadius:
                                      BorderRadius.all(Radius.circular(8.0)),
                                  image: DecorationImage(
                                    image: AssetImage(
                                        'assets/icon/moviegallery.jpg'),
                                    fit: BoxFit.cover,
                                  ),
                                ),
                              ),
                            ),
                            //const Text(' 갤러리')
                          ],
                        ),
                      ],
                    ),
                  );
                },
              ),
            ));
  }

  Widget profileImage(index) {
    return Center(
      child: Stack(
        children: <Widget>[
          SizedBox(
              height: 400,
              width: 300,
              child: ClipRRect(
                  borderRadius: BorderRadius.all(Radius.circular(10.0)),
                  child: byteImage)),
          Positioned(
              bottom: 10,
              right: 10,
              child: InkWell(
                  onTap: () async {
                    getImage();
                    /*showModalBottomSheet(
                      context: context, builder: ((builder) => bottomSheet()));
                      */
                  },
                  child: ClipRRect(
                      borderRadius: BorderRadius.circular(30.0),
                      child: Image(
                          width: 40,
                          image: AssetImage('assets/icon/camera.gif'),
                          fit: BoxFit.fill))))
        ],
      ),
    );
  }
}

enum openOption { Y, N }

/// 버튼 토글용
enum PlayerButtonState { open, hidden }

ValueNotifier<PlayerButtonState>? _playButtonNotifier;

Widget _toggleButton({bool isBottomButton = false, double iconSize = 30}) {
  _playButtonNotifier = ValueNotifier(PlayerButtonState.open);
  _playButtonNotifier?.value = PlayerButtonState.hidden;
  return ValueListenableBuilder<PlayerButtonState>(
      valueListenable: _playButtonNotifier!,
      builder: (_, state, __) {
        switch (state) {
          case PlayerButtonState.open:
            return IconButton(
              iconSize: iconSize,
              icon: const Padding(
                padding: EdgeInsets.all(8.0),
                child: Icon(
                  Icons.open_in_new,
                  color: Colors.white,
                ),
              ),
              onPressed: () async {},
            );
          case PlayerButtonState.hidden:
            return IconButton(
              iconSize: iconSize,
              icon: const Padding(
                padding: EdgeInsets.all(8.0),
                child: Icon(
                  Icons.hide_image_rounded,
                  color: Colors.black12,
                ),
              ),
              onPressed: () async {},
            );
        }
      });
}
728x90
반응형
Comments