using Microsoft.AspNetCore.Components; using System.ComponentModel; using System.Globalization; 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; } public List? NowGarden { get; set; } 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; } public bool IsAuthorized { get => AccountInfo != null; } public string? Name { get => AccountInfo?.Name; } public string? Email { get => AccountInfo?.Email; } public IEnumerable? AddressNames { get => AddressList?.Select(a => a.Address); } // Selected Address private AddressResponseData? _selectedAddress; public AddressResponseData? SelectedAddress { get { if (_selectedAddress == null) { string selectedAddressJson = Preferences.Default.Get("selectedaddress", string.Empty); if (!string.IsNullOrEmpty(selectedAddressJson)) _selectedAddress = JsonSerializer.Deserialize(selectedAddressJson); } return _selectedAddress; } set { if (_selectedAddress != value) { _selectedAddress = value; if (_selectedAddress == null) Preferences.Default.Remove("selectedaddress"); else { string selectedAddressJson = JsonSerializer.Serialize(_selectedAddress); Preferences.Default.Set("selectedaddress", selectedAddressJson); } } } } public List? Following { get; private set; } public string? SelectedAddressName { get => SelectedAddress?.Address; } // data for selected address public List? CachedAddressStatuses { get; set; } public List? CachedAddressPics { get; set; } public MarkupString? CachedAddressBio { get; set; } private string? _cachedAddress; public string? CachedAddress { get => _cachedAddress; set { if (_cachedAddress != value) { _cachedAddress = value; CachedAddressStatuses = new List(); CachedAddressPics = new List(); CachedAddressBio = null; } } } // share intent stuff public event EventHandler? IntentReceived; private string? _shareString; public string? ShareString { get => _shareString; set { _shareString = value; IntentReceived?.Invoke(this, EventArgs.Empty); } } public string? ShareStringSubject { get; set; } private string? _sharePhoto; public string? SharePhoto { get => _sharePhoto; set { _sharePhoto = value; IntentReceived?.Invoke(this, EventArgs.Empty); } } public long? SharePhotoSize { get; set; } public string? SharePhotoContentType { get; set; } public string? SharePhotoText { get; set; } // refreshing public event PropertyChangedEventHandler? PropertyChanged; private bool _isRefreshing; public bool IsRefreshing { get => _isRefreshing; private set { _isRefreshing = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsRefreshing))); } } public void SendRefresh() => IsRefreshing = true; private static int _refresherCount = 0; private static Mutex mutex = new Mutex(); public class RefreshToken : IDisposable { public event EventHandler? Disposed; public void Dispose() => Disposed?.Invoke(this, EventArgs.Empty); } public RefreshToken GetRefreshToken() { mutex.WaitOne(); _refresherCount++; mutex.ReleaseMutex(); RefreshToken token = new RefreshToken(); token.Disposed += RefreshToken_Disposed; return token; } private void RefreshToken_Disposed(object? sender, EventArgs e) { mutex.WaitOne(); _refresherCount--; if (_refresherCount == 0) IsRefreshing = false; mutex.ReleaseMutex(); } private bool _canRefresh; public bool CanRefresh { get => _canRefresh; set { _canRefresh = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CanRefresh))); } } // api service private RestService api { get; set; } public State(RestService restService) { api = restService; } public async Task PopulateAccountDetails(string token) { api.AddToken(token); 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) { AccountInfo = await api.AccountInfo(); if (AccountInfo != null) { // quick fix for users without names (such as the review account) if(AccountInfo.Name == null) AccountInfo.Name = AccountInfo.Email.Split('@').FirstOrDefault() ?? "person"; Preferences.Default.Set("accountdetails", JsonSerializer.Serialize(AccountInfo)); } } // if we don't have the list of addresses, attempt to retrieve that. if (AddressList == null) { AddressList = await api.Addresses(); if (AddressList != null) { Preferences.Default.Set("accountaddresses", JsonSerializer.Serialize(AddressList)); SelectedAddress = AddressList.FirstOrDefault(); Preferences.Default.Set("selectedaddress", JsonSerializer.Serialize(SelectedAddress)); } } } public async Task RemoveAccountDetails() { Preferences.Default.Clear(); 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) { CachedAddressBio = await api.StatuslogBio(address); } return CachedAddressBio; } public async Task?> GetEphemeralMessages(bool forceRefresh = false) { if (forceRefresh || this.EphemeralMessages == null || this.EphemeralMessages.Count == 0) { this.EphemeralMessages = await api.Ephemeral(); } return this.EphemeralMessages; } public async Task?> GetDirectory(bool forceRefresh = false) { if (forceRefresh || this.AddressDirectory == null || this.AddressDirectory.Count == 0) { IdnMapping idn = new IdnMapping(); this.AddressDirectory = (await api.Directory()).Select(s => { if (s.StartsWith("xn--")) return idn.GetUnicode(s); else return s; }).ToList(); } return this.AddressDirectory; } public async Task?> GetStatuses(bool forceRefresh = false) { if (forceRefresh || this.Statuses == null || this.Statuses.Count == 0) { this.Statuses = await api.StatuslogLatest(); } return this.Statuses; } public async Task?> GetStatuses(string address, bool forceRefresh = false) { this.CachedAddress = address; if (forceRefresh || this.CachedAddressStatuses == null || this.CachedAddressStatuses.Count == 0) { this.CachedAddressStatuses = await api.Statuslog(address); } return this.CachedAddressStatuses; } public async Task?> GetNowGarden(bool forceRefresh = false) { if (forceRefresh || this.NowGarden == null || this.NowGarden.Count == 0) { this.NowGarden = await api.NowGarden(); } return this.NowGarden; } public async Task?> GetPics(bool forceRefresh = false) { if (forceRefresh || this.Pics == null || this.Pics.Count == 0) { this.Pics = await api.SomePics(); } return this.Pics; } public async Task?> GetPics(string address, bool forceRefresh = false) { CachedAddress = address; if (forceRefresh || this.CachedAddressPics == null || this.CachedAddressPics.Count == 0) { CachedAddressPics = (await api.SomePics(address)) ?? new List(); } return CachedAddressPics; } public async Task RefreshStatuses() { await GetStatuses(forceRefresh: true); if(SelectedAddressName != null) await GetStatuses(SelectedAddressName, forceRefresh: true); } public async Task RefreshPics() { await GetPics(forceRefresh: true); if (SelectedAddressName != null) await GetPics(SelectedAddressName, forceRefresh: true ); } 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); } } }