Skip to content
This repository has been archived by the owner on Jul 31, 2021. It is now read-only.

Commit

Permalink
export chat to google drive - complete version 0.2.9
Browse files Browse the repository at this point in the history
  • Loading branch information
mkofdwu committed Feb 23, 2021
1 parent dd9d36b commit 1dcb9b5
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 37 deletions.
2 changes: 1 addition & 1 deletion lib/pages/about.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class AboutPage extends StatelessWidget {
),
),
Text(
'Version: 0.2.5',
'Version: 0.2.9',
style: TextStyle(
fontSize: 14,
),
Expand Down
16 changes: 10 additions & 6 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:tundr/enums/chat_type.dart';
import 'package:tundr/models/chat.dart';
import 'package:tundr/models/media.dart';
import 'package:tundr/models/message.dart';
import 'package:tundr/pages/chat/message_sender.dart';
import 'package:tundr/pages/chat/controllers/message_sender.dart';
import 'package:tundr/pages/chat/widgets/chat_description.dart';
import 'package:tundr/pages/chat/widgets/message_field.dart';
import 'package:tundr/pages/chat/widgets/message_tile_content.dart';
Expand Down Expand Up @@ -67,11 +67,9 @@ class _ChatPageState extends State<ChatPage> {
);
}
}
if (mounted) {
_wasBlocked =
await UsersService.isBlockedBy(widget.chat.otherProfile.uid);
setState(() {});
}
_wasBlocked =
await UsersService.isBlockedBy(widget.chat.otherProfile.uid);
if (mounted) setState(() {});
});
}

Expand Down Expand Up @@ -371,6 +369,12 @@ class _ChatPageState extends State<ChatPage> {
child: ChatPopupMenu(
chat: widget.chat,
onUpdate: () => setState(() => _showChatOptions = false),
onSuccessfullyExportChat: () => showInfoDialog(
context: context,
title: 'Done!',
content:
'Your chat with ${widget.chat.otherProfile.name} was exported successfully',
),
),
),
),
Expand Down
14 changes: 14 additions & 0 deletions lib/pages/chat/services/google_auth_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:http/http.dart' as http;

class GoogleAuthClient extends http.BaseClient {
final Map<String, String> _headers;

final http.Client _client = http.Client();

GoogleAuthClient(this._headers);

@override
Future<http.StreamedResponse> send(http.BaseRequest request) {
return _client.send(request..headers.addAll(_headers));
}
}
94 changes: 65 additions & 29 deletions lib/pages/chat/widgets/popup_menu.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/drive/v2.dart' as drive;
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:tundr/enums/chat_type.dart';
import 'package:tundr/enums/media_type.dart';
import 'package:tundr/models/chat.dart';
import 'package:tundr/models/user_private_info.dart';
import 'package:tundr/pages/chat/services/google_auth_client.dart';
import 'package:tundr/store/user.dart';
import 'package:tundr/services/chats_service.dart';
import 'package:tundr/services/media_picker_service.dart';
import 'package:tundr/services/storage_service.dart';
import 'package:tundr/utils/show_info_dialog.dart';
import 'package:tundr/utils/show_question_dialog.dart';
import 'package:tundr/widgets/popup_menu.dart';

