XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

STHApplication

【MAUI】寄当家——安卓端交易管理系统

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Globalization;
using JiDangJia.Contracts;
using JiDangJia.Services;

namespace JiDangJia.Views;

public sealed record CommunityFeedState(string City,string Query,string Intent,string Category,string Subcategory,string Brand,string Condition,string MinPrice,string MaxPrice,bool ShowFilters,string ServiceCategory="",bool LocationResolved=false,string LocationStatus="");

public sealed class CommunityHub : ContentView,IDisposable
{
    private static readonly string[] Keys=["FOLLOW","CITY","RECOMMEND","SUPPLY","SERVICE","SQUARE","CHAT"];
    private static readonly string[] Names=["关注","同城","推荐","供需","服务","广场","私信"];
    private readonly ContentPage page;
    private readonly Action<int> changed;
    private readonly VerticalStackLayout body=Ui.Stack(),list=Ui.Stack(),controls=Ui.Stack(),tabs=Ui.Stack();
    private readonly Label state=Ui.Text("",12,Ui.Muted),notice=Ui.Text(CommunityCatalog.DefaultNotice,11,Ui.Bronze);
    private Dictionary<string,string> availableCategories=new();
    private readonly Entry search=new(){Placeholder="搜索物品 / 内容 / 姓名",MaxLength=120};
    private readonly Entry city=new(){Placeholder="城市,如沈阳",MaxLength=60};
    private readonly Picker category=new(){Title="物品大类"},sub=new(){Title="物品细类"},condition=new(){Title="成色"};
    private readonly Entry brand=new(){Placeholder="品牌",MaxLength=120},minimum=new(){Placeholder="最低固定价",Keyboard=Keyboard.Numeric},maximum=new(){Placeholder="最高固定价",Keyboard=Keyboard.Numeric};
    private readonly Button more;
    private Button? mine;
    private readonly string account=AuthClient.Current.AccountId;
    private string intent="",serviceCategory="",mineTitle="我的卡片";
    private int selected,version;
    private long? next;
    private bool disposed,showFilters,active;
    private readonly CancellationTokenSource lifetime=new();
    private bool locating,located;
    private string locationState="";
    private readonly bool embedded;
    public CommunityHub(ContentPage page,int selected,Action<int> changed,CommunityFeedState? saved=null,bool embedded=false,bool active=true)
    {
        this.page=page;this.selected=selected;this.changed=changed;this.embedded=embedded;this.active=active;serviceCategory=saved?.ServiceCategory??"";body.Padding=new Thickness(12,6,12,12);body.Spacing=8;controls.Spacing=7;list.Spacing=8;Content=body;
        located=saved?.LocationResolved??false;locationState=saved?.LocationStatus??"";
        city.Text=saved?.City??(selected==1?CommunityLocation.City:"");search.Text=saved?.Query;intent=CommunityCatalog.Intents.ContainsKey(saved?.Intent??"")?saved!.Intent:"";showFilters=saved?.ShowFilters??false;brand.Text=saved?.Brand;minimum.Text=saved?.MinPrice;maximum.Text=saved?.MaxPrice;
        ResetCategories(saved?.Category);
        condition.ItemsSource=new[]{"全部成色"}.Concat(CommunityCatalog.Conditions.Values).ToList();condition.SelectedIndex=Math.Max(0,CommunityCatalog.Conditions.Keys.ToList().IndexOf(saved?.Condition??"")+1);ResetSub();sub.SelectedIndex=Math.Max(0,CommunityCatalog.Subcategories(Category(),intent).Keys.ToList().IndexOf(saved?.Subcategory??"")+1);category.SelectedIndexChanged+=(_,_)=>ResetSub();
        more=Ui.Button("加载更多",()=>Load(true),false);more.IsVisible=false;
        notice.MaxLines=1;notice.FontSize=10;notice.LineBreakMode=LineBreakMode.TailTruncation;
        var risk=new Grid{ColumnDefinitions=[new(GridLength.Auto),new(GridLength.Star)],ColumnSpacing=10};risk.Add(Ui.Icon("shield",Ui.Gold,20));risk.Add(notice,1);
        if(selected is not (2 or 6))body.Add(Ui.Tap(Ui.Card(risk,10,new Thickness(9,7),Ui.Cream),()=>page.DisplayAlertAsync("社群风险告知",notice.Text,"我已了解"),"社群风险告知"));
        state.IsVisible=false;state.PropertyChanged+=(_,e)=>{if(e.PropertyName==nameof(Label.Text))state.IsVisible=!string.IsNullOrWhiteSpace(state.Text);};
        if(!embedded)body.Add(tabs);body.Add(controls);body.Add(state);body.Add(list);body.Add(more);RenderControls();
        Loaded+=async(_,_)=>{if(!embedded&&active&&!disposed)await Reload();};
        search.Completed+=async(_,_)=>await Load();
    }
    public void Dispose(){disposed=true;version++;lifetime.Cancel();CommunityVideo.SetActive(list,false);}
    public void SetActive(bool value){if(active==value||disposed)return;active=value;CommunityVideo.SetActive(list,value);if(!value)version++;else _=Reload();}
    public CommunityFeedState Snapshot()=>new(city.Text??"",search.Text??"",intent,Category(),Sub(),brand.Text??"",condition.SelectedIndex>0?CommunityCatalog.Conditions.Keys.ElementAt(condition.SelectedIndex-1):"",minimum.Text??"",maximum.Text??"",showFilters,serviceCategory,located,locationState);
    public async Task Reload(){if(disposed)return;if(selected==6){await Load();return;}try{var settings=await AuthClient.Current.CommunityRequest<CommunitySettings>("/settings");if(!disposed)notice.Text=settings.Notice;}catch(Exception ex){if(!disposed)state.Text=ex.Message;}await Load();}
    private void ResetCategories(string? previous=null){availableCategories=CommunityCatalog.CategoriesFor(intent);category.ItemsSource=new[]{"全部物品"}.Concat(availableCategories.Values).ToList();category.SelectedIndex=Math.Max(0,availableCategories.Keys.ToList().IndexOf(previous??"")+1);ResetSub();}
    private void ResetSub(){sub.ItemsSource=new[]{"全部细类"}.Concat(CommunityCatalog.Subcategories(Category(),intent).Values).ToList();sub.SelectedIndex=0;}
    private string Category()=>category.SelectedIndex>0?availableCategories.Keys.ElementAt(category.SelectedIndex-1):"";
    private string Sub()=>sub.SelectedIndex>0?CommunityCatalog.Subcategories(Category(),intent).Keys.ElementAt(sub.SelectedIndex-1):"";
    private async Task Select(int index){if(index==selected)return;selected=index;changed(index);intent="";ResetCategories();showFilters=false;RenderControls();await Load();}
    private void RenderControls()
    {
        // Removing a containing card does not remove its native child. Detach reused inputs first.
        foreach(var view in new View[]{city,search,category,sub,brand,condition,minimum,maximum})
        {
            if(view.Parent is Border border)border.Content=null;
            else if(view.Parent is Layout layout)layout.Children.Remove(view);
            else if(view.Parent is ContentView container)container.Content=null;
        }
        tabs.Clear();if(!embedded){var row=new HorizontalStackLayout{Spacing=5};for(int i=0;i<Names.Length;i++){int index=i;var b=Ui.Button(Names[i],()=>Select(index),i==selected);b.Padding=new Thickness(12,0);b.HeightRequest=39;row.Add(b);}tabs.Add(new ScrollView{Orientation=ScrollOrientation.Horizontal,Content=row,HorizontalScrollBarVisibility=ScrollBarVisibility.Never});}
        controls.Clear();mine=null;
        if(selected==6){controls.Add(Ui.Spread(Small("+ 新建私信",()=>page.Navigation.PushAsync(new ContactsPage())),Small("私信列表",()=>page.Navigation.PushAsync(new ConversationsPage()),false)));return;}
        if(selected==2){if(AuthClient.Current.IsAdmin)controls.Add(Small("+ 发布推荐",()=>page.Navigation.PushAsync(new PublishPostPage("RECOMMEND"))));return;}
        if(selected==1)
        {
            controls.Add(Ui.Spread(Small("📍 "+(city.Text?.Length>0?city.Text:"定位城市"),ChooseCity,false),Small("重新定位",async()=>{located=false;await Locate(true);await Load();},false)));
            if(locationState.Length>0)controls.Add(Ui.Text(locationState,10,Ui.Muted));
        }
        if(selected==3)
        {
            var intents=new HorizontalStackLayout{Spacing=5};foreach(var p in new Dictionary<string,string>{{"","全部"}}.Concat(CommunityCatalog.Intents)){string key=p.Key;var button=Ui.Button(p.Value,async()=>{var previous=Category();intent=key;ResetCategories(previous);RenderControls();await Load();},key==intent);button.Padding=new Thickness(10,0);button.HeightRequest=34;button.FontSize=12;intents.Add(button);}controls.Add(new HorizontalChoiceScroll{Content=intents});
            var actions=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Auto),new(GridLength.Auto)],ColumnSpacing=6};
            actions.Add(Small("+ 发布供需",async()=>{if(await CommunitySafetyUi.Write(page))await page.Navigation.PushAsync(new PublishPostPage("SUPPLY"));}));
            mine=Small(mineTitle,()=>page.Navigation.PushAsync(new PostsPage("MINE")),false);SemanticProperties.SetDescription(mine,"我的卡片");actions.Add(mine,1);
            actions.Add(Small(showFilters?"收起筛选":"筛选 · "+(city.Text?.Length>0?city.Text:"全部城市"),()=>{showFilters=!showFilters;RenderControls();return Task.CompletedTask;},false),2);controls.Add(actions);
            if(showFilters)controls.Add(Ui.Card(Ui.Stack(Small("城市 · "+(city.Text?.Length>0?city.Text:"全部城市"),ChooseCity,false),Ui.Spread(category,sub),Ui.Spread(brand,condition),Ui.Text("固定价区间(元)",11,Ui.Muted),Ui.Spread(minimum,maximum),Small("应用筛选",async()=>{showFilters=false;RenderControls();await Load();},false)),12,new Thickness(10)));
        }
        else if(selected==4)
        {
            var services=new HorizontalStackLayout{Spacing=6};foreach(var p in new Dictionary<string,string>{{"","全部服务"}}.Concat(ServiceCatalog.Categories)){var key=p.Key;services.Add(Small(p.Value,async()=>{serviceCategory=key;RenderControls();await Load();},key==serviceCategory));}controls.Add(new HorizontalChoiceScroll{Content=services});
            controls.Add(Ui.Spread(Small("+ 发布服务",async()=>{if(await CommunitySafetyUi.Write(page))await page.Navigation.PushAsync(new ServicePublishPage());}),Small("我的服务 / 发布",()=>page.Navigation.PushAsync(new PostsPage("MINE")),false)));
        }
        else if(selected==5)controls.Add(Ui.Spread(Small("+ 发布图文",async()=>{if(await CommunitySafetyUi.Write(page))await page.Navigation.PushAsync(new PublishPostPage());}),Small("我的发布",()=>page.Navigation.PushAsync(new PostsPage("MINE")),false)));
        search.FontSize=12;search.HeightRequest=36;
        var searchRow=new Grid{ColumnDefinitions=[new(GridLength.Star),new(GridLength.Auto),new(GridLength.Auto)],ColumnSpacing=6};searchRow.Add(Ui.Card(search,10,new Thickness(9,0)));searchRow.Add(Small("搜索",()=>Load(),false),1);
        if(selected==4)searchRow.Add(Small(city.Text?.Length>0?city.Text:"全部城市",ChooseCity,false),2);controls.Add(searchRow);
    }
    private static Button Small(string title,Func<Task> action,bool primary=true){var button=Ui.Button(title,action,primary);button.HeightRequest=34;button.FontSize=12;button.Padding=new Thickness(10,0);button.CornerRadius=10;return button;}
    private async Task ChooseCity(){var choice=await CityPickerPage.Choose(page,city.Text??"",selected!=1,value=>{if(disposed)return;city.Text=value;if(selected==1){CommunityLocation.City=value;located=true;locationState="已选择城市";}});if(choice is null||disposed)return;RenderControls();await Load();}
    private async Task Locate(bool force=false)
    {
        if(locating||located||disposed||!active)return;locating=true;state.Text="正在获取所在城市…";
        try{city.Text=await CommunityLocator.Current(lifetime.Token,force);located=true;locationState="已自动定位";}
        catch(Exception ex)when(ex is not OperationCanceledException){locationState=ex.Message;located=true;}
        finally{locating=false;if(!disposed)RenderControls();}
    }
    private decimal? Price(Entry e){if(string.IsNullOrWhiteSpace(e.Text))return null;if(!decimal.TryParse(e.Text,NumberStyles.Number,CultureInfo.InvariantCulture,out var n)||n<0)throw new ArgumentException("价格区间请填写非负数字");return n;}
    private async Task Load(bool more=false)
    {
        if(disposed||!active)return;int stamp=++version;int current=selected;if(!more){list.Clear();next=null;this.more.IsVisible=false;}state.Text="正在加载…";
        try
        {
            if(current==6)
            {
                void Local(){list.Clear();var data=CommunityChatArchive.Current.Conversations();foreach(var conversation in data)list.Add(CommunityUi.ConversationCard(conversation,page));if(data.Count==0)list.Add(Ui.Card(Ui.Empty("还没有私信","无需加好友,选择一个账户即可联系")));}
                Local();state.Text="";await CommunityChatArchive.Sync(lifetime.Token);if(disposed||stamp!=version||!active)return;Local();state.Text=CommunityChatArchive.LastError;return;
            }
            if(!await CommunitySafetyUi.Agreement(page)){state.Text="请阅读并同意社群协议后继续";return;}
            if(current==1){await Locate();if(disposed||stamp!=version||!active)return;if(string.IsNullOrWhiteSpace(city.Text)){state.Text="请开启定位,或点城市选择所在地";return;}}
            if(current==3){var limit=await AuthClient.Current.CommunityRequest<SupplyQuota>("/supply/quota");if(disposed||stamp!=version)return;mineTitle=$"我的卡片 {limit.Active}/{limit.Limit}";if(mine is not null)mine.Text=mineTitle;}
            var f=new FeedFilter(Keys[current],more?next??0:0,current is 1 or 3 or 4?city.Text?.Trim()??"":"",current==3?intent:"",current==3?Category():"",current==3?Sub():"",current==3?brand.Text?.Trim()??"":"",current==3&&condition.SelectedIndex>0?CommunityCatalog.Conditions.Keys.ElementAt(condition.SelectedIndex-1):"",current==2?"":search.Text?.Trim()??"",current==3?Price(minimum):null,current==3?Price(maximum):null,current==4?serviceCategory:"");
            var dataPage=await AuthClient.Current.CommunityRequest<PostPage>(CommunityUi.FeedPath(f));if(disposed||stamp!=version||account!=AuthClient.Current.AccountId)return;
            foreach(var post in dataPage.Items)list.Add(CommunityUi.PostCard(post,page));next=dataPage.NextBefore;this.more.IsVisible=next.HasValue;
            CommunityVideo.SetActive(list,active);
            if(list.Count==0)list.Add(Ui.Card(Ui.Empty(current==2?"暂无官方推荐":"暂无匹配内容",current==0?"关注同行后,会在这里显示他们的发布":current==1?"设置城市后可查看同城发布":"发布内容后可在这里交流")));state.Text="";
        }
        catch(Exception ex){if(!disposed&&stamp==version){state.Text=ex.Message;if(AuthClient.Current.AccountId.Length==0)LoginPage.ShowLogin();}}
    }
}

internal static class CommunityLocation
{
    private static string Key=>"community-city-"+AuthClient.Current.AccountId;
    public static string City{get=>Preferences.Default.Get(Key,"");set=>Preferences.Default.Set(Key,value);}
    public static string Address{get=>Preferences.Default.Get(Key+"-address","");set=>Preferences.Default.Set(Key+"-address",value);}
}