Notice
Recent Posts
Recent Comments
Link
250x250
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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
- 광동온더그린#프랜즈#가상CC#스크린골프#
- 쇼핑몰관리시스템#매입관리#시트메타#매입채널#엑셀업로드
- 마이봇#문서챗봇#PDF#TEXT#유투브#챗봇만들기#랭체인# langchain#벡터데이터#자료검색#챗GPT#GPT4#챗지피티
- 마이봇#API 설정
- PDF검색#PDF검색챗봇#NEXTJS#스터디#스타트업#랭체이#langchain#prisma#sqlite#
- 플러터#sms#mms#문자보내기
- 로우코드#ERP#관리시스템#상품관리#선택박스#자동화프로그램
- 마이봇#핸드폰대체#
- fcm#메세지전송#안드로이드메세지#플러터메세지전송
- figma#flutter#dhwise#피그마#플러터#피그마 to 플러터 #figma to flutter
- 마이봇#아이폰#아이폰심사#IT고시#
- #창작#SNS#스포츠#반려동물#연애#과제#레시피#활동#건강#운세#글쓰기#비즈니스 #AI비서#챗GPT#CHATGPT
- 마이봇#챗봇
- 시트메타#관리시스템#테이블연동#품목관리
- 마이봇#chatgpt#ai#인공지능
- firebase#message#메세지#플러터#안드로이드
- flutter#채팅창@메모창#url링크#날짜추가
- postgres#vector
- PDF#챗봇검색#서비스#GPT4#PGT3.5#GPT#랭체인#챗봇#CHATBOT#LLM#문서검색
- flutter#ios#앱개발#마이봇#
- 커피#그라인더#통돌이 오픈 #로스팅#드립커피#생두#원두
- 펫버틀러#서버연동#프로필등록#로그인서버연동#이미지#동영상#업로드용 화면#앱개발#플러터#반려생활#로딩바#loading bar#
- flutterfire configure#파이어베이스#플러터
- 마이봇#pdf챗봇#상담챗봇#faq챗봇#chatgpt#랭체인#llm
- 임대사업자#리걸테크#legaltech#마이봇#챗봇#법률챗봇#임대사업자챗봇#chatgpt#
- ax5#tree#grid#단계별 펼치기# depth #시트메타
- mediasaop#webrtc#미디어서버#
- 로우코드#lowcode#erp#관리시스템#시트메이트#시트메타#엑셀업로드#엑셀다운로드#그리드#데이터관리#생산관리시스템#로그관리#히스토리#입력체크
- 플러터#
- flutter#sqlite#chatGPT#
Archives
- Today
- Total
혼자서 앱 만드는 개발자 함께하는 AI 세상
[펫버틀러] 반려 동물 고민 상담 앱 - 개발 15 일차 (공개/비공개 날씨선택버튼(라디오 타입) ) 본문
반응형
- 다이어리를 작성하고 작성된 일기를 공개하설정하면 앱을 설치한 다른 유저에게 공유되도록 선택버튼 기능추가하였다.
- 그리고 다이어리의 필수 입력내용 날씨를 추가함
- 플러터에서 기본 제공하는 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
반응형
'개발일지' 카테고리의 다른 글
경기광주 초월 올푸드식자제 마트 이천 롯데 마트 양주 매장 투어 (0) | 2023.03.07 |
---|---|
통돌이 오븐 커피 로스팅으로 드립커피 즐기는 개발자 (0) | 2023.02.05 |
니로 5년차이상 시속 80km 이상시 엔진오일 빨간등 오일압력스위치교체 (1) | 2023.01.11 |
기아 니로 2017 스마트키 저출력 잠금장치 잘 안돼서 밧데리 교체 (0) | 2023.01.10 |
자동차 밧데리 교체 기아 니로 하이브리드 2017 (0) | 2022.12.19 |
Comments