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
- 시트메타#관리시스템#테이블연동#품목관리
- 마이봇#아이폰#아이폰심사#IT고시#
- 마이봇#챗봇
- flutter#sqlite#chatGPT#
- 마이봇#pdf챗봇#상담챗봇#faq챗봇#chatgpt#랭체인#llm
- 로우코드#ERP#관리시스템#상품관리#선택박스#자동화프로그램
- 광동온더그린#프랜즈#가상CC#스크린골프#
- fcm#메세지전송#안드로이드메세지#플러터메세지전송
- figma#flutter#dhwise#피그마#플러터#피그마 to 플러터 #figma to flutter
- 커피#그라인더#통돌이 오픈 #로스팅#드립커피#생두#원두
- 펫버틀러#서버연동#프로필등록#로그인서버연동#이미지#동영상#업로드용 화면#앱개발#플러터#반려생활#로딩바#loading bar#
- flutter#ios#앱개발#마이봇#
- 쇼핑몰관리시스템#매입관리#시트메타#매입채널#엑셀업로드
- flutterfire configure#파이어베이스#플러터
- mediasaop#webrtc#미디어서버#
- 플러터#sms#mms#문자보내기
- firebase#message#메세지#플러터#안드로이드
- 로우코드#lowcode#erp#관리시스템#시트메이트#시트메타#엑셀업로드#엑셀다운로드#그리드#데이터관리#생산관리시스템#로그관리#히스토리#입력체크
- ax5#tree#grid#단계별 펼치기# depth #시트메타
- #창작#SNS#스포츠#반려동물#연애#과제#레시피#활동#건강#운세#글쓰기#비즈니스 #AI비서#챗GPT#CHATGPT
- postgres#vector
- flutter#채팅창@메모창#url링크#날짜추가
- 마이봇#핸드폰대체#
- 마이봇#chatgpt#ai#인공지능
- PDF검색#PDF검색챗봇#NEXTJS#스터디#스타트업#랭체이#langchain#prisma#sqlite#
- 플러터#
- 마이봇#API 설정
- 임대사업자#리걸테크#legaltech#마이봇#챗봇#법률챗봇#임대사업자챗봇#chatgpt#
- 마이봇#문서챗봇#PDF#TEXT#유투브#챗봇만들기#랭체인# langchain#벡터데이터#자료검색#챗GPT#GPT4#챗지피티
- PDF#챗봇검색#서비스#GPT4#PGT3.5#GPT#랭체인#챗봇#CHATBOT#LLM#문서검색
Archives
- Today
- Total
혼자서 앱 만드는 개발자 함께하는 AI 세상
[경매수익율분석앱]채팅창으로 메모장 만들기 본문
반응형
나의경우 카톡에 나와의 대화를 활용해 메모를 남기는게 그것처럼 관심 경매물건에 링크를 걸어 메모를 남기는 기능을 추가하였다.
1. 채팅창을 sqlite 연동하여 로컬에 테이블에 저장하는 방식으로 변경
2. 이미지역시 sqlite byte 처리하여 등록
- 아래처럼 이미를 가져와서 string 으로 담는다.
- 스트링으롭 받은 것을 이미로 뿌려줌
메모에 htttp가 있을경우 링크를 추가 함
전체 소스
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:http/http.dart' as http;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:auctioncalcu/constants/constants.dart';
import 'package:auctioncalcu/providers/providers.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../models/message_memo.dart';
import '../widgets/widgets.dart';
import 'full_photo_page_byte.dart';
import 'pages.dart';
import 'package:auctioncalcu/utils/database_memo.dart';
import 'package:image/image.dart' as ImageProcess;
class MemoPage extends StatefulWidget {
MemoPage({Key? key, required this.arguments}) : super(key: key);
final MemoPageArguments arguments;
@override
MemoPageState createState() => MemoPageState();
}
class MemoPageState extends State<MemoPage> {
late String currentUserId;
final FirebaseMessaging firebaseMessaging = FirebaseMessaging.instance;
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
List<QueryDocumentSnapshot> listMessage = [];
int _limit = 20;
int _limitIncrement = 20;
String groupMemoId = "";
File? imageFile;
bool isLoading = false;
bool isShowSticker = false;
String imageUrl = "";
final TextEditingController textEditingController = TextEditingController();
final ScrollController listScrollController = ScrollController();
final FocusNode focusNode = FocusNode();
late MemoProvider memoProvider;
late AuthProvider authProvider;
@override
void initState() {
super.initState();
memoProvider = context.read<MemoProvider>();
authProvider = context.read<AuthProvider>();
memoList = getAllDatas();
focusNode.addListener(onFocusChange);
listScrollController.addListener(_scrollListener);
readLocal();
registerNotification();
}
Future<List<MessageMemo>> getAllDatas() async {
return await DatabaseMemo().getAllDatas();
}
late Future<List<MessageMemo>> memoList;
_scrollListener() {
if (!listScrollController.hasClients) return;
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange &&
_limit <= listMessage.length) {
setState(() {
_limit += _limitIncrement;
});
}
}
void registerNotification() {
firebaseMessaging.requestPermission();
}
void onFocusChange() {
if (focusNode.hasFocus) {
// Hide sticker when keyboard appear
setState(() {
isShowSticker = false;
});
}
}
void readLocal() {
if (authProvider.getUserFirebaseId()?.isNotEmpty == true) {
currentUserId = authProvider.getUserFirebaseId()!;
} else {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LoginPage()),
(Route<dynamic> route) => false,
);
}
String peerId = widget.arguments.peerId;
if (currentUserId.compareTo(peerId) > 0) {
groupMemoId = '$currentUserId-$peerId';
} else {
groupMemoId = '$peerId-$currentUserId';
}
memoProvider.updateDataFirestore(
FirestoreConstants.pathUserCollection,
currentUserId,
{FirestoreConstants.chattingWith: peerId},
);
}
var _byteImage;
Future getImage() async {
ImagePicker imagePicker = ImagePicker();
PickedFile? pickedFile;
pickedFile = await imagePicker.getImage(
source: ImageSource.gallery,
maxWidth: 400.0,
maxHeight: 400.0,
);
if (pickedFile != null) {
imageFile = File(pickedFile.path);
final _imageFile = ImageProcess.decodeImage(imageFile!.readAsBytesSync());
_byteImage = base64Encode(ImageProcess.encodePng(_imageFile!));
onSendMessage(_byteImage.toString(), TypeMessage.imageByte);
if (imageFile != null) {
setState(() {
// isLoading = true;
});
}
}
}
void getSticker() {
// Hide keyboard when sticker appear
focusNode.unfocus();
setState(() {
isShowSticker = !isShowSticker;
});
}
void onSendMessage(String content, int type) {
if (content.trim().isNotEmpty) {
textEditingController.clear();
memoProvider
.sendMessage(content, type, groupMemoId, currentUserId,
widget.arguments.peerId)
.then((value) {
setState(() {
memoList = getAllDatas();
});
});
if (listScrollController.hasClients) {
listScrollController.animateTo(0,
duration: Duration(milliseconds: 300), curve: Curves.easeOut);
}
} else {
Fluttertoast.showToast(
msg: 'Nothing to send', backgroundColor: ColorConstants.greyColor);
}
}
String formatISOTime(DateTime date) {
//converts date into the following format:
// or 2019-06-04T12:08:56.235-0700
var duration = date.timeZoneOffset;
if (duration.isNegative)
return (DateFormat("yyyy-MM-ddTHH:mm:ss.mmm").format(date) +
"-${duration.inHours.toString().padLeft(2, '0')}${(duration.inMinutes - (duration.inHours * 60)).toString().padLeft(2, '0')}");
else
return (DateFormat("yyyy-MM-ddTHH:mm:ss.mmm").format(date) +
"+${duration.inHours.toString().padLeft(2, '0')}${(duration.inMinutes - (duration.inHours * 60)).toString().padLeft(2, '0')}");
}
Widget buildItem(int index, MessageMemo messageMemo) {
if (messageMemo != null) {
// Right (my message)
final splitted = messageMemo.content.split(' ');
var date = new DateTime.fromMicrosecondsSinceEpoch(
int.parse(messageMemo.timestamp));
var time = DateFormat('M/d h:m').format(date);
return Row(
children: <Widget>[
messageMemo.type == TypeMessage.text
// Text
? Container(
child: GestureDetector(
child: RichText(
text: TextSpan(children: [
for (var str in splitted)
if (str.indexOf('www') > -1 || str.indexOf('http') > -1)
TextSpan(
style: TextStyle(color: Colors.blue),
text: str,
/* recognizer: new TapGestureRecognizer()
..onTap = () {
launchUrl(Uri.parse(str),
mode: LaunchMode.externalApplication);
}*/
)
else
TextSpan(
style: TextStyle(color: Colors.black),
text: str,
),
/*TextSpan(
style: TextStyle(color: Colors.green),
text: "\n\r" + time,
)
*/
])),
onTap: () {
var _url = '';
final splitted = messageMemo.content.split(' ');
for (var _url in splitted) {
var urlPattern =
r"(https?|http)://([-A-Z0-9.]+)(/[-A-Z0-9+&@#/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#/%=~_|!:,.;]*)?";
RegExpMatch? match =
RegExp(urlPattern, caseSensitive: false)
.firstMatch(_url);
if (match != null) {
launchUrl(Uri.parse(match![0]!),
mode: LaunchMode.externalApplication);
} else {
var urlPattern2 =
r"([-A-Z0-9.]+)(/[-A-Z0-9+&@#/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#/%=~_|!:,.;]*)?";
RegExpMatch? match2 =
RegExp(urlPattern2, caseSensitive: false)
.firstMatch(_url);
if (match2 != null)
launchUrl(Uri.parse('http://' + match2![0]!),
mode: LaunchMode.externalApplication);
}
}
},
),
padding: EdgeInsets.fromLTRB(15, 10, 15, 10),
//width: 200,
decoration: BoxDecoration(
color: ColorConstants.greyColor2,
borderRadius: BorderRadius.circular(8)),
margin: EdgeInsets.only(bottom: 10, right: 10),
)
: messageMemo.type == TypeMessage.imageByte
? Container(
child: GestureDetector(
child: Image.memory(
Base64Decoder().convert(messageMemo.content),
width: 200,
height: 200,
fit: BoxFit.cover,
),
onHorizontalDragDown: (details) {
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("선택한 이미지를...."),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
"닫기",
style: TextStyle(color: Colors.red),
),
),
TextButton(
onPressed: () {
DatabaseMemo()
.deleteData(int.parse(
messageMemo.timestamp))
.then((value) {
setState(() {
memoList = getAllDatas();
Navigator.pop(context);
});
});
},
child: Text(
"삭제",
style: TextStyle(color: Colors.red),
),
),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FullPhotoBytePage(
byte: messageMemo.content,
),
),
);
},
child: Text(
"확대보기",
style: TextStyle(color: Colors.red),
),
),
],
);
},
);
},
);
//s _delete(context, messageMemo);
},
),
margin: EdgeInsets.only(bottom: 20, right: 10),
)
: Container(
child: Image.asset(
'images/${messageMemo.content}.gif',
width: 100,
height: 100,
fit: BoxFit.cover,
),
margin: EdgeInsets.only(
bottom: isLastMessageRight(index) ? 20 : 10,
right: 10),
),
Container(
child: GestureDetector(
child: Text(
time,
style: TextStyle(fontSize: 12, color: Colors.black54),
),
onTap: () {
// final Uri _url = Uri.parse('https://www.naver.com');
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("선택한 메모를...."),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
"닫기",
style: TextStyle(color: Colors.red),
),
),
TextButton(
onPressed: () {
DatabaseMemo()
.deleteData(int.parse(messageMemo.timestamp))
.then((value) {
setState(() {
memoList = getAllDatas();
Navigator.pop(context);
});
});
},
child: Text(
"삭제",
style: TextStyle(color: Colors.red),
),
),
],
);
},
);
},
);
},
)),
],
mainAxisAlignment: MainAxisAlignment.start,
);
} else {
return SizedBox.shrink();
}
}
void _delete(BuildContext context, MessageMemo messageMemo) {
showCupertinoDialog(
context: context,
builder: (BuildContext ctx) {
return CupertinoAlertDialog(
title: const Text('삭제하기'),
content: const Text('해당물건을 삭제하시겠습니까?'),
actions: [
// The "Yes" button
CupertinoDialogAction(
onPressed: () {
DatabaseMemo()
.deleteData(int.parse(messageMemo.timestamp))
.then((value) {
setState(() {
memoList = getAllDatas();
Navigator.pop(context);
});
});
// Navigator.of(context).pop();
},
child: const Text('삭제'),
isDefaultAction: true,
isDestructiveAction: true,
),
// The "No" button
CupertinoDialogAction(
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => FullPhotoBytePage(
byte: messageMemo.content,
),
),
);
},
child: const Text('확대'),
isDefaultAction: false,
isDestructiveAction: false,
)
],
);
});
}
bool isLastMessageLeft(int index) {
if ((index > 0 &&
listMessage[index - 1].get(FirestoreConstants.idFrom) ==
currentUserId) ||
index == 0) {
return true;
} else {
return false;
}
}
bool isLastMessageRight(int index) {
if ((index > 0 &&
listMessage[index - 1].get(FirestoreConstants.idFrom) !=
currentUserId) ||
index == 0) {
return true;
} else {
return false;
}
}
Future<bool> onBackPress() {
if (isShowSticker) {
setState(() {
isShowSticker = false;
});
} else {
memoProvider.updateDataFirestore(
FirestoreConstants.pathUserCollection,
currentUserId,
{FirestoreConstants.chattingWith: null},
);
Navigator.pop(context);
}
return Future.value(false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
this.widget.arguments.peerNickname,
style: TextStyle(color: ColorConstants.primaryColor),
),
centerTitle: true,
actions: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color.fromARGB(255, 179, 109, 5),
),
),
onPressed: () {},
icon: const Icon(
color: Colors.white,
// <-- Icon
Icons.delete,
size: 24.0,
),
label: const Text('전체삭제',
style: TextStyle(color: Colors.white)), // <-- Text
),
],
)
]),
body: SafeArea(
child: WillPopScope(
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
// List of messages
buildListMessage(),
// Sticker
// isShowSticker ? buildSticker() : SizedBox.shrink(),
// Input content
buildInput(),
],
),
// Loading
buildLoading()
],
),
onWillPop: onBackPress,
),
),
);
}
Widget buildLoading() {
return Positioned(
child: isLoading ? LoadingView() : SizedBox.shrink(),
);
}
Widget buildInput() {
return Container(
child: Row(
children: <Widget>[
// Button send image
Material(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 1),
child: IconButton(
icon: Icon(Icons.image),
onPressed: getImage,
color: ColorConstants.primaryColor,
),
),
color: Colors.white,
),
// Edit text
Flexible(
child: Container(
child: TextField(
onSubmitted: (value) {
onSendMessage(textEditingController.text, TypeMessage.text);
},
style:
TextStyle(color: ColorConstants.primaryColor, fontSize: 15),
controller: textEditingController,
decoration: InputDecoration.collapsed(
hintText: '메모 입력...',
hintStyle: TextStyle(color: ColorConstants.greyColor),
),
focusNode: focusNode,
autofocus: true,
),
),
),
// Button send message
Material(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 8),
child: IconButton(
icon: Icon(Icons.add_card),
onPressed: () =>
onSendMessage(textEditingController.text, TypeMessage.text),
color: ColorConstants.primaryColor,
),
),
color: Colors.white,
),
],
),
width: double.infinity,
height: 50,
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: ColorConstants.greyColor2, width: 0.5)),
color: Colors.white),
);
}
Widget buildListMessage() {
return Flexible(
child: FutureBuilder(
future: memoList,
builder: (context, snapshot) {
if (snapshot.hasData) {
var memoList = snapshot.data as List<MessageMemo>;
if (memoList.length > 0) {
return ListView.builder(
itemCount: memoList.length,
itemBuilder: (context, index) => (memoList[index].type == 3)
? buildItem(index, memoList[index])
: buildItem(index, memoList[index]),
reverse: true,
controller: listScrollController,
);
} else {
return Center(child: Text("No message here yet..."));
}
} else {
return Center(
child: CircularProgressIndicator(
color: ColorConstants.themeColor,
));
}
}),
);
}
}
class MemoPageArguments {
final String peerId;
final String peerAvatar;
final String peerNickname;
final String token;
MemoPageArguments(
{required this.peerId,
required this.peerAvatar,
required this.peerNickname,
required this.token});
}
728x90
반응형
'YB경매 앱개발' 카테고리의 다른 글
[YB경매]앱출시심사 완료 그러나 또다른 심각한 문제 해결 (0) | 2022.12.16 |
---|
Comments