Compare commits

...

3 Commits

Author SHA1 Message Date
08b3a6d051
chore: bump version to 1.0.2 2023-11-13 23:15:14 -05:00
be6ca6a176
refactor: extract tabs into files 2023-11-13 23:14:57 -05:00
ed81c07715
refactor: organise code 2023-11-13 23:04:17 -05:00
23 changed files with 222 additions and 217 deletions

View File

@ -1,6 +1,6 @@
using Dalamud.Configuration;
namespace EorzeaVotes;
namespace EorzeaVotes.Config;
[Serializable]
internal class Configuration : IPluginConfiguration {
@ -17,32 +17,3 @@ internal class Configuration : IPluginConfiguration {
public int YearStartedPlaying;
public Mbti? MyersBriggs = null;
}
// For statistical breakdowns, a text box is not useful. Other not included to
// not "other" people.
internal enum Gender {
Female,
NonBinary,
Male,
}
// ReSharper disable IdentifierTypo
internal enum Mbti {
Enfj,
Enfp,
Entj,
Entp,
Esfj,
Esfp,
Estj,
Estp,
Infj,
Infp,
Intj,
Intp,
Isfj,
Isfp,
Istj,
Istp,
}
// ReSharper enable IdentifierTypo

View File

@ -1,4 +1,12 @@
namespace EorzeaVotes;
namespace EorzeaVotes.Config;
// For statistical breakdowns, a text box is not useful. Other not included to
// not "other" people.
internal enum Gender {
Female,
NonBinary,
Male,
}
internal static class GenderExt {
internal static string Name(this Gender? gender) {

View File

@ -0,0 +1,21 @@
namespace EorzeaVotes.Config;
// ReSharper disable IdentifierTypo
internal enum Mbti {
Enfj,
Enfp,
Entj,
Entp,
Esfj,
Esfp,
Estj,
Estp,
Infj,
Infp,
Intj,
Intp,
Isfj,
Isfp,
Istj,
Istp,
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<TargetFramework>net7.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

View File

@ -1,4 +1,4 @@
namespace EorzeaVotes;
namespace EorzeaVotes.Model;
[Serializable]
internal class BasicQuestion : IQuestion {

View File

@ -1,6 +1,8 @@
using EorzeaVotes.Config;
using EorzeaVotes.Util;
using Lumina.Excel.GeneratedSheets;
namespace EorzeaVotes;
namespace EorzeaVotes.Model;
internal enum Breakdown {
Country,
@ -107,22 +109,6 @@ internal static class BreakdownExt {
};
}
internal static Statistic GetStatistic(this Breakdown breakdown) {
return breakdown switch {
Breakdown.Country => Statistic.Country,
Breakdown.Gender => Statistic.Gender,
Breakdown.Age => Statistic.BirthDate,
Breakdown.ZodiacSign => Statistic.BirthDate,
Breakdown.CharacterRace => Statistic.CharacterRace,
Breakdown.CharacterClan => Statistic.CharacterClan,
Breakdown.CharacterGender => Statistic.CharacterGender,
Breakdown.CharacterHomeWorld => Statistic.CharacterHomeWorld,
Breakdown.YearStartedPlaying => Statistic.YearStartedPlaying,
Breakdown.MyersBriggs => Statistic.MyersBriggs,
_ => throw new ArgumentException("unknown breakdown", nameof(breakdown)),
};
}
private static readonly string[] CountryCodes = ISO3166.Country.List
.Select(c => c.TwoLetterCode)
.ToArray();

View File

@ -1,6 +1,7 @@
using EorzeaVotes.Util;
using ImGuiNET;
namespace EorzeaVotes;
namespace EorzeaVotes.Model;
[Serializable]
internal class FullQuestion : IQuestion {

View File

@ -1,7 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace EorzeaVotes;
namespace EorzeaVotes.Model;
[JsonConverter(typeof(QuestionConverter))]
internal interface IQuestion {

View File

@ -1,4 +1,4 @@
namespace EorzeaVotes;
namespace EorzeaVotes.Model;
[Serializable]
internal class RegisterResponse {

View File

@ -1,4 +1,6 @@
namespace EorzeaVotes;
using EorzeaVotes.Config;
namespace EorzeaVotes.Model;
[Serializable]
internal class VoteRequest {

View File

@ -2,6 +2,9 @@
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using EorzeaVotes.Config;
using EorzeaVotes.Ui;
using EorzeaVotes.Util;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;

View File

@ -1,6 +1,7 @@
using System.Collections;
using System.Net.Http.Headers;
using Dalamud.Game.ClientState.Objects.Enums;
using EorzeaVotes.Model;
using Newtonsoft.Json;
namespace EorzeaVotes;

View File

@ -1,45 +0,0 @@
namespace EorzeaVotes;
internal enum Statistic {
Country,
Gender,
BirthDate,
CharacterRace,
CharacterClan,
CharacterGender,
CharacterHomeWorld,
YearStartedPlaying,
MyersBriggs,
}
internal static class StatisticExt {
internal static string ToSnakeCase(this Statistic stat) {
return stat switch {
Statistic.Country => "country",
Statistic.Gender => "gender",
Statistic.BirthDate => "birth_date",
Statistic.CharacterRace => "character_race",
Statistic.CharacterClan => "character_clan",
Statistic.CharacterGender => "character_gender",
Statistic.CharacterHomeWorld => "character_home_world",
Statistic.YearStartedPlaying => "year_started_playing",
Statistic.MyersBriggs => "myers_briggs",
_ => throw new ArgumentException("unknown statistic", nameof(stat)),
};
}
internal static string ToHuman(this Statistic stat) {
return stat switch {
Statistic.Country => "Country",
Statistic.Gender => "Gender",
Statistic.BirthDate => "Birth date",
Statistic.CharacterRace => "Character race",
Statistic.CharacterClan => "character clan",
Statistic.CharacterGender => "Character gender",
Statistic.CharacterHomeWorld => "Character home world",
Statistic.YearStartedPlaying => "Year started playing",
Statistic.MyersBriggs => "Myers-Briggs Type Indicator",
_ => throw new ArgumentException("unknown statistic", nameof(stat)),
};
}
}

View File

@ -1,4 +1,4 @@
namespace EorzeaVotes;
namespace EorzeaVotes.Ui;
internal interface IDrawable {
DrawStatus Draw();

View File

@ -1,7 +1,8 @@
using Dalamud.Interface;
using EorzeaVotes.Util;
using ImGuiNET;
namespace EorzeaVotes;
namespace EorzeaVotes.Ui;
internal static class ImGuiHelper {
internal static void Help(string text) {
@ -83,7 +84,7 @@ internal static class ImGuiHelper {
return false;
}
var match = Regexes.DateRegex().Match(input);
var match = Util.Regexes.DateRegex().Match(input);
if (!match.Success) {
date = null;
return true;

View File

@ -1,8 +1,10 @@
using System.Numerics;
using Dalamud.Interface.Utility;
using EorzeaVotes.Model;
using EorzeaVotes.Util;
using ImGuiNET;
namespace EorzeaVotes;
namespace EorzeaVotes.Ui;
internal class MoreDetailsWindow : IDrawable {
private Plugin Plugin { get; }
@ -83,7 +85,7 @@ internal class MoreDetailsWindow : IDrawable {
keyMap.TryGetValue(key, out keyLabel);
}
keyLabel ??= Util.SnakeToHuman(key);
keyLabel ??= Util.Util.SnakeToHuman(key);
if (!ImGui.CollapsingHeader(keyLabel)) {
continue;

View File

@ -1,8 +1,12 @@
using System.Numerics;
using Dalamud.Interface.Utility;
using EorzeaVotes.Config;
using EorzeaVotes.Model;
using EorzeaVotes.Ui.Tabs;
using EorzeaVotes.Util;
using ImGuiNET;
namespace EorzeaVotes;
namespace EorzeaVotes.Ui;
internal class PluginUi : IDisposable {
private Plugin Plugin { get; }
@ -14,12 +18,17 @@ internal class PluginUi : IDisposable {
#endif
private string _dateScratch = string.Empty;
private bool _voting;
private readonly List<IDrawable> _drawables = new();
private QuestionsTab QuestionsTab { get; }
private SettingsTab SettingsTab { get; }
internal PluginUi(Plugin plugin) {
this.Plugin = plugin;
this.QuestionsTab = new QuestionsTab(this.Plugin);
this.SettingsTab = new SettingsTab(this.Plugin);
this.UpdateDateScratch();
this.Plugin.Interface.UiBuilder.Draw += this.Draw;
}
@ -28,7 +37,23 @@ internal class PluginUi : IDisposable {
this.Plugin.Interface.UiBuilder.Draw -= this.Draw;
}
private void Draw() {
private void UpdateDateScratch() {
this._dateScratch = this.Plugin.Config.BirthDate?.ToString("O") ?? string.Empty;
}
internal bool IsMoreDetailsOpen(Guid id) {
return this._drawables.Any(d => d is MoreDetailsWindow mdw && mdw.Question.Id == id);
}
internal void OpenMoreDetails(FullQuestion question) {
if (this.IsMoreDetailsOpen(question.Id)) {
return;
}
this._drawables.Add(new MoreDetailsWindow(this.Plugin, question));
}
private void DrawDrawables() {
this._drawables.RemoveAll(drawable => {
try {
return drawable.Draw() == DrawStatus.Finished;
@ -37,6 +62,10 @@ internal class PluginUi : IDisposable {
return true;
}
});
}
private void Draw() {
this.DrawDrawables();
if (!this.Visible) {
return;
@ -58,7 +87,7 @@ internal class PluginUi : IDisposable {
}
}
private bool DrawOptionalQuestions() {
internal bool DrawOptionalQuestions() {
var anyChanged = false;
anyChanged |= ImGuiHelper.EnumDropDownVertical(
@ -76,7 +105,8 @@ internal class PluginUi : IDisposable {
);
if (dateInput) {
this._dateScratch = this.Plugin.Config.BirthDate?.ToString("O") ?? string.Empty;
anyChanged = true;
this.UpdateDateScratch();
}
if (this.Plugin.Config.BirthDate != null) {
@ -147,108 +177,7 @@ internal class PluginUi : IDisposable {
using var endTabBar = new OnDispose(ImGui.EndTabBar);
this.DrawQuestionsTab();
this.DrawSettingsTab();
}
private void DrawQuestionsTab() {
if (!ImGui.BeginTabItem("Questions")) {
return;
}
using var endTabItem = new OnDispose(ImGui.EndTabItem);
if (this.Plugin.Manager.Count == 0) {
ImGuiHelpers.CenteredText("No questions yet - waiting for plugin approval.");
return;
}
ImGuiHelpers.CenteredText("Active question");
var active = this.Plugin.Manager.FirstOrDefault(q => q.Active);
if (active == null) {
ImGui.TextUnformatted("There is no active question at the moment.");
} else {
ImGui.TextUnformatted(active.Text);
for (var i = 0; i < active.Answers.Length; i++) {
if (i != 0) {
ImGui.SameLine();
}
var disabled = this._voting || active is FullQuestion;
using var endDisabled = ImGuiHelper.WithDisabled(disabled);
if (!ImGui.Button(active.Answers[i])) {
continue;
}
this._voting = true;
var answer = i;
Task.Run(async () => {
try {
// first vote
await this.Plugin.Manager.Vote(active.Id, (ushort) answer);
// then get updated info
await this.Plugin.Manager.Check();
} finally {
this._voting = false;
}
});
}
if (active is FullQuestion full) {
full.DrawResponses();
var open = this._drawables.Any(drawable => drawable is MoreDetailsWindow mdw && mdw.Question.Id == full.Id);
using (ImGuiHelper.WithDisabled(open)) {
if (ImGui.Button($"More details##{full.Id}", new Vector2(ImGui.GetContentRegionAvail().X, 0))) {
this._drawables.Add(new MoreDetailsWindow(this.Plugin, full));
}
}
}
}
ImGui.Separator();
this.DrawInactive();
}
private void DrawInactive() {
ImGuiHelpers.CenteredText("Inactive questions");
using var endChild = new OnDispose(ImGui.EndChild);
if (!ImGui.BeginChild("ev-inactive-questions", ImGui.GetContentRegionAvail(), false)) {
return;
}
foreach (var question in this.Plugin.Manager) {
if (question.Active || question is not FullQuestion full) {
continue;
}
ImGui.TextUnformatted(full.Text);
full.DrawResponses();
var open = this._drawables.Any(drawable => drawable is MoreDetailsWindow mdw && mdw.Question.Id == full.Id);
using (ImGuiHelper.WithDisabled(open)) {
if (ImGui.Button($"More details##{full.Id}", new Vector2(ImGui.GetContentRegionAvail().X, 0))) {
this._drawables.Add(new MoreDetailsWindow(this.Plugin, full));
}
}
ImGui.Spacing();
}
}
private void DrawSettingsTab() {
if (!ImGui.BeginTabItem("Settings")) {
return;
}
using var endTabItem = new OnDispose(ImGui.EndTabItem);
if (this.DrawOptionalQuestions()) {
this.Plugin.SaveConfig();
}
this.QuestionsTab.Draw();
this.SettingsTab.Draw();
}
}

View File

@ -0,0 +1,101 @@
using System.Numerics;
using Dalamud.Interface.Utility;
using EorzeaVotes.Model;
using EorzeaVotes.Util;
using ImGuiNET;
namespace EorzeaVotes.Ui.Tabs;
internal class QuestionsTab {
private Plugin Plugin { get; }
private bool _voting;
internal QuestionsTab(Plugin plugin) {
this.Plugin = plugin;
}
internal void Draw() {
if (!ImGui.BeginTabItem("Questions")) {
return;
}
using var endTabItem = new OnDispose(ImGui.EndTabItem);
if (this.Plugin.Manager.Count == 0) {
ImGuiHelpers.CenteredText("No questions yet - waiting for plugin approval.");
return;
}
ImGuiHelpers.CenteredText("Active question");
var active = this.Plugin.Manager.FirstOrDefault(q => q.Active);
if (active == null) {
ImGui.TextUnformatted("There is no active question at the moment.");
} else {
ImGui.TextUnformatted(active.Text);
for (var i = 0; i < active.Answers.Length; i++) {
if (i != 0) {
ImGui.SameLine();
}
var disabled = this._voting || active is FullQuestion;
using var endDisabled = ImGuiHelper.WithDisabled(disabled);
if (!ImGui.Button(active.Answers[i])) {
continue;
}
this._voting = true;
var answer = i;
Task.Run(async () => {
try {
// first vote
await this.Plugin.Manager.Vote(active.Id, (ushort) answer);
// then get updated info
await this.Plugin.Manager.Check();
} finally {
this._voting = false;
}
});
}
if (active is FullQuestion full) {
full.DrawResponses();
this.DrawMoreDetailsButton(full);
}
}
ImGui.Separator();
this.DrawInactive();
}
private void DrawMoreDetailsButton(FullQuestion full) {
var open = this.Plugin.Ui.IsMoreDetailsOpen(full.Id);
using var endDisabled = ImGuiHelper.WithDisabled(open);
if (ImGui.Button($"More details##{full.Id}", new Vector2(ImGui.GetContentRegionAvail().X, 0))) {
this.Plugin.Ui.OpenMoreDetails(full);
}
}
private void DrawInactive() {
ImGuiHelpers.CenteredText("Inactive questions");
using var endChild = new OnDispose(ImGui.EndChild);
if (!ImGui.BeginChild("ev-inactive-questions", ImGui.GetContentRegionAvail(), false)) {
return;
}
foreach (var question in this.Plugin.Manager) {
if (question.Active || question is not FullQuestion full) {
continue;
}
ImGui.TextUnformatted(full.Text);
full.DrawResponses();
this.DrawMoreDetailsButton(full);
ImGui.Spacing();
}
}
}

View File

@ -0,0 +1,24 @@
using EorzeaVotes.Util;
using ImGuiNET;
namespace EorzeaVotes.Ui.Tabs;
internal class SettingsTab {
private Plugin Plugin { get; }
internal SettingsTab(Plugin plugin) {
this.Plugin = plugin;
}
internal void Draw() {
if (!ImGui.BeginTabItem("Settings")) {
return;
}
using var endTabItem = new OnDispose(ImGui.EndTabItem);
if (this.Plugin.Ui.DrawOptionalQuestions()) {
this.Plugin.SaveConfig();
}
}
}

View File

@ -1,4 +1,4 @@
namespace EorzeaVotes;
namespace EorzeaVotes.Util;
internal class OnDispose : IDisposable {
private Action Action { get; }

View File

@ -1,6 +1,6 @@
using System.Text.RegularExpressions;
namespace EorzeaVotes;
namespace EorzeaVotes.Util;
internal static partial class Regexes {
[GeneratedRegex(@"(\d{4})-(\d{2})-(\d{2})", RegexOptions.Compiled)]

View File

@ -1,7 +1,7 @@
using Dalamud.IoC;
using Dalamud.Plugin.Services;
namespace EorzeaVotes;
namespace EorzeaVotes.Util;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
internal class Service {

View File

@ -1,4 +1,4 @@
namespace EorzeaVotes;
namespace EorzeaVotes.Util;
internal static class Util {
internal static string SnakeToHuman(string text) {