From afabd343f575792ba1c42ea0cd58dd895dc13bc6 Mon Sep 17 00:00:00 2001 From: synzr Date: Thu, 20 Nov 2025 21:08:33 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=8B=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?MVVM,=20=D1=87=D0=B0=D1=81=D1=82=D0=B8=D1=87=D0=BD=D0=BE=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=20?= =?UTF-8?q?=D0=B2=D1=85=D0=BE=D0=B4=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Intersvyaz/App.xaml.cs | 2 + src/Intersvyaz/Assets/IntersvyazLogo.png | Bin 0 -> 5562 bytes src/Intersvyaz/Common/BindableBase.cs | 41 +++++++++++ src/Intersvyaz/Common/RelayCommand.cs | 56 +++++++++++++++ src/Intersvyaz/Intersvyaz.csproj | 6 ++ src/Intersvyaz/ViewModels/LoginViewModel.cs | 75 ++++++++++++++++++++ src/Intersvyaz/Views/LoginPage.xaml | 34 ++++++++- src/Intersvyaz/Views/LoginPage.xaml.cs | 29 ++------ 8 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 src/Intersvyaz/Assets/IntersvyazLogo.png create mode 100644 src/Intersvyaz/Common/BindableBase.cs create mode 100644 src/Intersvyaz/Common/RelayCommand.cs create mode 100644 src/Intersvyaz/ViewModels/LoginViewModel.cs diff --git a/src/Intersvyaz/App.xaml.cs b/src/Intersvyaz/App.xaml.cs index fd43df0..cc7e8bd 100644 --- a/src/Intersvyaz/App.xaml.cs +++ b/src/Intersvyaz/App.xaml.cs @@ -18,6 +18,7 @@ using SimpleInjector; using Intersvyaz.Views; using Intersvyaz.Net; using Intersvyaz.Core.Services; +using Intersvyaz.ViewModels; namespace Intersvyaz { @@ -121,6 +122,7 @@ namespace Intersvyaz var container = new Container(); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Transient); + container.Register(Lifestyle.Transient); return container; } } diff --git a/src/Intersvyaz/Assets/IntersvyazLogo.png b/src/Intersvyaz/Assets/IntersvyazLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..aa8051312251ca90ffebb47765ce99750815a08f GIT binary patch literal 5562 zcmV;r6-DZaP)Cy`<6o!JlU4l|LzEPULL3|}Je&dw|YGt2I_vq{^n z?LtYWG?SDPm}v?!uM!p-mW4bTNbM9i4oM(Nh-Bqy0UG(rCS>yqCYqxI(O>y|)O9cs>7DT?YXCe)Kr3uK1Ux63 zpZsv;s+V3^|MG8MefDo7M>WwjUYF1oYXoY8fTv{hUyuH%`|S;{-+AD8q`5ZcYONXp zjX~f9Ac-n-cqvN7@?|3~jeYKqF8)1SU;3Z*17|;Od_}{ucIUct+D% zJ5y_?S!>n^Xas7Hz@*4#%jUlB$5*UaxwmiUf9U<0+TSc)gGQi{5vYo61_n?0pIW)@ z|CT@c=u*Q+?U97u2yWy~pl#O(Oa%fJlg+@9q2SBw)^B?V;xrZaN!Otf z$VZ^TVk#n=ojYS|S9f--*|c%vY(r9;UOF|{-PY!61f~&zvdQMaC!h7du~gjyh=b!r4^j6hMc!HJX6ZCV=YkWJjdX2zW7lDFggBgu^T@1#f1H&&o^Q>P8YS){=U)@fiTc#113Iy`VroVsC zfBxdN@X*;&TKnV^^8`_Ew>0A-bqOkaYH6NzCE+Ddkp!9t>J1`B(#P*)t>bVc!Qy#7i-{16M8i2yh1p=f~<8j}Y)< zFksMNP!`jPr!(J&9!~eO*gpv#NL>&j!CL?jvb!wl*dsmgPpB6&5@k0N3)knio1?1x zfG{#x*cBv*+b>Auu#n{bF*`q}cYvs<#x6@XLuW_SKFtvC)eN0Jc{}Pg$#ffr=ov39 zJgsC9JnJZ|tD;ZRG`mx#Y%V6m9gG}Kl*}KC#d$zF3aEf2*nX>w!?iq%7T_mr{@1jaUmvk}T8x}&jy z<|wxxI6&Z!4h*!R4$fl7+0R7fD$hAQ{wT8fw|{vhj2R8&cLzYMQ8N+6I&| zpbr0}p8%L3@m`P~4}><7 zK>$*Oxak@(}jVQjc|G5On?}0$mdLwA*Ih5=?I~0F@ep{3(|2EV*A{B z10YR~x90=NMwt|#0~i#m$6m(mR|MvW1kuEb?zN&0g)@D5!g&x=6aO$Sw3D9t$cxbR12vWKSiaJ@cs>#0xq8o$NN-uTA ztk6wnr1ro{m6|vWq~q3`-m}V;WukMCCWpH~Fu5F6`dkE-GEMgkE>AIVW_#^qLtfyJ zSCK8k2>!#*nP^7n!ub&z8NNhgUtaqb$yp1fym6tVre*&aT+?aGKcl^B>Q3aRoO9l+ zT)g&80)fmxaLjHjb!v9tya!ay;Ihg?w-Cay7b&IWg90much~HOeom#m?+_Wa*``1n zb5$1i2{#Tz-!7he@+n4gY-Cv~d5KI&>JrHm?+uGtBv^=M`EU7)o0lzL{?3di95uzX zJwXI-x)F(j1A>&(e~bdBbJe0^%@Twe#R`HuyTrhTlcQmi+I=SP#c%#R{u@oY#x%Pk*|t=l-^}JMp6b+-dtHs+`$H1v&09lTC)$rDJJd%>cpD1^YXecyTYWNlUhe5wTNC zE56!34svN~B1ao(%AFHS?9tWI1j9^SO0bZb8fLP&M5)xJTT-d9)5J5Iug$rM=FYoW ziAYaf{rnUNSt5yo9G?{{Oq`eF{a#!@OAG~Xoj)gd+x$79(UEJR(a~$+;mg+t@B6zS z_1t^ULm{rC<{$U+N%&+}%`2V36{uMx_n{&XpkHN^awL?EnsN__#)nfTTg}+`wFT2& zNe;4+rm>8s!NK0VK25@#{i71?F;e5hJ6I?=K1`V5B*!l5$AQ zmLJzzhA*5OrOyV>(yh18q1#wShOh9x(p4I{bd^%6(O@chwc~H@YVCv_wG!3wZBHyQ z$(!OcjE)vQZY@V7$!Y0~7M2(cc+aI0vC39^#XyiL=jjviu`iZNNTX?R#oDj#As=z$ z#XQ@rCIODamgTuSpw>fd^K)dnv7&}xYy*pnw*TBkg4HgmoIGo^5`e)&;9AWXZt?hxw-F1 z^rOh9$6_>camb-U2uF~-41b;&qFFaI(QWhR(hWB@5zlOtjDLBRqtMA1?y%|)r? z*zFh3eR1E8t#91-MtF7WtIs__>z@2~dh+`ZO-g>gB6K<@%WQtc=fYcF?>v3-=!>WO zx37=%_ijBmGJKUFX6iD}Y|?^;NTNqt{jJq^E#F#9G=z>4_tE9aJs!Z_Ca&MkE-`TN>sh0&U!Z8qQnDhglk<{e)5BlAb93NYdlX06L@wu=m zlM5hcPabRS+xFYrd*1!oi8G&X+rcv$J~g{S(!6G=ZXzFbl7~(KWP1`wlIdMCh~2dY;!7S{tiECKo11sK6r zE^QHC_A3j8W+|A7VM7|_)PUuJwfX=%z9-qfX0^dpcS(EQwsG6rt&MfZH8#);{Ytl! zcog9P3q>~gvZXMojTM8n_+tA=n;+h33`;Y$aO~P=ORrx(a5~T=9rO)1UX$DgrzxpO_~|9 z$=D-=G0$me^&$82DV6_5YxT-!a7jVp#b_-3T_yJw16Ikwc&_icu!_gyJO~Lw_2yt( z-rLE7f)kNpZeJzWA~kz(;Y1{g;zCurYV0kiDqtb_Ufc6ZDVG}d2qv5aekg1Iu}B*e zPqPw1?YzWl=Lrp_^kCqM1my%#SGiZVC#|1n6YMYeVZ-N}=Gd4sQ6`O!h>a3`@! zV_FFCtgyaLGXj#FWMdjZs=~G4lm@3W8NoEa3N?-*h5Cs(!OxbsPu>L2HZXH?C@v9z z-RQHHAY3B5BdL(RR3)Ac#R%ZVNn9-vKd1!GDiMxE8ACI(GDkvA> zvgKa0`UfLz>=(kMW zpsEuvUVn!O50#7*9C+2VRtN_fio7?TDeG|hrfkT@Py(~NTySC^mM2*9TjDc}j)_a< za`3pR$y>t(NVYiHln zd<%u}Wvmp}_aEQ4JQMKb!X2UAv!|UW7HK6w0QUs<2L7DLvVUtB%M3vxoGVKtGVk%w zBpr0K3ddkCK#rV+_$1Y712N%MvV5XqPE5Q{Zt`na1%H|kXXSN?>=&7o<4a8U~M&J2C#j(;`bS%M=eWAR1Gpf zK6@7KJD$9zkC_VV9<=Kw?nfX;M;r$-2M~5rN?^mBlPdl|KiXg~$V;j)?6Joc@)xpW zvfBoz7w_(HefIVdzTw_bWYfH8LBjvncakB_><~hbAcKT35|QH{uO$ziUOuJ4l%}z) z1skaqN+55UlGf+dUktE9$E*A_b-ay>`am!k03f0a8Q2FwS$iZcrP_}T=D-rk13b6a zhl+_3INFBSF0h4o_F<0`hHSToEA^|T6cd}RoZ5Rtn^7Oc-Ad%C000GJNklg|Q z0-cHws<5MIp8yUl864~w6SuDovo^yrq7kltb=MtB|MFX3KmEsTuP#;gO5)SAkDHa> zqdx!!o!KLG4X#G=-bUqO@J4&B8;*OV2*)|dxLO(F$50rZo(Gir9uWK>B2LVGxLhdnw+!L>yOeE|@7R{GBUVx`2BIUb!hpg8uV=6>dr`ER>+6943kV1o@tn_2 z2nmiW^QF}ySO5nIE4FTvB4z#zly0Rsg~mNIKnzxRuBW#%uH4NJL1x^B@e_}H2WOyEQbwz&%yShkQ?vuMby+N+6ZeF%aC}d!VaSG>WO&E6Er}zsDDSQbGDx)TJ-W+% zGC`HNsCsc~Ap1HuZq{VVqm#MINBQZ=lC1-s!kwpB82apG5iXY39v^kJtNd~UWq*|- z8XL&A$?do5!_}UKu-#6t{1_;>cRZVDMkhySCpF*%pVSCN0)mME>1>bsF?1^7I8V{G zxzd1FGwO~q<`cCqozIC|x&0w!nwTxAuLPt_S(F^`u!Jqh1L6T55Mn%lff7TuAuY=H zuo|0Du7EUiTr=7Towz6Da)v8hm%-g1oeG`!^myx=-M{e*!Eg~xnrP~>p=B7QvlK@F z0~ehfojuBf-x7o&NH1QxzEfbkT_&;?c|knD0|G37fs%vqNiW?ds=rWr-IL9peW!vW zmo7fHYQ+!cNp69VQWFj1pg$UcMnj;k$!6caQ~uH6;Vn=7_=mGGpApihiKfwdn6_6V zkU^ks$p%ET@1s5a&;N4G?DIoIyia2=(ezDfk0#S*ebESL1g07RuCFf121FD2cyIq7 zHf>sPI2KKi51YZ7X!w5Vk4B(j5vV(|**9>?zn_Wb{deD8a3~r}_>5U?d*hmD8n%aN z!!-gK1nP=xKr{z-?d{*LW;C(hJ%>MN-WBh~`F+M*ebESL1R52Ax*?kZp3!{r;Ya-& zp6_08=wPgC&!G?SYhR6utG2tA2x#36i9lVD4T$DY{P0`PtbA(0#dGJ|b|3y@XG5Z? zjn)Wg1d1Xs?PLR@xo}~4%ahB0GS3jAjh9WmMXl8d8Uc+!!y+)PWHWGND0uYn(YOBd zpC3NG`_K;T&uF4)*si1v*9eqGVA{xL;~Vckxc!~Y;m7~`f8PG^P;W*1Gm9U1F8JLC z)^&d8fpsDHOE+EY}3X~YYy()|M$D%?<=w>*9KEk$Kl^U z-0owN!K{W!2frnfFv#dexLiFtpGH6f2 z$V3X$`XzWj?DjF~m}G|HTOUrqkC_kRwP*ys`Uo^$3yrBVdHG{cbZ>g||F`anZ?F0X zuY61{mc4=qgyWY)cG5x|X{Qm;2-F_}##ET}uMa);{obwbckZfi*<`mZLoW6x;$@WI zM}{JsIL~enc4W{2jetg=ZV)i0!jkpBUHgklmrZWFcweTMA{8Icba)>}{mTd} zvk>My`lAuh2-G10MjhHz&?KI@^nzHhFXLm9!D$r7B@97bD8O+EgyGjxDFQmbMxgcx z7`3m$yBa_&JgW(syeZ726J+tjk68^94Q4*xRi-s*1T+GkBG6D|!-SGJvh|T}UQ%^4 z>G)ZK@FUJsRJ1aUfJVS`1pW*F0RR7;ycpg9000I_L_t&o0FaxdWfaT+pa1{>07*qo IM6N<$g35`t+yDRo literal 0 HcmV?d00001 diff --git a/src/Intersvyaz/Common/BindableBase.cs b/src/Intersvyaz/Common/BindableBase.cs new file mode 100644 index 0000000..ee2a927 --- /dev/null +++ b/src/Intersvyaz/Common/BindableBase.cs @@ -0,0 +1,41 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Intersvyaz.Common +{ + public class BindableBase : INotifyPropertyChanged + { + /// + /// Событие об изменении значения свойства. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Установка значения в заднее поля свойства с вызовом события. + /// + /// Тип значения. + /// Заднее поле свойства. + /// Новое значение. + /// Имя свойства. (Необязательный параметр) + /// При компилиции, имя свойства будет автоматически заполнено. + protected void SetProperty(ref T back, T value, [CallerMemberName] string propertyName = null) + { + if (!Equals(back, value)) + { + back = value; + OnPropertyChanged(propertyName); + } + } + + /// + /// Вызов события об изменении + /// + /// Имя свойства. (Необязательный параметр) + /// При компилиции, имя свойства будет автоматически заполнено. + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Intersvyaz/Common/RelayCommand.cs b/src/Intersvyaz/Common/RelayCommand.cs new file mode 100644 index 0000000..18a36f7 --- /dev/null +++ b/src/Intersvyaz/Common/RelayCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Intersvyaz.Common +{ + public class RelayCommand : ICommand + { + /// + /// Действие команды. + /// + private readonly Action _execute; + + /// + /// Действие проверки «можно ли исполнить команду?». + /// + private readonly Predicate _canExecute = null; + + /// + public event EventHandler CanExecuteChanged; + + public RelayCommand(Action execute) + { + _execute = execute; + } + + public RelayCommand(Action execute, Predicate canExecute) + { + _execute = execute; + _canExecute = canExecute; + } + + /// + public bool CanExecute(object parameter) + { + return _canExecute == null || _canExecute(parameter); + } + + /// + public void Execute(object parameter) + { + _execute(parameter); + } + + /// + /// Вызов события изменения состояния. + /// + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/src/Intersvyaz/Intersvyaz.csproj b/src/Intersvyaz/Intersvyaz.csproj index 0b9b5a0..91b3c7e 100644 --- a/src/Intersvyaz/Intersvyaz.csproj +++ b/src/Intersvyaz/Intersvyaz.csproj @@ -119,7 +119,10 @@ App.xaml + + + LoginPage.xaml @@ -131,6 +134,9 @@ + + Always + diff --git a/src/Intersvyaz/ViewModels/LoginViewModel.cs b/src/Intersvyaz/ViewModels/LoginViewModel.cs new file mode 100644 index 0000000..f7341a5 --- /dev/null +++ b/src/Intersvyaz/ViewModels/LoginViewModel.cs @@ -0,0 +1,75 @@ +using Intersvyaz.Common; +using Intersvyaz.Core.Services; +using System.Diagnostics; + +namespace Intersvyaz.ViewModels +{ + public class LoginViewModel : BindableBase + { + /// + /// Сервис сессии пользователя. + /// + private readonly ISessionService _sessionService; + + /// + /// Заднее поле имени пользователя. + /// + private string _username; + + /// + /// Заднее поле пароля пользователя. + /// + private string _password; + + /// + /// Имя пользователя. + /// + public string Username + { + get => _username; + set + { + SetProperty(ref _username, value); + LoginCommand.RaiseCanExecuteChanged(); + } + } + + /// + /// Пароль пользователя. + /// + public string Password + { + get => _password; + set + { + SetProperty(ref _password, value); + LoginCommand.RaiseCanExecuteChanged(); + } + } + + /// + /// Команда входа. + /// + public RelayCommand LoginCommand { get; } + + public LoginViewModel(ISessionService sessionService) + { + _sessionService = sessionService; + + LoginCommand = new RelayCommand(ExecuteLoginCommand, CanExecuteLoginCommand); + Username = Password = string.Empty; + } + + private async void ExecuteLoginCommand(object _) + { + var sessionData = await _sessionService.GetSessionAsync(Username, Password); + + // TODO: Обработка данных об сессии + } + + private bool CanExecuteLoginCommand(object _) + { + return !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password); + } + } +} diff --git a/src/Intersvyaz/Views/LoginPage.xaml b/src/Intersvyaz/Views/LoginPage.xaml index 6cc2df0..db8a8c3 100644 --- a/src/Intersvyaz/Views/LoginPage.xaml +++ b/src/Intersvyaz/Views/LoginPage.xaml @@ -6,9 +6,37 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" - Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" + DataContext="{x:Bind ViewModel}"> - - + + + + + + + + + + + + + + +