class ChatPopupMenu extends StatelessWidget {
class ChatPopupMenu extends StatefulWidget {
final Chat chat;
final Function onUpdate;
final Function onSuccessfullyExportChat;

ChatPopupMenu({
@required this.chat,
@required this.onUpdate,
@required this.onSuccessfullyExportChat,
});

ChatPopupMenu({@required this.chat, @required this.onUpdate});
@override
_ChatPopupMenuState createState() => _ChatPopupMenuState();
}

void _deleteChat(context) async {
class _ChatPopupMenuState extends State<ChatPopupMenu> {
void _deleteChat() async {
final confirm = await showQuestionDialog(
context: context,
title: 'Delete chat?',
Expand All @@ -27,35 +43,35 @@ class ChatPopupMenu extends StatelessWidget {
if (confirm) {
await ChatsService.deleteChat(
Provider.of<User>(context, listen: false).profile.uid,
chat.id,
widget.chat.id,
);
Navigator.pop(context);
}
}

void _blockAndDeleteChat(context) async {
void _blockAndDeleteChat() async {
final confirm = await showQuestionDialog(
context: context,
title: 'Block and delete chat?',
content:
'You can unblock ${chat.otherProfile.name} later if you want, but this chat cannot be retrieved',
'You can unblock ${widget.chat.otherProfile.name} later if you want, but this chat cannot be retrieved',
);
if (confirm) {
Provider.of<User>(context, listen: false)
.privateInfo
.blocked
.add(chat.otherProfile.uid);
.add(widget.chat.otherProfile.uid);
await Provider.of<User>(context, listen: false)
.writeField('blocked', UserPrivateInfo);
await ChatsService.deleteChat(
Provider.of<User>(context, listen: false).profile.uid,
chat.id,
widget.chat.id,
);
Navigator.pop(context);
}
}

void _changeWallpaper(context) async {
void _changeWallpaper() async {
final imageMedia = await MediaPickerService.pickMedia(
type: MediaType.image,
source: ImageSource.gallery,
Expand All @@ -71,60 +87,80 @@ class ChatPopupMenu extends StatelessWidget {
);
await ChatsService.updateChatDetails(
uid,
chat.id,
widget.chat.id,
{'wallpaperUrl': wallpaperUrl},
);
chat.wallpaperUrl = wallpaperUrl;
onUpdate();
widget.chat.wallpaperUrl = wallpaperUrl;
widget.onUpdate();
}

void _starChat(context) {
void _starChat() {
final uid = Provider.of<User>(context, listen: false).profile.uid;
ChatsService.updateChatDetails(uid, chat.id, {'type': 2});
chat.type = ChatType.starred;
onUpdate();
ChatsService.updateChatDetails(uid, widget.chat.id, {'type': 2});
widget.chat.type = ChatType.starred;
widget.onUpdate();
}

void _unstarChat(context) {
void _unstarChat() {
final uid = Provider.of<User>(context, listen: false).profile.uid;
ChatsService.updateChatDetails(uid, chat.id, {'type': 3});
chat.type = ChatType.normal;
onUpdate();
ChatsService.updateChatDetails(uid, widget.chat.id, {'type': 3});
widget.chat.type = ChatType.normal;
widget.onUpdate();
}

void _exportChat(context) {}
Future<void> _exportChat() async {
widget.onUpdate();
final userProfile = Provider.of<User>(context, listen: false).profile;
final googleSignIn =
GoogleSignIn.standard(scopes: [drive.DriveApi.driveScope]);
final account = await googleSignIn.signIn();
final authHeaders = await account.authHeaders;
final driveApi = drive.DriveApi(GoogleAuthClient(authHeaders));
final chatHistoryString =
await ChatsService.getChatHistory(widget.chat, userProfile);
final mediaStream =
Stream.value(chatHistoryString.codeUnits).asBroadcastStream();
final media = drive.Media(mediaStream, chatHistoryString.length);
final driveFile = drive.File();
driveFile.title = 'tundr chat with ${widget.chat.otherProfile.name}.txt';
final uploadedFile =
await driveApi.files.insert(driveFile, uploadMedia: media);
if (uploadedFile != null) {
widget.onSuccessfullyExportChat();
}
}

@override
Widget build(BuildContext context) {
return PopupMenu(
children: <Widget>[
MenuOption(
text: 'Wallpaper',
onPressed: () => _changeWallpaper(context),
onPressed: _changeWallpaper,
),
if (chat.type == ChatType.normal)
if (widget.chat.type == ChatType.normal)
MenuOption(
text: 'Star chat',
onPressed: () => _starChat(context),
onPressed: _starChat,
)
else if (chat.type == ChatType.starred)
else if (widget.chat.type == ChatType.starred)
MenuOption(
text: 'Unstar chat',
onPressed: () => _unstarChat(context),
onPressed: _unstarChat,
),
MenuDivider(),
MenuOption(
text: 'Export chat',
onPressed: () => _exportChat(context),
onPressed: _exportChat,
),
MenuDivider(),
MenuOption(
text: 'Delete chat',
onPressed: () => _deleteChat(context),
onPressed: _deleteChat,
),
MenuOption(
text: 'Block and delete chat',
onPressed: () => _blockAndDeleteChat(context),
onPressed: _blockAndDeleteChat,
),
],
);
Expand Down
26 changes: 26 additions & 0 deletions lib/services/chats_service.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:intl/intl.dart';
import 'package:tundr/constants/firebase_ref.dart';
import 'package:tundr/enums/chat_type.dart';
import 'package:tundr/enums/media_type.dart';
import 'package:tundr/models/chat.dart';
import 'package:tundr/models/message.dart';
import 'package:tundr/models/user_profile.dart';
Expand All @@ -27,6 +29,30 @@ class ChatsService {
Future.wait(querySnapshot.docs.map((doc) => Message.fromDoc(doc))));
}

static Future<String> getChatHistory(Chat chat, UserProfile user) async {
var chatHistory = '';
final querySnapshot = await chatsRef
.doc(chat.id)
.collection('messages')
.orderBy('sentOn', descending: true)
.get();
final dateFormat = DateFormat('yyyy/MM/dd hh:mm a');
for (final messageDoc in querySnapshot.docs) {
final data = messageDoc.data();
final sentOn = dateFormat.format(data['sentOn'].toDate());
final senderName =
data['senderUid'] == user.uid ? user.name : chat.otherProfile.name;
final mediaType = data['mediaType'] == null
? ''
: '<' +
MediaType.values[data['mediaType']].toString().substring(10) +
'>';
final text = data['text'];
chatHistory += '$sentOn $senderName: ${mediaType} $text\n';
}
return chatHistory;
}

static Future<String> startConversation(String otherUid, Message message) =>
callHttpsFunction<String>('startConversation', {
'otherUid': otherUid,
Expand Down
4 changes: 3 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: tundr
description: tundr clean up removing support for web.

version: 0.2.8+24
version: 0.2.9+25

environment:
sdk: '>=2.2.2 <3.0.0'
Expand All @@ -28,6 +28,8 @@ dependencies:
uuid: ^2.0.4
video_compress: ^2.1.1
video_player: ^1.0.1
googleapis: ^1.0.0
google_sign_in: ^4.5.9

dev_dependencies:
flutter_driver:
Expand Down

0 comments on commit 1dcb9b5

Please sign in to comment.