mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
contact view page
This commit is contained in:
parent
7291f1656c
commit
e7a4b59379
14 changed files with 324 additions and 118 deletions
|
|
@ -4,11 +4,11 @@ Don't be lonely, get twonly! Send pictures to a friend in real time and be sure
|
|||
|
||||
|
||||
## TODOS bevor first beta
|
||||
- Pro Invitation codes
|
||||
- Push Notification (Android)
|
||||
- Contact view page
|
||||
- Verify contact view
|
||||
- Context Menu
|
||||
|
||||
- Pro Invitation codes
|
||||
- Push Notification (Android)
|
||||
- Settings
|
||||
- Notification
|
||||
- Real deployment aufsetzen, direkt auf Netcup?
|
||||
|
|
|
|||
44
lib/src/components/alert_dialog.dart
Normal file
44
lib/src/components/alert_dialog.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
Future<bool> showAlertDialog(
|
||||
BuildContext context, String title, String content) async {
|
||||
Completer<bool> completer = Completer<bool>();
|
||||
|
||||
Widget okButton = TextButton(
|
||||
child: Text(context.lang.ok),
|
||||
onPressed: () {
|
||||
completer.complete(true);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text(context.lang.cancel),
|
||||
onPressed: () {
|
||||
completer.complete(false);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: [
|
||||
cancelButton,
|
||||
okButton,
|
||||
],
|
||||
);
|
||||
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
39
lib/src/components/better_list_title.dart
Normal file
39
lib/src/components/better_list_title.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
class BetterListTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final Color? color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const BetterListTile({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
this.color,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 19,
|
||||
),
|
||||
child: FaIcon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
text,
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,6 @@
|
|||
"addDrawing": "Drawing",
|
||||
"addEmoji": "Emoji",
|
||||
"toogleFlashLight": "Toggle the flash light",
|
||||
"chatListDetailTitle": "Your chat with {username}",
|
||||
"searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.",
|
||||
"errorUnknown": "An unexpected error has occurred. Please try again later.",
|
||||
"errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.",
|
||||
|
|
|
|||
|
|
@ -100,18 +100,6 @@ class DbContacts extends CvModelBase {
|
|||
}
|
||||
}
|
||||
|
||||
static Future _updateFlameCounter(int userId, int totalMediaCounter) async {
|
||||
Map<String, dynamic> valuesToUpdate = {
|
||||
columnTotalMediaCounter: totalMediaCounter
|
||||
};
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
valuesToUpdate,
|
||||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<Contact>> _getAllUsers() async {
|
||||
try {
|
||||
var users = await dbProvider.db!.query(tableName, columns: [
|
||||
|
|
@ -146,31 +134,45 @@ class DbContacts extends CvModelBase {
|
|||
}
|
||||
}
|
||||
|
||||
static Future blockUser(int userId, {bool unblock = false}) async {
|
||||
Map<String, dynamic> valuesToUpdate = {
|
||||
columnBlocked: unblock ? 0 : 1,
|
||||
};
|
||||
static Future _update(int userId, Map<String, dynamic> updates,
|
||||
{bool notifyFlutter = true}) async {
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
valuesToUpdate,
|
||||
updates,
|
||||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
globalCallBackOnContactChange();
|
||||
if (notifyFlutter) {
|
||||
globalCallBackOnContactChange();
|
||||
}
|
||||
}
|
||||
|
||||
static Future changeDisplayName(int userId, String newDisplayName) async {
|
||||
if (newDisplayName == "") return;
|
||||
Map<String, dynamic> updates = {
|
||||
columnDisplayName: newDisplayName,
|
||||
};
|
||||
await _update(userId, updates);
|
||||
}
|
||||
|
||||
static Future _updateFlameCounter(int userId, int totalMediaCounter) async {
|
||||
Map<String, dynamic> updates = {columnTotalMediaCounter: totalMediaCounter};
|
||||
await _update(userId, updates, notifyFlutter: false);
|
||||
}
|
||||
|
||||
static Future blockUser(int userId, {bool unblock = false}) async {
|
||||
Map<String, dynamic> updates = {
|
||||
columnBlocked: unblock ? 0 : 1,
|
||||
};
|
||||
await _update(userId, updates);
|
||||
}
|
||||
|
||||
static Future acceptUser(int userId) async {
|
||||
Map<String, dynamic> valuesToUpdate = {
|
||||
Map<String, dynamic> updates = {
|
||||
columnAccepted: 1,
|
||||
columnRequested: 0,
|
||||
};
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
valuesToUpdate,
|
||||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
globalCallBackOnContactChange();
|
||||
await _update(userId, updates);
|
||||
}
|
||||
|
||||
static Future deleteUser(int userId) async {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ class DbMessage {
|
|||
bool get messageReceived => messageOtherId != null;
|
||||
|
||||
bool isMedia() {
|
||||
return messageContent is TextMessageContent ||
|
||||
messageContent is MediaMessageContent;
|
||||
return messageContent is MediaMessageContent;
|
||||
}
|
||||
|
||||
MessageSendState getSendState() {
|
||||
|
|
@ -378,9 +377,9 @@ class DbMessages extends CvModelBase {
|
|||
jsonDecode(fromDb[i][columnMessageContentJson]));
|
||||
|
||||
var tmp = content;
|
||||
if (tmp is TextMessageContent && messageOpenedAt != null) {
|
||||
if (messageOpenedAt != null) {
|
||||
messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]);
|
||||
if (messageOpenedAt != null) {
|
||||
if (tmp is TextMessageContent && messageOpenedAt != null) {
|
||||
if ((DateTime.now()).difference(messageOpenedAt).inHours >= 24) {
|
||||
deleteTextContent(fromDb[i][columnMessageId], tmp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,10 +180,7 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
widget.isRealTwonly,
|
||||
widget.maxShowTime,
|
||||
);
|
||||
|
||||
// TODO: pop back to the HomeView page popUntil did not work. check later how to improve in case of pushing more then 2
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
globalUpdateOfHomeViewPageIndex(1);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:collection';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
|
|
@ -11,6 +12,7 @@ import 'package:twonly/src/providers/download_change_provider.dart';
|
|||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||
import 'package:twonly/src/views/chats/media_viewer_view.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/contact/contact_view.dart';
|
||||
|
||||
class ChatListEntry extends StatelessWidget {
|
||||
const ChatListEntry(this.message, this.user, this.lastMessageFromSameUser,
|
||||
|
|
@ -187,7 +189,28 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
|||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.chatListDetailTitle(widget.user.displayName)),
|
||||
title: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
||||
return ContactView(widget.user);
|
||||
}));
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
InitialsAvatar(
|
||||
displayName: widget.user.displayName,
|
||||
fontSize: 19,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Text(widget.user.displayName),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
|
|
@ -9,7 +10,6 @@ import 'package:twonly/src/model/contacts_model.dart';
|
|||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||
import 'package:twonly/src/providers/api/api.dart';
|
||||
import 'package:twonly/src/views/onboarding/register_view.dart';
|
||||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
|
||||
|
|
|
|||
32
lib/src/views/contact/contact_verify_view.dart
Normal file
32
lib/src/views/contact/contact_verify_view.dart
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ContactVerifyView extends StatefulWidget {
|
||||
const ContactVerifyView(this.contact, {super.key});
|
||||
|
||||
final Contact contact;
|
||||
|
||||
@override
|
||||
State<ContactVerifyView> createState() => _ContactVerifyViewState();
|
||||
}
|
||||
|
||||
class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Verify ${widget.contact.displayName}"),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
SizedBox(height: 50),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
140
lib/src/views/contact/contact_view.dart
Normal file
140
lib/src/views/contact/contact_view.dart
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/components/better_list_title.dart';
|
||||
import 'package:twonly/src/components/flame.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||
import 'package:twonly/src/views/contact/contact_verify_view.dart';
|
||||
|
||||
class ContactView extends StatefulWidget {
|
||||
const ContactView(this.contact, {super.key});
|
||||
|
||||
final Contact contact;
|
||||
|
||||
@override
|
||||
State<ContactView> createState() => _ContactViewState();
|
||||
}
|
||||
|
||||
class _ContactViewState extends State<ContactView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
int flameCounter = context
|
||||
.watch<MessagesChangeProvider>()
|
||||
.flamesCounter[widget.contact.userId.toInt()] ??
|
||||
0;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(""),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: InitialsAvatar(
|
||||
displayName: widget.contact.displayName,
|
||||
fontSize: 30,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.contact.displayName,
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
if (flameCounter > 0)
|
||||
FlameCounterWidget(widget.contact, flameCounter, 110000000),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 50),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.pencil,
|
||||
text: "Nickname",
|
||||
onTap: () async {
|
||||
String? newUsername =
|
||||
await showNicknameChangeDialog(context, widget.contact);
|
||||
if (newUsername != null && newUsername != "") {
|
||||
await DbContacts.changeDisplayName(
|
||||
widget.contact.userId.toInt(), newUsername);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.shieldHeart,
|
||||
text: "Verify safety number",
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ContactVerifyView(widget.contact);
|
||||
},
|
||||
));
|
||||
},
|
||||
),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.ban,
|
||||
color: Colors.red,
|
||||
text: "Block",
|
||||
onTap: () async {
|
||||
bool block = await showAlertDialog(
|
||||
context,
|
||||
"Block ${widget.contact.displayName}",
|
||||
"A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users..");
|
||||
if (block) {
|
||||
await DbContacts.blockUser(widget.contact.userId.toInt());
|
||||
// go back to the first
|
||||
if (context.mounted) {
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> showNicknameChangeDialog(
|
||||
BuildContext context, Contact contact) {
|
||||
final TextEditingController controller =
|
||||
TextEditingController(text: contact.displayName);
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Change nickname'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(hintText: "New nickname"),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Submit'),
|
||||
onPressed: () {
|
||||
String inputText = controller.text;
|
||||
Navigator.of(context).pop(inputText); // Return the input text
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:twonly/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:twonly/src/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/model/json/user_data.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/signal.dart';
|
||||
|
|
@ -186,43 +187,3 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> showAlertDialog(
|
||||
BuildContext context, String title, String content) async {
|
||||
Completer<bool> completer = Completer<bool>();
|
||||
// set up the button
|
||||
Widget okButton = TextButton(
|
||||
child: Text(context.lang.ok),
|
||||
onPressed: () {
|
||||
completer.complete(true); // Complete the future with true
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
|
||||
Widget cancelButton = TextButton(
|
||||
child: Text(context.lang.cancel),
|
||||
onPressed: () {
|
||||
completer.complete(false); // Complete the future with true
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: [
|
||||
cancelButton,
|
||||
okButton,
|
||||
],
|
||||
);
|
||||
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:restart_app/restart_app.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/onboarding/register_view.dart';
|
||||
|
||||
class AccountView extends StatelessWidget {
|
||||
const AccountView({super.key});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/src/components/better_list_title.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/model/json/user_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -79,7 +80,7 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
],
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.user,
|
||||
text: context.lang.settingsAccount,
|
||||
onTap: () {
|
||||
|
|
@ -89,13 +90,13 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
}));
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.shieldHeart,
|
||||
text: context.lang.settingsSubscription,
|
||||
onTap: () {},
|
||||
),
|
||||
const Divider(),
|
||||
SettingsListTile(
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.sun,
|
||||
text: context.lang.settingsAppearance,
|
||||
onTap: () {
|
||||
|
|
@ -105,7 +106,7 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
}));
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.lock,
|
||||
text: context.lang.settingsPrivacy,
|
||||
onTap: () {
|
||||
|
|
@ -115,7 +116,7 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
}));
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.bell,
|
||||
text: context.lang.settingsNotification,
|
||||
onTap: () async {
|
||||
|
|
@ -140,7 +141,7 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
},
|
||||
),
|
||||
const Divider(),
|
||||
SettingsListTile(
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.circleQuestion,
|
||||
text: context.lang.settingsHelp,
|
||||
onTap: () {
|
||||
|
|
@ -156,34 +157,3 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsListTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const SettingsListTile({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 19,
|
||||
),
|
||||
child: FaIcon(
|
||||
icon,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(text),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue