diff --git a/Components/Layout/AvatarMenuLinks.razor b/Components/Layout/AvatarMenuLinks.razor index ee3b4cb..913fb6c 100644 --- a/Components/Layout/AvatarMenuLinks.razor +++ b/Components/Layout/AvatarMenuLinks.razor @@ -8,12 +8,6 @@ Address Directory -@* @if (State.IsAuthorized) { - - - Feed - -} *@ @foreach (AddressResponseData address in State.AddressList ?? new List()) { @address.Address @@ -38,6 +32,13 @@ } } +@if (State.FeatureFollowing && State.IsAuthorized) { + + + Feed + + +} @if (State.IsAuthorized) { diff --git a/Components/Layout/FollowingList.razor b/Components/Layout/FollowingList.razor new file mode 100644 index 0000000..3af791d --- /dev/null +++ b/Components/Layout/FollowingList.razor @@ -0,0 +1,23 @@ +@inject State State + +@if (State.Following != null) { +
+ + + + Following + + + @foreach (string address in State.Following) { + + + @address + + } +
+} + +@code { + [Parameter] + public string @class { get; set; } +} \ No newline at end of file diff --git a/Components/Layout/NavLinks.razor b/Components/Layout/NavLinks.razor index 4988737..974e8f6 100644 --- a/Components/Layout/NavLinks.razor +++ b/Components/Layout/NavLinks.razor @@ -1,4 +1,5 @@ - +@inject State State +
Status.lol
@@ -20,11 +21,14 @@
Address Directory
-@* - - - -
Feed
-
-
-
*@ \ No newline at end of file +@if (State.FeatureFollowing) { + + + + +
Feed
+
+ +
+
+} \ No newline at end of file diff --git a/Components/Pages/Feed.razor b/Components/Pages/Feed.razor index 8416ec0..1e5f953 100644 --- a/Components/Pages/Feed.razor +++ b/Components/Pages/Feed.razor @@ -1,8 +1,48 @@ @page "/feed" -

Feed

+@implements IDisposable +@inject IJSRuntime JS +@inject State State -WIP + + + + A feed of all the statuses and pics of the people you follow. + + +@if (feed != null) foreach (StatusOrPic item in feed) { + if (item.IsStatus) { + + } + else if (item.IsPic) { + + } +} + + @code { + private IOrderedEnumerable? feed; + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + if (feed == null || feed.Count() == 0) feed = await State.GetFeed(); + State.PropertyChanged += StateChanged; + State.CanRefresh = true; + await InvokeAsync(StateHasChanged); + await JS.InvokeVoidAsync("removeElementById", "feedLoading"); + } + + private async void StateChanged(object? sender, PropertyChangedEventArgs e) { + if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) { + using (State.GetRefreshToken()) { + feed = await State.GetFeed(true); + await InvokeAsync(StateHasChanged); + } + } + } + + public void Dispose() { + State.PropertyChanged -= StateChanged; + State.CanRefresh = false; + } } diff --git a/Components/Pages/Person.razor b/Components/Pages/Person.razor index 802a8e8..b60e1ba 100644 --- a/Components/Pages/Person.razor +++ b/Components/Pages/Person.razor @@ -5,14 +5,27 @@ -

@Address

