diff --git a/src/API/Discord.API.Status/Models/AffectedComponent.cs b/src/API/Discord.API.Status/Models/AffectedComponent.cs index db3e3ee3e..21fcc2cd8 100644 --- a/src/API/Discord.API.Status/Models/AffectedComponent.cs +++ b/src/API/Discord.API.Status/Models/AffectedComponent.cs @@ -7,17 +7,32 @@ namespace Discord.API.Status.Models { + /// + /// A component that is affected by an issue. + /// public class AffectedComponent { + /// + /// Gets the code of the issue that altered the component status. + /// [JsonPropertyName("code")] public string Code { get; set; } + /// + /// Gets the name of the comonent. + /// [JsonPropertyName("name")] public string Name { get; set; } + /// + /// Gets the status of the component before the issue. + /// [JsonPropertyName("old_status")] public string OldStatus { get; set; } + /// + /// Gets the status of the component with the issue. + /// [JsonPropertyName("new_status")] public string NewStatus { get; set; } } diff --git a/src/API/Discord.API.Status/Models/AllMetrics.cs b/src/API/Discord.API.Status/Models/AllMetrics.cs index 0e6824d39..e87474a58 100644 --- a/src/API/Discord.API.Status/Models/AllMetrics.cs +++ b/src/API/Discord.API.Status/Models/AllMetrics.cs @@ -7,14 +7,26 @@ namespace Discord.API.Status.Models { + /// + /// The set of all response time metrics over a period. + /// public class AllMetrics { + /// + /// Gets the peroid of time the metrics cover. + /// [JsonPropertyName("period")] public Period Period { get; set; } + /// + /// Gets the set of individual response time elements. + /// [JsonPropertyName("metrics")] public MetricElement[] Metrics { get; set; } + /// + /// Gets the summary of the response time. + /// [JsonPropertyName("summary")] public Summary Summary { get; set; } } diff --git a/src/API/Discord.API.Status/Models/Component.cs b/src/API/Discord.API.Status/Models/Component.cs index c468c4c7b..75b730c2b 100644 --- a/src/API/Discord.API.Status/Models/Component.cs +++ b/src/API/Discord.API.Status/Models/Component.cs @@ -8,38 +8,74 @@ namespace Discord.API.Status.Models { + /// + /// A component status of the Discord API. + /// public partial class Component { + /// + /// Gets the status of the component. + /// [JsonPropertyName("status")] public string Status { get; set; } + /// + /// Gets the name of the component. + /// [JsonPropertyName("name")] public string Name { get; set; } + /// + /// Gets the time the component was created. + /// [JsonPropertyName("created_at")] public DateTimeOffset CreatedAt { get; set; } + /// + /// Gets the time the component status was last updated. + /// [JsonPropertyName("updated_at")] public DateTimeOffset UpdatedAt { get; set; } + /// + /// Gets the ordering position of the component. + /// [JsonPropertyName("position")] public long Position { get; set; } + /// + /// Gets the description of the component. + /// [JsonPropertyName("description")] public string? Description { get; set; } + /// + /// Gets a value indicating whether or not the component is a showcase. + /// [JsonPropertyName("showcase")] public bool Showcase { get; set; } + /// + /// Gets the id of the component. + /// [JsonPropertyName("id")] public string Id { get; set; } + /// + /// Gets the page id of the component. + /// [JsonPropertyName("page_id")] public string PageId { get; set; } + /// + /// Gets the group id of the component. + /// [JsonPropertyName("group_id")] public object GroupId { get; set; } + /// + /// Gets a list of child components. + /// [JsonPropertyName("components")] public string[]? Components { get; set; } } diff --git a/src/API/Discord.API.Status/Models/Datum.cs b/src/API/Discord.API.Status/Models/Datum.cs index f63271af8..2ff08f822 100644 --- a/src/API/Discord.API.Status/Models/Datum.cs +++ b/src/API/Discord.API.Status/Models/Datum.cs @@ -4,11 +4,20 @@ namespace Discord.API.Status.Models { + /// + /// A response time datum. + /// public partial class Datum { + /// + /// Gets the timestamp represented. + /// [JsonPropertyName("timestamp")] public long Timestamp { get; set; } + /// + /// Gets the average response time during this time. + /// [JsonPropertyName("value")] public ushort Value { get; set; } } diff --git a/src/API/Discord.API.Status/Models/Incident.cs b/src/API/Discord.API.Status/Models/Incident.cs index 44f4d1017..e9b7f57e0 100644 --- a/src/API/Discord.API.Status/Models/Incident.cs +++ b/src/API/Discord.API.Status/Models/Incident.cs @@ -8,77 +8,152 @@ namespace Discord.API.Status.Models { + /// + /// An incident in the Discord API. + /// public partial class Incident { + /// + /// Gets the name of the incident. + /// [JsonPropertyName("name")] public string Name { get; set; } + /// + /// Gets the status of the api. + /// [JsonPropertyName("status")] public string Status { get; set; } + /// + /// Gets the time the incident began. + /// [JsonPropertyName("created_at")] public DateTimeOffset? CreatedAt { get; set; } + /// + /// Gets the time of the last incident update. + /// [JsonPropertyName("updated_at")] public DateTimeOffset? UpdatedAt { get; set; } + /// + /// Gets the time monitoring began. + /// [JsonPropertyName("monitoring_at")] public DateTimeOffset? MonitoringAt { get; set; } + /// + /// Gets the time the incident was resolved at. + /// [JsonPropertyName("resolved_at")] public DateTimeOffset? ResolvedAt { get; set; } + /// + /// Gets the impact of the incident. + /// [JsonPropertyName("impact")] public string Impact { get; set; } + /// + /// Gets the short link url to the incident. + /// [JsonPropertyName("shortlink")] public string Shortlink { get; set; } + /// + /// Gets a value indicating whether or not the post mortem of the incident is ignored. + /// [JsonPropertyName("postmortem_ignored")] public bool PostmortemIgnored { get; set; } + /// + /// Gets the post mortem of the incident. + /// [JsonPropertyName("postmortem_body")] public object PostmortemBody { get; set; } + /// + /// Gets the time the post mortem body was last updated. + /// [JsonPropertyName("postmortem_body_last_updated_at")] public object PostmortemBodyLastUpdatedAt { get; set; } + /// + /// Gets the time when the post mortem was published. + /// [JsonPropertyName("postmortem_published_at")] public object PostmortemPublishedAt { get; set; } + /// + /// Gets a value indicating whether or not post mortem subscribers are notified. + /// [JsonPropertyName("postmortem_notified_subscribers")] public bool PostmortemNotifiedSubscribers { get; set; } + /// + /// Gets a value indicating whether or not the post mortem was posted on Twitter. + /// [JsonPropertyName("postmortem_notified_twitter")] public bool PostmortemNotifiedTwitter { get; set; } + /// + /// TODO: Investigate. + /// [JsonPropertyName("scheduled_for")] public object ScheduledFor { get; set; } + /// + /// TODO: Investigate. + /// [JsonPropertyName("scheduled_until")] public object ScheduledUntil { get; set; } + /// + /// TODO: Investigate. + /// [JsonPropertyName("scheduled_remind_prior")] public bool ScheduledRemindPrior { get; set; } + /// + /// TODO: Investigate. + /// [JsonPropertyName("scheduled_reminded_at")] public object ScheduledRemindedAt { get; set; } + /// + /// TODO: Investigate. + /// [JsonPropertyName("impact_override")] public string ImpactOverride { get; set; } + /// + /// TODO: Investigate. + /// [JsonPropertyName("scheduled_auto_in_progress")] public bool ScheduledAutoInProgress { get; set; } + /// + /// Gets a value indicating whether or not the incident is scheduled for auto complete. + /// [JsonPropertyName("scheduled_auto_completed")] public bool ScheduledAutoCompleted { get; set; } + /// + /// Gets the incident id. + /// [JsonPropertyName("id")] public string Id { get; set; } + /// + /// Gets the page id for the incident. + /// [JsonPropertyName("page_id")] public string PageId { get; set; } + /// + /// Gets a list of updates to the incident. + /// [JsonPropertyName("incident_updates")] public IncidentUpdate[] IncidentUpdates { get; set; } } diff --git a/src/API/Discord.API.Status/Models/Index.cs b/src/API/Discord.API.Status/Models/Index.cs index 61c732e69..7d0274a8f 100644 --- a/src/API/Discord.API.Status/Models/Index.cs +++ b/src/API/Discord.API.Status/Models/Index.cs @@ -7,17 +7,32 @@ namespace Discord.API.Status.Models { + /// + /// An index of the discord api status. + /// public class Index { + /// + /// Gets the index page. + /// [JsonPropertyName("page")] public Page Page { get; set; } + /// + /// Gets the status for the index. + /// [JsonPropertyName("status")] public StatusClass Status { get; set; } + /// + /// Gets the component statuses for the index. + /// [JsonPropertyName("components")] public Component[] Components { get; set; } + /// + /// Gets the index incidents. + /// [JsonPropertyName("incidents")] public Incident[] Incidents { get; set; } } diff --git a/src/API/Discord.API.Status/Models/Period.cs b/src/API/Discord.API.Status/Models/Period.cs index 4a4dcbee2..45c5a2985 100644 --- a/src/API/Discord.API.Status/Models/Period.cs +++ b/src/API/Discord.API.Status/Models/Period.cs @@ -7,14 +7,26 @@ namespace Discord.API.Status.Models { + /// + /// The period of time a metrics set covers. + /// public partial class Period { + /// + /// Gets the number of metrics in the peroid. + /// [JsonPropertyName("count")] public long Count { get; set; } + /// + /// Gets the interval of time between metric. + /// [JsonPropertyName("interval")] public long Interval { get; set; } + /// + /// Gets the interval identifier. + /// [JsonPropertyName("identifier")] public string Identifier { get; set; } } diff --git a/src/API/Discord.API/Gateways/Models/Handshake/Ready.cs b/src/API/Discord.API/Gateways/Models/Handshake/Ready.cs index ffc0da312..4e2a38847 100644 --- a/src/API/Discord.API/Gateways/Models/Handshake/Ready.cs +++ b/src/API/Discord.API/Gateways/Models/Handshake/Ready.cs @@ -28,6 +28,9 @@ internal class Ready [JsonPropertyName("guilds")] public JsonGuild[] Guilds { get; set; } + [JsonPropertyName("private_channels")] + public JsonChannel[] PrivateChannels { get; set; } + [JsonPropertyName("session_id")] public string SessionId { get; set; } diff --git a/src/API/Discord.API/Models/Enums/Guilds/ExplicitContentFilterLevel.cs b/src/API/Discord.API/Models/Enums/Guilds/ExplicitContentFilterLevel.cs deleted file mode 100644 index 22a4f3cc2..000000000 --- a/src/API/Discord.API/Models/Enums/Guilds/ExplicitContentFilterLevel.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Quarrel © 2022 - -namespace Discord.API.Models.Enums.Guilds -{ - /// - /// An enum representing the content filter level for a guild. - /// - public enum ExplicitContentFilterLevel - { - /// - /// No explicit content filtering. - /// - Disabled = 0, - - /// - /// Filter content members without roles. - /// - MembersWithoutRoles = 1, - - /// - /// Filters content for all members. - /// - AllMembers = 2 - } -} diff --git a/src/API/Discord.API/Models/Enums/Settings/ExplicitContentFilterLevel.cs b/src/API/Discord.API/Models/Enums/Settings/ExplicitContentFilterLevel.cs new file mode 100644 index 000000000..e5c6f5ef5 --- /dev/null +++ b/src/API/Discord.API/Models/Enums/Settings/ExplicitContentFilterLevel.cs @@ -0,0 +1,25 @@ +// Quarrel © 2022 + +namespace Discord.API.Models.Enums.Settings +{ + /// + /// The content filter level. + /// + public enum ExplicitContentFilterLevel : int + { + /// + /// Filter content from nobody. + /// + None, + + /// + /// Filter content not from my friends + /// + Public, + + /// + /// Filter content from everyone. + /// + All, + } +} diff --git a/src/API/Discord.API/Models/Json/Channels/JsonChannel.cs b/src/API/Discord.API/Models/Json/Channels/JsonChannel.cs index f5cee2a30..3da146f54 100644 --- a/src/API/Discord.API/Models/Json/Channels/JsonChannel.cs +++ b/src/API/Discord.API/Models/Json/Channels/JsonChannel.cs @@ -68,10 +68,6 @@ internal class JsonChannel [JsonPropertyName("rtc_region")] public string? RTCRegion { get; set; } - // Direct - [JsonPropertyName("recipient")] - public JsonUser? Recipient { get; set; } - // Group [JsonPropertyName("icon")] public string? Icon { get; set; } diff --git a/src/API/Discord.API/Models/Json/Guilds/JsonGuild.cs b/src/API/Discord.API/Models/Json/Guilds/JsonGuild.cs index 18c07ba6b..de091b7f7 100644 --- a/src/API/Discord.API/Models/Json/Guilds/JsonGuild.cs +++ b/src/API/Discord.API/Models/Json/Guilds/JsonGuild.cs @@ -1,6 +1,7 @@ // Quarrel © 2022 using Discord.API.Models.Enums.Guilds; +using Discord.API.Models.Enums.Settings; using Discord.API.Models.Json.Channels; using Discord.API.Models.Json.Emojis; using Discord.API.Models.Json.Roles; diff --git a/src/API/Discord.API/Models/Json/Messages/JsonCall.cs b/src/API/Discord.API/Models/Json/Messages/JsonCall.cs new file mode 100644 index 000000000..ee98278ea --- /dev/null +++ b/src/API/Discord.API/Models/Json/Messages/JsonCall.cs @@ -0,0 +1,19 @@ +// Quarrel © 2022 + +using System; +using System.Text.Json.Serialization; + +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + +namespace Discord.API.Models.Json.Messages +{ + internal class JsonCall + { + [JsonPropertyName("ended_timestamp")] + public DateTimeOffset EndedTimestamp { get; set; } + + [JsonPropertyName("participants"), JsonNumberHandling(Constants.ReadWriteAsString)] + public ulong[] Participants { get; set; } + } +} diff --git a/src/API/Discord.API/Models/Json/Messages/JsonChannelMention.cs b/src/API/Discord.API/Models/Json/Messages/JsonChannelMention.cs new file mode 100644 index 000000000..55af9da3b --- /dev/null +++ b/src/API/Discord.API/Models/Json/Messages/JsonChannelMention.cs @@ -0,0 +1,25 @@ +// Quarrel © 2022 + +using Discord.API.Models.Enums.Channels; +using System.Text.Json.Serialization; + +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + +namespace Discord.API.Models.Json.Messages +{ + internal class JsonChannelMention + { + [JsonPropertyName("id")] + public ulong Id { get; set; } + + [JsonPropertyName("guild_id")] + public ulong GuildId { get; set; } + + [JsonPropertyName("type")] + public ChannelType ChannelType { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + } +} diff --git a/src/API/Discord.API/Models/Json/Messages/JsonMessage.cs b/src/API/Discord.API/Models/Json/Messages/JsonMessage.cs index b5c99be07..820a9892e 100644 --- a/src/API/Discord.API/Models/Json/Messages/JsonMessage.cs +++ b/src/API/Discord.API/Models/Json/Messages/JsonMessage.cs @@ -1,9 +1,9 @@ // Quarrel © 2022 +using Discord.API.Models.Enums.Messages; using Discord.API.Models.Json.Messages.Embeds; using Discord.API.Models.Json.Reactions; using Discord.API.Models.Json.Users; -using Discord.API.Models.Enums.Messages; using System; using System.Text.Json.Serialization; @@ -32,6 +32,9 @@ internal class JsonMessage [JsonPropertyName("author")] public JsonUser? Author { get; set; } + [JsonPropertyName("call")] + public JsonCall? Call { get; set; } + [JsonPropertyName("member")] public JsonGuildMember? Member { get; set; } @@ -56,6 +59,9 @@ internal class JsonMessage [JsonPropertyName("mention_roles"), JsonNumberHandling(Constants.ReadWriteAsString)] public ulong[]? RoleMentions { get; set; } + [JsonPropertyName("mention_channels")] + public JsonChannelMention[]? ChannelMentions { get; set; } + [JsonPropertyName("attachments")] public JsonAttachment[]? Attachments { get; set; } diff --git a/src/API/Discord.API/Models/Json/Settings/JsonUserSettings.cs b/src/API/Discord.API/Models/Json/Settings/JsonUserSettings.cs index 41a1301ac..2d1107e5b 100644 --- a/src/API/Discord.API/Models/Json/Settings/JsonUserSettings.cs +++ b/src/API/Discord.API/Models/Json/Settings/JsonUserSettings.cs @@ -1,5 +1,6 @@ // Quarrel © 2022 +using Discord.API.Models.Enums.Settings; using System.Text.Json.Serialization; // JSON models don't need to respect standard nullable rules. @@ -48,5 +49,7 @@ internal class JsonUserSettings [JsonPropertyName("guild_folders")] public JsonGuildFolder[] GuildFolders { get; set; } + [JsonPropertyName("explicit_content_filter")] + public ExplicitContentFilterLevel ExplicitContentFilter { get; set; } } } diff --git a/src/API/Patreon.API/Rest/PatreonRestFactory.cs b/src/API/Patreon.API/Rest/PatreonRestFactory.cs index 50248f671..700d41ca4 100644 --- a/src/API/Patreon.API/Rest/PatreonRestFactory.cs +++ b/src/API/Patreon.API/Rest/PatreonRestFactory.cs @@ -17,7 +17,5 @@ public PatreonRestFactory(string token) { _token = token; } - - } } diff --git a/src/Quarrel.Client/Events/QuarrelClient.Events.Ready.cs b/src/Quarrel.Client/Events/QuarrelClient.Events.Ready.cs index d7a285a63..2a1337c80 100644 --- a/src/Quarrel.Client/Events/QuarrelClient.Events.Ready.cs +++ b/src/Quarrel.Client/Events/QuarrelClient.Events.Ready.cs @@ -1,9 +1,7 @@ // Quarrel © 2022 using CommunityToolkit.Diagnostics; -using Discord.API.Gateways; using Discord.API.Gateways.Models.Handshake; -using Quarrel.Client.Models.Users; namespace Quarrel.Client { @@ -22,6 +20,11 @@ private void OnReady(Ready ready) AddGuild(guild); } + foreach (var channel in ready.PrivateChannels) + { + AddChannel(channel); + } + foreach (var readState in ready.ReadStates) { AddReadState(readState); @@ -39,7 +42,7 @@ private void OnReady(Ready ready) UpdateSettings(ready.Settings); - Guard.IsNotNull(_selfUser, nameof(Client.QuarrelClient._selfUser)); + Guard.IsNotNull(_selfUser, nameof(_selfUser)); LoggedIn?.Invoke(this, _selfUser); } diff --git a/src/Quarrel.Client/Models/Channels/Abstract/PrivateChannel.cs b/src/Quarrel.Client/Models/Channels/Abstract/PrivateChannel.cs new file mode 100644 index 000000000..f28557d5b --- /dev/null +++ b/src/Quarrel.Client/Models/Channels/Abstract/PrivateChannel.cs @@ -0,0 +1,53 @@ +// Quarrel © 2022 + +using Discord.API.Models.Json.Channels; +using Quarrel.Client.Models.Channels.Interfaces; + +namespace Quarrel.Client.Models.Channels.Abstract +{ + /// + /// The base class for private channels. + /// + public abstract class PrivateChannel : Channel, IPrivateChannel + { + internal PrivateChannel(JsonChannel restChannel, QuarrelClient context) : + base(restChannel, context) + { + LastMessageId = restChannel.LastMessageId; + RTCRegion = restChannel.RTCRegion; + } + + /// + public int? MentionCount { get; private set; } + + /// + public ulong? LastMessageId { get; private set; } + + /// + public ulong? LastReadMessageId { get; private set; } + + /// + public string? RTCRegion { get; private set; } + + /// + public bool IsUnread => LastMessageId > LastReadMessageId; + + int? IMessageChannel.MentionCount + { + get => MentionCount; + set => MentionCount = value; + } + + ulong? IMessageChannel.LastMessageId + { + get => LastMessageId; + set => LastMessageId = value; + } + + ulong? IMessageChannel.LastReadMessageId + { + get => LastReadMessageId; + set => LastReadMessageId = value; + } + } +} diff --git a/src/Quarrel.Client/Models/Channels/DirectChannel.cs b/src/Quarrel.Client/Models/Channels/DirectChannel.cs index ce14ba276..fbf140e12 100644 --- a/src/Quarrel.Client/Models/Channels/DirectChannel.cs +++ b/src/Quarrel.Client/Models/Channels/DirectChannel.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Diagnostics; using Discord.API.Models.Json.Channels; +using Discord.API.Models.Json.Users; using Quarrel.Client.Models.Channels.Abstract; using Quarrel.Client.Models.Channels.Interfaces; using Quarrel.Client.Models.Users; @@ -11,52 +12,18 @@ namespace Quarrel.Client.Models.Channels /// /// A direct message channel managed by a . /// - public class DirectChannel : Channel, IDirectChannel + public class DirectChannel : PrivateChannel, IDirectChannel { - internal DirectChannel(JsonChannel restChannel, QuarrelClient context) : base(restChannel, context) - { - Guard.IsNotNull(restChannel.Recipient, nameof(restChannel.Recipient)); - - RecipientId = restChannel.Recipient.Id; - LastMessageId = restChannel.LastMessageId; - RTCRegion = restChannel.RTCRegion; - } - - /// - public ulong RecipientId { get; private set; } - - /// - public int? MentionCount { get; private set; } - - /// - public ulong? LastMessageId { get; private set; } - - /// - public ulong? LastReadMessageId { get; private set; } - - /// - public bool IsUnread => LastMessageId > LastReadMessageId; - - int? IMessageChannel.MentionCount + internal DirectChannel(JsonChannel restChannel, QuarrelClient context) : + base(restChannel, context) { - get => MentionCount; - set => MentionCount = value; - } + Guard.IsNotNull(restChannel.Recipients, nameof(restChannel.Recipients)); - ulong? IMessageChannel.LastMessageId - { - get => LastMessageId; - set => LastMessageId = value; - } - - ulong? IMessageChannel.LastReadMessageId - { - get => LastReadMessageId; - set => LastReadMessageId = value; + RecipientId = restChannel.Recipients[0].Id; } /// - public string? RTCRegion { get; private set; } + public ulong RecipientId { get; private set; } /// /// Gets the recipient of the direct message channel. @@ -73,16 +40,21 @@ internal override void PrivateUpdateFromJsonChannel(JsonChannel jsonChannel) { base.PrivateUpdateFromJsonChannel(jsonChannel); - if (jsonChannel.Recipient is not null) + if (jsonChannel.Recipients is not null) { - Context.AddUser(jsonChannel.Recipient); + Context.AddUser(jsonChannel.Recipients[0]); } } internal override JsonChannel ToJsonChannel() { JsonChannel restChannel = base.ToJsonChannel(); - restChannel.Recipient = Context.GetUserInternal(RecipientId)?.ToRestUser(); + var recipient = Context.GetUserInternal(RecipientId)?.ToRestUser(); + if (recipient is not null) + { + restChannel.Recipients = new JsonUser[] { recipient }; + } + return restChannel; } } diff --git a/src/Quarrel.Client/Models/Channels/GroupChannel.cs b/src/Quarrel.Client/Models/Channels/GroupChannel.cs index 10eff8381..222293e42 100644 --- a/src/Quarrel.Client/Models/Channels/GroupChannel.cs +++ b/src/Quarrel.Client/Models/Channels/GroupChannel.cs @@ -13,7 +13,7 @@ namespace Quarrel.Client.Models.Channels /// /// A group dm channel managed by a . /// - public class GroupChannel : Channel, IGroupChannel + public class GroupChannel : PrivateChannel, IGroupChannel { internal GroupChannel(JsonChannel restChannel, QuarrelClient context) : base(restChannel, context) @@ -23,51 +23,20 @@ internal GroupChannel(JsonChannel restChannel, QuarrelClient context) : OwnerId = restChannel.OwnerId.Value; - RTCRegion = restChannel.RTCRegion; Recipients = restChannel.Recipients.Select(x => new User(x, context)).ToArray(); - LastMessageId = restChannel.LastMessageId; + Icon = restChannel.Icon; } /// public ulong OwnerId { get; private set; } - /// - public string? RTCRegion { get; private set; } - /// public User[] Recipients { get; private set; } - IUser[] IGroupChannel.Recipients => Recipients; - - /// - public int? MentionCount { get; internal set; } - - /// - public ulong? LastMessageId { get; internal set; } - - /// - public ulong? LastReadMessageId { get; internal set; } - /// - public bool IsUnread => LastMessageId > LastReadMessageId; + public string? Icon { get; private set; } - int? IMessageChannel.MentionCount - { - get => MentionCount; - set => MentionCount = value; - } - - ulong? IMessageChannel.LastMessageId - { - get => LastMessageId; - set => LastMessageId = value; - } - - ulong? IMessageChannel.LastReadMessageId - { - get => LastReadMessageId; - set => LastReadMessageId = value; - } + IUser[] IGroupChannel.Recipients => Recipients; internal override JsonChannel ToJsonChannel() { diff --git a/src/Quarrel.Client/Models/Channels/Interfaces/IAudioChannel.cs b/src/Quarrel.Client/Models/Channels/Interfaces/IAudioChannel.cs index ae0640e4b..aa73b7fed 100644 --- a/src/Quarrel.Client/Models/Channels/Interfaces/IAudioChannel.cs +++ b/src/Quarrel.Client/Models/Channels/Interfaces/IAudioChannel.cs @@ -5,7 +5,7 @@ namespace Quarrel.Client.Models.Channels.Interfaces /// /// An interface for channels voice channels or channels with calling. /// - internal interface IAudioChannel : IChannel + public interface IAudioChannel : IChannel { /// /// The region of the voice server. diff --git a/src/Quarrel.Client/Models/Channels/Interfaces/IDirectChannel.cs b/src/Quarrel.Client/Models/Channels/Interfaces/IDirectChannel.cs index 86f777b5e..8c6b641fa 100644 --- a/src/Quarrel.Client/Models/Channels/Interfaces/IDirectChannel.cs +++ b/src/Quarrel.Client/Models/Channels/Interfaces/IDirectChannel.cs @@ -5,7 +5,7 @@ namespace Quarrel.Client.Models.Channels.Interfaces /// /// An interface for direct message channels. /// - internal interface IDirectChannel : IPrivateChannel, IMessageChannel, IAudioChannel + public interface IDirectChannel : IPrivateChannel { ulong RecipientId { get; } } diff --git a/src/Quarrel.Client/Models/Channels/Interfaces/IGroupChannel.cs b/src/Quarrel.Client/Models/Channels/Interfaces/IGroupChannel.cs index b90eabcfa..44dd10f80 100644 --- a/src/Quarrel.Client/Models/Channels/Interfaces/IGroupChannel.cs +++ b/src/Quarrel.Client/Models/Channels/Interfaces/IGroupChannel.cs @@ -7,13 +7,18 @@ namespace Quarrel.Client.Models.Channels.Interfaces /// /// An interface for group channels. /// - internal interface IGroupChannel : IPrivateChannel, IMessageChannel, IAudioChannel + public interface IGroupChannel : IPrivateChannel { /// /// The id of the user that owns the channel. /// ulong OwnerId { get; } + /// + /// Gets the icon id. + /// + string? Icon { get; } + /// /// The list of users in the channel. /// diff --git a/src/Quarrel.Client/Models/Channels/Interfaces/IPrivateChannel.cs b/src/Quarrel.Client/Models/Channels/Interfaces/IPrivateChannel.cs index 6dadedf55..be00ccbf5 100644 --- a/src/Quarrel.Client/Models/Channels/Interfaces/IPrivateChannel.cs +++ b/src/Quarrel.Client/Models/Channels/Interfaces/IPrivateChannel.cs @@ -5,7 +5,7 @@ namespace Quarrel.Client.Models.Channels.Interfaces /// /// An interface for channels in DMs. /// - internal interface IPrivateChannel : IChannel + public interface IPrivateChannel : IChannel, IMessageChannel, IAudioChannel { } } diff --git a/src/Quarrel.Client/Models/Guilds/Guild.cs b/src/Quarrel.Client/Models/Guilds/Guild.cs index 54c4aa87a..14b30340a 100644 --- a/src/Quarrel.Client/Models/Guilds/Guild.cs +++ b/src/Quarrel.Client/Models/Guilds/Guild.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Diagnostics; using Discord.API.Models.Enums.Guilds; +using Discord.API.Models.Enums.Settings; using Discord.API.Models.Json.Guilds; using Quarrel.Client.Models.Base; using Quarrel.Client.Models.Channels.Abstract; diff --git a/src/Quarrel.Client/Models/Guilds/Interfaces/IGuild.cs b/src/Quarrel.Client/Models/Guilds/Interfaces/IGuild.cs index 6b5aa6f57..ed5ebee9f 100644 --- a/src/Quarrel.Client/Models/Guilds/Interfaces/IGuild.cs +++ b/src/Quarrel.Client/Models/Guilds/Interfaces/IGuild.cs @@ -1,6 +1,7 @@ // Quarrel © 2022 using Discord.API.Models.Enums.Guilds; +using Discord.API.Models.Enums.Settings; using Quarrel.Client.Models.Base.Interfaces; namespace Quarrel.Client.Models.Guilds.Interfaces diff --git a/src/Quarrel.Client/Models/Settings/Settings.cs b/src/Quarrel.Client/Models/Settings/Settings.cs index da4e19550..f3cb10a0a 100644 --- a/src/Quarrel.Client/Models/Settings/Settings.cs +++ b/src/Quarrel.Client/Models/Settings/Settings.cs @@ -22,6 +22,7 @@ internal Settings(JsonUserSettings jsonUserSettings, QuarrelClient context) : InlineAttachementMedia = jsonUserSettings.InlineAttachementMedia; Locale = jsonUserSettings.Locale; ShowCurrentGame = jsonUserSettings.ShowCurrentGame; + ContentFilterLevel = jsonUserSettings.ExplicitContentFilter; switch (jsonUserSettings.Theme) { @@ -85,6 +86,11 @@ internal Settings(JsonUserSettings jsonUserSettings, QuarrelClient context) : /// public bool ShowCurrentGame { get; private set; } + /// + /// Gets if the user's presence includes the current game. + /// + public ExplicitContentFilterLevel ContentFilterLevel { get; private set; } + /// /// Gets the Discord theme for the user. /// diff --git a/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs b/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs index 7e8438e3f..74b1e7783 100644 --- a/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs +++ b/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs @@ -5,7 +5,7 @@ namespace Quarrel.Client.Models.Users.Interfaces { - internal interface IUser : ISnowflakeItem + public interface IUser : ISnowflakeItem { /// /// Gets the user's username. diff --git a/src/Quarrel.Client/QuarrelClient.Methods.cs b/src/Quarrel.Client/QuarrelClient.Methods.cs index 9062faf8f..5e6b4c3db 100644 --- a/src/Quarrel.Client/QuarrelClient.Methods.cs +++ b/src/Quarrel.Client/QuarrelClient.Methods.cs @@ -2,12 +2,16 @@ using CommunityToolkit.Diagnostics; using Discord.API.Models.Json.Messages; +using Quarrel.Client.Models.Channels; +using Quarrel.Client.Models.Channels.Interfaces; using Quarrel.Client.Models.Guilds; using Quarrel.Client.Models.Messages; using Quarrel.Client.Models.Settings; using Quarrel.Client.Models.Users; using Refit; using System; +using System.Collections; +using System.Collections.Generic; using System.Threading.Tasks; namespace Quarrel.Client @@ -22,6 +26,17 @@ public partial class QuarrelClient return CurrentUser; } + public User? GetUser(ulong id) + { + _userMap.TryGetValue(id, out var user); + return user; + } + + public Settings? GetSettings() + { + return _settings; + } + /// /// Gets messages in a channel. /// @@ -106,6 +121,35 @@ public GuildFolder[] GetMyGuildFolders() return _settings.Folders; } + public IPrivateChannel[] GetPrivateChannels() + { + IPrivateChannel[] privateChannels = new IPrivateChannel[_privateChannels.Count]; + int i = 0; + foreach (var channelId in _privateChannels) + { + var channel = GetChannelInternal(channelId); + if (channel is IPrivateChannel directChannel) + { + privateChannels[i] = directChannel; + i++; + } + } + + Array.Resize(ref privateChannels, i); + Array.Sort(privateChannels, Comparer.Create((item1, item2) => + { + if (!item2.LastMessageId.HasValue) return -1; + if (!item1.LastMessageId.HasValue) return 1; + + long compare = (long)item2.LastMessageId.Value - (long)item1.LastMessageId.Value; + if (compare < 0) return -1; + if (compare > 0) return 1; + return 0; + })); + + return privateChannels; + } + private async Task MakeRefitRequest(Func> request) { try diff --git a/src/Quarrel.Client/QuarrelClient.State.cs b/src/Quarrel.Client/QuarrelClient.State.cs index 3c6367f2c..d5fb2e71d 100644 --- a/src/Quarrel.Client/QuarrelClient.State.cs +++ b/src/Quarrel.Client/QuarrelClient.State.cs @@ -11,6 +11,7 @@ using Quarrel.Client.Models.Settings; using Quarrel.Client.Models.Users; using System.Collections.Concurrent; +using System.Collections.Generic; namespace Quarrel.Client { @@ -24,6 +25,7 @@ public partial class QuarrelClient private readonly ConcurrentDictionary _channelMap; private readonly ConcurrentDictionary _userMap; private readonly ConcurrentDictionary<(ulong GuildId, ulong UserId), GuildMember> _guildsMemberMap; + private readonly HashSet _privateChannels; internal SelfUser? CurrentUser => _selfUser; @@ -109,6 +111,15 @@ internal bool AddChannel(JsonChannel jsonChannel, ulong? guildId = null) { guild.AddChannel(channel.Id); } + else if (jsonChannel.Recipients is not null) + { + foreach (var recipient in jsonChannel.Recipients) + { + AddUser(recipient); + } + + _privateChannels.Add(channel.Id); + } return true; } diff --git a/src/Quarrel.Client/QuarrelClient.cs b/src/Quarrel.Client/QuarrelClient.cs index d5a6df682..1bf434aeb 100644 --- a/src/Quarrel.Client/QuarrelClient.cs +++ b/src/Quarrel.Client/QuarrelClient.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Concurrent; using System.Threading.Tasks; +using System.Collections.Generic; namespace Quarrel.Client { @@ -32,6 +33,7 @@ public QuarrelClient() _channelMap = new ConcurrentDictionary(); _userMap = new ConcurrentDictionary(); _guildsMemberMap = new ConcurrentDictionary<(ulong GuildId, ulong UserId), GuildMember>(); + _privateChannels = new HashSet(); } /// diff --git a/src/Quarrel.ViewModels/Bindables/Abstract/BindableItem.cs b/src/Quarrel.ViewModels/Bindables/Abstract/BindableItem.cs index b580c4534..ea4e0ea24 100644 --- a/src/Quarrel.ViewModels/Bindables/Abstract/BindableItem.cs +++ b/src/Quarrel.ViewModels/Bindables/Abstract/BindableItem.cs @@ -1,6 +1,7 @@ // Quarrel © 2022 using Microsoft.Toolkit.Mvvm.ComponentModel; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; namespace Quarrel.Bindables.Abstract @@ -10,6 +11,11 @@ namespace Quarrel.Bindables.Abstract /// public abstract class BindableItem : ObservableObject { + /// + /// Gets the for the . + /// + protected readonly IDiscordService _discordService; + /// /// Gets an that can run code on the UI Thread. /// @@ -18,8 +24,9 @@ public abstract class BindableItem : ObservableObject /// /// Initializes a new instance of the class. /// - public BindableItem(IDispatcherService dispatcherService) + public BindableItem(IDiscordService discordService, IDispatcherService dispatcherService) { + _discordService = discordService; _dispatcherService = dispatcherService; } } diff --git a/src/Quarrel.ViewModels/Bindables/Abstract/SelectableItem.cs b/src/Quarrel.ViewModels/Bindables/Abstract/SelectableItem.cs index 5f2ff193f..3a4a74ff2 100644 --- a/src/Quarrel.ViewModels/Bindables/Abstract/SelectableItem.cs +++ b/src/Quarrel.ViewModels/Bindables/Abstract/SelectableItem.cs @@ -1,6 +1,7 @@ // Quarrel © 2022 using Quarrel.Bindables.Interfaces; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; namespace Quarrel.Bindables.Abstract @@ -15,8 +16,8 @@ public abstract class SelectableItem : BindableItem, ISelectableItem /// /// Initializes a new instance of the class. /// - public SelectableItem(IDispatcherService dispatcherService) : - base(dispatcherService) + public SelectableItem(IDiscordService discordService, IDispatcherService dispatcherService) : + base(discordService, dispatcherService) { } diff --git a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs index 70b754012..d79596b46 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs @@ -5,7 +5,9 @@ using Quarrel.Client.Models.Channels.Abstract; using Quarrel.Client.Models.Channels.Interfaces; using Quarrel.Client.Models.Users; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; +using Quarrel.Services.Localization; using System; namespace Quarrel.Bindables.Channels.Abstract @@ -20,13 +22,18 @@ public abstract partial class BindableChannel : SelectableItem /// /// Initializes a new instance of the class. /// - internal BindableChannel(IDispatcherService dispatcherService, Channel channel) : - base(dispatcherService) + internal BindableChannel(IDiscordService discordService, IDispatcherService dispatcherService, Channel channel) : + base(discordService, dispatcherService) { _channel = channel; _channel.ItemUpdated += AckUpdateRoot; } + /// + /// Gets the id of the channel. + /// + public ulong Id => Channel.Id; + /// /// Gets the name of the channel as displayed. /// @@ -35,7 +42,7 @@ internal BindableChannel(IDispatcherService dispatcherService, Channel channel) /// /// Gets a bool representing whether or not the channel is a text channel. /// - public abstract bool IsTextChannel { get; } + public virtual bool IsTextChannel => true; /// /// Gets the wrapped . @@ -78,17 +85,29 @@ private void AckUpdateRoot(object sender, EventArgs e) /// /// Creates a new instance of a based on the type. /// - /// The dispatcher service to pass to the . + /// The to pass to the . + /// The to pass to the . + /// The to pass to the . /// The channel to wrap. /// The current user's guild member for the channel's guild. Null if not a guild channel. /// The parent category of the channel. - public static BindableChannel? Create(IDispatcherService dispatcherService, IChannel channel, GuildMember member, BindableCategoryChannel? parent = null) + public static BindableChannel? Create(IDiscordService discordService, ILocalizationService localizationService, IDispatcherService dispatcherService, IChannel channel, GuildMember? member = null, BindableCategoryChannel? parent = null) { + if (member is null) + { + return channel switch + { + DirectChannel c => new BindableDirectChannel(discordService, dispatcherService, c), + GroupChannel c => new BindableGroupChannel(discordService, localizationService, dispatcherService, c), + _ => null + }; + } + return channel switch { - GuildTextChannel c => new BindableTextChannel(dispatcherService, c, member, parent), - VoiceChannel c => new BindableVoiceChannel(dispatcherService, c, member, parent), - CategoryChannel c => new BindableCategoryChannel(dispatcherService, c, member), + GuildTextChannel c => new BindableTextChannel(discordService, dispatcherService, c, member, parent), + VoiceChannel c => new BindableVoiceChannel(discordService, dispatcherService, c, member, parent), + CategoryChannel c => new BindableCategoryChannel(discordService, dispatcherService, c, member), _ => null }; } diff --git a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs index 0cead33a2..e9eb56cd3 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs @@ -6,17 +6,19 @@ using Quarrel.Client.Models.Channels.Interfaces; using Quarrel.Client.Models.Permissions; using Quarrel.Client.Models.Users; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; +using Quarrel.Services.Localization; namespace Quarrel.Bindables.Channels.Abstract { /// - /// A wrapper of an that can be bound to the UI. + /// A wrapper of a that can be bound to the UI. /// public abstract class BindableGuildChannel : BindableChannel { - internal BindableGuildChannel(IDispatcherService dispatcherService, GuildChannel channel, GuildMember selfMember, BindableCategoryChannel? parent = null) : - base(dispatcherService, channel) + internal BindableGuildChannel(IDiscordService discordService, IDispatcherService dispatcherService, GuildChannel channel, GuildMember selfMember, BindableCategoryChannel? parent = null) : + base(discordService, dispatcherService, channel) { CategoryChannel = parent; @@ -56,13 +58,15 @@ internal BindableGuildChannel(IDispatcherService dispatcherService, GuildChannel /// /// Creates a new based on the type. /// - /// The dispatcher service to pass to the . + /// The to pass to the . + /// The to pass to the . + /// The to pass to the . /// The channel to wrap. /// The current user's guild member for the channel's guild. /// The channel's parent category. - public static BindableGuildChannel? Create(IDispatcherService dispatcherService, IGuildChannel channel, GuildMember member, BindableCategoryChannel? parent = null) + public static BindableGuildChannel? Create(IDiscordService discordService, ILocalizationService localizationService, IDispatcherService dispatcherService, IGuildChannel channel, GuildMember member, BindableCategoryChannel? parent = null) { - return BindableChannel.Create(dispatcherService, channel, member, parent) as BindableGuildChannel; + return BindableChannel.Create(discordService, localizationService, dispatcherService, channel, member, parent) as BindableGuildChannel; } /// diff --git a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindablePrivateChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindablePrivateChannel.cs new file mode 100644 index 000000000..6d90b2077 --- /dev/null +++ b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindablePrivateChannel.cs @@ -0,0 +1,36 @@ +// Quarrel © 2022 + +using Quarrel.Bindables.Channels.Interfaces; +using Quarrel.Client.Models.Channels.Abstract; +using Quarrel.Client.Models.Channels.Interfaces; +using Quarrel.Services.Discord; +using Quarrel.Services.Dispatcher; +using Quarrel.Services.Localization; + +namespace Quarrel.Bindables.Channels.Abstract +{ + /// + /// A wrapper of an that can be bound to the UI. + /// + public abstract class BindablePrivateChannel : BindableChannel, IBindableMessageChannel + { + /// + /// Initializes a new instance of the class. + /// + internal BindablePrivateChannel(IDiscordService discordService, IDispatcherService dispatcherService, PrivateChannel privateChannel) : + base(discordService, dispatcherService, privateChannel) + { + } + + /// + public bool IsAccessible => true; + + /// + public IMessageChannel MessageChannel => (IMessageChannel)Channel; + + public static BindablePrivateChannel? Create(IDiscordService discordService, ILocalizationService localizationService, IDispatcherService dispatcherService, IPrivateChannel channel) + { + return BindableChannel.Create(discordService, localizationService, dispatcherService, channel) as BindablePrivateChannel; + } + } +} diff --git a/src/Quarrel.ViewModels/Bindables/Channels/BindableCategoryChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/BindableCategoryChannel.cs index 178dfc531..32a13f9c7 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/BindableCategoryChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/BindableCategoryChannel.cs @@ -3,6 +3,7 @@ using Quarrel.Bindables.Channels.Abstract; using Quarrel.Client.Models.Channels; using Quarrel.Client.Models.Users; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; namespace Quarrel.Bindables.Channels @@ -12,8 +13,8 @@ namespace Quarrel.Bindables.Channels /// public class BindableCategoryChannel : BindableGuildChannel { - internal BindableCategoryChannel(IDispatcherService dispatcherService, CategoryChannel channel, GuildMember selfMember) : - base(dispatcherService, channel, selfMember) + internal BindableCategoryChannel(IDiscordService discordService, IDispatcherService dispatcherService, CategoryChannel channel, GuildMember selfMember) : + base(discordService, dispatcherService, channel, selfMember) { } diff --git a/src/Quarrel.ViewModels/Bindables/Channels/BindableChannelGroup.cs b/src/Quarrel.ViewModels/Bindables/Channels/BindableChannelGroup.cs index 2b33ea1aa..4552cbb2e 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/BindableChannelGroup.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/BindableChannelGroup.cs @@ -2,6 +2,7 @@ using Quarrel.Bindables.Abstract; using Quarrel.Bindables.Channels.Abstract; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using System.Collections; using System.Collections.Generic; @@ -15,8 +16,8 @@ namespace Quarrel.Bindables.Channels /// public class BindableChannelGroup : BindableItem, IGrouping { - internal BindableChannelGroup(IDispatcherService dispatcherService, BindableCategoryChannel? key) : - base(dispatcherService) + internal BindableChannelGroup(IDiscordService discordService, IDispatcherService dispatcherService, BindableCategoryChannel? key) : + base(discordService, dispatcherService) { Key = key; Children = new ObservableCollection(); diff --git a/src/Quarrel.ViewModels/Bindables/Channels/BindableDirectChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/BindableDirectChannel.cs new file mode 100644 index 000000000..bb94019d4 --- /dev/null +++ b/src/Quarrel.ViewModels/Bindables/Channels/BindableDirectChannel.cs @@ -0,0 +1,38 @@ +// Quarrel © 2022 + +using CommunityToolkit.Diagnostics; +using Quarrel.Bindables.Channels.Abstract; +using Quarrel.Bindables.Channels.Interfaces; +using Quarrel.Bindables.Users; +using Quarrel.Client.Models.Channels; +using Quarrel.Client.Models.Channels.Interfaces; +using Quarrel.Services.Discord; +using Quarrel.Services.Dispatcher; + +namespace Quarrel.Bindables.Channels +{ + /// + /// A wrapper of an that can be bound to the UI. + /// + public class BindableDirectChannel : BindablePrivateChannel, IBindableMessageChannel + { + internal BindableDirectChannel(IDiscordService discordService, IDispatcherService dispatcherService, DirectChannel directChannel) : + base(discordService, dispatcherService, directChannel) + { + BindableUser? user = _discordService.GetUser(DirectChannel.RecipientId); + Guard.IsNotNull(user); + Recipient = user; + } + + /// + public IDirectChannel DirectChannel => (IDirectChannel)Channel; + + /// + public override string? Name => Recipient.User.Username; + + /// + /// Gets the recipient of the direct messages as a . + /// + public BindableUser Recipient { get; } + } +} diff --git a/src/Quarrel.ViewModels/Bindables/Channels/BindableGroupChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/BindableGroupChannel.cs new file mode 100644 index 000000000..2fe2394fe --- /dev/null +++ b/src/Quarrel.ViewModels/Bindables/Channels/BindableGroupChannel.cs @@ -0,0 +1,61 @@ +// Quarrel © 2022 + +using CommunityToolkit.Diagnostics; +using Quarrel.Bindables.Channels.Abstract; +using Quarrel.Bindables.Channels.Interfaces; +using Quarrel.Bindables.Users; +using Quarrel.Client.Models.Channels; +using Quarrel.Client.Models.Channels.Interfaces; +using Quarrel.Services.Discord; +using Quarrel.Services.Dispatcher; +using Quarrel.Services.Localization; +using System.Linq; + +namespace Quarrel.Bindables.Channels +{ + /// + /// A wrapper of an that can be bound to the UI. + /// + public class BindableGroupChannel : BindablePrivateChannel, IBindableMessageChannel + { + private ILocalizationService _localizationService; + + internal BindableGroupChannel(IDiscordService discordService, ILocalizationService localizationService, IDispatcherService dispatcherService, GroupChannel groupChannel) : + base(discordService, dispatcherService, groupChannel) + { + _localizationService = localizationService; + + Guard.IsNotNull(groupChannel.Recipients); + Recipients = new BindableUser[groupChannel.Recipients.Length]; + int i = 0; + foreach (var recipient in groupChannel.Recipients) + { + BindableUser? user = _discordService.GetUser(recipient.Id); + Guard.IsNotNull(user); + Recipients[i] = user; + i++; + } + } + + /// + public IGroupChannel GroupChannel => (IGroupChannel)Channel; + + /// + public override string? Name => Channel.Name ?? _localizationService.CommaList(Recipients.Select(x => x.User.Username).ToArray()); + + /// + /// Gets the icon url of the group channel. + /// + public string? IconUrl => GroupChannel.Icon is null ? null : $"https://cdn.discordapp.com/channel-icons/{Channel.Id}/{GroupChannel.Icon}.png"; + + /// + /// Gets the recipients of the group channel as a array. + /// + public BindableUser[] Recipients { get; } + + /// + /// Gets the number of members in the group channel. + /// + public int MemberCount => Recipients.Length + 1; + } +} diff --git a/src/Quarrel.ViewModels/Bindables/Channels/BindableTextChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/BindableTextChannel.cs index c51c7e376..70b60cec7 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/BindableTextChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/BindableTextChannel.cs @@ -5,6 +5,7 @@ using Quarrel.Client.Models.Channels; using Quarrel.Client.Models.Channels.Interfaces; using Quarrel.Client.Models.Users; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; namespace Quarrel.Bindables.Channels @@ -14,13 +15,10 @@ namespace Quarrel.Bindables.Channels /// public class BindableTextChannel : BindableGuildChannel, IBindableMessageChannel { - internal BindableTextChannel(IDispatcherService dispatcherService, GuildTextChannel channel, GuildMember selfMember, BindableCategoryChannel? parent = null) : - base(dispatcherService, channel, selfMember, parent) + internal BindableTextChannel(IDiscordService discordService, IDispatcherService dispatcherService, GuildTextChannel channel, GuildMember selfMember, BindableCategoryChannel? parent = null) : + base(discordService, dispatcherService, channel, selfMember, parent) { } - - /// - public ulong Id => Channel.Id; /// public override bool IsTextChannel => true; diff --git a/src/Quarrel.ViewModels/Bindables/Channels/BindableVoiceChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/BindableVoiceChannel.cs index 02ba5ce51..675240fab 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/BindableVoiceChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/BindableVoiceChannel.cs @@ -3,6 +3,7 @@ using Quarrel.Bindables.Channels.Abstract; using Quarrel.Client.Models.Channels; using Quarrel.Client.Models.Users; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; namespace Quarrel.Bindables.Channels @@ -12,8 +13,8 @@ namespace Quarrel.Bindables.Channels /// public class BindableVoiceChannel : BindableGuildChannel { - internal BindableVoiceChannel(IDispatcherService dispatcherService, VoiceChannel channel, GuildMember selfMember, BindableCategoryChannel? parent = null) : - base(dispatcherService, channel, selfMember, parent) + internal BindableVoiceChannel(IDiscordService discordService, IDispatcherService dispatcherService, VoiceChannel channel, GuildMember selfMember, BindableCategoryChannel? parent = null) : + base(discordService, dispatcherService, channel, selfMember, parent) { } diff --git a/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuild.cs b/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuild.cs index 56534930f..823cd2933 100644 --- a/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuild.cs +++ b/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuild.cs @@ -2,35 +2,35 @@ using Microsoft.Toolkit.Mvvm.ComponentModel; using Quarrel.Bindables.Abstract; +using Quarrel.Bindables.Channels; +using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Bindables.Guilds.Interfaces; +using Quarrel.Client.Models.Channels.Interfaces; using Quarrel.Client.Models.Guilds; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using System; +using System.Collections.Generic; namespace Quarrel.Bindables.Guilds { /// /// A wrapper of a that can be bound to the UI. /// - public partial class BindableGuild : SelectableItem, IBindableGuildListItem + public partial class BindableGuild : SelectableItem, IBindableSelectableGuildItem, IBindableGuildListItem { [AlsoNotifyChangeFor(nameof(IconUrl))] [AlsoNotifyChangeFor(nameof(IconUri))] [ObservableProperty] private Guild _guild; - internal BindableGuild(IDispatcherService dispatcherService, Guild guild) : - base(dispatcherService) + internal BindableGuild(IDiscordService discordService, IDispatcherService dispatcherService, Guild guild) : + base(discordService, dispatcherService) { _guild = guild; } - /// - /// The id of the selected channel in the guild. - /// - /// - /// This is used to reopen a channel when navigating to a guild. - /// + /// public ulong? SelectedChannelId { get; set; } /// @@ -45,5 +45,48 @@ internal BindableGuild(IDispatcherService dispatcherService, Guild guild) : /// public string? Name => Guild.Name; + + /// + public IEnumerable? GetGroupedChannels(out IBindableSelectableChannel? selectedChannel) + { + var channels = _discordService.GetGuildChannels(this, out selectedChannel); + + var groups = new Dictionary + { + { 0, new BindableChannelGroup(_discordService, _dispatcherService, null) } + }; + + foreach (var channel in channels) + { + if (channel is BindableCategoryChannel bindableCategory) + { + groups.Add(channel.Channel.Id, new BindableChannelGroup(_discordService, _dispatcherService, bindableCategory)); + } + } + + foreach (var channel in channels) + { + if (channel is not null && channel is not BindableCategoryChannel) + { + ulong parentId = 0; + if (channel.Channel is INestedChannel nestedChannel) + { + parentId = nestedChannel.CategoryId ?? 0; + } + + if (groups.TryGetValue(parentId, out var group)) + { + group.AddChild(channel); + } + } + } + + if (groups[0].Children.Count == 0) + { + groups.Remove(0); + } + + return groups.Values; + } } } diff --git a/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuildFolder.cs b/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuildFolder.cs index a75fe2cc1..eb3dec06f 100644 --- a/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuildFolder.cs +++ b/src/Quarrel.ViewModels/Bindables/Guilds/BindableGuildFolder.cs @@ -4,6 +4,7 @@ using Quarrel.Bindables.Abstract; using Quarrel.Bindables.Guilds.Interfaces; using Quarrel.Client.Models.Settings; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using System.Collections.ObjectModel; @@ -17,8 +18,8 @@ public partial class BindableGuildFolder : BindableItem, IBindableGuildListItem [ObservableProperty] private GuildFolder _folder; - internal BindableGuildFolder(IDispatcherService dispatcherService, GuildFolder folder) : - base(dispatcherService) + internal BindableGuildFolder(IDiscordService discordService, IDispatcherService dispatcherService, GuildFolder folder) : + base(discordService, dispatcherService) { _folder = folder; @@ -26,7 +27,7 @@ internal BindableGuildFolder(IDispatcherService dispatcherService, GuildFolder f Children = new ObservableCollection(); foreach (var guild in guilds) { - Children.Add(new BindableGuild(dispatcherService, guild)); + Children.Add(new BindableGuild(discordService, dispatcherService, guild)); } } diff --git a/src/Quarrel.ViewModels/Bindables/Guilds/BindableHomeItem.cs b/src/Quarrel.ViewModels/Bindables/Guilds/BindableHomeItem.cs new file mode 100644 index 000000000..26fa074d6 --- /dev/null +++ b/src/Quarrel.ViewModels/Bindables/Guilds/BindableHomeItem.cs @@ -0,0 +1,53 @@ +// Quarrel © 2022 + +using Quarrel.Bindables.Abstract; +using Quarrel.Bindables.Channels; +using Quarrel.Bindables.Channels.Interfaces; +using Quarrel.Bindables.Guilds.Interfaces; +using Quarrel.Services.Discord; +using Quarrel.Services.Dispatcher; +using Quarrel.Services.Localization; +using System.Collections.Generic; + +namespace Quarrel.Bindables.Guilds +{ + /// + /// An artifical guild item for selecting DMs. + /// + public class BindableHomeItem : SelectableItem, IBindableSelectableGuildItem, IBindableGuildListItem + { + private const string HomeResouece = "Guilds/Home"; + private readonly ILocalizationService _localizationService; + + /// + /// Initializes a new isntance of the class. + /// + public BindableHomeItem(IDiscordService discordService, IDispatcherService dispatcherService, ILocalizationService localizationService) : + base(discordService, dispatcherService) + { + _localizationService = localizationService; + } + + /// + public string? Name => _localizationService[HomeResouece]; + + /// + public ulong? SelectedChannelId { get; set; } + + /// + public IEnumerable? GetGroupedChannels(out IBindableSelectableChannel? selected) + { + var channels = _discordService.GetPrivateChannels(this, out selected); + var group = new BindableChannelGroup(_discordService, _dispatcherService, null); + foreach (var channel in channels) + { + if (channel is not null) + { + group.AddChild(channel); + } + } + + return new BindableChannelGroup[] { group }; + } + } +} diff --git a/src/Quarrel.ViewModels/Bindables/Guilds/Interfaces/IBindableSelectableGuildItem.cs b/src/Quarrel.ViewModels/Bindables/Guilds/Interfaces/IBindableSelectableGuildItem.cs new file mode 100644 index 000000000..fec19838d --- /dev/null +++ b/src/Quarrel.ViewModels/Bindables/Guilds/Interfaces/IBindableSelectableGuildItem.cs @@ -0,0 +1,25 @@ +// Quarrel © 2022 + +using Quarrel.Bindables.Channels; +using Quarrel.Bindables.Channels.Interfaces; +using Quarrel.Bindables.Interfaces; +using System.Collections.Generic; + +namespace Quarrel.Bindables.Guilds.Interfaces +{ + /// + /// An interface for items that can be treated as a selected guild. + /// + public interface IBindableSelectableGuildItem : IBindableGuildListItem, ISelectableItem + { + /// + /// The id of the selected channel in the guild. + /// + /// + /// This is used to reopen a channel when navigating to a guild. + /// + ulong? SelectedChannelId { get; set; } + + IEnumerable? GetGroupedChannels(out IBindableSelectableChannel? selected); + } +} diff --git a/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs b/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs index 874bf6525..c1f81d895 100644 --- a/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs +++ b/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs @@ -3,6 +3,7 @@ using Microsoft.Toolkit.Mvvm.ComponentModel; using Quarrel.Bindables.Abstract; using Quarrel.Client.Models.Messages; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; namespace Quarrel.Bindables.Messages @@ -18,8 +19,8 @@ public partial class BindableMessage : SelectableItem /// /// Initializes a new instance of the class. /// - internal BindableMessage(IDispatcherService dispatcherService, Message message) : - base(dispatcherService) + internal BindableMessage(IDiscordService discordService, IDispatcherService dispatcherService, Message message) : + base(discordService, dispatcherService) { _message = message; } diff --git a/src/Quarrel.ViewModels/Bindables/Users/BindableSelfUser.cs b/src/Quarrel.ViewModels/Bindables/Users/BindableSelfUser.cs index 99a385307..7edaf01ce 100644 --- a/src/Quarrel.ViewModels/Bindables/Users/BindableSelfUser.cs +++ b/src/Quarrel.ViewModels/Bindables/Users/BindableSelfUser.cs @@ -4,6 +4,7 @@ using Quarrel.Bindables.Abstract; using Quarrel.Bindables.Users.Interfaces; using Quarrel.Client.Models.Users; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using System; @@ -23,8 +24,8 @@ public partial class BindableSelfUser : BindableItem, IBindableUser /// /// Initializes a new instance of the class. /// - internal BindableSelfUser(IDispatcherService dispatcherService, SelfUser selfUser) : - base(dispatcherService) + internal BindableSelfUser(IDiscordService discordService, IDispatcherService dispatcherService, SelfUser selfUser) : + base(discordService, dispatcherService) { _selfUser = selfUser; } diff --git a/src/Quarrel.ViewModels/Bindables/Users/BindableUser.cs b/src/Quarrel.ViewModels/Bindables/Users/BindableUser.cs index c3a8c60c9..bddcf1f2e 100644 --- a/src/Quarrel.ViewModels/Bindables/Users/BindableUser.cs +++ b/src/Quarrel.ViewModels/Bindables/Users/BindableUser.cs @@ -3,6 +3,7 @@ using Quarrel.Bindables.Abstract; using Quarrel.Bindables.Users.Interfaces; using Quarrel.Client.Models.Users; +using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using System; @@ -18,8 +19,8 @@ public partial class BindableUser : BindableItem, IBindableUser /// /// Initializes a new instance of the class. /// - internal BindableUser(IDispatcherService dispatcherService, User user) : - base(dispatcherService) + internal BindableUser(IDiscordService discordService, IDispatcherService dispatcherService, User user) : + base(discordService, dispatcherService) { _user = user; } diff --git a/src/Quarrel.ViewModels/Extensions/System/EnumExtensions.cs b/src/Quarrel.ViewModels/Extensions/System/EnumExtensions.cs index af9ed6105..0cc0169a6 100644 --- a/src/Quarrel.ViewModels/Extensions/System/EnumExtensions.cs +++ b/src/Quarrel.ViewModels/Extensions/System/EnumExtensions.cs @@ -5,8 +5,14 @@ namespace System { + /// + /// A static class containing extensions on enums. + /// public static class EnumExtensions { + /// + /// Gets the string value of an that has a . + /// public static string GetStringValue(this Enum value) { Type type = value.GetType(); diff --git a/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs b/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs index 144756bc7..fcbc24b97 100644 --- a/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs +++ b/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs @@ -30,9 +30,21 @@ public partial class DiscordService return null; } - return new BindableSelfUser(_dispatcherService, user); + return new BindableSelfUser(this, _dispatcherService, user); } - + + /// + public BindableUser? GetUser(ulong id) + { + var user = _quarrelClient.GetUser(id); + if (user is not null) + { + return new BindableUser(this, _dispatcherService, user); + } + + return null; + } + /// public BindableGuild[] GetMyGuilds() { @@ -40,7 +52,7 @@ public BindableGuild[] GetMyGuilds() BindableGuild[] guilds = new BindableGuild[rawGuilds.Length]; for (int i = 0; i < rawGuilds.Length; i++) { - guilds[i] = new BindableGuild(_dispatcherService, rawGuilds[i]); + guilds[i] = new BindableGuild(this, _dispatcherService, rawGuilds[i]); } return guilds; @@ -53,7 +65,7 @@ public BindableGuildFolder[] GetMyGuildFolders() BindableGuildFolder[] folders = new BindableGuildFolder[rawFolders.Length]; for (int i = 0; i < rawFolders.Length; i++) { - folders[i] = new BindableGuildFolder(_dispatcherService, rawFolders[i]); + folders[i] = new BindableGuildFolder(this, _dispatcherService, rawFolders[i]); } return folders; @@ -67,7 +79,7 @@ public async Task GetChannelMessagesAsync(IBindableMessageCha BindableMessage[] messages = new BindableMessage[rawMessages.Length]; for (int i = 0; i < messages.Length; i++) { - messages[i] = new BindableMessage(_dispatcherService, rawMessages[i]); + messages[i] = new BindableMessage(this, _dispatcherService, rawMessages[i]); } return messages; @@ -105,7 +117,7 @@ public async Task GetChannelMessagesAsync(IBindableMessageCha var channel = rawChannels[i]; if (channel is CategoryChannel categoryChannel) { - var bindableCategoryChannel = new BindableCategoryChannel(_dispatcherService, categoryChannel, member); + var bindableCategoryChannel = new BindableCategoryChannel(this, _dispatcherService, categoryChannel, member); categories.Add(channel.Id, bindableCategoryChannel); channels[i] = bindableCategoryChannel; } @@ -122,7 +134,7 @@ public async Task GetChannelMessagesAsync(IBindableMessageCha category = categories[nestedChannel.CategoryId.Value]; } - channel = BindableGuildChannel.Create(_dispatcherService, nestedChannel, member, category); + channel = BindableGuildChannel.Create(this, _localizationService, _dispatcherService, nestedChannel, member, category); if (channel is not null && (channel.Channel.Id == guild.SelectedChannelId || (selectedChannel is null && channel.IsAccessible)) && channel is IBindableSelectableChannel messageChannel) @@ -134,48 +146,28 @@ public async Task GetChannelMessagesAsync(IBindableMessageCha return channels; } - + /// - public IEnumerable? GetGuildChannelsGrouped(BindableGuild guild, out IBindableSelectableChannel? selectedChannel) + public BindablePrivateChannel?[] GetPrivateChannels(BindableHomeItem home, out IBindableSelectableChannel? selectedChannel) { - var channels = GetGuildChannels(guild, out selectedChannel); - - var groups = new Dictionary - { - { 0, new BindableChannelGroup(_dispatcherService, null) } - }; - - foreach (var channel in channels) + selectedChannel = null; + IPrivateChannel[] rawChannels = _quarrelClient.GetPrivateChannels(); + BindablePrivateChannel?[] channels = new BindablePrivateChannel[rawChannels.Length]; + int i = 0; + foreach (var channel in rawChannels) { - if (channel is BindableCategoryChannel bindableCategory) - { - groups.Add(channel.Channel.Id, new BindableChannelGroup(_dispatcherService, bindableCategory)); - } - } + channels[i] = BindablePrivateChannel.Create(this, _localizationService, _dispatcherService, channel); - foreach (var channel in channels) - { - if (channel is not null && channel is not BindableCategoryChannel) + if (channels[i] is IBindableSelectableChannel selectableChannel && + selectableChannel.Id == home.SelectedChannelId) { - ulong parentId = 0; - if (channel.Channel is INestedChannel nestedChannel) - { - parentId = nestedChannel.CategoryId ?? 0; - } - - if (groups.TryGetValue(parentId, out var group)) - { - group.AddChild(channel); - } + selectedChannel = selectableChannel; } - } - if (groups[0].Children.Count == 0) - { - groups.Remove(0); + i++; } - return groups.Values; + return channels; } } } diff --git a/src/Quarrel.ViewModels/Services/Discord/DiscordService.cs b/src/Quarrel.ViewModels/Services/Discord/DiscordService.cs index 57ca6d04d..5a94b547a 100644 --- a/src/Quarrel.ViewModels/Services/Discord/DiscordService.cs +++ b/src/Quarrel.ViewModels/Services/Discord/DiscordService.cs @@ -2,6 +2,8 @@ using CommunityToolkit.Diagnostics; using Microsoft.Toolkit.Mvvm.Messaging; +using Quarrel.Bindables.Channels.Abstract; +using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Client; using Quarrel.Client.Models.Users; using Quarrel.Messages; @@ -9,6 +11,7 @@ using Quarrel.Services.Analytics.Enums; using Quarrel.Services.Analytics.Models; using Quarrel.Services.Dispatcher; +using Quarrel.Services.Localization; using Quarrel.Services.Storage.Accounts.Models; using System; using System.Threading.Tasks; @@ -22,15 +25,17 @@ public partial class DiscordService : IDiscordService { private readonly QuarrelClient _quarrelClient; private readonly IAnalyticsService _analyticsService; + private readonly ILocalizationService _localizationService; private readonly IDispatcherService _dispatcherService; private readonly IMessenger _messenger; /// /// Initializes a new instance of the class. /// - public DiscordService(IAnalyticsService analyticsService, IDispatcherService dispatcherService, IMessenger messenger) + public DiscordService(IAnalyticsService analyticsService, ILocalizationService localizationService, IDispatcherService dispatcherService, IMessenger messenger) { _analyticsService = analyticsService; + _localizationService = localizationService; _dispatcherService = dispatcherService; _messenger = messenger; _quarrelClient = new QuarrelClient(); diff --git a/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs b/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs index b1844a6a9..0eedddeb1 100644 --- a/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs +++ b/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs @@ -1,15 +1,11 @@ // Quarrel © 2022 -using Quarrel.Bindables.Channels; using Quarrel.Bindables.Channels.Abstract; using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Bindables.Guilds; using Quarrel.Bindables.Messages; using Quarrel.Bindables.Users; -using Quarrel.Client.Models.Channels.Interfaces; -using Quarrel.Client.Models.Guilds; using Quarrel.Services.Analytics.Enums; -using System.Collections.Generic; using System.Threading.Tasks; namespace Quarrel.Services.Discord @@ -25,6 +21,13 @@ public interface IDiscordService /// The current user as a . BindableSelfUser? GetMe(); + /// + /// Gets a user by id. + /// + /// The id of the user to get. + /// The user of an id. + BindableUser? GetUser(ulong userId); + /// /// Logs into the discord service by token. /// @@ -58,16 +61,16 @@ public interface IDiscordService /// Gets the channels in a guild. /// /// The guild to get the channels for. - /// An array of s from the guild. /// The selected channel as an . + /// An array of s from the guild. BindableGuildChannel?[] GetGuildChannels(BindableGuild guild, out IBindableSelectableChannel? selectedChannel); /// - /// Gets the channels in a guild as channel groups by category. + /// Gets the user's direct message channels. /// - /// The to get the channels from. + /// The . /// The selected channel as an . - /// The s for the guild in category groups. - IEnumerable? GetGuildChannelsGrouped(BindableGuild guild, out IBindableSelectableChannel? selectedChannel); + /// An array of s. + BindablePrivateChannel?[] GetPrivateChannels(BindableHomeItem home, out IBindableSelectableChannel? selectedChannel); } } diff --git a/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs b/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs index cd303efbf..306c7bf79 100644 --- a/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs +++ b/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs @@ -22,6 +22,11 @@ public interface ILocalizationService /// Localized if valid, otherwise returns an empty . string this[string key, params object[] args] { get; } + /// + /// Gets a list of items as as a string with and. + /// + string CommaList(params string[] args); + /// /// Gets a value indicating whether or not the current language is written right to left. /// diff --git a/src/Quarrel.ViewModels/Services/Windows/IWindowService.cs b/src/Quarrel.ViewModels/Services/Windows/IWindowService.cs index 6ba74c60a..634fcc94c 100644 --- a/src/Quarrel.ViewModels/Services/Windows/IWindowService.cs +++ b/src/Quarrel.ViewModels/Services/Windows/IWindowService.cs @@ -4,8 +4,14 @@ namespace Quarrel.Services.Windows { + /// + /// An interface for a service that handles multi-window operations. + /// public interface IWindowService { + /// + /// A method that opens a secondary window. + /// void OpenSecondaryWindow(); } } diff --git a/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs b/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs index 4518f5dd1..fafb55235 100644 --- a/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs @@ -5,8 +5,10 @@ using Microsoft.Toolkit.Mvvm.Messaging; using Quarrel.Bindables.Users; using Quarrel.Messages; +using Quarrel.Messages.Navigation.SubPages; using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; +using Quarrel.ViewModels.SubPages.Settings; namespace Quarrel.ViewModels { @@ -46,6 +48,7 @@ public CurrentUserViewModel(IMessenger messenger, IDiscordService discordService [ICommand] public void NavigateToSettings() { + _messenger.Send(new NavigateToSubPageMessage(typeof(UserSettingsPageViewModel))); } } } diff --git a/src/Quarrel.ViewModels/ViewModels/Panels/ChannelsViewModel.cs b/src/Quarrel.ViewModels/ViewModels/Panels/ChannelsViewModel.cs index 1d0b4e06e..dae0865a8 100644 --- a/src/Quarrel.ViewModels/ViewModels/Panels/ChannelsViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/Panels/ChannelsViewModel.cs @@ -5,6 +5,7 @@ using Quarrel.Bindables.Channels; using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Bindables.Guilds; +using Quarrel.Bindables.Guilds.Interfaces; using Quarrel.Messages.Navigation; using Quarrel.Services.Discord; using System.Collections.Generic; @@ -19,7 +20,7 @@ public partial class ChannelsViewModel : ObservableRecipient private readonly IMessenger _messenger; private readonly IDiscordService _discordService; - private BindableGuild? _currentGuild; + private IBindableSelectableGuildItem? _currentGuild; private IBindableSelectableChannel? _selectedChannel; private IEnumerable? _groupedSource; @@ -32,7 +33,7 @@ public ChannelsViewModel(IMessenger messenger, IDiscordService discordService) _messenger = messenger; _discordService = discordService; - _messenger.Register>(this, (_, m) => LoadChannels(m.Guild)); + _messenger.Register>(this, (_, m) => LoadChannels(m.Guild)); } /// @@ -73,7 +74,7 @@ public IEnumerable? GroupedSource /// Loads the channels for a guild. /// /// The guild to load. - public void LoadChannels(BindableGuild guild) + public void LoadChannels(IBindableSelectableGuildItem guild) { if (guild == _currentGuild) { @@ -81,7 +82,7 @@ public void LoadChannels(BindableGuild guild) } _currentGuild = guild; - var channels = _discordService.GetGuildChannelsGrouped(guild, out IBindableSelectableChannel? selected); + var channels = guild.GetGroupedChannels(out IBindableSelectableChannel? selected); GroupedSource = channels; SelectedChannel = selected; } diff --git a/src/Quarrel.ViewModels/ViewModels/Panels/GuildsViewModel.cs b/src/Quarrel.ViewModels/ViewModels/Panels/GuildsViewModel.cs index ac3d080c3..5b08a664c 100644 --- a/src/Quarrel.ViewModels/ViewModels/Panels/GuildsViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/Panels/GuildsViewModel.cs @@ -8,6 +8,7 @@ using Quarrel.Messages.Navigation; using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; +using Quarrel.Services.Localization; using System.Collections.Concurrent; using System.Collections.ObjectModel; @@ -19,18 +20,20 @@ namespace Quarrel.ViewModels public partial class GuildsViewModel : ObservableRecipient { private readonly IMessenger _messenger; + private readonly ILocalizationService _localizationService; private readonly IDiscordService _discordService; private readonly IDispatcherService _dispatcherService; private readonly ConcurrentDictionary _guilds; - private BindableGuild? _selectedGuild; + private IBindableSelectableGuildItem? _selectedGuild; /// /// Initializes a new instance of the class. /// - public GuildsViewModel(IMessenger messenger, IDiscordService discordService, IDispatcherService dispatcherService) + public GuildsViewModel(IMessenger messenger, ILocalizationService localizationService, IDiscordService discordService, IDispatcherService dispatcherService) { _messenger = messenger; + _localizationService = localizationService; _discordService = discordService; _dispatcherService = dispatcherService; @@ -44,7 +47,7 @@ public GuildsViewModel(IMessenger messenger, IDiscordService discordService, IDi /// /// Gets or sets the selected guild. /// - public BindableGuild? SelectedGuild + public IBindableSelectableGuildItem? SelectedGuild { get => _selectedGuild; set @@ -55,7 +58,7 @@ public BindableGuild? SelectedGuild if (SetProperty(ref _selectedGuild, value) && value is not null) { value.IsSelected = true; - _messenger.Send(new NavigateToGuildMessage(value)); + _messenger.Send(new NavigateToGuildMessage(value)); } } } @@ -73,6 +76,8 @@ public void LoadGuilds() var folders = _discordService.GetMyGuildFolders(); _dispatcherService.RunOnUIThread(() => { + Source.Clear(); + Source.Add(new BindableHomeItem(_discordService, _dispatcherService, _localizationService)); foreach (var folder in folders) { if (folder.Folder.Id is null) diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/Abstract/UserSettingsSubPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/Abstract/UserSettingsSubPageViewModel.cs new file mode 100644 index 000000000..b7f364810 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/Abstract/UserSettingsSubPageViewModel.cs @@ -0,0 +1,26 @@ +// Quarrel © 2022 + +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract +{ + public abstract class UserSettingsSubPageViewModel : ObservableObject + { + protected readonly ILocalizationService _localizationService; + protected readonly IStorageService _storageService; + + public UserSettingsSubPageViewModel(ILocalizationService localizationService, IStorageService storageService) + { + _localizationService = localizationService; + _storageService = storageService; + } + + public abstract string Glyph { get; } + + public abstract string Title { get; } + + public virtual bool IsActive => false; + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/BehaviorPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/BehaviorPageViewModel.cs new file mode 100644 index 000000000..d19c1a274 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/BehaviorPageViewModel.cs @@ -0,0 +1,24 @@ +// Quarrel © 2022 + +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages +{ + public class BehaviorPageViewModel : UserSettingsSubPageViewModel + { + private const string BehaviorResource = "UserSettings/Behavior"; + + internal BehaviorPageViewModel(ILocalizationService localizationService, IStorageService storageService) : + base(localizationService, storageService) + { + } + + /// + public override string Glyph => ""; + + /// + public override string Title => _localizationService[BehaviorResource]; + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/ConnectionsPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/ConnectionsPageViewModel.cs new file mode 100644 index 000000000..17578450e --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/ConnectionsPageViewModel.cs @@ -0,0 +1,24 @@ +// Quarrel © 2022 + +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages +{ + public class ConnectionsPageViewModel : UserSettingsSubPageViewModel + { + private const string ConnectionsResource = "UserSettings/Connections"; + + internal ConnectionsPageViewModel(ILocalizationService localizationService, IStorageService storageService) : + base(localizationService, storageService) + { + } + + /// + public override string Glyph => ""; + + /// + public override string Title => _localizationService[ConnectionsResource]; + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/DisplayPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/DisplayPageViewModel.cs new file mode 100644 index 000000000..10151ff90 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/DisplayPageViewModel.cs @@ -0,0 +1,24 @@ +// Quarrel © 2022 + +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages +{ + public class DisplayPageViewModel : UserSettingsSubPageViewModel + { + private const string ConnectionsResource = "UserSettings/Display"; + + internal DisplayPageViewModel(ILocalizationService localizationService, IStorageService storageService) : + base(localizationService, storageService) + { + } + + /// + public override string Glyph => ""; + + /// + public override string Title => _localizationService[ConnectionsResource]; + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/MyAccountPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/MyAccountPageViewModel.cs new file mode 100644 index 000000000..71b7c8c1c --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/MyAccountPageViewModel.cs @@ -0,0 +1,73 @@ +// Quarrel © 2022 + +using Quarrel.Bindables.Users; +using Quarrel.Services.Discord; +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages +{ + public class MyAccountPageViewModel : UserSettingsSubPageViewModel + { + private const string MyAccountResource = "UserSettings/MyAccount"; + private readonly IDiscordService _discordService; + + private string? _email; + private string _username; + private int _discriminator; + private string? _aboutMe; + + internal MyAccountPageViewModel(ILocalizationService localizationService, IStorageService storageService, IDiscordService discordService) : + base(localizationService, storageService) + { + _discordService = discordService; + + var user = _discordService.GetMe(); + if (user is not null) + { + SetBaseValues(user); + } + } + + /// + public override string Glyph => ""; + + /// + public override string Title => _localizationService[MyAccountResource]; + + public override bool IsActive => true; + + public string? Email + { + get => _email; + set => SetProperty(ref _email, value); + } + + public string Username + { + get => _username; + set => SetProperty(ref _username, value); + } + + public int Discriminator + { + get => _discriminator; + set => SetProperty(ref _discriminator, value); + } + + public string? AboutMe + { + get => _aboutMe; + set => SetProperty(ref _aboutMe, value); + } + + private void SetBaseValues(BindableSelfUser user) + { + Email = user.SelfUser.Email; + Username = user.SelfUser.Username; + Discriminator = user.SelfUser.Discriminator; + AboutMe = user.SelfUser.Bio; + } + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/NotificationsPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/NotificationsPageViewModel.cs new file mode 100644 index 000000000..4cb13c4e0 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/NotificationsPageViewModel.cs @@ -0,0 +1,24 @@ +// Quarrel © 2022 + +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages +{ + public class NotificationsPageViewModel : UserSettingsSubPageViewModel + { + private const string NotificationsResource = "UserSettings/Notifications"; + + internal NotificationsPageViewModel(ILocalizationService localizationService, IStorageService storageService) : + base(localizationService, storageService) + { + } + + /// + public override string Glyph => ""; + + /// + public override string Title => _localizationService[NotificationsResource]; + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/PrivacyPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/PrivacyPageViewModel.cs new file mode 100644 index 000000000..bc592a857 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/PrivacyPageViewModel.cs @@ -0,0 +1,73 @@ +// Quarrel © 2022 + +using Discord.API.Models.Enums.Settings; +using Quarrel.Services.Discord; +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages +{ + public class PrivacyPageViewModel : UserSettingsSubPageViewModel + { + private const string PrivacyResource = "UserSettings/Privacy"; + private readonly IDiscordService _discordService; + private ExplicitContentFilterLevel _contentFilterLevel; + + internal PrivacyPageViewModel(ILocalizationService localizationService, IStorageService storageService, IDiscordService discordService) : + base(localizationService, storageService) + { + _discordService = discordService; + } + + public override string Glyph => ""; + + public override string Title => _localizationService[PrivacyResource]; + + public override bool IsActive => true; + + private ExplicitContentFilterLevel ContentFilterLevel + { + get => _contentFilterLevel; + set + { + if (SetProperty(ref _contentFilterLevel, value)) + { + OnPropertyChanged(nameof(FilterNone)); + OnPropertyChanged(nameof(FilterPublic)); + OnPropertyChanged(nameof(FilterAll)); + } + } + } + + public bool FilterAll + { + get => _contentFilterLevel == ExplicitContentFilterLevel.All; + set + { + if (!value) return; + ContentFilterLevel = ExplicitContentFilterLevel.All; + } + } + + public bool FilterPublic + { + get => _contentFilterLevel == ExplicitContentFilterLevel.Public; + set + { + if (!value) return; + ContentFilterLevel = ExplicitContentFilterLevel.Public; + } + } + + public bool FilterNone + { + get => _contentFilterLevel == ExplicitContentFilterLevel.None; + set + { + if (!value) return; + ContentFilterLevel = ExplicitContentFilterLevel.None; + } + } + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/VoicePageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/VoicePageViewModel.cs new file mode 100644 index 000000000..cc742acd7 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/VoicePageViewModel.cs @@ -0,0 +1,24 @@ +// Quarrel © 2022 + +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; + +namespace Quarrel.ViewModels.SubPages.UserSettings.Pages +{ + public class VoicePageViewModel : UserSettingsSubPageViewModel + { + private const string VoiceResource = "UserSettings/Voice"; + + internal VoicePageViewModel(ILocalizationService localizationService, IStorageService storageService) : + base(localizationService, storageService) + { + } + + /// + public override string Glyph => ""; + + /// + public override string Title => _localizationService[VoiceResource]; + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsPageViewModel.cs new file mode 100644 index 000000000..9ad3b4dd2 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsPageViewModel.cs @@ -0,0 +1,43 @@ +// Quarrel © 2022 + +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Quarrel.Services.Discord; +using Quarrel.Services.Localization; +using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings.Pages; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; +using System.Collections.ObjectModel; + +namespace Quarrel.ViewModels.SubPages.Settings +{ + public class UserSettingsPageViewModel : ObservableObject + { + private readonly ILocalizationService _localizationService; + + private UserSettingsSubPageViewModel _selectedSubPage; + + public UserSettingsPageViewModel(ILocalizationService localizationService, IStorageService storageService, IDiscordService discordService) + { + _localizationService = localizationService; + + Pages = new ObservableCollection(); + + Pages.Add(new MyAccountPageViewModel(localizationService, storageService, discordService)); + Pages.Add(new PrivacyPageViewModel(_localizationService, storageService, discordService)); + Pages.Add(new ConnectionsPageViewModel(_localizationService, storageService)); + + Pages.Add(new DisplayPageViewModel(_localizationService, storageService)); + Pages.Add(new BehaviorPageViewModel(_localizationService, storageService)); + Pages.Add(new NotificationsPageViewModel(_localizationService, storageService)); + Pages.Add(new VoicePageViewModel(_localizationService, storageService)); + } + + public UserSettingsSubPageViewModel SelectedSubPage + { + get => _selectedSubPage; + set => SetProperty(ref _selectedSubPage, value); + } + + public ObservableCollection Pages { get; } + } +} diff --git a/src/Quarrel/App.Services.cs b/src/Quarrel/App.Services.cs index 3250ed46c..24fb2bf30 100644 --- a/src/Quarrel/App.Services.cs +++ b/src/Quarrel/App.Services.cs @@ -19,6 +19,7 @@ using Quarrel.ViewModels.SubPages.DiscordStatus; using Quarrel.ViewModels.SubPages.Host; using Quarrel.ViewModels.SubPages.Meta; +using Quarrel.ViewModels.SubPages.Settings; using System; using Windows.Storage; @@ -63,6 +64,7 @@ private IServiceProvider ConfigureServices() services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); #if DEV ApplyDitryOverrides(services); diff --git a/src/Quarrel/App.xaml b/src/Quarrel/App.xaml index 4ab0dd8dd..1f7879319 100644 --- a/src/Quarrel/App.xaml +++ b/src/Quarrel/App.xaml @@ -20,6 +20,7 @@ + diff --git a/src/Quarrel/Controls/Shell/ExtendedSplashScreen.xaml b/src/Quarrel/Controls/Host/ExtendedSplashScreen.xaml similarity index 95% rename from src/Quarrel/Controls/Shell/ExtendedSplashScreen.xaml rename to src/Quarrel/Controls/Host/ExtendedSplashScreen.xaml index c09901395..858ac41a4 100644 --- a/src/Quarrel/Controls/Shell/ExtendedSplashScreen.xaml +++ b/src/Quarrel/Controls/Host/ExtendedSplashScreen.xaml @@ -1,8 +1,8 @@  - + diff --git a/src/Quarrel/Controls/Shell/Panels/ChannelPanel.xaml b/src/Quarrel/Controls/Panels/Channels/ChannelPanel.xaml similarity index 73% rename from src/Quarrel/Controls/Shell/Panels/ChannelPanel.xaml rename to src/Quarrel/Controls/Panels/Channels/ChannelPanel.xaml index 2f40a79f6..c3bdeab30 100644 --- a/src/Quarrel/Controls/Shell/Panels/ChannelPanel.xaml +++ b/src/Quarrel/Controls/Panels/Channels/ChannelPanel.xaml @@ -1,12 +1,12 @@  - + - - + + - - + + diff --git a/src/Quarrel/Controls/Shell/Panels/ChannelPanel.xaml.cs b/src/Quarrel/Controls/Panels/Channels/ChannelPanel.xaml.cs similarity index 93% rename from src/Quarrel/Controls/Shell/Panels/ChannelPanel.xaml.cs rename to src/Quarrel/Controls/Panels/Channels/ChannelPanel.xaml.cs index f5c24d271..8eb1036a2 100644 --- a/src/Quarrel/Controls/Shell/Panels/ChannelPanel.xaml.cs +++ b/src/Quarrel/Controls/Panels/Channels/ChannelPanel.xaml.cs @@ -1,13 +1,12 @@ // Quarrel © 2022 using Microsoft.Extensions.DependencyInjection; -using Quarrel.Bindables.Channels.Abstract; using Quarrel.Bindables.Channels.Interfaces; using Quarrel.ViewModels.Panels; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Quarrel.Controls.Shell.Panels +namespace Quarrel.Controls.Panels.Channels { public sealed partial class ChannelPanel : UserControl { diff --git a/src/Quarrel/Controls/Shell/CurrentUserButton.xaml b/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml similarity index 97% rename from src/Quarrel/Controls/Shell/CurrentUserButton.xaml rename to src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml index a5e78483b..add0e0d10 100644 --- a/src/Quarrel/Controls/Shell/CurrentUserButton.xaml +++ b/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml @@ -1,5 +1,5 @@  diff --git a/src/Quarrel/Controls/Shell/CurrentUserButton.xaml.cs b/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml.cs similarity index 91% rename from src/Quarrel/Controls/Shell/CurrentUserButton.xaml.cs rename to src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml.cs index 3cea4fc4a..a00b97d87 100644 --- a/src/Quarrel/Controls/Shell/CurrentUserButton.xaml.cs +++ b/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml.cs @@ -4,7 +4,7 @@ using Quarrel.ViewModels; using Windows.UI.Xaml.Controls; -namespace Quarrel.Controls.Shell +namespace Quarrel.Controls.Panels.Channels { public sealed partial class CurrentUserButton : UserControl { diff --git a/src/Quarrel/Controls/Panels/Channels/GuildHeader.xaml b/src/Quarrel/Controls/Panels/Channels/GuildHeader.xaml new file mode 100644 index 000000000..48b27daea --- /dev/null +++ b/src/Quarrel/Controls/Panels/Channels/GuildHeader.xaml @@ -0,0 +1,21 @@ + + + + diff --git a/src/Quarrel/Controls/Panels/Channels/GuildHeader.xaml.cs b/src/Quarrel/Controls/Panels/Channels/GuildHeader.xaml.cs new file mode 100644 index 000000000..2c0f7a7e0 --- /dev/null +++ b/src/Quarrel/Controls/Panels/Channels/GuildHeader.xaml.cs @@ -0,0 +1,19 @@ +// Quarrel © 2022 + +using Microsoft.Extensions.DependencyInjection; +using Quarrel.ViewModels; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.Controls.Panels.Channels +{ + public sealed partial class GuildHeader : UserControl + { + public GuildHeader() + { + this.InitializeComponent(); + DataContext = App.Current.Services.GetRequiredService(); + } + + public GuildsViewModel ViewModel => (GuildsViewModel)DataContext; + } +} diff --git a/src/Quarrel/Controls/Shell/Panels/GuildPanel.xaml b/src/Quarrel/Controls/Panels/Guilds/GuildPanel.xaml similarity index 76% rename from src/Quarrel/Controls/Shell/Panels/GuildPanel.xaml rename to src/Quarrel/Controls/Panels/Guilds/GuildPanel.xaml index cf4441527..3d24d2adb 100644 --- a/src/Quarrel/Controls/Shell/Panels/GuildPanel.xaml +++ b/src/Quarrel/Controls/Panels/Guilds/GuildPanel.xaml @@ -1,25 +1,27 @@  - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Visible + + + + + + + + + + + + + + + diff --git a/src/Quarrel/Controls/Panels/Messages/MessageBox.xaml.cs b/src/Quarrel/Controls/Panels/Messages/MessageBox.xaml.cs new file mode 100644 index 000000000..ec77d2168 --- /dev/null +++ b/src/Quarrel/Controls/Panels/Messages/MessageBox.xaml.cs @@ -0,0 +1,14 @@ +// Quarrel © 2022 + +using Windows.UI.Xaml.Controls; + +namespace Quarrel.Controls.Panels.Messages +{ + public sealed partial class MessageBox : UserControl + { + public MessageBox() + { + this.InitializeComponent(); + } + } +} diff --git a/src/Quarrel/Controls/Panels/Messages/MessagePanel.xaml b/src/Quarrel/Controls/Panels/Messages/MessagePanel.xaml new file mode 100644 index 000000000..aa5eefe67 --- /dev/null +++ b/src/Quarrel/Controls/Panels/Messages/MessagePanel.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Quarrel/Controls/Shell/Panels/MessagePanel.xaml.cs b/src/Quarrel/Controls/Panels/Messages/MessagePanel.xaml.cs similarity index 91% rename from src/Quarrel/Controls/Shell/Panels/MessagePanel.xaml.cs rename to src/Quarrel/Controls/Panels/Messages/MessagePanel.xaml.cs index 163fb9377..8adb04d41 100644 --- a/src/Quarrel/Controls/Shell/Panels/MessagePanel.xaml.cs +++ b/src/Quarrel/Controls/Panels/Messages/MessagePanel.xaml.cs @@ -4,7 +4,7 @@ using Quarrel.ViewModels.Panels; using Windows.UI.Xaml.Controls; -namespace Quarrel.Controls.Shell.Panels +namespace Quarrel.Controls.Panels.Messages { public sealed partial class MessagePanel : UserControl { diff --git a/src/Quarrel/Controls/Shell/Panels/MessagePanel.xaml b/src/Quarrel/Controls/Shell/Panels/MessagePanel.xaml deleted file mode 100644 index 52bbac408..000000000 --- a/src/Quarrel/Controls/Shell/Panels/MessagePanel.xaml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml b/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml index 340cf6e12..9c2953efa 100644 --- a/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml +++ b/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml @@ -1,83 +1,101 @@ - - - - - - - - + - - - - - - - - + + + - - - - - - - - - - - - + + + + + - - - - - - + + - - - - - - - - - - + + + + + + + + + + + diff --git a/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml.cs b/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml.cs index 753a67693..e17c1acd9 100644 --- a/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml.cs +++ b/src/Quarrel/Controls/Shell/QuarrelCommandBar.xaml.cs @@ -9,12 +9,13 @@ using Quarrel.ViewModels.Panels; using Quarrel.ViewModels.SubPages.DiscordStatus; using Quarrel.ViewModels.SubPages.Meta; +using Quarrel.ViewModels.SubPages.Settings; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace Quarrel.Controls.Shell { - public sealed partial class QuarrelCommandBar : CommandBar + public sealed partial class QuarrelCommandBar : UserControl { private readonly IWindowService _windowService; private readonly IMessenger _messenger; @@ -60,6 +61,9 @@ private void GoToDiscordStatus(object sender, RoutedEventArgs e) private void GoToAbout(object sender, RoutedEventArgs e) => _messenger.Send(new NavigateToSubPageMessage(typeof(AboutPageViewModel))); + private void GoToSettings(object sender, RoutedEventArgs e) + => _messenger.Send(new NavigateToSubPageMessage(typeof(UserSettingsPageViewModel))); + private static void OnShowHamburgerButtonPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) { QuarrelCommandBar commandBar = (QuarrelCommandBar)d; @@ -71,7 +75,7 @@ private static void OnToggleMemberButtonPropertyChanged(DependencyObject d, Depe { QuarrelCommandBar commandBar = (QuarrelCommandBar)d; bool newValue = (bool)args.NewValue; - commandBar.ToggleMembersABB.Visibility = newValue ? Visibility.Visible : Visibility.Collapsed; + commandBar.ToggleMembersBTN.Visibility = newValue ? Visibility.Visible : Visibility.Collapsed; } private void OpenInNewWindow(object sender, RoutedEventArgs e) diff --git a/src/Quarrel/Controls/Shell/Shell.xaml b/src/Quarrel/Controls/Shell/Shell.xaml index f1ae1b38b..18d4b27ee 100644 --- a/src/Quarrel/Controls/Shell/Shell.xaml +++ b/src/Quarrel/Controls/Shell/Shell.xaml @@ -6,7 +6,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:qc="using:Quarrel.Controls" - xmlns:panels="using:Quarrel.Controls.Shell.Panels" + xmlns:panelsg="using:Quarrel.Controls.Panels.Guilds" + xmlns:panelsc="using:Quarrel.Controls.Panels.Channels" + xmlns:panelsm="using:Quarrel.Controls.Panels.Messages" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400" @@ -22,14 +24,25 @@ - + - - - + + + + + + + + + + @@ -44,9 +57,10 @@ Background="{ThemeResource PrimaryPaneBackground}" Visibility="Collapsed"/> - + diff --git a/src/Quarrel/Converters/DataTemplates/Channels/MemberCountConverter.cs b/src/Quarrel/Converters/DataTemplates/Channels/MemberCountConverter.cs new file mode 100644 index 000000000..1b9c6831c --- /dev/null +++ b/src/Quarrel/Converters/DataTemplates/Channels/MemberCountConverter.cs @@ -0,0 +1,20 @@ +// Quarrel © 2022 + +using Microsoft.Extensions.DependencyInjection; +using Quarrel.Services.Localization; + +namespace Quarrel.Converters.DataTemplates.Channels +{ + /// + /// Localizes the member count. + /// + public class MemberCountConverter + { + private const string MemberCountResource = "Channels/MemberCount"; + + public static string Convert(int count) + { + return App.Current.Services.GetRequiredService()[MemberCountResource, count]; + } + } +} diff --git a/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageColorConverter.cs b/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageColorConverter.cs new file mode 100644 index 000000000..76fbba72b --- /dev/null +++ b/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageColorConverter.cs @@ -0,0 +1,36 @@ +// Quarrel © 2022 + +using Discord.API.Models.Enums.Messages; +using Windows.UI.Xaml.Media; + +namespace Quarrel.Converters.DataTemplates.Messages +{ + public class InfoMessageColorConverter + { + public static Brush Convert(MessageType type) + { + string resource = type switch + { + MessageType.RecipientAdd or + MessageType.GuildMemberJoin or + MessageType.ChannelFollowAdd or + MessageType.GuildDiscoveryRequalified or + MessageType.ThreadCreated=> "DiscordGreenBrush", + + MessageType.ChannelNameChange or + MessageType.ChannelIconChange or + MessageType.GuildDiscoveryGracePeriodInitialWarning => "DiscordYellowBrush", + + MessageType.GuildDiscoveryDisqualified or + MessageType.GuildDiscoveryGracePeriodFinalWarning or + MessageType.RecipientRemove => "DiscordRedBrush", + + MessageType.Call or + MessageType.ChannelPinnedMessage or + _ => "InvertedBackground", + }; + + return (Brush)App.Current.Resources[resource]; + } + } +} diff --git a/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageContentConverter.cs b/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageContentConverter.cs new file mode 100644 index 000000000..a190269e2 --- /dev/null +++ b/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageContentConverter.cs @@ -0,0 +1,29 @@ +// Quarrel © 2022 + +using Discord.API.Models.Enums.Messages; +using Microsoft.Extensions.DependencyInjection; +using Quarrel.Bindables.Messages; +using Quarrel.Services.Localization; + +namespace Quarrel.Converters.DataTemplates.Messages +{ + public class InfoMessageContentConverter + { + public static string Convert(BindableMessage message) + { + string resource = message.Message.Type switch + { + MessageType.RecipientAdd => "InfoMessage/RecipientAdd", + MessageType.RecipientRemove => "InfoMessage/RecipientRemove", + MessageType.Call => "InfoMessage/Call", + MessageType.ChannelNameChange => "InfoMessage/ChannelNameChange", + MessageType.ChannelIconChange => "InfoMessage/ChannelIconChange", + MessageType.ChannelPinnedMessage => "InfoMessage/ChannelPinnedMessage", + MessageType.GuildMemberJoin => "InfoMessage/GuildMemberJoin", + _ => "InfoMessage/Unknown", + }; + + return App.Current.Services.GetRequiredService()[resource]; + } + } +} diff --git a/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageIconConverter.cs b/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageIconConverter.cs new file mode 100644 index 000000000..8c3ec47a2 --- /dev/null +++ b/src/Quarrel/Converters/DataTemplates/Messages/InfoMessageIconConverter.cs @@ -0,0 +1,28 @@ +// Quarrel © 2022 + +using Discord.API.Models.Enums.Messages; + +namespace Quarrel.Converters.DataTemplates.Messages +{ + public class InfoMessageIconConverter + { + public static string Convert(MessageType type) + { + return type switch + { + MessageType.Call => "", + + MessageType.ChannelPinnedMessage => "", + + MessageType.ChannelIconChange or + MessageType.ChannelNameChange => "", + + MessageType.GuildMemberJoin or + MessageType.RecipientAdd => "", + + MessageType.RecipientRemove => "", + _ => "?", + }; + } + } +} diff --git a/src/Quarrel/Converters/Time/SmartTimeFormatConverter.cs b/src/Quarrel/Converters/Time/SmartTimeFormatConverter.cs new file mode 100644 index 000000000..c2bf1edc8 --- /dev/null +++ b/src/Quarrel/Converters/Time/SmartTimeFormatConverter.cs @@ -0,0 +1,76 @@ +// Quarrel © 2022 + +using Microsoft.Extensions.DependencyInjection; +using Quarrel.Services.Localization; +using System; + +namespace Quarrel.Converters.Time +{ + public class SmartTimeFormatConverter + { + public static string Convert(DateTimeOffset time) + { + ILocalizationService localizationService = App.Current.Services.GetRequiredService(); + string resource; + DateTimeOffset now = DateTimeOffset.Now; + var timeDiff = now - time; + + // Minutes + if (timeDiff.TotalMinutes < 1) + { + resource = "Time/LTMinuteAgo"; + return localizationService[resource]; + } + if (timeDiff.TotalMinutes < 2) + { + resource = "Time/OneMinuteAgo"; + return localizationService[resource]; + } + if (timeDiff.TotalMinutes < 3) + { + resource = "Time/TwoMinutesAgo"; + return localizationService[resource]; + } + if (timeDiff.TotalHours < 1) + { + resource = "Time/MinutesAgo"; + int count = (int)timeDiff.TotalMinutes; + return localizationService[resource, count]; + } + + // Hours + //if (timeDiff.TotalHours < 2) + //{ + // resource = "Time/OneHourAgo"; + // return localizationService[resource]; + //} + //if (timeDiff.TotalHours < 3) + //{ + // resource = "Time/TwoHoursAgo"; + // return localizationService[resource]; + //} + //if (timeDiff.TotalHours < 7) + //{ + // resource = "Time/HoursAgo"; + // int count = (int)timeDiff.TotalHours; + // return localizationService[resource, count]; + //} + + // Day + Time + string shortTime = time.ToString("t"); + if (time.DayOfYear == now.DayOfYear && time.Year == now.Year) + { + resource = "Time/TodayAt"; + return localizationService[resource, shortTime]; + } + DateTime yesterday = (DateTime.Today - TimeSpan.FromDays(1)).Date; + if (time.DayOfYear == yesterday.DayOfYear && time.Year == yesterday.Year) + { + resource = "Time/YesterdayAt"; + return localizationService[resource, shortTime]; + } + + return time.ToString("d"); + } + } +} diff --git a/src/Quarrel/DataTemplates/ChannelTemplates.xaml b/src/Quarrel/DataTemplates/ChannelTemplates.xaml index be0dbad38..5bea45bba 100644 --- a/src/Quarrel/DataTemplates/ChannelTemplates.xaml +++ b/src/Quarrel/DataTemplates/ChannelTemplates.xaml @@ -2,22 +2,24 @@ x:Class="Quarrel.DataTemplates.ChannelTemplates" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:cconvert="using:Quarrel.Converters.DataTemplates.Channels" xmlns:a="using:Quarrel.Attached" xmlns:bindablechannels="using:Quarrel.Bindables.Channels"> - + - + - + @@ -35,6 +37,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Quarrel/DataTemplates/GuildTemplates.xaml b/src/Quarrel/DataTemplates/GuildTemplates.xaml index 5ffdbc694..7db364fa7 100644 --- a/src/Quarrel/DataTemplates/GuildTemplates.xaml +++ b/src/Quarrel/DataTemplates/GuildTemplates.xaml @@ -13,6 +13,14 @@ + + + + + + + + diff --git a/src/Quarrel/DataTemplates/MessageTemplates.xaml b/src/Quarrel/DataTemplates/MessageTemplates.xaml new file mode 100644 index 000000000..16e7c76a0 --- /dev/null +++ b/src/Quarrel/DataTemplates/MessageTemplates.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Quarrel/DataTemplates/MessageTemplates.xaml.cs b/src/Quarrel/DataTemplates/MessageTemplates.xaml.cs new file mode 100644 index 000000000..db6fa4295 --- /dev/null +++ b/src/Quarrel/DataTemplates/MessageTemplates.xaml.cs @@ -0,0 +1,14 @@ +// Quarrel © 2022 + +using Windows.UI.Xaml; + +namespace Quarrel.DataTemplates +{ + public sealed partial class MessageTemplates : ResourceDictionary + { + public MessageTemplates() + { + this.InitializeComponent(); + } + } +} diff --git a/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf b/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf index 2d6d7fff2..fb624ad84 100644 --- a/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf +++ b/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf @@ -188,6 +188,189 @@ Quarrel Release קווראל שחרור + + Call + שיחה + + + Open Channel in New Window + פתח הערוץ בחלון חדש + + + Toggle Members Panel + פאנל חברים + + + Discord Status + סטטוס דיסקורד + + + About + אודות + + + Settings + הגדרות + + + Send message + שלח הודעה + + + Home + בית + + + {0} Members + {0} חברים + + + {0} and {1} + {0}ו {1} + This is used for an and list of two items + + + {0}, {1} + {0}, {1} + This is used to form a comma list {0}, {{1}, and {2}} + + + {0}, and {1} + {0}, ו{1} + This is used for the base item of and conjunction + + + My Account + החשבון שלי + + + Privacy + פרטיות + + + Connections + חיבורים + + + Display + להציג + + + Behavior + התנהגות + + + Notifications + התראות + + + Voice + קול + + + About Me + עליי + + + Don't analyze any DMs + אין לנתח הודעות ישירות + + + I live dangerously + אני גר בצורה מסוכנת + + + Discriminator + מאבחן + + + Analyze and automatically delete direct messages containing explicit content + ניתוח ומחק באופן אוטומטי הודעות ישירות המכילות תוכן מפורשת + + + DIRECT MESSAGE FILTERING + מסנן הודעה ישירה + + + Email + אימייל + + + Analyze all DMs, except from my friends + נתח את כל ההודעות הישירות, למעט מחבריי + + + My friends are nice + החברים שלי נחמדים + + + Analyze all DMs + לנתח את כל ההודעות הישירות + + + Protect me + הגן עליי + + + Username + שם משתמש + + + {author} started a call + {author} started a call + + + {author} added {mention.0} to the group + {author} added {mention.0} to the group + + + {author} removed {mention.0} from the group + {author} removed {mention.0} from the group + + + Unknown message + Unknown message + + + {author} changed the channel icon + {author} changed the channel icon + + + {author} changed the channel name to {content} + {author} changed the channel name to {content} + + + {author pinned {message_reference} to the channel + {author pinned {message_reference} to the channel + + + {author} joined the server + {author} joined the server + + + Less than a minute ago + Less than a minute ago + + + {0} minutes ago + {0} minutes ago + + + A minute ago + A minute ago + + + Today at {0} + Today at {0} + + + 2 minutes ago + 2 minutes ago + + + Yesterday at {0} + Yesterday at {0} + diff --git a/src/Quarrel/Quarrel.csproj b/src/Quarrel/Quarrel.csproj index f9722e50e..9661374e5 100644 --- a/src/Quarrel/Quarrel.csproj +++ b/src/Quarrel/Quarrel.csproj @@ -104,19 +104,25 @@ Shadow.xaml - + CurrentUserButton.xaml - + ExtendedSplashScreen.xaml - + + GuildHeader.xaml + + ChannelPanel.xaml - + GuildPanel.xaml - + + MessageBox.xaml + + MessagePanel.xaml @@ -139,11 +145,16 @@ UserIcon.xaml + + + + + @@ -154,12 +165,17 @@ GuildTemplates.xaml + + MessageTemplates.xaml + - - - - + + + + + + @@ -190,10 +206,19 @@ LoginPage.xaml - + CreditPage.xaml + + MyAccountPage.xaml + + + PrivacyPage.xaml + + + UserSettingsPage.xaml + @@ -4143,23 +4168,31 @@ Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -4195,6 +4228,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -4239,6 +4276,18 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + diff --git a/src/Quarrel/Selectors/ChannelEnabledStyleSelector.cs b/src/Quarrel/Selectors/Channels/ChannelEnabledStyleSelector.cs similarity index 94% rename from src/Quarrel/Selectors/ChannelEnabledStyleSelector.cs rename to src/Quarrel/Selectors/Channels/ChannelEnabledStyleSelector.cs index b9e35bcb6..e1625bd3e 100644 --- a/src/Quarrel/Selectors/ChannelEnabledStyleSelector.cs +++ b/src/Quarrel/Selectors/Channels/ChannelEnabledStyleSelector.cs @@ -4,7 +4,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Quarrel.Selectors +namespace Quarrel.Selectors.Channels { public class ChannelEnabledStyleSelector : StyleSelector { diff --git a/src/Quarrel/Selectors/ChannelTemplateSelector.cs b/src/Quarrel/Selectors/Channels/ChannelTemplateSelector.cs similarity index 72% rename from src/Quarrel/Selectors/ChannelTemplateSelector.cs rename to src/Quarrel/Selectors/Channels/ChannelTemplateSelector.cs index 33b967d69..00bb9e124 100644 --- a/src/Quarrel/Selectors/ChannelTemplateSelector.cs +++ b/src/Quarrel/Selectors/Channels/ChannelTemplateSelector.cs @@ -5,7 +5,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Quarrel.Selectors +namespace Quarrel.Selectors.Channels { /// /// A template selector for the channel type. @@ -22,6 +22,16 @@ public class ChannelTemplateSelector : DataTemplateSelector /// public DataTemplate? VoiceChannelTemplate { get; set; } + /// + /// Gets or sets the direct channel template. + /// + public DataTemplate? DirectChannelTemplate { get; set; } + + /// + /// Gets or sets the group channel template. + /// + public DataTemplate? GroupChannelTemplate { get; set; } + /// /// Gets or sets the category channel template. /// @@ -35,8 +45,10 @@ public class ChannelTemplateSelector : DataTemplateSelector return channel switch { BindableTextChannel => TextChannelTemplate, - BindableCategoryChannel => CategoryChannelTemplate, BindableVoiceChannel => VoiceChannelTemplate, + BindableDirectChannel => DirectChannelTemplate, + BindableGroupChannel => GroupChannelTemplate, + BindableCategoryChannel => CategoryChannelTemplate, _ => null, }; } diff --git a/src/Quarrel/Selectors/GuildStyleSelector.cs b/src/Quarrel/Selectors/Guilds/GuildStyleSelector.cs similarity index 80% rename from src/Quarrel/Selectors/GuildStyleSelector.cs rename to src/Quarrel/Selectors/Guilds/GuildStyleSelector.cs index 870736e66..eccf54f08 100644 --- a/src/Quarrel/Selectors/GuildStyleSelector.cs +++ b/src/Quarrel/Selectors/Guilds/GuildStyleSelector.cs @@ -4,20 +4,23 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Quarrel.Selectors +namespace Quarrel.Selectors.Guilds { public class GuildStyleSelector : StyleSelector { - public Style? GuildFolderStyle { get; set; } - public Style? GuildStyle { get; set; } + public Style? HomeItemStyle { get; set; } + + public Style? GuildFolderStyle { get; set; } + protected override Style? SelectStyleCore(object item, DependencyObject container) { return item switch { - BindableGuildFolder => GuildFolderStyle, BindableGuild => GuildStyle, + BindableHomeItem => HomeItemStyle, + BindableGuildFolder => GuildFolderStyle, _ => null, }; } diff --git a/src/Quarrel/Selectors/GuildTemplateSelector.cs b/src/Quarrel/Selectors/Guilds/GuildTemplateSelector.cs similarity index 81% rename from src/Quarrel/Selectors/GuildTemplateSelector.cs rename to src/Quarrel/Selectors/Guilds/GuildTemplateSelector.cs index 013139309..a9819ce61 100644 --- a/src/Quarrel/Selectors/GuildTemplateSelector.cs +++ b/src/Quarrel/Selectors/Guilds/GuildTemplateSelector.cs @@ -4,24 +4,27 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Quarrel.Selectors +namespace Quarrel.Selectors.Guilds { /// /// A template selector for the guild type. /// public class GuildTemplateSelector : DataTemplateSelector { - public DataTemplate? GuildFolderTemplate { get; set; } - public DataTemplate? GuildTemplate { get; set; } + public DataTemplate? HomeItemTemplate { get; set; } + + public DataTemplate? GuildFolderTemplate { get; set; } + /// protected override DataTemplate? SelectTemplateCore(object item) { return item switch { - BindableGuildFolder => GuildFolderTemplate, BindableGuild => GuildTemplate, + BindableHomeItem => HomeItemTemplate, + BindableGuildFolder => GuildFolderTemplate, _ => null, }; } diff --git a/src/Quarrel/Selectors/Messages/MessageTemplateSelector.cs b/src/Quarrel/Selectors/Messages/MessageTemplateSelector.cs new file mode 100644 index 000000000..fdb71a58f --- /dev/null +++ b/src/Quarrel/Selectors/Messages/MessageTemplateSelector.cs @@ -0,0 +1,49 @@ +// Quarrel © 2022 + +using Discord.API.Models.Enums.Messages; +using Quarrel.Bindables.Messages; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.Selectors.Messages +{ + public class MessageTemplateSelector : DataTemplateSelector + { + public DataTemplate DefaultTemplate { get; set; } + + public DataTemplate InfoTemplate { get; set; } + + protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) + { + BindableMessage message = (BindableMessage)item; + return message.Message.Type switch + { + MessageType.Default or MessageType.Reply => DefaultTemplate, + + MessageType.RecipientAdd or + MessageType.RecipientRemove or + MessageType.Call or + MessageType.ChannelNameChange or + MessageType.ChannelIconChange or + MessageType.ChannelPinnedMessage or + MessageType.GuildMemberJoin or + MessageType.UserPremiumGuildSubscription or + MessageType.UserPremiumGuildSubscriptionTier1 or + MessageType.UserPremiumGuildSubscriptionTier2 or + MessageType.UserPremiumGuildSubscriptionTier3 or + MessageType.ChannelFollowAdd or + MessageType.GuildDiscoveryDisqualified or + MessageType.GuildDiscoveryRequalified or + MessageType.GuildDiscoveryGracePeriodInitialWarning or + MessageType.GuildDiscoveryGracePeriodFinalWarning or + MessageType.ThreadCreated or + MessageType.ApplicationCommand or + MessageType.ThreadStarterMessage or + MessageType.GuildInviteReminder or + MessageType.ContextMenuCommand => InfoTemplate, + + _ => null, + }; + } + } +} diff --git a/src/Quarrel/Selectors/SubPageTemplateSelector.cs b/src/Quarrel/Selectors/SubPages/SubPageTemplateSelector.cs similarity index 79% rename from src/Quarrel/Selectors/SubPageTemplateSelector.cs rename to src/Quarrel/Selectors/SubPages/SubPageTemplateSelector.cs index 504809667..46c250904 100644 --- a/src/Quarrel/Selectors/SubPageTemplateSelector.cs +++ b/src/Quarrel/Selectors/SubPages/SubPageTemplateSelector.cs @@ -2,19 +2,23 @@ using Quarrel.ViewModels.SubPages.DiscordStatus; using Quarrel.ViewModels.SubPages.Meta; +using Quarrel.ViewModels.SubPages.Settings; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Quarrel.Selectors +namespace Quarrel.Selectors.SubPages { public class SubPageTemplateSelector : DataTemplateSelector { public DataTemplate? AboutTemplate { get; set; } - + public DataTemplate? CreditTemplate { get; set; } public DataTemplate? DiscordStatusTemplate { get; set; } - + + public DataTemplate? UserSettingsTemplate { get; set; } + + /// protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) { @@ -23,6 +27,7 @@ public class SubPageTemplateSelector : DataTemplateSelector AboutPageViewModel => AboutTemplate, CreditPageViewModel => CreditTemplate, DiscordStatusViewModel => DiscordStatusTemplate, + UserSettingsPageViewModel => UserSettingsTemplate, _ => null, }; } diff --git a/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsPageSelector.cs b/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsPageSelector.cs new file mode 100644 index 000000000..25b94429e --- /dev/null +++ b/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsPageSelector.cs @@ -0,0 +1,40 @@ +// Quarrel © 2022 + +using Quarrel.ViewModels.SubPages.UserSettings.Pages; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.Selectors.SubPages.UserSettings +{ + public class UserSettingsPageSelector : DataTemplateSelector + { + public DataTemplate BehaviorsTemplate { get; set; } + + public DataTemplate ConnectionsTemplate { get; set; } + + public DataTemplate DisplayTemplate { get; set; } + + public DataTemplate MyAccountTemplate { get; set; } + + public DataTemplate NotificationsTemplate { get; set; } + + public DataTemplate PrivacyTemplate { get; set; } + + public DataTemplate VoiceTemplate { get; set; } + + protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) + { + return item switch + { + BehaviorPageViewModel => BehaviorsTemplate, + ConnectionsPageViewModel => ConnectionsTemplate, + DisplayPageViewModel => DisplayTemplate, + MyAccountPageViewModel => MyAccountTemplate, + NotificationsPageViewModel => NotificationsTemplate, + PrivacyPageViewModel => PrivacyTemplate, + VoicePageViewModel => VoiceTemplate, + _ => null, + }; + } + } +} diff --git a/src/Quarrel/Services/Localization/LocalizationService.cs b/src/Quarrel/Services/Localization/LocalizationService.cs index a1a8d7a4a..43efa8f58 100644 --- a/src/Quarrel/Services/Localization/LocalizationService.cs +++ b/src/Quarrel/Services/Localization/LocalizationService.cs @@ -1,5 +1,6 @@ // Quarrel © 2022 +using System; using System.Globalization; using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources.Core; @@ -11,6 +12,10 @@ namespace Quarrel.Services.Localization /// public class LocalizationService : ILocalizationService { + private const string AndResource = "And"; + private const string OxfordAndResource = "OxfordAnd"; + private const string AndConjunctionResource = "AndConjunction"; + private ResourceLoader? _loader; private ResourceLoader Loader @@ -44,5 +49,33 @@ public bool IsRightToLeftLanguage /// public bool IsNeutralLanguage => CultureInfo.CurrentCulture.Name == "en-US"; + + public string CommaList(params string[] args) + { + if (args.Length == 1) + { + return args[0]; + } + else if (args.Length == 2) + { + return this[AndResource, args[0], args[1]]; + } + else + { + return OxfordCommaList(args); + } + } + + private string OxfordCommaList(Span args) + { + if (args.Length == 2) + { + return this[OxfordAndResource, args[0], args[1]]; + } + else + { + return this[AndConjunctionResource, args[0], OxfordCommaList(args.Slice(1))]; + } + } } } diff --git a/src/Quarrel/Services/Windows/WindowService.cs b/src/Quarrel/Services/Windows/WindowService.cs index 828ee6487..411135781 100644 --- a/src/Quarrel/Services/Windows/WindowService.cs +++ b/src/Quarrel/Services/Windows/WindowService.cs @@ -19,6 +19,7 @@ public WindowService(ILocalizationService localizationService) _localizationService = localizationService; } + /// public async void OpenSecondaryWindow() { var currentAppView = ApplicationView.GetForCurrentView(); diff --git a/src/Quarrel/Strings/en-US/Resources.resw b/src/Quarrel/Strings/en-US/Resources.resw index 43f757912..82134b640 100644 --- a/src/Quarrel/Strings/en-US/Resources.resw +++ b/src/Quarrel/Strings/en-US/Resources.resw @@ -157,6 +157,14 @@ View code on GitHub + + {0} and {1} + This is used for an and list of two items + + + {0}, {1} + This is used to form a comma list {0}, {{1}, and {2}} + Quarrel @@ -169,6 +177,27 @@ Quarrel Insider + + {0} Members + + + About + + + Call + + + Open Channel in New Window + + + Settings + + + Discord Status + + + Toggle Members Panel + Commits @@ -226,6 +255,33 @@ LOADING + + Home + + + {author} started a call + + + {author} changed the channel icon + + + {author} changed the channel name to {content} + + + {author pinned {message_reference} to the channel + + + {author} joined the server + + + {author} added {mention.0} to the group + + + {author} removed {mention.0} from the group + + + Unknown message + Logging in will intercept a token that allows Quarrel to interact with Discord through your account. @@ -254,4 +310,86 @@ Token + + Send message + + + {0}, and {1} + This is used for the base item of and conjunction + + + Less than a minute ago + + + {0} minutes ago + + + A minute ago + + + Today at {0} + + + 2 minutes ago + + + Yesterday at {0} + + + About Me + + + Behavior + + + Connections + + + Don't analyze any DMs + + + I live dangerously + + + Discriminator + + + Display + + + Analyze and automatically delete direct messages containing explicit content + + + DIRECT MESSAGE FILTERING + + + Email + + + My Account + + + Analyze all DMs, except from my friends + + + My friends are nice + + + Notifications + + + Privacy + + + Analyze all DMs + + + Protect me + + + Username + + + Voice + \ No newline at end of file diff --git a/src/Quarrel/Strings/he-IL/Resources.resw b/src/Quarrel/Strings/he-IL/Resources.resw index 8b7cf636a..fc2f8b8a4 100644 --- a/src/Quarrel/Strings/he-IL/Resources.resw +++ b/src/Quarrel/Strings/he-IL/Resources.resw @@ -149,4 +149,100 @@ קווראל שחרור + + שיחה + + + פתח הערוץ בחלון חדש + + + פאנל חברים + + + סטטוס דיסקורד + + + אודות + + + הגדרות + + + שלח הודעה + + + בית + + + {0} חברים + + + {0}ו {1} + This is used for an and list of two items + + + {0}, {1} + This is used to form a comma list {0}, {{1}, and {2}} + + + {0}, ו{1} + This is used for the base item of and conjunction + + + החשבון שלי + + + פרטיות + + + חיבורים + + + להציג + + + התנהגות + + + התראות + + + קול + + + עליי + + + אין לנתח הודעות ישירות + + + אני גר בצורה מסוכנת + + + מאבחן + + + ניתוח ומחק באופן אוטומטי הודעות ישירות המכילות תוכן מפורשת + + + מסנן הודעה ישירה + + + אימייל + + + נתח את כל ההודעות הישירות, למעט מחבריי + + + החברים שלי נחמדים + + + לנתח את כל ההודעות הישירות + + + הגן עליי + + + שם משתמש + \ No newline at end of file diff --git a/src/Quarrel/Styles/GuildItemStyles.xaml b/src/Quarrel/Styles/GuildItemStyles.xaml index 108689dc4..4549c197d 100644 --- a/src/Quarrel/Styles/GuildItemStyles.xaml +++ b/src/Quarrel/Styles/GuildItemStyles.xaml @@ -1,7 +1,6 @@  - + diff --git a/src/Quarrel/SubPages/DiscordStatus/DiscordStatusPage.xaml b/src/Quarrel/SubPages/DiscordStatus/DiscordStatusPage.xaml index 95a411733..e3e1cf79a 100644 --- a/src/Quarrel/SubPages/DiscordStatus/DiscordStatusPage.xaml +++ b/src/Quarrel/SubPages/DiscordStatus/DiscordStatusPage.xaml @@ -11,7 +11,6 @@ mc:Ignorable="d" d:DesignHeight="512" d:DesignWidth="512" - MaxHeight="512" MaxWidth="512" MinHeight="512" MinWidth="512"> diff --git a/src/Quarrel/SubPages/Host/SubPageHost.xaml b/src/Quarrel/SubPages/Host/SubPageHost.xaml index db0d2a7e3..a83aa0b41 100644 --- a/src/Quarrel/SubPages/Host/SubPageHost.xaml +++ b/src/Quarrel/SubPages/Host/SubPageHost.xaml @@ -6,9 +6,10 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:convert="using:Quarrel.Converters" - xmlns:selectors="using:Quarrel.Selectors" + xmlns:sselectors="using:Quarrel.Selectors.SubPages" xmlns:statuspage="using:Quarrel.SubPages.DiscordStatus" xmlns:metapages="using:Quarrel.SubPages.Meta" + xmlns:usersettings="using:Quarrel.SubPages.UserSettings" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400" @@ -26,11 +27,16 @@ + + + + - + diff --git a/src/Quarrel/SubPages/UserSettings/Pages/MyAccountPage.xaml b/src/Quarrel/SubPages/UserSettings/Pages/MyAccountPage.xaml new file mode 100644 index 000000000..0a60c2c2b --- /dev/null +++ b/src/Quarrel/SubPages/UserSettings/Pages/MyAccountPage.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Quarrel/SubPages/UserSettings/Pages/MyAccountPage.xaml.cs b/src/Quarrel/SubPages/UserSettings/Pages/MyAccountPage.xaml.cs new file mode 100644 index 000000000..c95215737 --- /dev/null +++ b/src/Quarrel/SubPages/UserSettings/Pages/MyAccountPage.xaml.cs @@ -0,0 +1,17 @@ +// Quarrel © 2022 + +using Quarrel.ViewModels.SubPages.UserSettings.Pages; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.SubPages.UserSettings.Pages +{ + public sealed partial class MyAccountPage : UserControl + { + public MyAccountPage() + { + this.InitializeComponent(); + } + + public MyAccountPageViewModel ViewModel => (MyAccountPageViewModel)DataContext; + } +} diff --git a/src/Quarrel/SubPages/UserSettings/Pages/PrivacyPage.xaml b/src/Quarrel/SubPages/UserSettings/Pages/PrivacyPage.xaml new file mode 100644 index 000000000..ddf6ee8f0 --- /dev/null +++ b/src/Quarrel/SubPages/UserSettings/Pages/PrivacyPage.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Quarrel/SubPages/UserSettings/Pages/PrivacyPage.xaml.cs b/src/Quarrel/SubPages/UserSettings/Pages/PrivacyPage.xaml.cs new file mode 100644 index 000000000..6d00f80a2 --- /dev/null +++ b/src/Quarrel/SubPages/UserSettings/Pages/PrivacyPage.xaml.cs @@ -0,0 +1,17 @@ +// Quarrel © 2022 + +using Quarrel.ViewModels.SubPages.UserSettings.Pages; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.SubPages.UserSettings.Pages +{ + public sealed partial class PrivacyPage : UserControl + { + public PrivacyPage() + { + this.InitializeComponent(); + } + + public PrivacyPageViewModel ViewModel => (PrivacyPageViewModel)DataContext; + } +} diff --git a/src/Quarrel/SubPages/UserSettings/UserSettingsPage.xaml b/src/Quarrel/SubPages/UserSettings/UserSettingsPage.xaml new file mode 100644 index 000000000..ac71a87fe --- /dev/null +++ b/src/Quarrel/SubPages/UserSettings/UserSettingsPage.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Quarrel/SubPages/UserSettings/UserSettingsPage.xaml.cs b/src/Quarrel/SubPages/UserSettings/UserSettingsPage.xaml.cs new file mode 100644 index 000000000..c98092dc7 --- /dev/null +++ b/src/Quarrel/SubPages/UserSettings/UserSettingsPage.xaml.cs @@ -0,0 +1,17 @@ +// Quarrel © 2022 + +using Quarrel.ViewModels.SubPages.Settings; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.SubPages.UserSettings +{ + public sealed partial class UserSettingsPage : UserControl + { + public UserSettingsPage() + { + this.InitializeComponent(); + } + + public UserSettingsPageViewModel ViewModel => (UserSettingsPageViewModel)DataContext; + } +}