Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
aaa1e66
message [nfc]: Simplify this copy of realmEmptyTopicDisplayName; expl…
gnprice Jul 7, 2025
d4afa40
message [nfc]: Use getter for realmEmptyTopicDisplayName rather than …
gnprice Jul 7, 2025
c5e1d23
realm [nfc]: Introduce RealmStore, initially vacuous
gnprice Jul 4, 2025
a578947
profile test: Have displayInProfileSummary default to false, not true
gnprice Jul 6, 2025
221ba81
test [nfc]: Move eg.customProfileField out from one test file
gnprice Jul 6, 2025
6b1f9af
store test: Test handling of customProfileFields
gnprice Jul 6, 2025
5efff58
realm [nfc]: Move data here from PerAccountStore
gnprice Jul 4, 2025
3098213
realm [nfc]: Move proxy boilerplate out of central store.dart
gnprice Jul 4, 2025
a2fa946
store [nfc]: Expose PerAccountStoreBase.core to subclasses
gnprice Jul 4, 2025
fea9e01
realm [nfc]: Add HasRealmStore for other substores to use
gnprice Jul 4, 2025
4b56bba
narrow [nfc]: Inline away TopicNarrow.processTopicLikeServer
gnprice Jul 23, 2025
45accee
realm [nfc]: Move processTopicLikeServer to substore, from API code
gnprice Jul 4, 2025
b30df8b
realm [nfc]: Add Duration getters for durations
gnprice Jul 4, 2025
290067a
presence [nfc]: Use RealmStore for server/realm settings
gnprice Jul 4, 2025
49539ff
realm [nfc]: Add server settings for typing status
gnprice Jul 4, 2025
2321e89
typing_status [nfc]: Use RealmStore for server settings
gnprice Jul 4, 2025
86ce025
dartdoc: Stop section-divider comments from getting absorbed into docs
gnprice Jul 6, 2025
2b045d2
realm [nfc]: Organize realm settings, from current API doc
gnprice Jul 4, 2025
1e11327
realm [nfc]: Cut duplicate, some out of date todo-comments on updating
gnprice Jul 4, 2025
5d0188f
store [nfc]: Organize substore parameters uniformly
gnprice Jul 4, 2025
db8809b
emoji [nfc]: Drop setServerEmojiData from main interface
gnprice Jul 6, 2025
1d62146
emoji [nfc]: Move proxy boilerplate out to substore file
gnprice Jul 7, 2025
d1b0812
user [nfc]: Provide RealmStore to UserStore
gnprice Jul 4, 2025
d71f081
user [nfc]: Move hasPassedWaitingPeriod here
gnprice Jul 4, 2025
9a5960a
user [nfc]: Move userDisplayEmail here
gnprice Jul 4, 2025
5233cfb
user [nfc]: Move proxy boilerplate out to substore file
gnprice Jul 7, 2025
623bcb4
user [nfc]: Add HasUserStore for other substores to use
gnprice Jul 6, 2025
fab85ca
channel [nfc]: Provide UserStore to ChannelStore
gnprice Jul 4, 2025
6b47ff0
channel [nfc]: Move hasPostingPermission here
gnprice Jul 4, 2025
72cf5d6
channel [nfc]: Move proxy boilerplate out to substore file
gnprice Apr 4, 2025
f83c9c1
message test [nfc]: Move sendMessage smoke test to follow sendMessage
gnprice Jul 6, 2025
9499dbe
message [nfc]: Consolidate "disposed" checks onto substore methods
gnprice Jul 4, 2025
4055216
message [nfc]: Drop reconcileMessages from main interface
gnprice Jul 6, 2025
2b0944f
message [nfc]: Move proxy boilerplate out to substore file
gnprice Jul 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions lib/api/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -837,53 +837,6 @@ extension type const TopicName(String _value) {
/// using [canonicalize].
bool isSameAs(TopicName other) => canonicalize() == other.canonicalize();

/// Process this topic to match how it would appear on a message object from
/// the server.
///
/// This returns the [TopicName] the server would be predicted to include
/// in a message object resulting from sending to this [TopicName]
/// in a [sendMessage] request.
///
/// This [TopicName] is required to have no leading or trailing whitespace.
///
/// For a client that supports empty topics, when FL>=334, the server converts
/// `store.realmEmptyTopicDisplayName` to an empty string; when FL>=370,
/// the server converts "(no topic)" to an empty string as well.
///
/// See API docs:
/// https://zulip.com/api/send-message#parameter-topic
TopicName processLikeServer({
required int zulipFeatureLevel,
required String? realmEmptyTopicDisplayName,
}) {
assert(_value.trim() == _value);
// TODO(server-10) simplify this away
if (zulipFeatureLevel < 334) {
// From the API docs:
// > Before Zulip 10.0 (feature level 334), empty string was not a valid
// > topic name for channel messages.
assert(_value.isNotEmpty);
return this;
}

// TODO(server-10) simplify this away
if (zulipFeatureLevel < 370 && _value == kNoTopicTopic) {
// From the API docs:
// > Before Zulip 10.0 (feature level 370), "(no topic)" was not
// > interpreted as an empty string.
return TopicName(kNoTopicTopic);
}

if (_value == kNoTopicTopic || _value == realmEmptyTopicDisplayName) {
// From the API docs:
// > When "(no topic)" or the value of realm_empty_topic_display_name
// > found in the POST /register response is used for [topic],
// > it is interpreted as an empty string.
return TopicName('');
}
return TopicName(_value);
}

TopicName.fromJson(this._value);

String toJson() => apiName;
Expand Down
4 changes: 2 additions & 2 deletions lib/example/sticky_header.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,14 @@ class ExampleVerticalDouble extends StatelessWidget {
}
}

