mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
fix #52
This commit is contained in:
parent
ec095fea9c
commit
01a24cc6dc
11 changed files with 180 additions and 76 deletions
|
|
@ -4,6 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||
class BetterListTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final Widget? subtitle;
|
||||
final Color? color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
|
|
@ -12,6 +13,7 @@ class BetterListTile extends StatelessWidget {
|
|||
required this.icon,
|
||||
required this.text,
|
||||
this.color,
|
||||
this.subtitle,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
|
|
@ -33,6 +35,7 @@ class BetterListTile extends StatelessWidget {
|
|||
text,
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
subtitle: subtitle,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ enum MessageKind {
|
|||
textMessage,
|
||||
media,
|
||||
contactRequest,
|
||||
avatarChange,
|
||||
profileChange,
|
||||
rejectRequest,
|
||||
acceptRequest,
|
||||
opened,
|
||||
|
|
@ -94,8 +94,8 @@ class MessageContent {
|
|||
return MediaMessageContent.fromJson(json);
|
||||
case MessageKind.textMessage:
|
||||
return TextMessageContent.fromJson(json);
|
||||
case MessageKind.avatarChange:
|
||||
return AvatarContent.fromJson(json);
|
||||
case MessageKind.profileChange:
|
||||
return ProfileContent.fromJson(json);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -172,16 +172,20 @@ class TextMessageContent extends MessageContent {
|
|||
}
|
||||
}
|
||||
|
||||
class AvatarContent extends MessageContent {
|
||||
String svg;
|
||||
AvatarContent({required this.svg});
|
||||
class ProfileContent extends MessageContent {
|
||||
String avatarSvg;
|
||||
String displayName;
|
||||
ProfileContent({required this.avatarSvg, required this.displayName});
|
||||
|
||||
static AvatarContent fromJson(Map json) {
|
||||
return AvatarContent(svg: json['svg']);
|
||||
static ProfileContent fromJson(Map json) {
|
||||
return ProfileContent(
|
||||
avatarSvg: json['avatarSvg'],
|
||||
displayName: json['displayName'],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map toJson() {
|
||||
return {'svg': svg};
|
||||
return {'avatarSvg': avatarSvg, 'displayName': displayName};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@
|
|||
"messageSendState_Loading": "Herunterladen",
|
||||
"imageEditorDrawOk": "Zeichnung machen",
|
||||
"settingsTitle": "Einstellungen",
|
||||
"settingsProfile": "Profil",
|
||||
"settingsProfileCustomizeAvatar": "Avatar anpassen",
|
||||
"settingsProfileEditDisplayName": "Anzeigename",
|
||||
"settingsProfileEditDisplayNameNew": "Neuer Anzeigename",
|
||||
"settingsAccount": "Konto",
|
||||
"settingsSubscription": "Abonnement",
|
||||
"settingsAppearance": "Erscheinungsbild",
|
||||
|
|
@ -74,6 +78,7 @@
|
|||
"contactVerifyNumberClearVerification": "Verifizierung aufheben",
|
||||
"contactVerifyNumberLongDesc": "Um die Ende-zu-Ende-Verschlüsselung mit {username} zu verifizieren, vergleiche die Zahlen mit ihrem Gerät. Die Person kann auch deinen Code mit ihrem Gerät scannen.",
|
||||
"contactNickname": "Spitzname",
|
||||
"contactNicknameNew": "Neuer Spitzname",
|
||||
"contactBlock": "Blockieren",
|
||||
"contactBlockTitle": "Blockiere {username}",
|
||||
"contactBlockBody": "Ein blockierter Benutzer kann dir keine Nachrichten mehr senden, und sein Profil ist nicht mehr sichtbar. Um die Blockierung eines Benutzers aufzuheben, navigiere einfach zu Einstellungen > Datenschutz > Blockierte Benutzer.",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@
|
|||
"messageSendState_Loading": "Downloading",
|
||||
"imageEditorDrawOk": "Take drawing",
|
||||
"settingsTitle": "Settings",
|
||||
"settingsProfile": "Profile",
|
||||
"settingsProfileCustomizeAvatar": "Customize your avatar",
|
||||
"settingsProfileEditDisplayName": "Displayname",
|
||||
"settingsProfileEditDisplayNameNew": "New Displayname",
|
||||
"settingsAccount": "Konto",
|
||||
"settingsSubscription": "Subscription",
|
||||
"settingsAppearance": "Appearance",
|
||||
|
|
@ -75,6 +79,7 @@
|
|||
"contactVerifyNumberClearVerification": "Clear verification",
|
||||
"contactVerifyNumberLongDesc": "To verify the end-to-end encryption with {username}, compare the numbers with their device. The person can also scan your code with their device.",
|
||||
"contactNickname": "Nickname",
|
||||
"contactNicknameNew": "New nickname",
|
||||
"contactBlock": "Block",
|
||||
"contactBlockTitle": "Block {username}",
|
||||
"contactBlockBody": "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.",
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ Future notifyContactAboutOpeningMessage(
|
|||
);
|
||||
}
|
||||
|
||||
Future notifyContactsAboutAvatarChange() async {
|
||||
Future notifyContactsAboutProfileChange() async {
|
||||
List<Contact> contacts =
|
||||
await twonlyDatabase.contactsDao.getAllNotBlockedContacts();
|
||||
|
||||
|
|
@ -140,8 +140,11 @@ Future notifyContactsAboutAvatarChange() async {
|
|||
null,
|
||||
contact.userId,
|
||||
MessageJson(
|
||||
kind: MessageKind.avatarChange,
|
||||
content: AvatarContent(svg: user.avatarSvg!),
|
||||
kind: MessageKind.profileChange,
|
||||
content: ProfileContent(
|
||||
avatarSvg: user.avatarSvg!,
|
||||
displayName: user.displayName,
|
||||
),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -199,12 +199,15 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
final update = ContactsCompanion(accepted: Value(true));
|
||||
twonlyDatabase.contactsDao.updateContact(fromUserId, update);
|
||||
localPushNotificationNewMessage(fromUserId.toInt(), message, 8888888);
|
||||
notifyContactsAboutAvatarChange();
|
||||
notifyContactsAboutProfileChange();
|
||||
break;
|
||||
case MessageKind.avatarChange:
|
||||
case MessageKind.profileChange:
|
||||
var content = message.content;
|
||||
if (content is AvatarContent) {
|
||||
final update = ContactsCompanion(avatarSvg: Value(content.svg));
|
||||
if (content is ProfileContent) {
|
||||
final update = ContactsCompanion(
|
||||
avatarSvg: Value(content.avatarSvg),
|
||||
displayName: Value(content.displayName),
|
||||
);
|
||||
twonlyDatabase.contactsDao.updateContact(fromUserId, update);
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class ApiProvider {
|
|||
if (!globalIsAppInBackground) {
|
||||
tryTransmitMessages();
|
||||
tryDownloadAllMediaFiles();
|
||||
notifyContactsAboutAvatarChange();
|
||||
notifyContactsAboutProfileChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ProfileView(),
|
||||
builder: (context) => SettingsMainView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -77,7 +77,7 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ProfileView(),
|
||||
builder: (context) => SettingsMainView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -134,21 +134,22 @@ Future<String?> showNicknameChangeDialog(
|
|||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Change nickname'),
|
||||
title: Text(context.lang.contactNickname),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(hintText: "New nickname"),
|
||||
decoration:
|
||||
InputDecoration(hintText: context.lang.contactNicknameNew),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
child: Text(context.lang.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Submit'),
|
||||
child: Text(context.lang.ok),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pop(controller.text); // Return the input text
|
||||
|
|
|
|||
|
|
@ -2,40 +2,66 @@ import 'dart:math';
|
|||
|
||||
import 'package:avatar_maker/avatar_maker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/src/components/better_list_title.dart';
|
||||
import 'package:twonly/src/json_models/userdata.dart';
|
||||
import 'package:twonly/src/providers/api/api.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
class AvatarCreator extends StatefulWidget {
|
||||
const AvatarCreator({super.key});
|
||||
class ProfileView extends StatefulWidget {
|
||||
const ProfileView({super.key});
|
||||
|
||||
@override
|
||||
State<AvatarCreator> createState() => _AvatarCreatorState();
|
||||
State<ProfileView> createState() => _ProfileViewState();
|
||||
}
|
||||
|
||||
class _AvatarCreatorState extends State<AvatarCreator> {
|
||||
class _ProfileViewState extends State<ProfileView> {
|
||||
UserData? user;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initAsync();
|
||||
}
|
||||
|
||||
Future initAsync() async {
|
||||
user = await getUser();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future updateUserDisplayname(String displayName) async {
|
||||
UserData? user = await getUser();
|
||||
if (user == null) return null;
|
||||
user.displayName = displayName;
|
||||
if (user.avatarCounter == null) {
|
||||
user.avatarCounter = 1;
|
||||
} else {
|
||||
user.avatarCounter = user.avatarCounter! + 1;
|
||||
}
|
||||
await updateUser(user);
|
||||
await notifyContactsAboutProfileChange();
|
||||
initAsync();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Your Avatar"),
|
||||
centerTitle: true,
|
||||
title: Text(context.lang.settingsProfile),
|
||||
),
|
||||
body: ListView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 25,
|
||||
),
|
||||
SizedBox(
|
||||
height: 25,
|
||||
),
|
||||
AvatarMakerAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
radius: 100,
|
||||
radius: 80,
|
||||
),
|
||||
SizedBox(
|
||||
height: 25,
|
||||
height: 10,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
|
|
@ -46,11 +72,11 @@ class _AvatarCreatorState extends State<AvatarCreator> {
|
|||
height: 35,
|
||||
child: ElevatedButton.icon(
|
||||
icon: Icon(Icons.edit),
|
||||
label: Text("Customize"),
|
||||
label: Text(context.lang.settingsProfileCustomizeAvatar),
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NewPage(),
|
||||
builder: (context) => ModifyAvatar(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -59,8 +85,20 @@ class _AvatarCreatorState extends State<AvatarCreator> {
|
|||
Spacer(flex: 2),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 100,
|
||||
SizedBox(height: 20),
|
||||
const Divider(),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.userPen,
|
||||
text: context.lang.settingsProfileEditDisplayName,
|
||||
subtitle: (user == null) ? null : Text(user!.displayName),
|
||||
onTap: () async {
|
||||
final displayName =
|
||||
await showDisplayNameChangeDialog(context, user!.displayName);
|
||||
|
||||
if (context.mounted && displayName != null && displayName != "") {
|
||||
updateUserDisplayname(displayName);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -68,8 +106,8 @@ class _AvatarCreatorState extends State<AvatarCreator> {
|
|||
}
|
||||
}
|
||||
|
||||
class NewPage extends StatelessWidget {
|
||||
const NewPage({super.key});
|
||||
class ModifyAvatar extends StatelessWidget {
|
||||
const ModifyAvatar({super.key});
|
||||
|
||||
Future updateUserAvatar(String json, String svg) async {
|
||||
UserData? user = await getUser();
|
||||
|
|
@ -83,14 +121,16 @@ class NewPage extends StatelessWidget {
|
|||
user.avatarCounter = user.avatarCounter! + 1;
|
||||
}
|
||||
await updateUser(user);
|
||||
await notifyContactsAboutAvatarChange();
|
||||
await notifyContactsAboutProfileChange();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.settingsProfileCustomizeAvatar),
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
|
|
@ -158,3 +198,39 @@ class NewPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> showDisplayNameChangeDialog(
|
||||
BuildContext context, String currentName) {
|
||||
final TextEditingController controller =
|
||||
TextEditingController(text: currentName);
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(context.lang.settingsProfileEditDisplayName),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: context.lang.settingsProfileEditDisplayNameNew),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text(context.lang.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(context.lang.ok),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pop(controller.text); // Return the input text
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -7,18 +7,18 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/settings/account_view.dart';
|
||||
import 'package:twonly/src/views/settings/appearance_view.dart';
|
||||
import 'package:twonly/src/views/settings/avatar_creator.dart';
|
||||
import 'package:twonly/src/views/settings/profile_view.dart';
|
||||
import 'package:twonly/src/views/settings/help_view.dart';
|
||||
import 'package:twonly/src/views/settings/privacy_view.dart';
|
||||
|
||||
class ProfileView extends StatefulWidget {
|
||||
const ProfileView({super.key});
|
||||
class SettingsMainView extends StatefulWidget {
|
||||
const SettingsMainView({super.key});
|
||||
|
||||
@override
|
||||
State<ProfileView> createState() => _ProfileViewState();
|
||||
State<SettingsMainView> createState() => _SettingsMainViewState();
|
||||
}
|
||||
|
||||
class _ProfileViewState extends State<ProfileView> {
|
||||
class _SettingsMainViewState extends State<SettingsMainView> {
|
||||
UserData? userData;
|
||||
|
||||
@override
|
||||
|
|
@ -46,37 +46,41 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
padding: const EdgeInsets.all(30),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) {
|
||||
return AvatarCreator();
|
||||
}));
|
||||
initAsync();
|
||||
},
|
||||
child: ContactAvatar(
|
||||
userData: userData!,
|
||||
fontSize: 30,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
userData!.displayName,
|
||||
style: TextStyle(fontSize: 20),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
Text(
|
||||
userData!.username,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
await Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) {
|
||||
return ProfileView();
|
||||
}));
|
||||
initAsync();
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
ContactAvatar(
|
||||
userData: userData!,
|
||||
fontSize: 30,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
Container(width: 20, color: Colors.transparent),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
userData!.displayName,
|
||||
style: TextStyle(fontSize: 20),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
Text(
|
||||
userData!.username,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
|
|
|
|||
Loading…
Reference in a new issue