Skip to content

Commit 0469f02

Browse files
committed
profile: Implement profile screen for users
Added profile screen with user information and custom profile fields, linked from sender name and avatar in message list. Support in initial_snapshot and models for the related `RealmDefaultExternalAccounts` and `CustomUserProfileFields` also added, as event handling for `custom_profile_fields`. User presence (zulip#196) and user status (zulip#197) are not yet displayed or tracked here. Fixes: zulip#195
1 parent 8831c2d commit 0469f02

20 files changed

+895
-91
lines changed

lib/api/model/events.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ sealed class Event {
2222
case 'update': return UserSettingsUpdateEvent.fromJson(json);
2323
default: return UnexpectedEvent.fromJson(json);
2424
}
25+
case 'custom_profile_fields': return CustomProfileFieldsEvent.fromJson(json);
2526
case 'realm_user':
2627
switch (json['op'] as String) {
2728
case 'add': return RealmUserAddEvent.fromJson(json);
@@ -130,6 +131,24 @@ class UserSettingsUpdateEvent extends Event {
130131
Map<String, dynamic> toJson() => _$UserSettingsUpdateEventToJson(this);
131132
}
132133

134+
/// A Zulip event of type `custom_profile_fields`: https://zulip.com/api/get-events#custom_profile_fields
135+
@JsonSerializable(fieldRename: FieldRename.snake)
136+
class CustomProfileFieldsEvent extends Event {
137+
@override
138+
@JsonKey(includeToJson: true)
139+
String get type => 'custom_profile_fields';
140+
141+
final List<CustomProfileField> fields;
142+
143+
CustomProfileFieldsEvent({required super.id, required this.fields});
144+
145+
factory CustomProfileFieldsEvent.fromJson(Map<String, dynamic> json) =>
146+
_$CustomProfileFieldsEventFromJson(json);
147+
148+
@override
149+
Map<String, dynamic> toJson() => _$CustomProfileFieldsEventToJson(this);
150+
}
151+
133152
/// A Zulip event of type `realm_user`.
134153
///
135154
/// The corresponding API docs are in several places for
@@ -211,7 +230,7 @@ class RealmUserUpdateEvent extends RealmUserEvent {
211230
@JsonKey(readValue: _readFromPerson) final int? avatarVersion;
212231
@JsonKey(readValue: _readFromPerson) final String? timezone;
213232
@JsonKey(readValue: _readFromPerson) final int? botOwnerId;
214-
@JsonKey(readValue: _readFromPerson) final int? role; // TODO enum
233+
@JsonKey(readValue: _readFromPerson) final UserRole? role;
215234
@JsonKey(readValue: _readFromPerson) final bool? isBillingAdmin;
216235
@JsonKey(readValue: _readFromPerson) final String? deliveryEmail; // TODO handle JSON `null`
217236
@JsonKey(readValue: _readFromPerson) final RealmUserUpdateCustomProfileField? customProfileField;

lib/api/model/events.g.dart

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/api/model/initial_snapshot.dart

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class InitialSnapshot {
2323

2424
final List<CustomProfileField> customProfileFields;
2525

26+
final Map<String, RealmDefaultExternalAccount> realmDefaultExternalAccounts;
27+
2628
// TODO etc., etc.
2729

2830
final List<RecentDmConversation> recentPrivateConversations;
@@ -78,6 +80,7 @@ class InitialSnapshot {
7880
required this.zulipVersion,
7981
this.zulipMergeBase,
8082
required this.alertWords,
83+
required this.realmDefaultExternalAccounts,
8184
required this.customProfileFields,
8285
required this.recentPrivateConversations,
8386
required this.subscriptions,
@@ -96,6 +99,84 @@ class InitialSnapshot {
9699
Map<String, dynamic> toJson() => _$InitialSnapshotToJson(this);
97100
}
98101

102+
/// As in [InitialSnapshot.customProfileFields].
103+
///
104+
/// For docs, search for "custom_profile_fields:"
105+
/// in <https://zulip.com/api/register-queue>.
106+
@JsonSerializable(fieldRename: FieldRename.snake)
107+
class CustomProfileField {
108+
final int id;
109+
@JsonKey(unknownEnumValue: CustomProfileFieldType.unknown)
110+
final CustomProfileFieldType type; // TODO(server-6) a value added
111+
final int order;
112+
final String name;
113+
final String hint;
114+
final String fieldData;
115+
final bool? displayInProfileSummary; // TODO(server-6)
116+
117+
CustomProfileField({
118+
required this.id,
119+
required this.type,
120+
required this.order,
121+
required this.name,
122+
required this.hint,
123+
required this.fieldData,
124+
required this.displayInProfileSummary,
125+
});
126+
127+
factory CustomProfileField.fromJson(Map<String, dynamic> json) =>
128+
_$CustomProfileFieldFromJson(json);
129+
130+
Map<String, dynamic> toJson() => _$CustomProfileFieldToJson(this);
131+
}
132+
133+
/// As in [CustomProfileField.type].
134+
@JsonEnum(fieldRename: FieldRename.snake, valueField: "apiValue")
135+
enum CustomProfileFieldType {
136+
shortText(apiValue: 1),
137+
longText(apiValue: 2),
138+
choice(apiValue: 3),
139+
date(apiValue: 4),
140+
link(apiValue: 5),
141+
user(apiValue: 6),
142+
externalAccount(apiValue: 7),
143+
pronouns(apiValue: 8),
144+
unknown(apiValue: null);
145+
146+
const CustomProfileFieldType({
147+
required this.apiValue
148+
});
149+
150+
final int? apiValue;
151+
152+
int toJson() => _$CustomProfileFieldTypeEnumMap[this]!;
153+
}
154+
155+
/// An item in `realm_default_external_accounts`.
156+
///
157+
/// For docs, search for "realm_default_external_accounts:"
158+
/// in <https://zulip.com/api/register-queue>.
159+
@JsonSerializable(fieldRename: FieldRename.snake)
160+
class RealmDefaultExternalAccount {
161+
final String name;
162+
final String text;
163+
final String hint;
164+
final String urlPattern;
165+
166+
RealmDefaultExternalAccount({
167+
required this.name,
168+
required this.text,
169+
required this.hint,
170+
required this.urlPattern,
171+
});
172+
173+
factory RealmDefaultExternalAccount.fromJson(Map<String, dynamic> json) {
174+
return _$RealmDefaultExternalAccountFromJson(json);
175+
}
176+
177+
Map<String, dynamic> toJson() => _$RealmDefaultExternalAccountToJson(this);
178+
}
179+
99180
/// An item in `recent_private_conversations`.
100181
///
101182
/// For docs, search for "recent_private_conversations:"

lib/api/model/initial_snapshot.g.dart

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/api/model/model.dart

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,6 @@ import 'package:json_annotation/json_annotation.dart';
22

33
part 'model.g.dart';
44

5-
/// As in [InitialSnapshot.customProfileFields].
6-
///
7-
/// For docs, search for "custom_profile_fields:"
8-
/// in <https://zulip.com/api/register-queue>.
9-
@JsonSerializable(fieldRename: FieldRename.snake)
10-
class CustomProfileField {
11-
final int id;
12-
final int type; // TODO enum; also TODO(server-6) a value added
13-
final int order;
14-
final String name;
15-
final String hint;
16-
final String fieldData;
17-
final bool? displayInProfileSummary; // TODO(server-6)
18-
19-
CustomProfileField({
20-
required this.id,
21-
required this.type,
22-
required this.order,
23-
required this.name,
24-
required this.hint,
25-
required this.fieldData,
26-
required this.displayInProfileSummary,
27-
});
28-
29-
factory CustomProfileField.fromJson(Map<String, dynamic> json) =>
30-
_$CustomProfileFieldFromJson(json);
31-
32-
Map<String, dynamic> toJson() => _$CustomProfileFieldToJson(this);
33-
}
34-
35-
@JsonSerializable(fieldRename: FieldRename.snake)
36-
class ProfileFieldUserData {
37-
final String value;
38-
final String? renderedValue;
39-
40-
ProfileFieldUserData({required this.value, this.renderedValue});
41-
42-
factory ProfileFieldUserData.fromJson(Map<String, dynamic> json) =>
43-
_$ProfileFieldUserDataFromJson(json);
44-
45-
Map<String, dynamic> toJson() => _$ProfileFieldUserDataToJson(this);
46-
}
47-
485
/// As in [InitialSnapshot.realmUsers], [InitialSnapshot.realmNonActiveUsers], and [InitialSnapshot.crossRealmBots].
496
///
507
/// In the Zulip API, the items in realm_users, realm_non_active_users, and
@@ -69,7 +26,8 @@ class User {
6926
bool isBot;
7027
int? botType; // TODO enum
7128
int? botOwnerId;
72-
int role; // TODO enum
29+
@JsonKey(unknownEnumValue: UserRole.unknown)
30+
UserRole role;
7331
String timezone;
7432
String? avatarUrl; // TODO distinguish null from missing https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20omitted.20vs.2E.20null.20in.20JSON/near/1551759
7533
int avatarVersion;
@@ -120,6 +78,41 @@ class User {
12078
Map<String, dynamic> toJson() => _$UserToJson(this);
12179
}
12280

81+
/// As in [User.profileData].
82+
@JsonSerializable(fieldRename: FieldRename.snake)
83+
class ProfileFieldUserData {
84+
final String value;
85+
final String? renderedValue;
86+
87+
ProfileFieldUserData({required this.value, this.renderedValue});
88+
89+
factory ProfileFieldUserData.fromJson(Map<String, dynamic> json) =>
90+
_$ProfileFieldUserDataFromJson(json);
91+
92+
Map<String, dynamic> toJson() => _$ProfileFieldUserDataToJson(this);
93+
}
94+
95+
/// As in [User.role].
96+
@JsonEnum(fieldRename: FieldRename.snake, valueField: "apiValue")
97+
enum UserRole{
98+
owner(apiValue: 100, label: "Owner"),
99+
administrator(apiValue: 200, label: "Admin"),
100+
moderator(apiValue: 300, label: "Moderator"),
101+
member(apiValue: 400, label: "Member"),
102+
guest(apiValue: 600, label: "Guest"),
103+
unknown(apiValue: null, label: "Unknown");
104+
105+
const UserRole({
106+
required this.apiValue,
107+
required this.label,
108+
});
109+
110+
final int? apiValue;
111+
final String label;
112+
113+
int toJson() => _$UserRoleEnumMap[this]!;
114+
}
115+
123116
/// As in `streams` in the initial snapshot.
124117
///
125118
/// Not called `Stream` because dart:async uses that name.

0 commit comments

Comments
 (0)