////////////////////////////////////////////////////////////////////////////
//|//////////////////////////////////////////////////////////////////////////
//
// That's it!
//
// The rest of this file is boring infrastructure for navigating to the
// different examples, and for having some content to put inside them.
//
////////////////////////////////////////////////////////////////////////////
//|//////////////////////////////////////////////////////////////////////////

class WideHeader extends StatelessWidget {
const WideHeader({super.key, required this.i});
Expand Down
59 changes: 56 additions & 3 deletions lib/model/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart';
import '../api/model/events.dart';
import '../api/model/initial_snapshot.dart';
import '../api/model/model.dart';
import 'store.dart';
import 'user.dart';

/// The portion of [PerAccountStore] for channels, topics, and stuff about them.
///
/// This type is useful for expressing the needs of other parts of the
/// implementation of [PerAccountStore], to avoid circularity.
///
/// The data structures described here are implemented at [ChannelStoreImpl].
mixin ChannelStore {
mixin ChannelStore on UserStore {
/// All known channels/streams, indexed by [ZulipStream.streamId].
///
/// The same [ZulipStream] objects also appear in [streamsByName].
Expand Down Expand Up @@ -136,6 +138,30 @@ mixin ChannelStore {
return true;
}
}

bool hasPostingPermission({
required ZulipStream inChannel,
required User user,
required DateTime byDate,
}) {
final role = user.role;
// We let the users with [unknown] role to send the message, then the server
// will decide to accept it or not based on its actual role.
if (role == UserRole.unknown) return true;

switch (inChannel.channelPostPolicy) {
case ChannelPostPolicy.any: return true;
case ChannelPostPolicy.fullMembers: {
if (!role.isAtLeast(UserRole.member)) return false;
return role == UserRole.member
? hasPassedWaitingPeriod(user, byDate: byDate)
: true;
}
case ChannelPostPolicy.moderators: return role.isAtLeast(UserRole.moderator);
case ChannelPostPolicy.administrators: return role.isAtLeast(UserRole.administrator);
case ChannelPostPolicy.unknown: return true;
}
}
}

/// Whether and how a given [UserTopicEvent] will affect the results
Expand All @@ -160,13 +186,38 @@ enum UserTopicVisibilityEffect {
}
}