@Address
- +@if (State.FeatureFollowing) +{ +
+ @if (State.IsFollowing(Address)) { + + } + else { + + } +
+}
diff --git a/Models/Pic.cs b/Models/Pic.cs index 559d0c3..dd40e0e 100644 --- a/Models/Pic.cs +++ b/Models/Pic.cs @@ -19,13 +19,14 @@ namespace Neighbourhood.omg.lol.Models { public string Description { get; set; } public string DescriptionHtml { get => Description == null ? string.Empty : Utilities.MdToHtml(Description); } + public DateTimeOffset CreatedTime { get => DateTimeOffset.UnixEpoch.AddSeconds(Created); } + [JsonPropertyName("exif")] public JsonElement ExifJson { get; set; } public string RelativeTime { get { - DateTimeOffset createdTime = DateTimeOffset.UnixEpoch.AddSeconds(Created); - TimeSpan offset = DateTimeOffset.UtcNow - createdTime; + TimeSpan offset = DateTimeOffset.UtcNow - CreatedTime; var offsetString = string.Empty; if (offset.TotalDays >= 1) offsetString = $"{Math.Floor(offset.TotalDays)} days ago"; diff --git a/Models/State.cs b/Models/State.cs index eb83763..677806e 100644 --- a/Models/State.cs +++ b/Models/State.cs @@ -5,6 +5,8 @@ using System.Text.Json; namespace Neighbourhood.omg.lol.Models { public class State : INotifyPropertyChanged { + // Feature flags + public bool FeatureFollowing { get; } = true; // Main data lists public List? Statuses { get; set; } public List? Pics { get; set; } @@ -12,6 +14,8 @@ namespace Neighbourhood.omg.lol.Models { public List? EphemeralMessages { get; set; } public List? AddressDirectory { get; set; } + public List? Feed { get; set; } + // Account data public AccountResponseData? AccountInfo { get; set; } public AddressResponseList? AddressList { get; set; } @@ -42,6 +46,8 @@ namespace Neighbourhood.omg.lol.Models { } } } + + public List? Following { get; private set; } public string? SelectedAddressName { get => SelectedAddress?.Address; } // data for selected address @@ -143,10 +149,12 @@ namespace Neighbourhood.omg.lol.Models { string accountJson = Preferences.Default.Get("accountdetails", string.Empty); string addressJson = Preferences.Default.Get("accountaddresses", string.Empty); string selectedAddressJson = Preferences.Default.Get("selectedaddress", string.Empty); + string followingJson = Preferences.Default.Get("following", string.Empty); if (!string.IsNullOrEmpty(accountJson)) AccountInfo = JsonSerializer.Deserialize(accountJson); if (!string.IsNullOrEmpty(addressJson)) AddressList = JsonSerializer.Deserialize(addressJson); if (!string.IsNullOrEmpty(selectedAddressJson)) SelectedAddress = JsonSerializer.Deserialize(selectedAddressJson); + if (!string.IsNullOrEmpty(followingJson)) Following = JsonSerializer.Deserialize>(followingJson); // if we haven't got account info, attempt to retrieve it. if (AccountInfo == null) { @@ -174,9 +182,23 @@ namespace Neighbourhood.omg.lol.Models { AccountInfo = null; AddressList = null; SelectedAddress = null; + Following = null; api.RemoveToken(); } + public bool IsFollowing(string address) => Following?.Contains(address) ?? false; + public void Follow(string address) { + if (Following == null) Following = new List(); + Following.Add(address); + Preferences.Default.Set("following", JsonSerializer.Serialize(Following)); + } + + public void Unfollow(string address) { + if (Following == null) Following = new List(); + Following.Remove(address); + Preferences.Default.Set("following", JsonSerializer.Serialize(Following)); + } + public async Task GetBio(string address, bool forceRefresh = false) { CachedAddress = address; if (forceRefresh || CachedAddressBio == null) { @@ -252,5 +274,16 @@ namespace Neighbourhood.omg.lol.Models { } public async Task RefreshNow() => await GetNowGarden(forceRefresh: true); + public async Task> GetFeed(bool forceRefresh = false) { + if(forceRefresh || Feed == null || Feed.Count == 0) { + Feed = new List(); + foreach(string address in Following ?? new List()) { + Feed.AddRange((await GetStatuses(address, forceRefresh))?.Select(s => new StatusOrPic { Status = s }) ?? new List()); + Feed.AddRange((await GetPics(address, forceRefresh))?.Select(p => new StatusOrPic { Pic = p }) ?? new List()); + } + } + return Feed.OrderByDescending(s => s.CreatedTime); + } + } } diff --git a/Models/Status.cs b/Models/Status.cs index 5dca643..8e1297e 100644 --- a/Models/Status.cs +++ b/Models/Status.cs @@ -14,6 +14,8 @@ namespace Neighbourhood.omg.lol.Models { public string RenderedMarkdown { get; set; } public string ExternalUrl { get; set; } + public DateTimeOffset CreatedTime { get => DateTimeOffset.UnixEpoch.AddSeconds(Convert.ToInt64(Created)); } + public string EmojiOrDefault { get { return string.IsNullOrEmpty(Emoji) ? "✨" : Emoji; diff --git a/Models/StatusOrPic.cs b/Models/StatusOrPic.cs new file mode 100644 index 0000000..a305873 --- /dev/null +++ b/Models/StatusOrPic.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neighbourhood.omg.lol.Models { + public class StatusOrPic { + public Status? Status { get; set; } + public Pic? Pic { get; set; } + + public bool IsStatus { get => Status != null; } + public bool IsPic { get => Pic != null; } + + public DateTimeOffset? CreatedTime { get => Status?.CreatedTime ?? Pic?.CreatedTime; } + } +} diff --git a/Neighbourhood.omg.lol.csproj b/Neighbourhood.omg.lol.csproj index ed07211..5658330 100644 --- a/Neighbourhood.omg.lol.csproj +++ b/Neighbourhood.omg.lol.csproj @@ -28,8 +28,8 @@ com.companyname.neighbourhood.omg.lol - 1.0 - 1 + 0.9.1 + 3 14.2 14.0 @@ -44,13 +44,13 @@ au.death.lol.omg.neighbourhood 0.9.1 - 2 + 3 au.death.lol.omg.neighbourhood 0.9.1 - 2 + 3 @@ -58,7 +58,7 @@ 0.9.1 True D:\Neighbourhood.omg.lol\neighbourhood.omg.lol.keystore - 2 + 3 a!zobzizl neighbourhood.omg.lol a!zobzizl @@ -67,31 +67,31 @@ au.death.lol.omg.neighbourhood 0.9.1 - 2 + 3 au.death.lol.omg.neighbourhood 0.9.1 - 2 + 3 au.death.lol.omg.neighbourhood 0.9.1 - 2 + 3 au.death.lol.omg.neighbourhood 0.9.1 - 2 + 3 au.death.lol.omg.neighbourhood 0.9.1 - 2 + 3 diff --git a/Platforms/Android/AndroidManifest.xml b/Platforms/Android/AndroidManifest.xml index 1bb9e82..329c4f0 100644 --- a/Platforms/Android/AndroidManifest.xml +++ b/Platforms/Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + diff --git a/wwwroot/css/style.css b/wwwroot/css/style.css index 1256de8..b6a7879 100644 --- a/wwwroot/css/style.css +++ b/wwwroot/css/style.css @@ -464,4 +464,14 @@ article { nav label:is(.checkbox, .radio, .switch) { white-space: break-spaces; flex: 1 1 100%; +} + +menu > details .row, menu > li > details .row { + padding: .5rem 1rem; + min-block-size: 3rem; + flex: 1; +} + +menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover,:focus,.active) { + background-color: var(--active); } \ No newline at end of file