mixin ProxyChannelStore on ChannelStore {
@protected
ChannelStore get channelStore;

@override
Map<int, ZulipStream> get streams => channelStore.streams;

@override
Map<String, ZulipStream> get streamsByName => channelStore.streamsByName;

@override
Map<int, Subscription> get subscriptions => channelStore.subscriptions;

@override
UserTopicVisibilityPolicy topicVisibilityPolicy(int streamId, TopicName topic) =>
channelStore.topicVisibilityPolicy(streamId, topic);

@override
Map<int, Map<TopicName, UserTopicVisibilityPolicy>> get debugTopicVisibility =>
channelStore.debugTopicVisibility;
}

/// The implementation of [ChannelStore] that does the work.
///
/// Generally the only code that should need this class is [PerAccountStore]
/// itself. Other code accesses this functionality through [PerAccountStore],
/// or through the mixin [ChannelStore] which describes its interface.
class ChannelStoreImpl with ChannelStore {
factory ChannelStoreImpl({required InitialSnapshot initialSnapshot}) {
class ChannelStoreImpl extends HasUserStore with ChannelStore {
factory ChannelStoreImpl({
required UserStore users,
required InitialSnapshot initialSnapshot,
}) {
final subscriptions = Map.fromEntries(initialSnapshot.subscriptions.map(
(subscription) => MapEntry(subscription.streamId, subscription)));

Expand All @@ -186,6 +237,7 @@ class ChannelStoreImpl with ChannelStore {
}

return ChannelStoreImpl._(
users: users,
streams: streams,
streamsByName: streams.map((_, stream) => MapEntry(stream.name, stream)),
subscriptions: subscriptions,
Expand All @@ -194,6 +246,7 @@ class ChannelStoreImpl with ChannelStore {
}

ChannelStoreImpl._({
required super.users,
required this.streams,
required this.streamsByName,
required this.subscriptions,
Expand Down
2 changes: 1 addition & 1 deletion lib/model/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,7 @@ class GlobalTimeNode extends InlineContentNode {
}
}

////////////////////////////////////////////////////////////////
//|//////////////////////////////////////////////////////////////

/// Parser for the inline-content subtrees within Zulip content HTML.
///
Expand Down
25 changes: 23 additions & 2 deletions lib/model/emoji.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,30 @@ mixin EmojiStore {
// TODO cut debugServerEmojiData once we can query for lists of emoji;
// have tests make those queries end-to-end
Map<String, List<String>>? get debugServerEmojiData;
}

mixin ProxyEmojiStore on EmojiStore {
@protected
EmojiStore get emojiStore;

void setServerEmojiData(ServerEmojiData data);
@override
EmojiDisplay emojiDisplayFor({
required ReactionType emojiType,
required String emojiCode,
required String emojiName
}) {
return emojiStore.emojiDisplayFor(
emojiType: emojiType, emojiCode: emojiCode, emojiName: emojiName);
}

@override
Iterable<EmojiCandidate> popularEmojiCandidates() => emojiStore.popularEmojiCandidates();

@override
Iterable<EmojiCandidate> allEmojiCandidates() => emojiStore.allEmojiCandidates();

@override
Map<String, List<String>>? get debugServerEmojiData => emojiStore.debugServerEmojiData;
}

/// The implementation of [EmojiStore] that does the work.
Expand Down Expand Up @@ -374,7 +396,6 @@ class EmojiStoreImpl extends PerAccountStoreBase with EmojiStore {
return _allEmojiCandidates ??= _generateAllCandidates();
}

@override
void setServerEmojiData(ServerEmojiData data) {
_serverEmojiData = data.codeToNames;
_popularCandidates = null;
Expand Down
Loading
Loading