un positionnement de paramètre workflow des pros

vnext
Paul Schneider 8 years ago
parent 7140a70278
commit bad14bbcd8
322 changed files with 25893 additions and 3844 deletions

@ -9,7 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookAStar.iOS", "BookAStar\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookAStar", "BookAStar\BookAStar\BookAStar.csproj", "{A0815650-0A0A-47B0-8826-771F0E1AD137}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Client", "Yavsc.Client\Yavsc.Client.csproj", "{67F9D3A8-F71E-4428-913F-C37AE82CDB24}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YavscLib", "YavscLib\YavscLib.csproj", "{67F9D3A8-F71E-4428-913F-C37AE82CDB24}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Accounts;
namespace BookAStar.Droid.Accounts
{
class YavscAccountAuthenticator : AbstractAccountAuthenticator
{
public YavscAccountAuthenticator(Context context): base(context)
{
}
public override Bundle AddAccount(AccountAuthenticatorResponse response, string accountType, string authTokenType, string[] requiredFeatures, Bundle options)
{
throw new NotImplementedException();
}
public override Bundle ConfirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
{
throw new NotImplementedException();
}
public override Bundle EditProperties(AccountAuthenticatorResponse response, string accountType)
{
throw new NotImplementedException();
}
public override Bundle GetAuthToken(AccountAuthenticatorResponse response, Account account, string authTokenType, Bundle options)
{
throw new NotImplementedException();
}
public override string GetAuthTokenLabel(string authTokenType)
{
throw new NotImplementedException();
}
public override Bundle HasFeatures(AccountAuthenticatorResponse response, Account account, string[] features)
{
throw new NotImplementedException();
}
public override Bundle UpdateCredentials(AccountAuthenticatorResponse response, Account account, string authTokenType, Bundle options)
{
throw new NotImplementedException();
}
}
}

@ -31,7 +31,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;DEV</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>0</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
@ -328,6 +328,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Accounts\YavscAccountAuthenticator.cs" />
<Compile Include="Helpers\Settings.cs" />
<Compile Include="Helpers\SimpleJsonPostMethod.cs" />
<Compile Include="Helpers\YavscHelpers.cs" />
@ -342,11 +343,14 @@
<Compile Include="Markdown\MarkdownViewModel.cs" />
<Compile Include="Markdown\MarkdownViewRenderer.cs" />
<Compile Include="Markdown\MarkdownWebChromeClient.cs" />
<Compile Include="Markdown\MDContextMenu.cs" />
<Compile Include="Markdown\MDWebView.cs" />
<Compile Include="OAuth2\YaOAuth2Authenticator.cs" />
<Compile Include="Rendering\ImageButtonRenderer.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SendFilesActivity.cs" />
<Compile Include="Services\AccountChooserService.cs" />
<Compile Include="Services\YavscChooserTargetService.cs" />
<Compile Include="Services\GcmListenerService.cs" />
<Compile Include="Services\GcmRegistrationIntentService.cs" />
@ -372,6 +376,9 @@
<AndroidResource Include="Resources\layout\EditEstimate.axml">
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\menu\md_menu.axml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\icon.png" />
@ -381,15 +388,17 @@
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml">
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\values\styles.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Yavsc.Client\Yavsc.Client.csproj">
<ProjectReference Include="..\..\YavscLib\YavscLib.csproj">
<Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
<Name>Yavsc.Client</Name>
<Name>YavscLib</Name>
</ProjectReference>
<ProjectReference Include="..\BookAStar\BookAStar.csproj">
<Project>{A0815650-0A0A-47B0-8826-771F0E1AD137}</Project>
@ -474,7 +483,9 @@
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\values\dimens.xml" />
<AndroidResource Include="Resources\values\strings.xml" />
<AndroidResource Include="Resources\values\strings.xml">
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\drawable\glyphish_07_map_marker.png" />
<AndroidResource Include="Resources\drawable\glyphish_13_target.png" />
<AndroidResource Include="Resources\drawable\glyphish_74_location.png" />
@ -499,6 +510,14 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable\peer_to_peer.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\xml\authenticator.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\xml\account_preferences.xml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>

@ -251,8 +251,8 @@ namespace BookAStar.Droid
{
Task.Run(async () =>
{
App.ShowBookQuery(
await DataManager.Current.BookQueries.Get(queryId));
var query = DataManager.Instance.BookQueries.LocalGet(queryId);
App.ShowBookQuery(query);
});
}
}
@ -319,15 +319,16 @@ namespace BookAStar.Droid
return manager.FindAccountsForService(Constants.ApplicationName);
});
}
YaOAuth2Authenticator auth = new YaOAuth2Authenticator(
clientId: "d9be5e97-c19d-42e4-b444-0e65863b19e1",
clientSecret: "blouh",
scope: "profile",
authorizeUrl: new Uri("http://dev.pschneider.fr/authorize"),
redirectUrl: new Uri("http://dev.pschneider.fr/oauth/success"),
accessTokenUrl: new Uri("http://dev.pschneider.fr/token"));
public void AddAccount()
{
var auth = new YaOAuth2Authenticator(
clientId: "d9be5e97-c19d-42e4-b444-0e65863b19e1",
clientSecret: "blouh",
scope: "profile",
authorizeUrl: new Uri(Constants.AuthorizeUrl),
redirectUrl: new Uri(Constants.RedirectUrl),
accessTokenUrl: new Uri(Constants.AccessTokenUrl));
Intent loginIntent = auth.GetUI(this);
var accStore = AccountStore.Create(this);
auth.Completed += (sender, eventArgs) =>
@ -348,65 +349,48 @@ namespace BookAStar.Droid
{
using (var client = new HttpClient())
{
// get me
// var request = new OAuth2Request("GET", new Uri(Constants.UserInfoUrl), null, eventArgs.Account);
var request = new HttpRequestMessage(HttpMethod.Get, Constants.UserInfoUrl);
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokens.AccessToken);
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string userJson = await response.Content.ReadAsStringAsync();
JObject jactiveUser = JObject.Parse(userJson);
Account acc = eventArgs.Account;
var uid = jactiveUser["Id"].Value<string>();
var username = jactiveUser["UserName"].Value<string>();
var roles = jactiveUser["Roles"].Values<string>().ToList();
var emails = jactiveUser["EMails"].Values<string>().ToList();
var newuser = new User
using (var request = new HttpRequestMessage(HttpMethod.Get, Constants.UserInfoUrl))
{
UserName = username,
EMails = emails,
Roles = roles,
Id = uid,
YavscTokens = tokens
};
MainSettings.SaveUser(newuser);
accStore.Save(acc, Constants.ApplicationName);
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokens.AccessToken);
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
string userJson = await response.Content.ReadAsStringAsync();
JObject jactiveUser = JObject.Parse(userJson);
Account acc = eventArgs.Account;
var uid = jactiveUser["Id"].Value<string>();
var username = jactiveUser["UserName"].Value<string>();
var roles = jactiveUser["Roles"].Values<string>().ToList();
var emails = jactiveUser["EMails"].Values<string>().ToList();
var avatar = jactiveUser["Avatar"].Value<string>();
var address = jactiveUser["Avatar"].Value<string>();
var newuser = new User
{
UserName = username,
EMails = emails,
Roles = roles,
Id = uid,
YavscTokens = tokens,
Avatar = avatar,
Address = address
};
MainSettings.SaveUser(newuser);
accStore.Save(acc, Constants.ApplicationName);
}
}
}
}
);
});
}
};
auth.Error += Auth_Error;
StartActivity(loginIntent);
}
public static void PopulateUserWithAccount(User u, Account a)
{
u.YavscTokens = new Model.Auth.Account.Tokens
{
AccessToken =
a.Properties["access_token"],
RefreshToken =
a.Properties["refresh_token"],
ExpiresIn =
int.Parse(a.Properties["expires_in"]),
TokenType = a.Properties["token_type"]
};
u.UserName = a.Username;
}
private void Auth_Error(object sender, AuthenticatorErrorEventArgs e)
{
// TODO handle

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Support.V7.App;
using XLabs.Ioc;
using XLabs.Platform.Mvvm;
using XLabs.Forms;
using static Android.Views.View;
namespace BookAStar.Droid.Markdown
{
class MDContextMenu : AppCompatDialog
{
private ActionMode mActionMode = null;
public MDContextMenu(Context context) : base(context)
{
}
public override void OnActionModeStarted(ActionMode mode)
{
if (mActionMode == null)
{
mActionMode = mode;
var menu = mode.Menu;
// Remove the default menu items (select all, copy, paste, search)
menu.Clear();
// If you want to keep any of the defaults,
// remove the items you don't want individually:
// menu.removeItem(android.R.id.[id_of_item_to_remove])
// Inflate your own menu items
mode.MenuInflater.Inflate(Resource.Menu.md_menu, menu);
}
mActionMode = mode;
base.OnActionModeStarted(mode);
}
public override void OnActionModeFinished(ActionMode mode)
{
base.OnActionModeFinished(mode);
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
public void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo)
{
/* if (menuInfo!=null)
{
var info = menuInfo.ToString();
}
menu.Add(0, 0, 0, "test");
var subMenu = menu.AddSubMenu(
0, 1, 1, "...");
subMenu.Add(0, 3, 0, "nkjnkjn");
var app = Resolver.Resolve<IXFormsApp>() as IXFormsApp<XFormsCompatApplicationDroid>;
var mgr = ClipboardManager.FromContext(app.AppContext);
if (mgr.HasText)
menu.Add(0, 0, 0, "Coller!");*/
//base.OnCreateContextMenu(menu, v, menuInfo);
}
}
}

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Webkit;
namespace BookAStar.Droid.Markdown
{
class MDWebView : WebView
{
public MDWebView (Context context) : base (context)
{
}
public override ActionMode StartActionMode(ActionMode.ICallback callback)
{
return base.StartActionMode(callback);
}
}
}

@ -17,7 +17,7 @@ using System.Linq;
using System.Text;
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorTemplatePreprocessor", "4.2.0.703")]
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorTemplatePreprocessor", "4.2.1.62")]
public partial class MarkdownEditor : MarkdownEditorBase
{

@ -9,6 +9,7 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Graphics;
namespace BookAStar.Droid.Markdown
{
@ -17,6 +18,7 @@ namespace BookAStar.Droid.Markdown
protected static MarkdownDeep.Markdown markdown = new MarkdownDeep.Markdown();
public string Content { get; set; }
public bool Editable { get; set; }
public string GetHtml()
{
return markdown.Transform(Content);

@ -12,6 +12,11 @@ using Android.Views;
namespace BookAStar.Droid
{
using Markdown;
using XLabs.Forms;
using XLabs.Ioc;
using XLabs.Platform.Mvvm;
using static View;
public class MarkdownViewRenderer : ViewRenderer<MarkdownView, WebView>
{
private WebView editorView;
@ -35,7 +40,9 @@ namespace BookAStar.Droid
{
if (view == null || xview == null) return;
var vch = view.ContentHeight;
xview.HeightRequest = vch > xview.MinimumHeightRequest ? vch : xview.MinimumHeightRequest;
// var oldH = xview.Height;
var newH = vch > xview.MinimumHeightRequest ? vch : xview.MinimumHeightRequest;
xview.HeightRequest = newH;
}
protected override void OnElementChanged(ElementChangedEventArgs<MarkdownView> e)
@ -55,10 +62,14 @@ namespace BookAStar.Droid
{
// Subscribe
editorTemplate.Model = new Markdown.MarkdownViewModel
{ Content = e.NewElement.Markdown, Editable = e.NewElement.Editable };
{
Content = e.NewElement.Markdown, Editable = e.NewElement.Editable
};
var html = editorTemplate.GenerateString();
EditorView.LoadDataWithBaseURL("file:///android_asset/",
html, "text/html", "utf-8", null);
EditorView.SetBackgroundColor(e.NewElement.BackgroundColor.ToAndroid());
}
}
@ -69,7 +80,7 @@ namespace BookAStar.Droid
Control.LoadUrl(string.Format("javascript: {0}", script));
}
}
MDContextMenu contextMenu;
private WebView CreateNativeControl()
{
editorView = new WebView(Context);
@ -89,9 +100,18 @@ namespace BookAStar.Droid
EditorView.Settings.DomStorageEnabled = true;
EditorView.AddJavascriptInterface(new JsBridgeMarkdown(this), "jsBridge");
EditorView.ViewTreeObserver.PreDraw += ViewTreeObserver_PreDraw;
//var app = Resolver.Resolve<IXFormsApp>() as IXFormsApp<XFormsCompatApplicationDroid>;
//contextMenu = new MDContextMenu(app.AppContext);
//EditorView.SetOnCreateContextMenuListener(contextMenu);
return EditorView;
}
private void EditorView_Touch(object sender, TouchEventArgs e)
{
}
private void ViewTreeObserver_PreDraw(object sender, ViewTreeObserver.PreDrawEventArgs e)
{
AdjustHeightRequest(Element, Control);

@ -368,7 +368,7 @@ public class YaOAuth2Authenticator : WebRedirectAuthenticator
var query = queryValues.FormEncode();
var req = WebRequest.Create(accessTokenUrl);
// (req as HttpWebRequest).Accept = "application/json";
(req as HttpWebRequest).Accept = "application/json";
req.Method = "POST";
var body = Encoding.UTF8.GetBytes(query);
req.ContentLength = body.Length;

@ -30,6 +30,12 @@
<action android:name="android.service.chooser.ChooserTargetService" />
</intent-filter>
</service>
<service android:name="fr.pschneider.bas.AccountChooserService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" />
</service>
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAVE_LOCK" />

@ -200,7 +200,7 @@ namespace BookAStar.Rendering
var uriImageLoader = imagesource as UriImageSource;
if (uriImageLoader != null && uriImageLoader.Uri != null)
{
using (var client = UserHelpers.CreateClient())
using (var client = UserHelpers.CreateJsonClient())
{
using (var response = await client.GetAsync(uriImageLoader.Uri))
{

File diff suppressed because it is too large Load Diff

@ -8,4 +8,4 @@
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:tabIndicatorColor="@android:color/white"
app:tabGravity="fill"
app:tabMode="fixed" />
app:tabMode="fixed" />

@ -7,7 +7,7 @@
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:popupTheme="@style/ThemeOverlay.AppCompat.Dark"
tools:context=".MainActivity"
app:layout_scrollFlags="scroll|enterAlways"
/>

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/copy"
android:title="@string/copy"/>
<group android:id="@+id/group">
<item android:id="@+id/past"
android:title="@string/past"
android:showAsAction="ifRoom|withText"/>
</group>
<item android:id="@+id/submenu_character"
android:title="@string/character" >
<menu>
<item android:id="@+id/bold"
android:title="@string/bold" />
<item android:id="@+id/italic"
android:title="@string/italic" />
<item android:id="@+id/underline"
android:title="@string/underline" />
</menu>
</item>
<item android:id="@+id/submenu_paragraph"
android:title="@string/paragraph" >
<menu>
<item android:id="@+id/header1"
android:onClick="onGroupItemClick"
android:title="@string/header1" />
<item android:id="@+id/header2"
android:onClick="onGroupItemClick"
android:title="@string/header2" />
</menu>
</item>
</menu>

@ -14,5 +14,25 @@
<string name="url">url</string>
<string name="url_hint">url_hint</string>
<string name="picture">picture</string>
<string name="google_app_id">325408689282</string>
<string name="google_app_id">325408689282</string>
<string name="pref_screen_title">Comptes Booking Star</string>
<string name="account_authenticator_label">Comptes Booking Star</string>
<string name="copy">Copier</string>
<string name="past">Coller</string>
<string name="character">Caractère</string>
<string name="bold">Gras</string>
<string name="italic">Italique</string>
<string name="underline">Sousligné</string>
<string name="paragraph">Paragraphe</string>
<string name="header1">Titre</string>
<string name="header2">Sous-titre</string>
<string name="accounts">Comptes</string>
<string name="bookingstar_accounts_pref_screen_summary">
Préférences des comptes Booking Star
</string>
</resources>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/accounts" />
<PreferenceScreen
android:key="bookingstar_accounts_pref_screen"
android:title="@string/pref_screen_title"
android:summary="@string/bookingstar_accounts_pref_screen_summary">
<intent
android:action="bookingstar_accounts_pref_screen.ACTION"
android:targetPackage="bookingstar_accounts_pref_screen.package"
android:targetClass="bookingstar_accounts_pref_screen.class" />
</PreferenceScreen>
</PreferenceScreen>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="typeOfAuthenticator"
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/account_authenticator_label"
android:accountPreferences="@xml/account_preferences"
/>

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using BookAStar.Droid.OAuth;
namespace BookAStar.Droid.Services
{
[Service(
Name = "fr.pschneider.bas.AccountChooserService",
Label = "Yavsc accounts service",
Icon = "@drawable/icon",
Exported = true,
Enabled = true
)]
[IntentFilter(new String[] { "android.accounts.AccountAuthenticator" })]
class AccountChooserService : Service
{
public static YaOAuth2Authenticator authenticator;
public override void OnCreate()
{
base.OnCreate();
}
public override IBinder OnBind(Intent intent)
{
throw new NotImplementedException();
}
}
}

@ -9,6 +9,9 @@ namespace BookAStar.Droid.Services
using Model.Social;
using Newtonsoft.Json;
using Model;
using Model.Social;
using Data;
using System.Linq;
namespace ClientApp
{
@ -44,18 +47,27 @@ namespace BookAStar.Droid.Services
if (topic == "BookQuery")
{
DateTime eventdate;
var sdatestr = data.GetString("EventDate");
DateTime.TryParse(sdatestr, out eventdate);
var locationJson = data.GetString("Location");
var location = JsonConvert.DeserializeObject<Location>(locationJson);
var cid = long.Parse(data.GetString("Id"));
var bq = new BookQueryData
var clientJson = data.GetString("Client");
var client = JsonConvert.DeserializeObject<ClientProviderInfo>(clientJson);
var bq = new BookQuery
{
Id = cid,
Location = location
Location = location,
Client = client,
Reason = data.GetString("Reason")
};
var dateString = data.GetString("EventDate");
DateTime evDate;
if (DateTime.TryParse(dateString, out evDate))
{
bq.EventDate = evDate;
}
SendBookQueryNotification(bq);
}
@ -82,14 +94,17 @@ namespace BookAStar.Droid.Services
notificationManager.Notify(0, notificationBuilder.Build());
}
void SendBookQueryNotification(BookQueryData bquery)
void SendBookQueryNotification(BookQuery bquery)
{
var bookquerynotifications = MainSettings.AddBookQueryNotification(bquery);
DataManager.Instance.BookQueries.Merge(bquery);
var bookquerynotifications = DataManager.Instance.BookQueries.Where(
q=> ! q.Read && q.EventDate > DateTime.Now
).ToArray();
var count = bookquerynotifications.Length;
var multiple = count > 1;
var title =
multiple ? $"{count} demandes" : bquery.Client.UserName;
var message = $"{bquery.EventDate} {bquery.Client.UserName} {bquery.Location.Address}";
var message = $"{bquery.EventDate} {bquery.Client.UserName} {bquery.Location.Address}\n {bquery.Reason}";
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);

@ -7,7 +7,6 @@ using Android.Widget;
namespace BookAStar.Droid
{
[Service]
public class MyGcmIntentService : IntentService
{
@ -31,6 +30,7 @@ namespace BookAStar.Droid
intent.SetClass(context, typeof(MyGcmIntentService));
context.StartService(intent);
}
static object locker = new object();
protected override void OnHandleIntent(Intent intent)
@ -74,16 +74,28 @@ namespace BookAStar.Droid
void SendNotification (string message)
{
var intent = new Intent (this, typeof(MainActivity));
/* Bundle valuesForActivity = new Bundle();
valuesForActivity.PutInt("count", count); */
var intent = new Intent (this, typeof(MainActivity));
intent.AddFlags (ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity (this, 0, intent, PendingIntentFlags.OneShot);
// Construct a back stack for cross-task navigation:
TaskStackBuilder stackBuilder = TaskStackBuilder.Create(this);
stackBuilder.AddParentStack(Java.Lang.Class.FromType(typeof(MainActivity)));
stackBuilder.AddNextIntent(intent);
// Create the PendingIntent with the back stack:
PendingIntent resultPendingIntent =
stackBuilder.GetPendingIntent(0, PendingIntentFlags.UpdateCurrent);
var notificationBuilder = new Notification.Builder(this)
var notificationBuilder = new Notification.Builder(this)
.SetAutoCancel(true)
.SetSmallIcon (Resource.Drawable.icon)
.SetContentTitle ("GCM Message")
.SetContentText (message)
.SetAutoCancel (true)
.SetContentIntent (pendingIntent);
.SetContentIntent(resultPendingIntent) // Start 2nd activity when the intent is clicked.
;
var notificationManager = (NotificationManager) GetSystemService(Context.NotificationService);
notificationManager.Notify (0, notificationBuilder.Build());

@ -171,10 +171,6 @@
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Yavsc.Client\Yavsc.Client.csproj">
<Project>{67F9D3A8-F71E-4428-913F-C37AE82CDB24}</Project>
<Name>Yavsc.Client</Name>
</ProjectReference>
<ProjectReference Include="..\BookAStar\BookAStar.csproj">
<Project>{A0815650-0A0A-47B0-8826-771F0E1AD137}</Project>
<Name>BookAStar</Name>

@ -9,23 +9,31 @@
<ResourceDictionary>
<Color x:Key="PageBackgroundColor">#FFAAAAFF</Color>
<Color x:Key="ContentBackgroundColor">#80AFAFAF</Color>
<Color x:Key="DashboardPageBackgroundColor">#FFCCCCFF</Color>
<Color x:Key="ContentBackgroundColor">#80FFFFFF</Color>
<Color x:Key="BackgroundColor">#FFFFFFFF</Color>
<Color x:Key="LabelBackgroundColor">#FFFFFFFF</Color>
<Color x:Key="PageForegroundColor">#000000</Color>
<Color x:Key="TextColor">#000000</Color>
<Color x:Key="LabelColor">#000000</Color>
<Color x:Key="ErrorTextColor">#500000</Color>
<Color x:Key="HeadingTextColor">Black</Color>
<Color x:Key="NormalTextColor">Blue</Color>
<Color x:Key="GroupingTextColor">#5050ff</Color>
<Color x:Key="OddColor">#207AFAFA</Color>
<Color x:Key="EmphasisTextColor">#800080</Color>
<Color x:Key="QuietTextColor">#404040</Color>
<Color x:Key="DisconnectedUserBgColor">#909090</Color>
<Color x:Key="ConnectedUserBgColor">#ffffff</Color>
<OnPlatform x:TypeArguments="Font" Android="Large" iOS="Large" WinPhone="Large" x:Key="HeaderFont" />
<OnPlatform x:TypeArguments="Color" Android="Red" iOS="Red" WinPhone="Red" x:Key="EmphasisTextColor" />
<OnPlatform x:TypeArguments="Font" Android="90" iOS="90" WinPhone="90" x:Key="LargeFontSize" />
<OnPlatform x:TypeArguments="Font" Android="50" iOS="50" WinPhone="50" x:Key="MediumFontSize" />
<OnPlatform x:TypeArguments="Font" Android="40" iOS="40" WinPhone="40" x:Key="SmallFontSize" />
<OnPlatform x:TypeArguments="x:Double" Android="22" iOS="22" WinPhone="22" x:Key="LargeFontSize" />
<OnPlatform x:TypeArguments="x:Double" Android="16" iOS="16" WinPhone="16" x:Key="MediumFontSize" />
<OnPlatform x:TypeArguments="x:Double" Android="14" iOS="14" WinPhone="14" x:Key="SmallFontSize" />
<OnPlatform x:TypeArguments="x:Double" Android="130" iOS="130" WinPhone="130" x:Key="BigUserAvatarSize" />
<Style x:Key="LabelPageHeadingStyle" TargetType="Label">
@ -43,12 +51,19 @@
<Setter Property="VerticalOptions" Value="Start" />
</Style>
<Style x:Key="ErrorLabelStyle" BasedOn="{StaticResource InputLabelStyle}" TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="{StaticResource ErrorTextColor}" />
</Style>
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource LabelColor}" />
<Setter Property="LineBreakMode" Value="WordWrap" />
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
</Style>
<Style x:Key="BigLabel" BasedOn="{StaticResource LabelStyle}" TargetType="Label">
<Style x:Key="BigLabelStyle" BasedOn="{StaticResource LabelStyle}" TargetType="Label">
<Setter Property="FontSize" Value="Large" />
</Style>
<Style x:Key="ContentLabelStyle" BasedOn="{StaticResource LabelStyle}" TargetType="Label">
@ -80,11 +95,14 @@
</Style>
<Style x:Key="PageStyle" TargetType="ContentPage">
<Setter Property="Padding" Value="5,5,5,5" />
<Setter Property="Padding" Value="15,15,15,15" />
<Setter Property="BackgroundColor" Value="{StaticResource PageBackgroundColor}" />
</Style>
<Style x:Key="DashboardPageStyle" TargetType="ContentPage">
<Setter Property="Padding" Value="10,10,10,10" />
<Setter Property="BackgroundColor" Value="{StaticResource DashboardPageBackgroundColor}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

@ -19,7 +19,6 @@ namespace BookAStar
using Data;
using Interfaces;
using Model;
using Model.UI;
using Pages;
using Plugin.Connectivity;
using Model.Social.Messaging;
@ -27,6 +26,11 @@ namespace BookAStar
using ViewModels.UserProfile;
using Pages.UserProfile;
using ViewModels.EstimateAndBilling;
using Pages.EstimatePages;
using ViewModels;
using Pages.Chat;
using System.Collections.Generic;
using Model.Social;
public partial class App : Application // superclass new in 1.3
{
@ -35,13 +39,14 @@ namespace BookAStar
[Obsolete("Instead using this, use new static properties.")]
public static App CurrentApp { get { return Current as App; } }
private static ExtendedMasterDetailPage masterDetail;
public static bool MasterPresented
{
get
{ return CurrentApp.masterDetail.IsPresented; }
{ return App.masterDetail.IsPresented; }
internal set
{ CurrentApp.masterDetail.IsPresented = value; }
{ masterDetail.IsPresented = value; }
}
public void Init()
@ -63,27 +68,27 @@ namespace BookAStar
MainSettings.UserChanged += MainSettings_UserChanged;
CrossConnectivity.Current.ConnectivityChanged += (conSender, args) =>
{ App.IsConnected = args.IsConnected; };
SetupHubConnection();
MainSettings_UserChanged(this, null);
if (CrossConnectivity.Current.IsConnected)
StartHubConnection();
StartConnexion();
}
// omg
private void OnError(object sender, EventArgs e)
{
}
// Called on rotation after OnSuspended
private void OnClosing(object sender, EventArgs e)
{
}
// FIXME Never called.
private void OnInitialize(object sender, EventArgs e)
{
}
// called on app startup, not on rotation
@ -92,17 +97,22 @@ namespace BookAStar
// TODO special startup pages as
// notification details or wizard setup page
}
private static INavigation Navigation
{
get
{
return masterDetail.Detail.Navigation;
}
}
// Called on rotation
private void OnSuspended(object sender, EventArgs e)
{
// TODO save the navigation stack
StopConnection();
int position = 0;
foreach (Page page in MainPage.Navigation.NavigationStack)
DataManager.Instance.AppState.Clear();
foreach (Page page in Navigation.NavigationStack)
{
DataManager.Current.AppState.Add(
DataManager.Instance.AppState.Add(
new PageState
{
Position = position++,
@ -110,28 +120,35 @@ namespace BookAStar
BindingContext = page.BindingContext
});
}
DataManager.Current.AppState.SaveEntity();
DataManager.Instance.AppState.SaveEntity();
}
// called on app startup, after OnStartup, not on rotation
private void OnAppResumed(object sender, EventArgs e)
{
StartConnexion();
// TODO restore the navigation stack
base.OnResume();
foreach (var pageState in DataManager.Current.AppState)
foreach (var pageState in DataManager.Instance.AppState)
{
var pageType = Type.GetType(pageState.PageType);
NavigationService.NavigateTo(
pageType, true, pageState.BindingContext);
if (pageState.PageType != null)
{
var pageType = Type.GetType(pageState.PageType);
if (pageState.BindingContext != null)
NavigationService.NavigateTo(
pageType, false, pageState.BindingContext);
else NavigationService.NavigateTo(
pageType, false);
}
}
DataManager.Current.AppState.Clear();
DataManager.Current.AppState.SaveEntity();
DataManager.Instance.AppState.Clear();
DataManager.Instance.AppState.SaveEntity();
}
// FIXME Not called?
private void OnRotation(object sender, EventArgs<Orientation> e)
{
}
public static GenericConfigSettingsMgr ConfigManager { protected set; get; }
@ -139,9 +156,6 @@ namespace BookAStar
private void Configure(IXFormsApp app)
{
ViewFactory.EnableCache = true;
ViewFactory.Register<ChatPage, ChatViewModel>(
r=> new ChatViewModel { ChatUser = MainSettings.UserName }
);
ViewFactory.Register<DashboardPage, DashboardViewModel>(
resolver => new DashboardViewModel());
ViewFactory.Register<BookQueryPage, BookQueryViewModel>();
@ -150,12 +164,11 @@ namespace BookAStar
ViewFactory.Register<EditEstimatePage, EditEstimateViewModel>();
ViewFactory.Register<UserFiles, DirectoryInfoViewModel>();
ViewFactory.Register<UserProfilePage, UserProfileViewModel>();
ViewFactory.Register<EstimateSigningPage, EditEstimateViewModel>();
ConfigManager = new GenericConfigSettingsMgr(s =>
MainSettings.AppSettings.GetValueOrDefault<string>(s, MainSettings.SettingsDefault), null);
}
ExtendedMasterDetailPage masterDetail;
public App(IPlatform instance)
{
// This declaration became obsolete by introduction
@ -170,39 +183,64 @@ namespace BookAStar
Init();
// Builds the Main page
BuildMainPage();
}
BookQueriesPage bQueriesPage;
AccountChooserPage accChooserPage;
HomePage homePage;
UserProfilePage userProfilePage;
private static UserProfilePage userProfilePage;
public static UserProfilePage UserProfilePage
{ get { return userProfilePage; } }
ChatPage chatPage;
private void ShowPage(Page page)
public static void ShowPage(Page page)
{
if (masterDetail.Detail.Navigation.NavigationStack.Contains(page))
if (Navigation.NavigationStack.Contains(page))
{
if (masterDetail.Detail.Navigation.NavigationStack.Last() == page) return;
masterDetail.Detail.Navigation.RemovePage(page);
if (Navigation.NavigationStack.Last() == page) return;
Navigation.RemovePage(page);
page.Parent = null;
}
masterDetail.Detail.Navigation.PushAsync(page);
Navigation.PushAsync(page);
}
private void BuildMainPage()
{
// TODO
// in case of App resume,
// do not create new BindingContext's,
// but use those from the AppState property
accChooserPage = new AccountChooserPage();
var bookQueries = new BookQueriesViewModel();
var userprofile = new UserProfileViewModel();
bQueriesPage = new BookQueriesPage
{
Title = "Demandes",
Icon = "icon.png",
BindingContext = new BookQueriesViewModel()
BindingContext = bookQueries
};
homePage = new HomePage() { Title = "Accueil", Icon = "icon.png" };
userProfilePage = new UserProfilePage { Title = "Profile utilisateur", Icon = "ic_corp_icon.png",
BindingContext = new UserProfileViewModel() };
homePage = new HomePage() {
Title = "Accueil",
Icon = "icon.png" };
homePage.BindingContext = new HomeViewModel {
BookQueries = bookQueries,
UserProfile = userprofile };
userProfilePage = new UserProfilePage {
Title = "Profile utilisateur",
Icon = "ic_corp_icon.png",
BindingContext = userprofile
};
chatPage = new ChatPage
{
Title = "Chat",
@ -259,11 +297,11 @@ namespace BookAStar
masterDetail.ToolbarItems.Add(tiPubChat);
this.MainPage = masterDetail;
NavigationService = new NavigationService(masterDetail.Detail.Navigation);
NavigationService = new NavigationService(Navigation);
}
public static Task<string> DisplayActionSheet(string title, string cancel, string destruction, string [] buttons)
{
var currentPage = CurrentApp.masterDetail.Detail.Navigation.NavigationStack.Last();
var currentPage = Navigation.NavigationStack.Last();
return currentPage.DisplayActionSheet(title, cancel, destruction, buttons);
}
@ -293,7 +331,7 @@ namespace BookAStar
if (isConnected)
{
// TODO Start all cloud related stuff
CurrentApp.StartHubConnection();
StartConnexion();
}
}
@ -308,18 +346,20 @@ namespace BookAStar
}
}
// Start the Hub connection
public async void StartHubConnection ()
public static async void StartConnexion ()
{
try
if (CrossConnectivity.Current.IsConnected)
try
{
if (chatHubConnection.State == ConnectionState.Disconnected)
await chatHubConnection.Start();
}
catch (WebException webex )
catch (WebException )
{
// TODO use webex, set this cx down status somewhere,
// & display it & maybe try again later.
}
catch (Exception ex)
catch (Exception )
{
// TODO use ex
}
@ -327,36 +367,53 @@ namespace BookAStar
public void SetupHubConnection()
{
if (chatHubConnection != null)
chatHubConnection.Dispose();
chatHubConnection = new HubConnection(Constants.SignalRHubsUrl);
chatHubConnection.Error += ChatHubConnection_Error;
chatHubProxy = chatHubConnection.CreateHubProxy("ChatHub");
chatHubProxy.On<string, string>("addPV", (n, m) => {
DataManager.Current.PrivateMessages.Add(
new ChatMessage
{
Message = m,
SenderId = n,
Date = DateTime.Now
}
var msg = new ChatMessage
{
Message = m,
SenderId = n,
Date = DateTime.Now
};
DataManager.Instance.PrivateMessages.Add(
msg
);
DataManager.Instance.ChatUsers.OnPrivateMessage(msg);
});
}
private void MainSettings_UserChanged(object sender, EventArgs e)
public static void StopConnection()
{
if (MainSettings.CurrentUser == null)
try
{
chatHubConnection.Dispose();
chatHubConnection = null;
chatHubProxy = null;
if (chatHubConnection.State != ConnectionState.Disconnected)
chatHubConnection.Stop();
}
else
catch (WebException)
{
// TODO use webex, set this cx down status somewhere,
// & display it & maybe try again later.
}
catch (Exception)
{
// TODO use ex
}
}
private void MainSettings_UserChanged(object sender, EventArgs e)
{
StopConnection();
if (MainSettings.CurrentUser != null)
{
var token = MainSettings.CurrentUser.YavscTokens.AccessToken;
SetupHubConnection();
if (chatHubConnection.Headers.ContainsKey("Authorization"))
chatHubConnection.Headers.Remove("Authorization");
chatHubConnection.Headers.Add("Authorization", $"Bearer {token}");
}
StartConnexion();
}
private void ChatHubConnection_Error(Exception obj)
@ -379,18 +436,18 @@ namespace BookAStar
}
}
public void PostDeviceInfo()
public static void PostDeviceInfo()
{
var info = PlatformSpecificInstance.GetDeviceInfo();
if (!string.IsNullOrWhiteSpace(info.GCMRegistrationId))
PlatformSpecificInstance.InvokeApi("gcm/register", info);
}
public static void ShowBookQuery (BookQueryData query)
public static void ShowBookQuery (BookQuery query)
{
var page = ViewFactory.CreatePage<BookQueryViewModel
, BookQueryPage>((b, p) => p.BindingContext = new BookQueryViewModel(query));
App.Current.MainPage.Navigation.PushAsync(page as Page);
var page = new BookQueryPage
{ BindingContext = new BookQueryViewModel(query) };
ShowPage(page);
}
}
}

@ -1,63 +0,0 @@
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class EditorMaxLengthValidator : Behavior<Editor>
{
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(MaxLengthValidator), 0);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
protected override void OnAttachedTo(Editor bindable)
{
bindable.TextChanged += bindable_TextChanged;
}
public bool IsValid { get; set; }
private void bindable_TextChanged(object sender, TextChangedEventArgs e)
{
IsValid = e.NewTextValue == null? false : ( e.NewTextValue.Length > 0 && e.NewTextValue.Length <= MaxLength ) ;
if (!IsValid) if (e.NewTextValue!=null) if (e.NewTextValue.Length > MaxLength)
((Editor)sender).Text = e.NewTextValue.Substring(0, MaxLength);
}
protected override void OnDetachingFrom(Editor bindable)
{
bindable.TextChanged -= bindable_TextChanged;
}
}
public class MaxLengthValidator : Behavior<Entry>
{
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(MaxLengthValidator), 0);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += bindable_TextChanged;
}
private void bindable_TextChanged(object sender, TextChangedEventArgs e)
{
//if (MaxLength != null && MaxLength.HasValue)
if (e.NewTextValue.Length > MaxLength)
((Entry)sender).Text = e.NewTextValue.Substring(0, MaxLength);
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= bindable_TextChanged;
}
}
}

@ -42,23 +42,43 @@
</Compile>
<Compile Include="Attributes\CurrencyAttribute.cs" />
<Compile Include="Attributes\DisplayAttribute.cs" />
<Compile Include="Behaviors\EmailValidatorBehavior.cs" />
<Compile Include="Behaviors\IntegerEntryBehavior.cs" />
<Compile Include="Behaviors\MaxLengthValidator.cs" />
<Compile Include="Behaviors\DecimalValidatorBehavior.cs" />
<Compile Include="Behaviors\PickerBehavior.cs" />
<Compile Include="Behaviors\StarBehavior.cs" />
<Compile Include="Converters\Behaviors\EmailValidatorBehavior.cs" />
<Compile Include="Converters\Behaviors\IntegerEntryBehavior.cs" />
<Compile Include="Converters\Behaviors\EditorMaxLengthValidator.cs" />
<Compile Include="Converters\Behaviors\DecimalValidatorBehavior.cs" />
<Compile Include="Converters\Behaviors\MarkdownViewLengthValidator.cs" />
<Compile Include="Converters\Behaviors\PickerBehavior.cs" />
<Compile Include="Converters\Behaviors\RegexValidatorBehavior.cs" />
<Compile Include="Converters\Behaviors\StarBehavior.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Converters\NotValueConverter.cs" />
<Compile Include="Converters\SignalRConnectionStateToObject.cs" />
<Compile Include="Data\LocalState.cs" />
<Compile Include="Data\ApiCallFailedException.cs" />
<Compile Include="Data\EstimateEntity.cs" />
<Compile Include="Data\NonCrUD\RemoteFiles.cs" />
<Compile Include="Model\Access\BlackListed.cs" />
<Compile Include="Model\Booking\MusicalPreference.cs" />
<Compile Include="Model\Booking\MusicalTendency.cs" />
<Compile Include="Model\FileSystem\UserDirectoryInfo.cs" />
<Compile Include="Model\FileSystem\UserFileInfo.cs" />
<Compile Include="Model\Settings\SignatureSettings.cs" />
<Compile Include="Model\Social\Messaging\ChatStatus.cs" />
<Compile Include="Model\Social\Messaging\PrivateMessage.cs" />
<Compile Include="Model\UI\PageState.cs" />
<Compile Include="Model\Social\Chat\ChatStatus.cs" />
<Compile Include="Model\Social\Chat\ChatMessage.cs" />
<Compile Include="Model\Social\LocationType.cs" />
<Compile Include="Pages\ClientPages\SearchPage.xaml.cs">
<DependentUpon>SearchPage.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\HomeViewModel.cs" />
<Compile Include="ViewModels\Messaging\ChatUserCollection.cs" />
<Compile Include="ViewModels\Messaging\ChatUserInfo.cs" />
<Compile Include="Model\Social\Chat\Connection.cs" />
<Compile Include="ViewModels\Searching\SearchingAnArtistViewModel.cs" />
<Compile Include="ViewModels\Validation\Error.cs" />
<Compile Include="ViewModels\Validation\ModelState.cs" />
<Compile Include="ViewModels\PageState.cs" />
<Compile Include="Pages\EstimatePages\EstimateSigningPage.xaml.cs">
<DependentUpon>EstimateSigningPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\EstimatePages\EstimatesClientPage.xaml.cs">
<DependentUpon>EstimatesClientPage.xaml</DependentUpon>
</Compile>
@ -85,14 +105,15 @@
<DesignTime>True</DesignTime>
<DependentUpon>Strings.en.resx</DependentUpon>
</Compile>
<Compile Include="ViewModels\EditingViewModel.cs" />
<Compile Include="ViewModels\Validation\EditingViewModel.cs" />
<Compile Include="Pages\DocSigning.xaml.cs">
<DependentUpon>DocSigning.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\Signing\DocSigningViewModel.cs" />
<Compile Include="ViewModels\Signing\EstimateSigningViewModel.cs" />
<Compile Include="ViewModels\Signing\SignaturePadConfigViewModel.cs" />
<Compile Include="ViewModels\UserProfile\UserProfileViewModel.cs" />
<Compile Include="ViewModels\UserProfile\DirectoryInfoViewModel.cs" />
<Compile Include="ViewModels\Validation\ErrorSeverity.cs" />
<Compile Include="Views\EnumPicker.cs" />
<Compile Include="Converters\BooleanToObjectConverter.cs" />
<Compile Include="Converters\EnumConverter.cs" />
@ -126,7 +147,7 @@
<Compile Include="Pages\EstimatePages\BookQueryPage.xaml.cs">
<DependentUpon>BookQueryPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ChatPage.xaml.cs">
<Compile Include="Pages\Chat\ChatPage.xaml.cs">
<DependentUpon>ChatPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\EstimatePages\EditBillingLinePage.xaml.cs">
@ -143,7 +164,7 @@
<Compile Include="Interfaces\IPlatform.cs" />
<Compile Include="Model\Blog\Blog.cs" />
<Compile Include="Model\Blog\BlogTag.cs" />
<Compile Include="Model\BookQueryData.cs" />
<Compile Include="Model\Booking\BookQuery.cs" />
<Compile Include="Model\Market\BaseProduct.cs" />
<Compile Include="Model\Workflow\BillingLine.cs" />
<Compile Include="Model\Manager.cs" />
@ -183,7 +204,7 @@
<Compile Include="Pages\EstimatePages\EditEstimatePage.xaml.cs">
<DependentUpon>EditEstimatePage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Oooops\HomePage.xaml.cs">
<Compile Include="Pages\HomePage.xaml.cs">
<DependentUpon>HomePage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Oooops\PinPage.cs" />
@ -198,9 +219,6 @@
<Compile Include="Views\ImageButton.cs" />
<Compile Include="Views\MarkdownView.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Pages\Oooops\SearchPage.xaml.cs">
<DependentUpon>SearchPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\UserProfile\AccountChooserPage.xaml.cs">
<DependentUpon>AccountChooserPage.xaml</DependentUpon>
</Compile>
@ -233,17 +251,13 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\Oooops\SearchPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\UserProfile\AccountChooserPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Model\Billing\" />
<Folder Include="Model\UI\" />
</ItemGroup>
<ItemGroup>
<Reference Include="ExifLib, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL">
@ -272,6 +286,12 @@
<Reference Include="Plugin.Connectivity.Abstractions">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Media">
<HintPath>..\..\packages\Xam.Plugin.Media.2.3.0\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Plugin.Media.dll</HintPath>
</Reference>
<Reference Include="Plugin.Media.Abstractions">
<HintPath>..\..\packages\Xam.Plugin.Media.2.3.0\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Plugin.Media.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Settings, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.dll</HintPath>
<Private>True</Private>
@ -296,6 +316,9 @@
<HintPath>..\..\packages\Microsoft.Net.Http.2.2.29\lib\portable-net45+win8+wpa81\System.Net.Http.Primitives.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Services">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\MonoAndroid\v1.0\System.Web.Services.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.2.127\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Xamarin.Forms.Core.dll</HintPath>
<Private>True</Private>
@ -351,12 +374,6 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Yavsc.Client\Yavsc.Client.csproj">
<Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
<Name>Yavsc.Client</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\EstimatePages\EditEstimatePage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
@ -390,7 +407,7 @@
<EmbeddedResource Include="Images\Users\icon_user_settings.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\Oooops\HomePage.xaml">
<EmbeddedResource Include="Pages\HomePage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
@ -416,7 +433,7 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\ChatPage.xaml">
<EmbeddedResource Include="Pages\Chat\ChatPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
@ -478,6 +495,27 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\EstimatePages\EstimateSigningPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\YavscLib\YavscLib.csproj">
<Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
<Name>YavscLib</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Images\Chat\talk.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\ClientPages\SearchPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.2.127\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.2.127\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Xamarin.Forms.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

@ -11,11 +11,13 @@ namespace BookAStar
{
public const string ApplicationName = "Booking Star";
#region Uri
#if DEV
public const string YavscHomeUrl = "http://dev.pschneider.fr";
#else
#if WDEV
public const string YavscHomeUrl = "http://192.168.0.29:5000";
#else
#if YAVSC
public const string YavscHomeUrl = "https://yavsc.pschneider.fr";
#else
@ -26,17 +28,19 @@ namespace BookAStar
#endif
#endif
#endif
#endif
public static readonly string AuthorizeUrl = YavscHomeUrl + "/authorize";
public static readonly string RedirectUrl = YavscHomeUrl + "/oauth/success";
public static readonly string AccessTokenUrl = YavscHomeUrl + "/token";
public static readonly string YavscApiUrl = YavscHomeUrl + "/api";
public static readonly string MobileRegistrationUrl = YavscApiUrl + "/gcm/register";
public static readonly string UserInfoUrl = YavscApiUrl + "/me";
public static readonly string BlogUrl = YavscApiUrl + "/blogs";
public static readonly string FsUrl = YavscApiUrl + "/fs";
public static readonly string SignalRHubsUrl = YavscHomeUrl + "/api/signalr/hubs";
#endregion
#region Permissions ids
public static int AllowBeATarget = 1;
#endregion
public static int CloudTimeout = 400;
}

@ -0,0 +1,80 @@
using System;
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class EditorMaxLengthValidator : Behavior<Editor>
{
public static readonly BindableProperty MaxLengthProperty =
BindableProperty.Create("MaxLength", typeof(int), typeof(EditorMaxLengthValidator), int.MaxValue);
public static readonly BindableProperty MinLengthProperty =
BindableProperty.Create("MinLength", typeof(int), typeof(EditorMaxLengthValidator), 0);
public static readonly BindableProperty IsValidProperty =
BindableProperty.Create("IsValid", typeof(bool), typeof(EditorMaxLengthValidator), false);
public static readonly BindableProperty ErrorProperty =
BindableProperty.Create("Error", typeof(string), typeof(EditorMaxLengthValidator), null);
public int MaxLength
{
get { return (int) GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public int MinLength
{
get { return (int) GetValue(MinLengthProperty); }
set { SetValue(MinLengthProperty, value); }
}
protected override void OnAttachedTo(Editor bindable)
{
bindable.TextChanged += bindable_TextChanged;
}
public bool IsValid {
get { return (bool) GetValue(IsValidProperty); }
set { SetValue(IsValidProperty, value);
}
}
public string Error
{
get
{
return (string) GetValue(ErrorProperty);
}
set
{
SetValue(ErrorProperty, value);
}
}
private void bindable_TextChanged(object sender, TextChangedEventArgs e)
{
if (e.NewTextValue != null && e.NewTextValue.Length >= MinLength)
{
if (e.NewTextValue.Length > MaxLength)
{
((Editor)sender).Text = e.NewTextValue.Substring(0, MaxLength);
Error = Strings.YourTextWasTooLong;
}
else Error = "";
IsValid = true;
}
else
{
Error = string.Format(Strings.MinMaxStringValidationError,
MinLength, MaxLength);
IsValid = false;
}
}
protected override void OnDetachingFrom(Editor bindable)
{
bindable.TextChanged -= bindable_TextChanged;
}
}
}

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class EmailValidatorBehavior : RegexValidatorBehavior
{
const string emailRegex = @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";
public EmailValidatorBehavior()
{
Regexp = emailRegex;
}
}
}

@ -6,7 +6,10 @@ namespace BookAStar.Behaviors
{
public static readonly BindableProperty MinProperty = BindableProperty.Create("Min", typeof(int), typeof(IntegerEntryBehavior), 0);
public static readonly BindableProperty MaxProperty = BindableProperty.Create("Max", typeof(int), typeof(IntegerEntryBehavior), 0);
public static readonly BindableProperty IsValidProperty =
BindableProperty.Create("IsValid", typeof(bool), typeof(IntegerEntryBehavior), false);
public static readonly BindableProperty ErrorProperty =
BindableProperty.Create("Error", typeof(string), typeof(IntegerEntryBehavior), null);
public int Min
{
get { return (int)GetValue(MinProperty); }
@ -30,8 +33,13 @@ namespace BookAStar.Behaviors
if (int.TryParse(e.NewTextValue, out val))
{
IsValid = (Min > Max) || (Max >= val && val >= Min);
Error = string.Format(Strings.MinMaxIntError, Min, Max);
}
else
{
IsValid = false;
Error = "";
}
else IsValid = false;
}
protected override void OnDetachingFrom(Entry bindable)
@ -39,6 +47,26 @@ namespace BookAStar.Behaviors
bindable.TextChanged -= bindable_TextChanged;
}
public bool IsValid { get; private set; }
public bool IsValid
{
get {
return (bool) GetValue(IsValidProperty);
}
set
{
SetValue(IsValidProperty, value);
}
}
public string Error
{
get
{
return (string) GetValue(ErrorProperty);
}
set
{
SetValue(ErrorProperty, value);
}
}
}
}

@ -0,0 +1,39 @@
using BookAStar.Views;
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class MarkdownViewLengthValidator : Behavior<MarkdownView>
{
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(MarkdownViewLengthValidator), 0);
public static readonly BindableProperty MinLengthProperty = BindableProperty.Create("MinLength", typeof(int), typeof(MarkdownViewLengthValidator), 0);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public int MinLength
{
get { return (int)GetValue(MinLengthProperty); }
set { SetValue(MinLengthProperty, value); }
}
protected override void OnAttachedTo(MarkdownView bindable)
{
bindable.Modified += bindable_TextChanged;
}
public bool IsValid { get; set; }
private void bindable_TextChanged(object sender, TextChangedEventArgs e)
{
IsValid = e.NewTextValue == null ? false : (e.NewTextValue.Length >= MinLength && e.NewTextValue.Length <= MaxLength);
if (!IsValid) if (e.NewTextValue != null) if (e.NewTextValue.Length > MaxLength)
((Editor)sender).Text = e.NewTextValue.Substring(0, MaxLength);
}
protected override void OnDetachingFrom(MarkdownView bindable)
{
bindable.Modified -= bindable_TextChanged;
}
}
}

@ -1,21 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace BookAStar.Behaviors
{
public class EmailValidatorBehavior : Behavior<Entry>
public class RegexValidatorBehavior : Behavior<Entry>
{
const string emailRegex = @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";
private string regexp = @"^([\d\w]+)$";
// Creating BindableProperties with Limited write access: http://iosapi.xamarin.com/index.aspx?link=M%3AXamarin.Forms.BindableObject.SetValue(Xamarin.Forms.BindablePropertyKey%2CSystem.Object)
static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(EmailValidatorBehavior), false);
static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(RegexValidatorBehavior), false);
public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;
@ -25,6 +19,19 @@ namespace BookAStar.Behaviors
private set { base.SetValue(IsValidPropertyKey, value); }
}
protected string Regexp
{
get
{
return regexp;
}
set
{
regexp = value;
}
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += HandleTextChanged;
@ -33,7 +40,7 @@ namespace BookAStar.Behaviors
void HandleTextChanged(object sender, TextChangedEventArgs e)
{
IsValid = (Regex.IsMatch(e.NewTextValue, emailRegex, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));
IsValid = (Regex.IsMatch(e.NewTextValue, regexp, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));
((Entry)sender).TextColor = IsValid ? Color.Default : Color.Red;
}

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BookAStar.Data
{
class ApiCallFailedException : Exception
{
public ApiCallFailedException(string message) : base(message)
{
}
public ApiCallFailedException(string message, Exception inner) : base(message, inner)
{
}
}
}

@ -3,45 +3,49 @@
using Model;
using Model.Blog;
using Model.Workflow;
using Model.UI;
using Model.Social.Messaging;
using Model.FileSystem;
using ViewModels.EstimateAndBilling;
using NonCrUD;
using ViewModels;
using Model.Access;
using ViewModels.Messaging;
using Model.Social;
public class DataManager
{
// TODO estimatetemplate rating service product tag
public RemoteEntityRO<BookQueryData, long> BookQueries { get; set; }
public RemoteEntity<Estimate, long> Estimates { get; set; }
public RemoteEntityRO<BookQuery, long> BookQueries { get; set; }
public ChatUserCollection ChatUsers { get; set; }
public EstimateEntity Estimates { get; set; }
public RemoteEntity<Blog, long> Blogspot { get; set; }
internal RemoteFilesEntity RemoteFiles { get; set; }
public LocalEntity<ClientProviderInfo,string> Contacts { get; set; }
internal LocalEntity<PageState, int> AppState { get; set; }
public LocalEntity<ClientProviderInfo, string> Contacts { get; set; }
internal RemoteEntity<BlackListed, long> BlackList { get; set; }
/// <summary>
/// They have no remote exisence ...
/// They've got no remote existence ...
/// </summary>
internal LocalEntity<EditEstimateViewModel, long> EstimationCache { get; set; }
internal LocalEntity<BillingLine, string> EstimateLinesTemplates { get; set; }
internal LocalEntity<ChatMessage, int> PrivateMessages { get; set; }
protected static DataManager current ;
internal LocalEntity<PageState, int> AppState { get; set; }
internal LocalEntity<string,string> ClientSignatures { get; set; }
internal LocalEntity<string, string> ProviderSignatures { get; set; }
public static DataManager Current
protected static DataManager instance = new DataManager();
public static DataManager Instance
{
get
{
if (current == null)
current = new DataManager();
return current;
return instance;
}
}
public DataManager()
{
BookQueries = new RemoteEntityRO<BookQueryData, long>("bookquery", q => q.Id);
Estimates = new RemoteEntity<Estimate, long>("estimate", x => x.Id);
BookQueries = new RemoteEntityRO<BookQuery, long>("bookquery", q => q.Id);
Estimates = new EstimateEntity();
Blogspot = new RemoteEntity<Blog, long>("blog", x=>x.Id);
Contacts = new LocalEntity<ClientProviderInfo, string>(c => c.UserId);
@ -50,7 +54,8 @@
EstimateLinesTemplates = new LocalEntity<BillingLine, string>(l => l.Description);
PrivateMessages = new LocalEntity<ChatMessage, int>(m=> m.GetHashCode());
RemoteFiles = new RemoteFilesEntity ();
BlackList = new RemoteEntity<BlackListed, long>("blacklist",u => u.Id);
ChatUsers = new ChatUserCollection();
PrivateMessages.Load();
BookQueries.Load();
Estimates.Load();
@ -60,6 +65,9 @@
EstimationCache.Load();
EstimateLinesTemplates.Load();
RemoteFiles.Load();
BlackList.Load();
ChatUsers.Load();
BlackList.Load();
}
}
}

@ -0,0 +1,62 @@

namespace BookAStar.Data
{
using Helpers;
using Model.Workflow;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
public class EstimateEntity : RemoteEntity<Estimate, long>
{
public EstimateEntity() : base("estimate", e => e.Id)
{
}
public async void SignAsProvider(Estimate estimate, Stream pngStream)
{
if (estimate.Id == 0)
{
var ok = await this.Create(estimate);
if (!ok)
{
await App.DisplayAlert("Erreur d'accès au serveur", "Echec de l'envoi de l'estimation");
return;
}
this.Add(estimate);
}
using (HttpClient client = UserHelpers.CreateJsonClient())
{
try
{
var requestContent = new MultipartFormDataContent();
var content = new StreamContent(pngStream);
var filename = $"prosign-{estimate.Id}.png";
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
content.Headers.Add("Content-Disposition", $"form-data; name=\"file\"; filename=\"{filename}\"");
requestContent.Add(content, "file", filename);
using (var response = await client.PostAsync(
Constants.YavscApiUrl + $"/pdfestimate/prosign/{estimate.Id}", requestContent))
{
if (!response.IsSuccessStatusCode)
{
var errContent = await response.Content.ReadAsStringAsync();
throw new ApiCallFailedException($"SignAsProvider: {response.StatusCode} / {errContent}");
}
var json = await response.Content.ReadAsStringAsync();
JsonConvert.PopulateObject(json, estimate);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
this.SaveEntity();
}
}
}

@ -6,13 +6,6 @@ namespace BookAStar.Data.NonCrUD
{
using Helpers;
using Model.FileSystem;
using System.Linq;
/*
public class DirectoryEntryChangingEvent : EventArgs
{
public UserDirectoryInfo OldItem { get; set; }
public UserDirectoryInfo NewItem { get; set; }
}*/
public class RemoteFilesEntity : RemoteEntity<UserDirectoryInfo, FileAddress>
{
@ -24,7 +17,7 @@ namespace BookAStar.Data.NonCrUD
public override async void Execute(object parameter)
{
BeforeExecute();
using (var client = UserHelpers.CreateClient())
using (var client = UserHelpers.CreateJsonClient())
{
// Get the whole data
try

@ -11,6 +11,7 @@ namespace BookAStar.Data
using Helpers;
using System.Diagnostics;
using System.Text;
using System.Web;
public class RemoteEntity<V, K> : LocalEntity<V, K>, ICommand where K : IEquatable<K>
{
@ -22,7 +23,7 @@ namespace BookAStar.Data
public bool CanExecute(object parameter)
{
return !IsExecuting && (MainSettings.CurrentUser != null);
return !IsExecuting;
}
public RemoteEntity(string controllerName, Func<V, K> getKey) : base(getKey)
@ -36,7 +37,7 @@ namespace BookAStar.Data
protected void BeforeExecute()
{
if (IsExecuting)
throw new InvalidOperationException("Already executing");
throw new InvalidOperationException(Strings.AlreadyExecuting);
IsExecuting = true;
if (CanExecuteChanged != null)
CanExecuteChanged.Invoke(this, new EventArgs());
@ -49,7 +50,7 @@ namespace BookAStar.Data
public virtual async void Execute(object parameter)
{
BeforeExecute();
using (HttpClient client = UserHelpers.CreateClient())
using (HttpClient client = UserHelpers.CreateJsonClient())
{
// Get the whole data
try
@ -71,7 +72,7 @@ namespace BookAStar.Data
}
catch (WebException webex)
{
throw new ServiceNotAvailable("No remote entity", webex);
throw new ServiceNotAvailable(Strings.ENoRemoteEntity, webex);
}
}
@ -95,7 +96,7 @@ namespace BookAStar.Data
protected Uri GetUri(K key)
{
return new Uri(ControllerUri.AbsoluteUri + "/" + key.ToString());
return new Uri(ControllerUri.AbsoluteUri + "/" + HttpUtility.UrlEncode(key.ToString()));
}
public virtual async Task<V> RemoteGet(K key)
@ -105,7 +106,7 @@ namespace BookAStar.Data
// Get the whole data
var uri = GetUri(key);
using (HttpClient client = UserHelpers.CreateClient())
using (HttpClient client = UserHelpers.CreateJsonClient())
{
using (var response = await client.GetAsync(uri))
{
@ -123,23 +124,27 @@ namespace BookAStar.Data
return item;
}
public virtual async void Create(V item)
public virtual async Task<bool> Create(V item)
{
bool created = false;
BeforeExecute();
using (HttpClient client = UserHelpers.CreateClient())
using (HttpClient client = UserHelpers.CreateJsonClient())
{
var stringContent = JsonConvert.SerializeObject(item);
HttpContent content = new StringContent(
stringContent, Encoding.UTF8, "application/json"
);
using (var response = await client.PostAsync(ControllerUri, content))
{
if (!response.IsSuccessStatusCode)
created = response.IsSuccessStatusCode;
if (!created)
{
// TODO throw custom exception, and catch to inform user
var errcontent = await response.Content.ReadAsStringAsync();
Debug.WriteLine($"Create failed posting {stringContent} @ {ControllerUri.AbsoluteUri}: {errcontent}");
Debug.WriteLine(string.Format(Strings.CreationFailed));
Debug.WriteLine(errcontent);
}
else
{
@ -148,31 +153,35 @@ namespace BookAStar.Data
JsonConvert.PopulateObject(recontent, item);
}
}
}
CurrentItem = item;
AfterExecuting();
return created;
}
public virtual async void Update(V item)
public virtual async Task<bool> Update(V item)
{
var updated = false;
BeforeExecute();
var uri = GetUri(GetKey(item));
using (HttpClient client = UserHelpers.CreateClient())
using (HttpClient client = UserHelpers.CreateJsonClient())
{
HttpContent content = new StringContent(
JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json"
);
using (var response = await client.PutAsync(uri, content))
{
if (!response.IsSuccessStatusCode)
updated = response.IsSuccessStatusCode;
if (!updated)
{// TODO throw custom exception, and catch to inform user
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var recontent = await response.Content.ReadAsStringAsync();
var errorcontent = await response.Content.ReadAsStringAsync();
Debug.WriteLine(string.Format(Strings.UpdateFailed));
Debug.WriteLine(errorcontent);
}
else Debug.WriteLine($"Update failed ({item} @ {uri.AbsolutePath} )");
}
@ -188,13 +197,14 @@ namespace BookAStar.Data
CurrentItem = item;
AfterExecuting();
return updated;
}
public virtual async void Delete(K key)
{
BeforeExecute();
var uri = GetUri(key);
using (HttpClient client = UserHelpers.CreateClient())
using (HttpClient client = UserHelpers.CreateJsonClient())
{
using (var response = await client.DeleteAsync(uri))
{

@ -17,18 +17,35 @@ namespace BookAStar.Helpers
{
var result = avatarPath == null ?
ImageSource.FromResource( "BookAStar.Images.Users.icon_user.png") :
avatarPath.StartsWith("res://") ?
ImageSource.FromResource(avatarPath.Substring(6)) :
ImageSource.FromUri(new Uri(avatarPath));
ImageSource.FromUri(new Uri(Constants.YavscHomeUrl+"/Avatars/"+avatarPath)) ;
return result;
}
public static HttpClient CreateClient()
public static ImageSource SmallAvatar(string avatarPath, string username)
{
return avatarPath == null ?
ImageSource.FromResource("BookAStar.Images.Users.icon_user.png") :
ImageSource.FromUri(new Uri($"{Constants.YavscHomeUrl}/Avatars/{username}.s.png"));
}
public static ImageSource ExtraSmallAvatar(string avatarPath, string username)
{
return avatarPath == null ?
ImageSource.FromResource("BookAStar.Images.Users.icon_user.png") :
ImageSource.FromUri(new Uri($"{Constants.YavscHomeUrl}/Avatars/{username}.xs.png"));
}
public static HttpClient CreateJsonClient()
{
return CreateJsonClient(MainSettings.CurrentUser.YavscTokens.AccessToken);
}
public static HttpClient CreateJsonClient(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", MainSettings.CurrentUser.YavscTokens.AccessToken);
"Bearer", accessToken);
client.DefaultRequestHeaders.Add("Accept", "application/json");
return client;
}
@ -42,7 +59,7 @@ namespace BookAStar.Helpers
/// <returns></returns>
public static async Task<bool> Upload(Stream inputStream, string fileName)
{
using (var client = CreateClient())
using (var client = CreateJsonClient())
{
using (var content =
new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,28 @@
using BookAStar.Model.Access;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yavsc.Models;
namespace BookAStar.Model.Access
{
public class BlackListed : IBlackListed
{
public long Id
{
get; set;
}
public string OwnerId
{
get; set;
}
public string UserId
{
get; set;
}
}
}

@ -1,16 +1,11 @@
using BookAStar.Helpers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace BookAStar.Model.Auth.Account
{
{
public class User : INotifyPropertyChanged
{
private string id;
@ -81,6 +76,20 @@ namespace BookAStar.Model.Auth.Account
OnPropertyChanged("Avatar");
}
}
private string address;
public string Address
{
get
{
return address;
}
set
{
address = value;
OnPropertyChanged("Address");
}
}
[JsonIgnore]
public ImageSource AvatarSource
{

@ -1,15 +1,16 @@
using BookAStar.Interfaces;
using BookAStar.Model.Social;

using System;
namespace BookAStar.Model
namespace BookAStar.Model.Social
{
public class BookQueryData : IBookQueryData
public class BookQuery
{
public ClientProviderInfo Client { get; set; }
public Location Location { get; set; }
public long Id { get; set; }
public DateTime EventDate { get; set; }
public decimal? Previsionnal { get; set; }
public string Reason { get; set; }
public bool Read { get; set; }
}
}

@ -0,0 +1,12 @@
namespace BookAStar.Model.Social
{
public class MusicalPreference : MusicalTendency {
public long OwnerId { get; set; }
public int Rate { get; set; }
}
}

@ -0,0 +1,13 @@
namespace BookAStar.Model.Social
{
public class MusicalTendency {
public long Id {get; set; }
public string Name { get ; set; }
}
}

@ -12,22 +12,14 @@ namespace BookAStar.Model
public string Avatar { get; set; }
public string UserId { get; set; }
// TODO Get User Professional status existence as a boolean
// And hack the avatar with
public int Rate { get; set; }
public string EMail { get; set; }
public string Phone { get; set; }
public Location BillingAddress { get; set; }
// TODO Get User Professional status existence as a boolean
// And hack the avatar with
[JsonIgnore]
public ImageSource AvatarOrNot
{
get
{
return UserHelpers.Avatar(Avatar);
}
}
public string ChatHubConnectionId { get; set; }
public override string ToString()
{

@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using BookAStar.Data;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BookAStar.Model.Social.Messaging
{
@ -11,5 +10,6 @@ namespace BookAStar.Model.Social.Messaging
public DateTime Date { get; set; }
public string SenderId { get; set; }
public string Message { get; set; }
public bool Read { get; set; }
}
}

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YavscLib;
namespace BookAStar.Model.Social.Chat
{
public class Connection : IConnection
{
public bool Connected
{
get; set;
}
public string ConnectionId
{
get; set;
}
public string UserAgent
{
get; set;
}
}
}

@ -0,0 +1,10 @@
namespace BookAStar.Model.Social
{
public class LocationType
{
public long Id { get; set; }
public string Name { get; set; }
}
}

@ -7,7 +7,6 @@ namespace BookAStar.Model.Workflow.Messaging
/// <summary>
/// Query, for a date, with a given perfomer, at this given place.
/// </summary>
public class BookQuery {
/// <summary>
/// The command identifier

@ -1,5 +1,5 @@
namespace Yavsc.Models
namespace Yavsc.Model
{
public partial class Tag
{

@ -1,6 +1,4 @@
using BookAStar.Data;
using BookAStar.Helpers;
using BookAStar.Model.Interfaces;

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@ -8,6 +6,9 @@ using System.Linq;
namespace BookAStar.Model.Workflow
{
using Data;
using Interfaces;
using Social;
public partial class Estimate : IEstimate
{
public long Id { get; set; }
@ -26,6 +27,7 @@ namespace BookAStar.Model.Workflow
/// <returns></returns>
public List<string> AttachedGraphics { get; set; }
[JsonIgnore]
// form in db
public string AttachedGraphicsString
{
get { return AttachedGraphics==null?null:string.Join(":", AttachedGraphics); }
@ -39,6 +41,7 @@ namespace BookAStar.Model.Workflow
/// </summary>
/// <returns></returns>
public List<string> AttachedFiles { get; set; }
// form in db
[JsonIgnore]
public string AttachedFilesString
{
@ -47,16 +50,28 @@ namespace BookAStar.Model.Workflow
}
public string OwnerId { get; set; }
[JsonIgnore]
public ClientProviderInfo Owner
{
get
{
if (!string.IsNullOrWhiteSpace(OwnerId))
{
var dm = DataManager.Instance;
return dm.Contacts.LocalGet(OwnerId);
}
return null;
}
}
public string ClientId { get; set; }
[JsonIgnore]
public BookQueryData Query
public BookQuery Query
{
get
{
if (CommandId.HasValue)
{
var dm = DataManager.Current;
var dm = DataManager.Instance;
return dm.BookQueries.LocalGet(CommandId.Value);
}
return null;
@ -67,7 +82,7 @@ namespace BookAStar.Model.Workflow
{
get
{
return DataManager.Current.Contacts.LocalGet(ClientId);
return DataManager.Instance.Contacts.LocalGet(ClientId);
}
}
@ -77,9 +92,14 @@ namespace BookAStar.Model.Workflow
return Bill?.Aggregate((decimal)0, (t, l) => t + l.Count * l.UnitaryCost) ?? (decimal)0;
}
}
public DateTime LatestValidationDate { get; set; }
/// <summary>
/// This validation comes first from the provider.
/// </summary>
public DateTime ProviderValidationDate { get; set; }
/// <summary>
/// Date for the agreement from the client
/// </summary>
public DateTime ClientApprouvalDate { get; set; }
}
}

@ -1,15 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.ChatPage"
x:Class="BookAStar.Pages.Chat.ChatPage"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
xmlns:controls="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
xmlns:toolkit="clr-namespace:XLabs.Forms.Mvvm;assembly=XLabs.Forms"
xmlns:converters="clr-namespace:BookAStar.Converters;assembly=BookAStar"
xmlns:local="clr-namespace:BookAStar;Assembly:BookAStar"
xmlns:extensions="clr-namespace:BookAStar.Extensions;assembly=BookAStar"
>
Style="{StaticResource PageStyle}" >
<TabbedPage.Resources>
<ResourceDictionary>
<converters:SignalRConnectionStateToObject x:Key="cxToStyleImage" x:TypeArguments="Style">
@ -100,20 +99,15 @@
<ContentPage Title="Contacts" Icon="peer_to_peer.png" >
<StackLayout Padding="5, 5, 5, 5">
<StackLayout Spacing = "12"
Orientation = "Horizontal"
Padding="5, 0, 5, 0">
<Entry x:Name="pvEntry" Placeholder = "enter your private message"
VerticalOptions = "Center"
HorizontalOptions = "FillAndExpand"></Entry>
<!--
<controls:ExtendedPicker x:Name="contactPicker" Display="{Binding UserName}"></controls:ExtendedPicker>
-->
<controls:ExtendedPicker x:Name="contactPicker" ItemsSource="{Binding Contacts}" />
<Button x:Name="sendPVButton" Text = "Send" HorizontalOptions = "End"
VerticalOptions = "Center"></Button>
Orientation = "Horizontal">
</StackLayout>
<ListView x:Name="PVList" HasUnevenRows="true" ItemsSource="{Binding PVs}">
<ListView.ItemTemplate HeightRequest="80"
<views:UserListView x:Name="chatUserList" />
<StackLayout BindingContext="{x:Reference Name=chatUserList}" IsVisible="{Binding HasASelection}">
<ListView x:Name="pvList" HasUnevenRows="true" ItemsSource="{Binding SelectedUser.PrivateMessages}" BindingContext="{x:Reference Name=chatUserList}">
<ListView.ItemTemplate HeightRequest="80"
VerticalOptions="StartAndExpand">
<DataTemplate>
<ViewCell>
@ -129,7 +123,13 @@
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- <views:UserListView ItemsSource="" /> -->
<Entry x:Name="pvEntry" Placeholder = "enter your private message"
VerticalOptions = "Center" HorizontalOptions = "FillAndExpand"></Entry>
<Button x:Name="sendPVButton" Text = "Send" HorizontalOptions = "End"
VerticalOptions = "Center"></Button>
</StackLayout>
</StackLayout>
</ContentPage>

@ -3,7 +3,7 @@ using System.Diagnostics;
using Microsoft.AspNet.SignalR.Client;
using Xamarin.Forms;
namespace BookAStar.Pages
namespace BookAStar.Pages.Chat
{
using Data;
using System.Linq;
@ -12,14 +12,28 @@ namespace BookAStar.Pages
public partial class ChatPage : TabbedPage
{
public string ChatUser { get; set; }
public ChatPage(ChatViewModel model)
{
Init();
BindingContext = model;
}
public ChatPage()
{
Init();
}
private void Init()
{
InitializeComponent();
Title = "Chat";
BindingContext = new ChatViewModel();
/*
ToolbarItems.Add(new ToolbarItem(
name: "...",
icon: null,
activated: () => { })); */
App.ChatHubConnection.StateChanged += ChatHubConnection_StateChanged;
sendButton.Clicked += async (sender, args) =>
{
IsBusy = true;
@ -38,30 +52,34 @@ namespace BookAStar.Pages
IsBusy = false;
};
sendPVButton.Clicked += async (sender, args) =>
chatUserList.BindingContext = DataManager.Instance.ChatUsers;
sendPVButton.Clicked += (sender, args) =>
{
string userName = contactPicker.SelectedItem as string;
if (string.IsNullOrEmpty(userName)) return;
var user = DataManager.Current.Contacts.Single(
c => c.UserName == userName);
if (string.IsNullOrEmpty(user.ChatHubConnectionId)) return;
IsBusy = true;
try
{
await App.ChatHubProxy.Invoke<string>("SendPV", user.ChatHubConnectionId, pvEntry.Text);
pvEntry.Text = null;
}
catch (Exception ex)
var dest = chatUserList.SelectedUser;
if (dest!=null)
{
Debug.WriteLine(ex);
IsBusy = true;
try
{
foreach (var cx in dest.ObservableConnections)
{
if (cx.Connected)
App.ChatHubProxy.Invoke<string>("SendPV", cx.ConnectionId, pvEntry.Text);
}
pvEntry.Text = null;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
IsBusy = false;
}
IsBusy = false;
};
}
private void ChatHubConnection_StateChanged(StateChange obj)
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.ClientPages.SearchPage"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
xmlns:controls="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
xmlns:toolkit="clr-namespace:XLabs.Forms.Mvvm;assembly=XLabs.Forms"
xmlns:converters="clr-namespace:BookAStar.Converters;assembly=BookAStar"
xmlns:local="clr-namespace:BookAStar;Assembly:BookAStar"
xmlns:extensions="clr-namespace:BookAStar.Extensions;assembly=BookAStar"
Style="{StaticResource PageStyle}" >
<TabbedPage.Children>
<ContentPage Title="Une star" Icon="">
<StackLayout Orientation="Horizontal">
<Editor x:Name="search_phrase" HorizontalOptions="FillAndExpand"/>
<Button x:Name="btn_update" HorizontalOptions="End" />
</StackLayout>
<DatePicker x:Name="search_date" />
</ContentPage>
</TabbedPage.Children>
<TabbedPage.Children>
<ContentPage Title="Un DJ" Icon="">
<StackLayout Orientation="Horizontal">
<Editor x:Name="search_phrase" HorizontalOptions="FillAndExpand"/>
<Button x:Name="btn_update" HorizontalOptions="End" />
</StackLayout>
<DatePicker x:Name="search_date" />
</ContentPage>
</TabbedPage.Children>
<TabbedPage.Children>
<ContentPage Title="Un chanteur" Icon="">
<StackLayout Orientation="Horizontal">
<Editor x:Name="search_phrase" HorizontalOptions="FillAndExpand"/>
<Button x:Name="btn_update" HorizontalOptions="End" />
</StackLayout>
<DatePicker x:Name="search_date" />
</ContentPage>
</TabbedPage.Children>
<TabbedPage.Children>
<ContentPage Title="Une formation musicale" Icon="">
<StackLayout Orientation="Horizontal">
<Editor x:Name="search_phrase" HorizontalOptions="FillAndExpand"/>
<Button x:Name="btn_update" HorizontalOptions="End" />
</StackLayout>
<DatePicker x:Name="search_date" />
</ContentPage>
</TabbedPage.Children>
</TabbedPage>

@ -6,11 +6,11 @@ using System.Threading.Tasks;
using Xamarin.Forms;
namespace BookAStar.Pages
namespace BookAStar.Pages.ClientPages
{
public partial class HomePage : ContentPage
public partial class SearchPage : TabbedPage
{
public HomePage()
public SearchPage()
{
InitializeComponent();
}

@ -3,7 +3,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:signature="clr-namespace:SignaturePad.Forms;assembly=SignaturePad.Forms"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
x:Class="BookAStar.ViewModels.Signing.DocSigning">
x:Class="BookAStar.ViewModels.Signing.Signing"
Style="{StaticResource PageStyle}">
<StackLayout>
<views:MarkdownView x:Name="mdView"
Editable="False"

@ -9,9 +9,9 @@ using Xamarin.Forms;
namespace BookAStar.ViewModels.Signing
{
public partial class DocSigning : ContentPage
public partial class Signing : ContentPage
{
public DocSigning()
public Signing()
{
InitializeComponent();
}

@ -30,29 +30,34 @@
<ScrollView>
<StackLayout Padding="10,10,10,10" x:Name="mainLayout">
<ListView RefreshCommand="{Binding RefreshQueries}" IsPullToRefreshEnabled="True"
ItemsSource="{Binding Queries}" x:Name="list" ItemTapped="OnViewDetail" HasUnevenRows="true" RowHeight="80">
<ListView.ItemTemplate HeightRequest="80" VerticalOptions="StartAndExpand">
ItemsSource="{Binding Queries}" x:Name="list" ItemTapped="OnViewDetail" HasUnevenRows="true"
SeparatorVisibility="Default" SeparatorColor="Black">
<ListView.ItemTemplate VerticalOptions="StartAndExpand">
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal" Padding="10,10,10,10" VerticalOptions="StartAndExpand">
<StackLayout Orientation="Vertical"
HeightRequest="80" VerticalOptions="StartAndExpand">
<StackLayout Orientation="Vertical" >
<Image Source="{Binding Client.Avatar}" />
<Label Text="{Binding Client.UserName}"
Style="{StaticResource labelStyle}"></Label>
</StackLayout>
<Label LineBreakMode="WordWrap" Text="{Binding EventDate, StringFormat='{0:dddd d MMMM à HH:mm}'}" FontSize="12" FontFamily="Italic"/>
</StackLayout>
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand">
<Label LineBreakMode="WordWrap" Text="{Binding Location.Address}"/>
<Label Text="{Binding Previsionnal}" />
<Label Text="{Binding Id}" HorizontalTextAlignment="End"/>
</StackLayout>
</StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Source="{Binding Avatar}" />
<Label Grid.Row="3" Grid.Column="0" Grid.RowSpan="2" Text="{Binding Client.UserName}" Style="{StaticResource labelStyle}"></Label>
<Label Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Grid.RowSpan="2" Text="{Binding Data.Reason}"></Label>
<Label Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" LineBreakMode="WordWrap" Text="{Binding EventDate, StringFormat='{0:dddd d MMMM à HH:mm}'}" FontFamily="Italic"/>
<Label Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" LineBreakMode="WordWrap" Text="{Binding Location.Address}"/>
<Label Grid.Row="4" Grid.Column="1" Text="{Binding Previsionnal}" />
<Label Grid.Row="4" Grid.Column="2" Text="{Binding Id}" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>

@ -14,20 +14,34 @@ namespace BookAStar.Pages
public BookQueriesPage()
{
InitializeComponent();
var model = new BookQueriesViewModel();
model.RefreshQueries =
new Command( () => {
DataManager.Current.BookQueries.Execute(null);
this.list.EndRefresh();
});
BindingContext = new BookQueriesViewModel();
}
public BookQueriesPage(BookQueriesViewModel model)
{
InitializeComponent();
BindingContext = model;
}
protected override void OnBindingContextChanged()
{
BookQueriesViewModel model = (BookQueriesViewModel) BindingContext;
if (model!=null)
{
model.RefreshQueries =
new Command(() =>
{
DataManager.Instance.BookQueries.Execute(null);
this.list.EndRefresh();
});
}
base.OnBindingContextChanged();
}
private void OnViewDetail(object sender, ItemTappedEventArgs e)
{
BookQueryData data = e.Item as BookQueryData;
App.NavigationService.NavigateTo<BookQueryPage>(true,data);
var item = e.Item as BookQueryViewModel;
App.NavigationService.NavigateTo<BookQueryPage>(true,item);
}
}
}

@ -19,20 +19,24 @@
<StackLayout x:Name="bookQueryLayout">
<StackLayout Orientation="Vertical">
<Image Source="{Binding Client.AvatarOrNot}" Aspect="AspectFit" VisualElement.HeightRequest="{StaticResource BigUserAvatarSize}"/>
<Image Source="{Binding Avatar}" Aspect="AspectFit" VisualElement.HeightRequest="{StaticResource BigUserAvatarSize}"/>
<Label Text="{Binding Client.UserName}" />
<Label Text="{Binding EventDate, StringFormat='le {0:dddd d MMMM yyyy à hh:mm}'}" />
<Label Text="{Binding Data.Reason}" />
<Label Text="{Binding Location.Address}" />
<Label Text="{Binding Previsional}" />
</StackLayout>
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" >
<maps:Map x:Name="map" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"></maps:Map>
<StackLayout Orientation="Horizontal">
<Button Text="{x:Static local:Strings.ViewEstimate}"
<Button Text="{Binding EditEstimateButtonText}" Clicked="OnEditEstimate" />
<Button Text="{x:Static local:Strings.DeclineQuery}" Clicked="OnDropQuery" />
<Button Text="{x:Static local:Strings.BlockThisUser}" Clicked="OnBlockThisUser" />
<Button Text="{x:Static local:Strings.ViewEstimate}"
BorderRadius="50" BorderWidth="2" BorderColor="Aqua" x:Name ="btn"
IsEnabled="{Binding EstimationDone}"
VisualElement.IsVisible="{Binding EstimationDone}"
Clicked="OnViewEstimate" Image="exclam.png" />
<Button Text="{Binding EditEstimateButtonText}" Clicked="OnEditEstimate" />
</StackLayout>
</StackLayout>
</StackLayout>

@ -7,14 +7,14 @@ namespace BookAStar.Pages
{
using Data;
using EstimatePages;
using Model;
using Model.Social;
using Model.Workflow;
using ViewModels.EstimateAndBilling;
public partial class BookQueryPage : ContentPage
{
public BookQueryData BookQuery
public BookQuery BookQuery
{
get
{
@ -32,14 +32,14 @@ namespace BookAStar.Pages
var pin = new Pin
{
Type = PinType.SavedPin,
Position = new Position(
lat, lon),
Position = new Xamarin.Forms.Maps.Position
(lat, lon),
Label = BookQuery.Client.UserName,
Address = BookQuery.Location.Address
};
map.Pins.Add(pin);
map.MoveToRegion(MapSpan.FromCenterAndRadius(
new Position(lat, lon), Distance.FromMeters(100)));
new Xamarin.Forms.Maps.Position(lat, lon), Distance.FromMeters(100)));
}
}
@ -47,14 +47,14 @@ namespace BookAStar.Pages
{
InitializeComponent();
}
public BookQueryPage(BookQueryData bookQuery=null)
public BookQueryPage(BookQueryViewModel bookQuery =null)
{
InitializeComponent();
// when TODO update?
// Task.Run( async () => { bookQuery = await App.CurrentApp.DataManager.BookQueries.Get(bookQueryId); });
BindingContext = new BookQueryViewModel(bookQuery);
BindingContext = bookQuery;
}
private void OnEditEstimate(object sender, EventArgs ev)
@ -65,31 +65,25 @@ namespace BookAStar.Pages
if (editEstimateViewModel == null)
{
// First search for an existing estimate
var estimateToEdit = DataManager.Current.Estimates.FirstOrDefault(
estimate=> estimate.CommandId == bookQueryViewModel.Id
editEstimateViewModel = DataManager.Instance.EstimationCache.FirstOrDefault(
estimate=> estimate.Query.Id == bookQueryViewModel.Id
);
if (estimateToEdit == null)
if (editEstimateViewModel == null)
{
DataManager.Current.Contacts.Merge(BookQuery.Client);
DataManager.Current.Contacts.SaveEntity();
estimateToEdit = new Estimate
DataManager.Instance.Contacts.Merge(BookQuery.Client);
DataManager.Instance.Contacts.SaveEntity();
editEstimateViewModel = new EditEstimateViewModel( new Estimate
{
ClientId = BookQuery.Client.UserId,
CommandId = BookQuery.Id,
OwnerId = MainSettings.CurrentUser.Id,
Id = 0
};
editEstimateViewModel = new EditEstimateViewModel(estimateToEdit);
});
DataManager.Instance.EstimationCache.Add(editEstimateViewModel);
}
else
editEstimateViewModel = new EditEstimateViewModel(estimateToEdit);
DataManager.Current.EstimationCache.Add(editEstimateViewModel);
}
App.NavigationService.NavigateTo<EditEstimatePage>(true,
editEstimateViewModel);
}
private async void OnViewEstimate(object sender, EventArgs ev)
@ -118,5 +112,13 @@ namespace BookAStar.Pages
}
base.OnSizeAllocated(width, height);
}
private void OnBlockThisUser(object sender, EventArgs ev)
{
throw new NotImplementedException();
}
private void OnDropQuery(object sender, EventArgs ev)
{
throw new NotImplementedException();
}
}
}

@ -44,12 +44,16 @@
<StackLayout x:Name="mainStackLayout">
<Label Text="Description de la ligne de facture"
Style="{StaticResource InputLabelStyle}"></Label>
<StackLayout Orientation="Horizontal" VerticalOptions="FillAndExpand">
<Editor HorizontalOptions="FillAndExpand" Text="{Binding Description, Mode=TwoWay}">
<Editor HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Text="{Binding Description, Mode=TwoWay}">
<Editor.Behaviors>
<behaviors:EditorMaxLengthValidator x:Name="descriptionValidator" MaxLength="512" />
<behaviors:EditorMaxLengthValidator x:Name="descriptionLenValidator" MaxLength="12" MinLength="3" />
</Editor.Behaviors>
</Editor>
<StackLayout Orientation="Horizontal">
<Image x:Name="descriptionSuccessErrorImage"
Style="{Binding Source={x:Reference descriptionLenValidator}, Path=IsValid, Converter={StaticResource boolToStyleImage}}" />
<Label Text="{Binding Source={x:Reference descriptionLenValidator}, Path=Error}"
Style="{StaticResource ErrorLabelStyle}"></Label>
</StackLayout>
<Label Text="Durée de la prestation"
@ -74,18 +78,21 @@
</StackLayout>
<Label Text="Quantité facturée" Style="{StaticResource InputLabelStyle}"></Label>
<StackLayout Orientation="Horizontal">
<Entry Text="{Binding Count, Mode=TwoWay}" Placeholder="Quantité" Keyboard="Numeric"
Style="{StaticResource BigEntry}">
<Entry.Behaviors>
<behaviors:IntegerEntryBehavior x:Name="countValidator" Min="0" Max="10" />
</Entry.Behaviors>
</Entry>
<Image x:Name="countSuccessErrorImage"
<StackLayout Orientation="Horizontal">
<Image x:Name="countSuccessErrorImage"
Style="{Binding Source={x:Reference countValidator},
Path=IsValid,
Converter={StaticResource boolToStyleImage}}" />
</StackLayout>
<Label Text="{Binding Source={x:Reference countValidator}, Path=Error}"
Style="{StaticResource ErrorLabelStyle}"></Label>
</StackLayout>
<Label Text="Prix unitaire" Style="{StaticResource InputLabelStyle}"></Label>
<StackLayout Orientation="Horizontal">
@ -95,7 +102,7 @@
<behaviors:DecimalValidatorBehavior x:Name="unitCostValidator" />
</Entry.Behaviors>
</Entry>
<Label Text="€" Style="{StaticResource BigLabel}" />
<Label Text="€" Style="{StaticResource BigLabelStyle}" />
<Image x:Name="unitaryCostSuccessErrorImage"
Style="{Binding Source={x:Reference unitCostValidator},
Path=IsValid,
@ -106,7 +113,7 @@
Command="{Binding ValidateCommand}"
Clicked="OnValidateClicked"></Button>
<Button Text="Supprimer"
Command="{Binding DeleteCommand}"
Command="{Binding RemoveCommand}"
Clicked="OnDeleteClicked"></Button>
</StackLayout>
</StackLayout>

@ -17,14 +17,17 @@ namespace BookAStar.Pages
picker.Items.Add(du);
BindingContext = model;
}
public void OnDeleteClicked(object sender, EventArgs e)
{
throw new NotImplementedException();
this.Navigation.PopAsync();
}
public void OnValidateClicked (object sender, EventArgs e)
{
this.Navigation.PopAsync();
}
protected override bool OnBackButtonPressed()
{
var bvm = (BillingLineViewModel)BindingContext;

@ -2,8 +2,11 @@
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.EditEstimatePage"
xmlns:converters="clr-namespace:BookAStar.Converters;assembly=BookAStar"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
xmlns:local="clr-namespace:BookAStar;assembly=BookAStar"
xmlns:behaviors="clr-namespace:BookAStar.Behaviors;assembly=BookAStar"
xmlns:extensions="clr-namespace:BookAStar.Extensions;assembly=BookAStar"
Style="{StaticResource PageStyle}">
<ContentPage.Resources>
<ResourceDictionary>
@ -13,14 +16,32 @@
<Style TargetType="Button">
<Setter Property="Style" Value="{StaticResource ButtonStyle}" />
</Style>
<converters:BooleanToObjectConverter x:Key="boolToStyleImage" x:TypeArguments="Style">
<converters:BooleanToObjectConverter.FalseObject>
<Style TargetType="Image">
<Setter Property="HeightRequest" Value="20" />
<Setter Property="Source"
Value="{extensions:ImageResource BookAStar.Images.Validation.error.png}" />
</Style>
</converters:BooleanToObjectConverter.FalseObject>
<converters:BooleanToObjectConverter.TrueObject>
<Style TargetType="Image">
<Setter Property="HeightRequest" Value="20" />
<Setter Property="Source"
Value="{extensions:ImageResource BookAStar.Images.Validation.success.png}" />
</Style>
</converters:BooleanToObjectConverter.TrueObject>
</converters:BooleanToObjectConverter>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<StackLayout Padding="10,10,10,10" x:Name="mainLayout">
<Grid MinimumHeightRequest="12">
<Grid HeightRequest="120">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@ -34,18 +55,20 @@
<Entry Placeholder="Saisissez un titre pour ce devis" Text="{Binding Title, Mode=TwoWay}" />
<views:MarkdownView x:Name="mdview"
Editable="True"
HorizontalOptions="FillAndExpand"
Markdown="{Binding Description, Mode=TwoWay}"
VerticalOptions="Start" />
Markdown="{Binding Description, Mode=TwoWay}">
<views:MarkdownView.Behaviors>
<behaviors:MarkdownViewLengthValidator x:Name="descriptionLenValidator" MaxLength="512" MinLength="3" />
</views:MarkdownView.Behaviors>
</views:MarkdownView>
<StackLayout x:Name="biAnVaLayout">
<ListView x:Name="BillListView" ItemsSource="{Binding Bill}"
MinimumHeightRequest="40" HasUnevenRows="true" VerticalOptions="FillAndExpand"
HeightRequest="40" ItemTapped="OnEditLine">
HasUnevenRows="true" ItemTapped="OnEditLine">
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<ViewCell.View>
<Grid MinimumHeightRequest="12">
<ViewCell.View Padding="5,5,5,5" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@ -54,6 +77,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="90" />
<ColumnDefinition Width="16" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Description}"
Grid.Row="0" Grid.Column="0" ></Label>
@ -64,6 +88,9 @@
<Label Text="{Binding UnitaryCost}"
Grid.Row="0" Grid.Column="3"
FontFamily="Monospace"></Label>
<Image Grid.Row="0" Grid.Column="4" Style="{Binding
Path=ViewModelState.IsValid,
Converter={StaticResource boolToStyleImage}}" ></Image>
</Grid>
</ViewCell.View>
</ViewCell>
@ -73,10 +100,9 @@
<StackLayout Orientation="Vertical">
<Button Text="Ajouter une ligne de facture" Clicked="OnNewCommanLine"></Button>
<Label FormattedText="{Binding FormattedTotal}"/>
<Button Text="Valider ce devis" Clicked="OnEstimateValidated" ></Button>
<Button x:Name="btnValidate" Text="Valider ce devis" Clicked="OnEstimateValidated" IsEnabled="{Binding ViewModelState.IsValid}" ></Button>
</StackLayout>
</StackLayout>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>

@ -4,16 +4,36 @@ using Xamarin.Forms;
namespace BookAStar.Pages
{
using Data;
using EstimatePages;
using Model.Workflow;
using ViewModels.EstimateAndBilling;
using ViewModels.Signing;
public partial class EditEstimatePage : ContentPage
{
public EditEstimateViewModel Model
{
get
{
return (EditEstimateViewModel)BindingContext;
}
}
public EditEstimatePage(EditEstimateViewModel model)
{
InitializeComponent();
BindingContext = model;
Model.CheckCommand = new Action<Estimate, ViewModels.Validation.ModelState>(
(e, m) =>
{
foreach (var line in model.Bill)
{
line.Check();
if (!line.ViewModelState.IsValid)
model.ViewModelState.AddError("Bill", "invalid line");
}
});
InitializeComponent();
Model.Check();
}
protected override void OnBindingContextChanged()
@ -24,7 +44,7 @@ namespace BookAStar.Pages
private void EditEstimatePage_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
DataManager.Current.EstimationCache.SaveEntity();
DataManager.Instance.EstimationCache.SaveEntity();
}
protected override void OnSizeAllocated(double width, double height)
@ -46,54 +66,62 @@ namespace BookAStar.Pages
var bill = ((EditEstimateViewModel)BindingContext).Bill;
var lineView = new BillingLineViewModel(com)
{ ValidateCommand = new Command(() => {
bill.Add(com);
DataManager.Current.EstimationCache.SaveEntity();
bill.Add(new BillingLineViewModel(com));
DataManager.Instance.EstimationCache.SaveEntity();
})};
lineView.PropertyChanged += LineView_PropertyChanged;
App.NavigationService.NavigateTo<EditBillingLinePage>(
true, lineView );
}
protected void OnEditLine(object sender, ItemTappedEventArgs e)
{
var line = (BillingLine)e.Item;
var bill = ((EditEstimateViewModel)BindingContext).Bill;
var lineView = new BillingLineViewModel(line)
{
ValidateCommand = new Command(() => {
DataManager.Current.EstimationCache.SaveEntity();
})
};
lineView.PropertyChanged += LineView_PropertyChanged;
lineView.PropertyChanged += LineView_PropertyChanged;
var line = (BillingLineViewModel)e.Item;
var evm = ((EditEstimateViewModel)BindingContext);
// update the validation command, that
// was creating a new line in the bill at creation time,
// now one only wants to update the line
line.ValidateCommand = new Command(() =>
{
evm.Check();
DataManager.Instance.EstimationCache.SaveEntity();
});
// and setup a removal command, that was not expected at creation time
line.RemoveCommand = new Command(() =>
{
evm.Bill.Remove(line);
evm.Check();
DataManager.Instance.EstimationCache.SaveEntity();
});
App.NavigationService.NavigateTo<EditBillingLinePage>(
true, lineView );
}
private void LineView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
DataManager.Current.EstimationCache.SaveEntity();
true, line );
BillListView.SelectedItem = null;
}
protected void OnEstimateValidated(object sender, EventArgs e)
protected async void OnEstimateValidated(object sender, EventArgs e)
{
var evm = (EditEstimateViewModel)BindingContext;
if (evm.Data.Id == 0)
var thisPage = this;
var evm = (EditEstimateViewModel) BindingContext;
var cmd = new Command<bool>( async (validated) =>
{
DataManager.Current.Estimates.Create(evm.Data);
// we have to manually add this item in our local collection,
// since we could prefer to update the whole collection
// from server, or whatever other scenario
DataManager.Current.Estimates.Add(evm.Data);
} else
if (validated) {
DataManager.Instance.EstimationCache.Remove(evm);
DataManager.Instance.EstimationCache.SaveEntity();
}
await thisPage.Navigation.PopAsync();
});
var response = await App.DisplayActionSheet(
Strings.SignOrNot, Strings.DonotsignEstimate,
Strings.CancelValidation, new string[] { Strings.Sign });
if (response == Strings.Sign)
{
DataManager.Current.Estimates.Update(evm.Data);
App.NavigationService.NavigateTo<EstimateSigningPage>(true,
new EstimateSigningViewModel(evm.Data) { ValidationCommand = cmd });
}
DataManager.Current.Estimates.SaveEntity();
DataManager.Current.EstimationCache.Remove(evm);
DataManager.Current.EstimationCache.SaveEntity();
Navigation.PopAsync();
else if (response == Strings.CancelValidation)
return;
else cmd.Execute(true);
}
}
}

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:signature="clr-namespace:SignaturePad.Forms;assembly=SignaturePad.Forms"
xmlns:views="clr-namespace:BookAStar.Views;assembly=BookAStar"
x:Class="BookAStar.Pages.EstimatePages.EstimateSigningPage">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="Style" Value="{StaticResource ContentLabelStyle}" />
</Style>
<Style TargetType="Button">
<Setter Property="Style" Value="{StaticResource ButtonStyle}" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Padding="10,10,10,10" x:Name="mainLayout">
<ScrollView>
<StackLayout>
<Grid MinimumHeightRequest="12">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Client.UserName}" ></Label>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding Query.Location.Address}" ></Label>
<Label Grid.Row="0" Grid.Column="2" Text="{Binding Query.EventDate, StringFormat='{0:dddd d MMMM yyyy à hh:mm}'}" ></Label>
</Grid>
<Label Text="{Binding Title}" Style="{StaticResource BigLabelStyle}" />
<views:MarkdownView x:Name="mdview"
HorizontalOptions="FillAndExpand"
Markdown="{Binding Description}"
/>
<ListView x:Name="billListView" ItemsSource="{Binding Bill}"
MinimumHeightRequest="40" HasUnevenRows="true" VerticalOptions="FillAndExpand"
HeightRequest="40" >
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<ViewCell.View>
<Grid MinimumHeightRequest="12">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="90" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Description}"
Grid.Row="0" Grid.Column="0" ></Label>
<Label Text="{Binding Duration, StringFormat=\{0\}}"
Grid.Row="0" Grid.Column="1" ></Label>
<Label Text="{Binding Count}"
Grid.Row="0" Grid.Column="2" ></Label>
<Label Text="{Binding UnitaryCost}"
Grid.Row="0" Grid.Column="3"
FontFamily="Monospace"></Label>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label FormattedText="{Binding FormattedTotal}"/>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding ProSignImage}" HorizontalOptions="Start"></Image>
<Image Source="{Binding CliSignImage}" HorizontalOptions="End"></Image>
</StackLayout>
</StackLayout>
</ScrollView>
<signature:SignaturePadView x:Name="padView"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
BackgroundColor="White"
CaptionText="{Binding Data.Owner.UserName}" CaptionTextColor="Black"
ClearText="Effacer!" ClearTextColor="Red"
PromptText="Prompt Here" PromptTextColor="Red"
SignatureLineColor="Aqua" StrokeColor="Black" StrokeWidth="2" />
<Button Clicked="OnValidate" Text="Valider cette signature" x:Name="btnValidate" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

@ -0,0 +1,74 @@
using SignaturePad.Forms;
using System;
using System.IO;
using System.Linq;
using Xamarin.Forms;
namespace BookAStar.Pages.EstimatePages
{
using Data;
using ViewModels.EstimateAndBilling;
using ViewModels.Signing;
public partial class EstimateSigningPage : ContentPage
{
public EstimateSigningPage(EstimateSigningViewModel model)
{
InitializeComponent();
BindingContext = model;
}
private async void OnValidate (object sender, EventArgs ev)
{
btnValidate.IsEnabled = false;
if (DataManager.Instance.Estimates.IsExecuting)
{
await App.DisplayAlert(Strings.OperationPending, Strings.oups);
return;
}
var evm = (EditEstimateViewModel)BindingContext;
var estimate = evm.Data;
var pngStream = await padView.GetImageStreamAsync(SignatureImageFormat.Png);
pngStream.Seek(0, SeekOrigin.Begin);
DataManager.Instance.Estimates.SignAsProvider(estimate, pngStream);
DataManager.Instance.Estimates.SaveEntity();
await Navigation.PopAsync();
var ParentValidationCommand = ((EstimateSigningViewModel)BindingContext).ValidationCommand;
if (ParentValidationCommand != null)
ParentValidationCommand.Execute(true);
}
private async void OnChangeTheme(object sender, EventArgs e)
{
var action = await DisplayActionSheet("Change Theme", "Cancel", null, "White", "Black", "Aqua");
switch (action)
{
case "White":
padView.BackgroundColor = Color.White;
padView.StrokeColor = Color.Black;
padView.ClearTextColor = Color.Black;
padView.ClearText = "Clear Markers";
break;
case "Black":
padView.BackgroundColor = Color.Black;
padView.StrokeColor = Color.White;
padView.ClearTextColor = Color.White;
padView.ClearText = "Clear Chalk";
break;
case "Aqua":
padView.BackgroundColor = Color.Aqua;
padView.StrokeColor = Color.Red;
padView.ClearTextColor = Color.Black;
padView.ClearText = "Clear The Aqua";
break;
}
}
}
}

@ -1,7 +1,40 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.EstimatesClientPage">
<Label Text="{Binding MainText}" VerticalOptions="Center" HorizontalOptions="Center" />
x:Class="BookAStar.Pages.EstimatesClientPage"
Style="{StaticResource PageStyle}">
<ScrollView>
<StackLayout Padding="10,10,10,10" x:Name="mainLayout">
<ListView RefreshCommand="{Binding RefreshCommand}" IsPullToRefreshEnabled="True"
ItemsSource="{Binding Estimates}" x:Name="estimates" ItemTapped="OnViewDetail" HasUnevenRows="true" RowHeight="80">
<ListView.ItemTemplate HeightRequest="80" VerticalOptions="StartAndExpand">
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal" Padding="10,10,10,10" VerticalOptions="StartAndExpand">
<StackLayout Orientation="Vertical"
HeightRequest="80" VerticalOptions="StartAndExpand">
<StackLayout Orientation="Vertical" >
<Image Source="{Binding Owner.Avatar}" />
<Label Text="{Binding Client.UserName}"
Style="{StaticResource labelStyle}"></Label>
</StackLayout>
<Label LineBreakMode="WordWrap" Text="{Binding EventDate, StringFormat='{0:dddd d MMMM à HH:mm}'}" FontSize="12" FontFamily="Italic"/>
</StackLayout>
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand">
<Label LineBreakMode="WordWrap" Text="{Binding Location.Address}"/>
<Label Text="{Binding Previsionnal}"/>
<Label Text="{Binding Id}" HorizontalTextAlignment="End"/>
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ScrollView>
</ContentPage>

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.EstimatesProviderPage">
x:Class="BookAStar.Pages.EstimatesProviderPage"
Style="{StaticResource PageStyle}">
<ScrollView>
<StackLayout Padding="10,10,10,10" x:Name="mainLayout">

@ -33,9 +33,6 @@
</Grid>
<Label Text="{Binding Title}" />
<Label Text="{Binding Description}" />
<views:MarkdownView x:Name="mdview"
HorizontalOptions="FillAndExpand"
Markdown="{Binding Description}"

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BookAStar;assembly=BookAStar"
x:Class="BookAStar.Pages.HomePage"
Style="{StaticResource PageStyle}"
Title="Accueil">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="Style" Value="{StaticResource ContentLabelStyle}" />
</Style>
<Style TargetType="Button">
<Setter Property="Style" Value="{StaticResource ButtonStyle}" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<TabbedPage.Children>
<!-- La recherche d'un pro -->
<TabbedPage Title="{x:Static local:Strings.SearchForAPro}" >
</TabbedPage>
<!--
les derniers sons/videos/articles postés
-->
<ContentPage Title="Blogspot">
<StackLayout Orientation="Horizontal">
<Editor x:Name="search_blog_phrase" HorizontalOptions="FillAndExpand"/>
<Button x:Name="btn_blog_update" HorizontalOptions="End" />
</StackLayout>
</ContentPage>
<!--
Les demandes devis en attente de réponse (pro)
-->
<ContentPage Title="Demandes de devis"
IsVisible="{Binding UserProfile.IsAPerformer}">
<StackLayout BindingContext="{Binding BookQueries}">
<ListView x:Name="querylist"
RefreshCommand="{Binding RefreshQueries}"
IsPullToRefreshEnabled="True"
ItemsSource="{Binding Queries}"
ItemTapped="OnViewBookQueryDetail"
HasUnevenRows="true"
SeparatorVisibility="Default"
SeparatorColor="Black">
<ListView.ItemTemplate VerticalOptions="StartAndExpand" >
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="4" Source="{Binding Avatar}" />
<Label Grid.Row="0" Grid.Column="1" Text="{Binding Client.UserName}" Style="{StaticResource LabelStyle}"></Label>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding Data.Reason}"></Label>
<Label Grid.Row="2" Grid.Column="1" LineBreakMode="WordWrap" Text="{Binding EventDate, StringFormat='{0:dddd d MMMM à HH:mm}'}" FontFamily="Italic"/>
<Label Grid.Row="3" Grid.Column="1" LineBreakMode="WordWrap" Text="{Binding Location.Address}" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
<!-- Les signatures de contrat en souffreances (pro) -->
<ContentPage Title="Contrats fournisseur" IsVisible="{Binding UserProfile.IsAPerformer}">
</ContentPage>
<!-- Les signatures de contrat en souffreances (client) -->
<ContentPage Title="Contrats client" IsVisible="{Binding UserProfile.IsAPerformer}">
</ContentPage>
<!-- Les annonces pro (pro) -->
<ContentPage Title="Annonces pro" IsVisible="{Binding UserProfile.IsAPerformer}">
<StackLayout Orientation="Horizontal" >
<Editor x:Name="search_pub_pro_phrase" HorizontalOptions="FillAndExpand" VerticalOptions="Start"/>
<Button x:Name="btn_pro_pub" HorizontalOptions="End" VerticalOptions="Start" Text="Chercher"/>
</StackLayout>
</ContentPage>
<!-- les petites annonces des clients (pro) -->
<ContentPage Title="Annonces client" Icon="" IsVisible="{Binding UserProfile.IsAPerformer}">
<StackLayout Orientation="Horizontal" >
<Editor x:Name="search_pub_cli_phrase" HorizontalOptions="FillAndExpand" VerticalOptions="Start"/>
<Button x:Name="btn_cli_pub" HorizontalOptions="End" VerticalOptions="Start" Text="Chercher"/>
</StackLayout>
</ContentPage>
</TabbedPage.Children>
</TabbedPage>

@ -0,0 +1,55 @@
using Xamarin.Forms;
namespace BookAStar.Pages
{
using Data;
using ViewModels;
using ViewModels.EstimateAndBilling;
public partial class HomePage
{
public HomePage()
{
InitializeComponent();
}
public HomePage(HomeViewModel model)
{
BindingContext = model;
}
public HomeViewModel Model {
get {
return (HomeViewModel) BindingContext;
}
set
{
BindingContext = value;
}
}
protected override void OnBindingContextChanged()
{
// this technique make this view model
// non-sharable between view or pages
if (Model != null)
{
// set the refresh command before using it
Model.BookQueries.RefreshQueries =
new Command(() =>
{
DataManager.Instance.BookQueries.Execute(null);
this.querylist.EndRefresh();
});
}
// Use the new refresh command
base.OnBindingContextChanged();
}
private void OnViewBookQueryDetail(object sender, ItemTappedEventArgs e)
{
var item = e.Item as BookQueryViewModel;
App.NavigationService.NavigateTo<BookQueryPage>(true, item);
}
}
}

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.HomePage"
Style="{StaticResource PageStyle}">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="Style" Value="{StaticResource ContentLabelStyle}" />
</Style>
<Style TargetType="Button">
<Setter Property="Style" Value="{StaticResource ButtonStyle}" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<Label Text="Blah" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BookAStar;Assembly:BookAStar"
x:Class="BookAStar.SearchPage" Title="Page de recherche"
Style="{StaticResource PageStyle}">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="Style" Value="{StaticResource ContentLabelStyle}" />
</Style>
<Style TargetType="Button">
<Setter Property="Style" Value="{StaticResource ButtonStyle}" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<StackLayout Orientation="Horizontal">
<Editor x:Name="search_phrase" HorizontalOptions="FillAndExpand"/>
<Button x:Name="btn_update" HorizontalOptions="End" />
</StackLayout>
<DatePicker x:Name="search_date" />
<ListView x:Name="list" ItemsSource="{x:Static local:Manager.Events}"
HasUnevenRows="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding ImgLocator}" HeightRequest="80" />
<StackLayout Orientation="Vertical">
<Label Text="{Binding Title}"/>
<StackLayout Orientation="Horizontal">
<StackLayout><Label Text="Heure:" FontAttributes="Italic" FontSize="9" />
<Label Text="{Binding StartDate, StringFormat='{0:H:mm}'}" VerticalOptions="End"/>
</StackLayout>
<StackLayout><Label Text="Lieu:" FontAttributes="Italic" FontSize="9" VerticalOptions="End"/>
<Label Text="{Binding Location.Name}"/></StackLayout>
<Label Text="{Binding Promotion}" FontSize="20" TextColor="Yellow" BackgroundColor="#102030"/>
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>

@ -1,31 +0,0 @@
using System;
using Xamarin.Forms;
using System.Collections.ObjectModel;
using BookAStar.Model.Social;
using BookAStar.Model.Workflow.Messaging;
using BookAStar.Pages;
namespace BookAStar
{
public partial class SearchPage : ContentPage
{
public ObservableCollection<LocalizedEvent> Events { get; private set; }
public SearchPage ()
{
InitializeComponent ();
BindingContext = this;
Events = Manager.Events;
list.ItemTapped += async (object sender, ItemTappedEventArgs e) => {
await Navigation.PushAsync(new EventDetail( (YaEvent) e.Item) { Title = "Détail de la soirée" } );
};
search_date.Date = DateTime.Now;
search_date.MinimumDate = DateTime.Now;
search_phrase.Text = "Suresnes";
btn_update.Clicked += (object sender, EventArgs e) => {
//
};
}
}
}

@ -3,7 +3,8 @@
xmlns:local="clr-namespace:BookAStar;assembly=BookAStar"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookAStar.Pages.UserProfile.AccountChooserPage"
Title="Paramètres Booking star" Style="{StaticResource PageStyle}"
Title="{x:Static local:Strings.UserAccounts}"
Style="{StaticResource PageStyle}"
xmlns:lc="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
xmlns:lb="clr-namespace:XLabs.Forms.Behaviors;assembly=XLabs.Forms">
@ -13,6 +14,7 @@
<ListView x:Name="AccountListView"
SeparatorVisibility="Default"
VerticalOptions="FillAndExpand"
ItemsSource="{x:Static local:MainSettings.AccountList}"
>
<ListView.Header>
<StackLayout Orientation="Horizontal">
@ -20,17 +22,18 @@
</StackLayout>
</ListView.Header>
<ListView.ItemTemplate HeightRequest="60" VerticalOptions="StartAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="5"
ColumnSpacing="10"
RowSpacing="2" >
<Image Source="{Binding AvatarSource}" HeightRequest="80"
x:Name="avatarImage"/>
<Label Grid.Column="0" Text="{Binding UserName}" >
<Image Grid.Column="0" Source="{Binding AvatarSource}" HeightRequest="80" />
<Label Grid.Column="1" Text="{Binding UserName}" >
</Label>
<Label Grid.Column="2" Text="{Binding EMails.ToString()}" />
<Label Grid.Column="3" Text="{Binding Roles.ToString()}" />
<Label Grid.Column="4" Text="{Binding Address}" />
</Grid>
</ViewCell>
</DataTemplate>

@ -14,37 +14,24 @@ namespace BookAStar.Pages.UserProfile
public partial class AccountChooserPage : ContentPage
{
public ICommand RemoteSettingsRefreshCommand { get; private set; }
public AccountChooserPage ()
{
InitializeComponent ();
AccountListView.ItemsSource = MainSettings.AccountList;
this.Musical = MainSettings.Musical;
this.Environ = MainSettings.Environ;
this.BindingContext = this;
AddAccountBtn.Clicked += AddAccountBtn_Clicked;
// avatarImage.
//RemoveAccountBouton.Clicked += RemoveAccountBouton_Clicked;
AccountListView.ItemSelected += Accounts_ItemSelected;
DumpParam = new RelayGesture((g, x) =>
{
if (g.GestureType == GestureType.Swipe && g.Direction == Directionality.Left)
{
RemoveAccount();
}
});
// MainSettings.UserChanged += MainSettings_UserChanged;
}
// Should be useless
private void MainSettings_UserChanged(object sender, EventArgs e)
{
AccountListView.SelectedItem = MainSettings.CurrentUser;
throw new NotImplementedException();
}
public RelayGesture DumpParam { get; set; }
public ObservableCollection<User> Accounts { get; private set; }
public Dictionary<string, double> Musical { get; private set; }
public Dictionary<string, double> Environ { get; private set; }
private void Accounts_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
@ -68,7 +55,6 @@ namespace BookAStar.Pages.UserProfile
protected override void OnAppearing()
{
base.OnAppearing();
AccountListView.SelectedItem = MainSettings.CurrentUser;
}
private void AddAccountBtn_Clicked(object sender, EventArgs e)

@ -7,7 +7,7 @@
x:Class="BookAStar.Pages.UserProfile.DashboardPage"
xmlns:lc="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
xmlns:lb="clr-namespace:XLabs.Forms.Behaviors;assembly=XLabs.Forms"
Style="{StaticResource PageStyle}">
Style="{StaticResource DashboardPageStyle}">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
@ -21,34 +21,43 @@
<ContentPage.Content>
<ScrollView>
<StackLayout BoxView.Color="{StaticResource ContentBackgroundColor}">
<Label Text="{Binding UserName}" Style="{StaticResource LabelPageHeadingStyle}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" XAlign="Center"
></Label>
<Image Source="{Binding Avatar}"
VisualElement.HeightRequest="{StaticResource BigUserAvatarSize}" />
<views:RatingView Rating="{Binding Rating, Mode=TwoWay}" x:Name="ratingView" />
<Button Text="{Binding PerformerStatus}" Clicked="OnViewPerformerStatus" />
<Button Text="{Binding UserQueries}" Clicked="OnViewUserQueries"
VisualElement.IsVisible="{Binding UserIsPro}"/>
<lc:GesturesContentView>
<Image Source="{Binding Avatar}" HeightRequest="{StaticResource BigUserAvatarSize}" >
<lb:Gestures.Interests>
<lb:GestureCollection>
<lb:GestureInterest GestureType="SingleTap" GestureCommand="{Binding AvatarCommand}" GestureParameter="{Binding Ready}"/>
<lb:GestureInterest GestureType="LongPress" GestureCommand="{Binding AvatarCommand}" GestureParameter="{Binding Ready}"/>
<lb:GestureInterest GestureType="Swipe" Direction="Left" GestureCommand="{Binding AvatarCommand}" GestureParameter="{Binding Ready}"/>
<lb:GestureInterest GestureType="Swipe" Direction="Right" GestureCommand="{Binding AvatarCommand}" GestureParameter="{Binding Ready}"/>
</lb:GestureCollection>
</lb:Gestures.Interests>
</Image>
</lc:GesturesContentView>
<Button Text="{Binding UserName}" Clicked="OnRefreshQuery" />
<Button Text="{Binding UserFilesLabel}" Clicked="OnManageFiles" />
<StackLayout Orientation="Horizontal" VisualElement.IsVisible="{Binding HaveAnUser}">
<Button Text="{Binding UserFilesText}" Clicked="OnManageFiles" />
<StackLayout VisualElement.IsVisible="{Binding HaveAnUser}">
<Button Text="{Binding PerformerStatus}" Clicked="OnViewPerformerStatus" />
<Button Text="{Binding UserQueries}" Clicked="OnViewUserQueries"
VisualElement.IsVisible="{Binding IsAPerformer}"/>
<StackLayout Orientation="Horizontal">
<Label Text="Recevoir les notifications push" StyleClass="Header" />
<Switch IsToggled="{Binding ReceivePushNotifications, Mode=TwoWay}"
HorizontalOptions="End" />
</StackLayout>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Utiliser ma position" StyleClass="Header" />
<Switch HorizontalOptions="End" IsToggled="{Binding AllowUseMyPosition, Mode=TwoWay}"/>
</StackLayout>
<StackLayout Orientation="Horizontal" VerticalOptions="Start"
VisualElement.IsVisible="{Binding UserIsPro}" >
<Label Text="Ne recevoir de demande de devis que de la part de professionnels uniquement" />
<Switch HorizontalOptions="End" IsToggled="{Binding AllowProBookingOnly, Mode=TwoWay}"/>
<StackLayout VisualElement.IsVisible="{Binding IsAPerformer}" >
<Label Text="{x:Static local:Strings.Profprof}" Style="{StaticResource LabelStyle}"/>
<views:RatingView Rating="{Binding Rating, Mode=TwoWay}" x:Name="ratingView" />
<StackLayout Orientation="Horizontal" VerticalOptions="Start">
<Label Text="{x:Static local:Strings.ClientProRequest}" />
<Switch HorizontalOptions="End" IsToggled="{Binding AllowProBookingOnly, Mode=TwoWay}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ScrollView>

@ -5,7 +5,12 @@ using XLabs.Forms.Behaviors;
namespace BookAStar.Pages.UserProfile
{
using Data;
using Helpers;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Net.Http;
using ViewModels.UserProfile;
using XLabs.Forms.Controls;
public partial class DashboardPage : ContentPage
{
@ -18,11 +23,43 @@ namespace BookAStar.Pages.UserProfile
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
// Assert ((DashboardViewModel)BindingContext!=null)
((DashboardViewModel)BindingContext).UserNameGesture
= new RelayGesture( (gesture,arg) => {
ShowPage<AccountChooserPage>(null, true);
});
}
public async void OnRefreshQuery(object sender, EventArgs e)
{
// TODO disable the button when current user is not registered
if (MainSettings.CurrentUser==null)
ShowPage<AccountChooserPage>(null, true);
else
{
IsBusy = true;
using (var client = UserHelpers.CreateJsonClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Get, Constants.UserInfoUrl))
{
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
string userJson = await response.Content.ReadAsStringAsync();
JObject jactiveUser = JObject.Parse(userJson);
var username = jactiveUser["UserName"].Value<string>();
var roles = jactiveUser["Roles"].Values<string>().ToList();
var emails = jactiveUser["EMails"].Values<string>().ToList();
var avatar = jactiveUser["Avatar"].Value<string>();
var address = jactiveUser["Avatar"].Value<string>();
var me = MainSettings.CurrentUser;
me.Address = address;
me.Avatar = avatar;
me.EMails = emails;
me.UserName = username;
me.Roles = roles;
MainSettings.SaveUser(me);
}
}
}
IsBusy = false;
}
}
public void OnManageFiles(object sender, EventArgs e)

@ -12,7 +12,7 @@ namespace BookAStar.Pages.UserProfile
public UserFiles()
{
InitializeComponent();
var current = DataManager.Current.RemoteFiles.CurrentItem;
var current = DataManager.Instance.RemoteFiles.CurrentItem;
if (current != null)
BindingContext = new DirectoryInfoViewModel(current);
else BindingContext = new DirectoryInfoViewModel
@ -33,8 +33,8 @@ namespace BookAStar.Pages.UserProfile
if (model != null)
model.RefreshCommand = new Command(() =>
{
DataManager.Current.RemoteFiles.Execute(null);
var item = DataManager.Current.RemoteFiles.CurrentItem;
DataManager.Instance.RemoteFiles.Execute(null);
var item = DataManager.Instance.RemoteFiles.CurrentItem;
if (item != null)
model.InnerModel = item;
// this.dirlist.EndRefresh();

@ -43,7 +43,7 @@
<StackLayout VisualElement.IsVisible="{Binding IsAPerformer}">
<StackLayout Orientation="Horizontal" VerticalOptions="Start"
VisualElement.IsVisible="{Binding UserIsPro}" >
VisualElement.IsVisible="{Binding IsAPerformer}" >
<Label Text="Ne recevoir de demande de devis que de la part de professionnels uniquement" />
<Switch HorizontalOptions="End" IsToggled="{Binding AllowProBookingOnly, Mode=TwoWay}"/>
</StackLayout>

@ -1,5 +1,7 @@

using BookAStar.ViewModels.UserProfile;
using Plugin.Media;
using Plugin.Media.Abstractions;
using System;
using Xamarin.Forms;
@ -13,10 +15,35 @@ namespace BookAStar.Pages.UserProfile
InitializeComponent();
AvatarButton.Clicked += AvatarButton_Clicked;
}
private void AvatarButton_Clicked (object sender, EventArgs e)
public UserProfilePage(UserProfileViewModel model)
{
throw new NotImplementedException();
InitializeComponent();
AvatarButton.Clicked += AvatarButton_Clicked;
BindingContext = model;
}
private async void AvatarButton_Clicked (object sender, EventArgs e)
{
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await DisplayAlert("No Camera", ":( No camera avaialble.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
{
Directory = "Avatars",
Name = "me.jpg"
});
if (file == null)
return;
// ImageSource.FromFile(file.Path);
/* ImageSource.FromStream(() =>
{
var stream = file.GetStream();
file.Dispose();
return stream;
}); */
}
public void OnManageFiles(object sender, EventArgs e)

@ -1,6 +1,4 @@
// Helpers/Settings.cs
using BookAStar.Model;
using BookAStar.Model.Auth.Account;
using Newtonsoft.Json;
using Plugin.Settings;
using Plugin.Settings.Abstractions;
@ -8,11 +6,12 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace BookAStar
{
using Model.Social;
using Model.Auth.Account;
using Data;
/// <summary>
/// This is the Settings static class that can be used in your Core solution or in any
@ -32,7 +31,7 @@ namespace BookAStar
#region Setting Constants
public static readonly string SettingsDefault = string.Empty;
public static readonly string EntityDataSettingsPrefix = "Ed";
public static readonly string EntityDataSettingsPrefix = Constants.YavscApiUrl;
private const string userNameKey = "user_id";
private const string PushNotificationsKey = "pushNotifs";
private const string AllowGPSUsageKey = "allowGPSUsage";
@ -75,23 +74,13 @@ namespace BookAStar
}
}
public const string bookQueryNotificationsKey = "BookQueryNotifications";
public static BookQueryData[] GetBookQueryNotifications()
public static BookQuery[] GetBookQueryNotifications()
{
// Do not return any null List
var json = AppSettings.GetValueOrDefault<string>(bookQueryNotificationsKey);
if (!string.IsNullOrWhiteSpace(json))
return JsonConvert.DeserializeObject<BookQueryData[]>(json);
return new BookQueryData[] { };
}
public static BookQueryData[] AddBookQueryNotification(BookQueryData query)
{
var existing = new List<BookQueryData>(GetBookQueryNotifications());
existing.Add(query);
var result = existing.ToArray();
AppSettings.AddOrUpdateValue(bookQueryNotificationsKey,
JsonConvert.SerializeObject(result));
return result;
return JsonConvert.DeserializeObject<BookQuery[]>(json);
return new BookQuery[] { };
}
public static string GoogleRegId
@ -104,7 +93,7 @@ namespace BookAStar
// Inform the server of it.
if (oldregid != value)
{
App.CurrentApp.PostDeviceInfo();
App.PostDeviceInfo();
}
}
get { return AppSettings.GetValueOrDefault<string>(GoogleRegIdKey); }
@ -155,15 +144,15 @@ namespace BookAStar
{
if (olduserid != value.Id)
{
App.CurrentApp.PostDeviceInfo();
App.PostDeviceInfo();
if (UserChanged!=null)
UserChanged.Invoke(App.CurrentApp, new EventArgs());
UserChanged.Invoke(App.Current, new EventArgs());
}
}
else if (olduserid != null)
{
if (UserChanged != null)
UserChanged.Invoke(App.CurrentApp, new EventArgs());
UserChanged.Invoke(App.Current, new EventArgs());
// TODO else Unregister this device
}
}
@ -171,21 +160,34 @@ namespace BookAStar
public static event EventHandler<EventArgs> UserChanged;
/// <summary>
/// Saves the given user account in the account list.
/// An existent presenting the same Id will be dropped.
/// </summary>
/// <param name="user"></param>
public static void SaveUser(User user)
{
var existent = AccountList.FirstOrDefault(u => u.UserName == user.UserName);
var existent = AccountList.FirstOrDefault(u => u.Id == user.Id);
if (existent != null)
AccountList.Remove(existent);
AccountList.Add(user);
var json = JsonConvert.SerializeObject(AccountList.ToArray());
AppSettings.AddOrUpdateValue(UserListsKey, json);
}
/// <summary>
/// Gets an account connection info, given its name
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
public static User GetUser(string username)
{
return AccountList.FirstOrDefault(a => a.UserName == username);
}
// FIXME real time usage
/// <summary>
/// Enables/disables push notifications
/// </summary>
public static bool PushNotifications
{
get
@ -203,6 +205,10 @@ namespace BookAStar
}
}
// FIXME real time usage
/// <summary>
/// Enables/disables GPS usage
/// </summary>
public static bool AllowGPSUsage
{
get
@ -218,7 +224,11 @@ namespace BookAStar
value);
}
}
// TODO make it a server side user's parameter
/// <summary>
/// Only allow professionals to ask for user's services
/// </summary>
public static bool AllowProBookingOnly
{
get

@ -61,6 +61,60 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Une execution est déjà en cours.
/// </summary>
public static string AlreadyExecuting {
get {
return ResourceManager.GetString("AlreadyExecuting", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Bloquer cet utilisateur.
/// </summary>
public static string BlockThisUser {
get {
return ResourceManager.GetString("BlockThisUser", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Annuler la validation.
/// </summary>
public static string CancelValidation {
get {
return ResourceManager.GetString("CancelValidation", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Restreindre la demande aux clients professionnels.
/// </summary>
public static string ClientProRequest {
get {
return ResourceManager.GetString("ClientProRequest", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à La création a échoué, contenu envoyé: {stringContent} @ Uri: {ControllerUri.AbsoluteUri}: Erreur : {errcontent}.
/// </summary>
public static string CreationFailed {
get {
return ResourceManager.GetString("CreationFailed", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Décliner cette proposition (envoyer un refus, et archiver la demande).
/// </summary>
public static string DeclineQuery {
get {
return ResourceManager.GetString("DeclineQuery", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Faire un devis.
/// </summary>
@ -70,6 +124,15 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Valider le devis sans signer.
/// </summary>
public static string DonotsignEstimate {
get {
return ResourceManager.GetString("DonotsignEstimate", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Editer le devis.
/// </summary>
@ -79,6 +142,15 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Erreur d&apos;accès aux données distantes.
/// </summary>
public static string ENoRemoteEntity {
get {
return ResourceManager.GetString("ENoRemoteEntity", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Star.
/// </summary>
@ -97,6 +169,51 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Géographiquement proche.
/// </summary>
public static string GeographicalyNear {
get {
return ResourceManager.GetString("GeographicalyNear", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Valeur invalide.
/// </summary>
public static string InvalidValue {
get {
return ResourceManager.GetString("InvalidValue", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Veuillez saisir une valeur entre {0} et {1}..
/// </summary>
public static string MinMaxIntError {
get {
return ResourceManager.GetString("MinMaxIntError", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Veuillez saisir une description (entre {0} et {1} caractères)..
/// </summary>
public static string MinMaxStringValidationError {
get {
return ResourceManager.GetString("MinMaxStringValidationError", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Pas de description.
/// </summary>
public static string NoDescription {
get {
return ResourceManager.GetString("NoDescription", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Un artiste.
/// </summary>
@ -115,6 +232,60 @@ namespace BookAStar {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Opération en cours.
/// </summary>
public static string OperationPending {
get {
return ResourceManager.GetString("OperationPending", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à oups..
/// </summary>
public static string oups {
get {
return ResourceManager.GetString("oups", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Profile professionnel.
/// </summary>
public static string Profprof {
get {
return ResourceManager.GetString("Profprof", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Rechercher un artiste.
/// </summary>
public static string SearchForAPro {
get {
return ResourceManager.GetString("SearchForAPro", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Signer.
/// </summary>
public static string Sign {
get {
return ResourceManager.GetString("Sign", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Signer le devis?.
/// </summary>
public static string SignOrNot {
get {
return ResourceManager.GetString("SignOrNot", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Incontournable.
/// </summary>
@ -134,12 +305,48 @@ namespace BookAStar {
}
/// <summary>
/// Recherche une chaîne localisée semblable à Voir le devis.
/// Recherche une chaîne localisée semblable à La mise à jour a échoué..
/// </summary>
public static string UpdateFailed {
get {
return ResourceManager.GetString("UpdateFailed", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Comptes utilisateur.
/// </summary>
public static string UserAccounts {
get {
return ResourceManager.GetString("UserAccounts", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Voir les devis validés.
/// </summary>
public static string ViewEstimate {
get {
return ResourceManager.GetString("ViewEstimate", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Vos fichiers .
/// </summary>
public static string YourFiles {
get {
return ResourceManager.GetString("YourFiles", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Votre texte a été taillé car il était trop long..
/// </summary>
public static string YourTextWasTooLong {
get {
return ResourceManager.GetString("YourTextWasTooLong", resourceCulture);
}
}
}
}

@ -117,12 +117,27 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AlreadyExecuting" xml:space="preserve">
<value>Une execution est déjà en cours</value>
</data>
<data name="CancelValidation" xml:space="preserve">
<value>Annuler la validation</value>
</data>
<data name="CreationFailed" xml:space="preserve">
<value>La création a échoué, contenu envoyé: {stringContent} @ Uri: {ControllerUri.AbsoluteUri}: Erreur : {errcontent}</value>
</data>
<data name="DoEstimate" xml:space="preserve">
<value>Faire un devis</value>
</data>
<data name="DonotsignEstimate" xml:space="preserve">
<value>Valider le devis sans signer</value>
</data>
<data name="EditEstimate" xml:space="preserve">
<value>Editer le devis</value>
</data>
<data name="ENoRemoteEntity" xml:space="preserve">
<value>Erreur d'accès aux données distantes</value>
</data>
<data name="FiveStars" xml:space="preserve">
<value>Star</value>
<comment>Da star, da one</comment>
@ -137,6 +152,18 @@
<data name="OneStar" xml:space="preserve">
<value>Étoile montante</value>
</data>
<data name="OperationPending" xml:space="preserve">
<value>Opération en cours</value>
</data>
<data name="oups" xml:space="preserve">
<value>oups.</value>
</data>
<data name="Sign" xml:space="preserve">
<value>Signer</value>
</data>
<data name="SignOrNot" xml:space="preserve">
<value>Signer le devis?</value>
</data>
<data name="ThreeStars" xml:space="preserve">
<value>Incontournable</value>
</data>
@ -144,6 +171,48 @@
<value>À ne manquer sous aucun prétexte</value>
</data>
<data name="ViewEstimate" xml:space="preserve">
<value>Voir le devis</value>
<value>Voir les devis validés</value>
</data>
<data name="UpdateFailed" xml:space="preserve">
<value>La mise à jour a échoué.</value>
</data>
<data name="MinMaxStringValidationError" xml:space="preserve">
<value>Veuillez saisir une description (entre {0} et {1} caractères).</value>
</data>
<data name="YourTextWasTooLong" xml:space="preserve">
<value>Votre texte a été taillé car il était trop long.</value>
</data>
<data name="NoDescription" xml:space="preserve">
<value>Pas de description</value>
</data>
<data name="InvalidValue" xml:space="preserve">
<value>Valeur invalide</value>
</data>
<data name="MinMaxIntError" xml:space="preserve">
<value>Veuillez saisir une valeur entre {0} et {1}.</value>
</data>
<data name="YourFiles" xml:space="preserve">
<value>Vos fichiers </value>
</data>
<data name="Profprof" xml:space="preserve">
<value>Profile professionnel</value>
</data>
<data name="ClientProRequest" xml:space="preserve">
<value>Restreindre la demande aux clients professionnels</value>
</data>
<data name="UserAccounts" xml:space="preserve">
<value>Comptes utilisateur</value>
</data>
<data name="BlockThisUser" xml:space="preserve">
<value>Bloquer cet utilisateur</value>
</data>
<data name="DeclineQuery" xml:space="preserve">
<value>Décliner cette proposition (envoyer un refus, et archiver la demande)</value>
</data>
<data name="GeographicalyNear" xml:space="preserve">
<value>Géographiquement proche</value>
</data>
<data name="SearchForAPro" xml:space="preserve">
<value>Rechercher un artiste</value>
</data>
</root>

@ -1,29 +0,0 @@
using BookAStar.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XLabs.Forms.Mvvm;
namespace BookAStar.ViewModels
{
/// <summary>
/// Used to make the DataManager know how
/// to sync local and remote data
/// </summary>
public class EditingViewModel: ViewModel
{
private LocalState state;
public LocalState State {
get
{
return state;
}
set
{
base.SetProperty<LocalState>(ref state, value);
}
}
}
}

@ -1,25 +1,54 @@
using BookAStar.Attributes;
using BookAStar.Interfaces;
using BookAStar.Model.Workflow;
using BookAStar.ViewModels.Validation;
using System;
using System.Globalization;
using System.Windows.Input;
using System.ComponentModel;
namespace BookAStar.ViewModels.EstimateAndBilling
{
public class BillingLineViewModel : EditingViewModel, IBillingLine
public class BillingLineViewModel : EditingViewModel<BillingLine>, IBillingLine
{
BillingLine data;
public ICommand RemoveCommand { get; set; }
public ICommand ValidateCommand { set; get; }
public BillingLineViewModel(BillingLine data): base(data)
{
CheckCommand = new Action<BillingLine, ModelState>(
(l,s) => {
if (string.IsNullOrWhiteSpace(l.Description))
{
s.AddError("Description",Strings.NoDescription);
}
if (l.UnitaryCost < 0) { s.AddError("UnitaryCost", Strings.InvalidValue); }
if (l.Count < 0) { s.AddError("Count", Strings.InvalidValue); }
});
SyncData();
}
public BillingLineViewModel(BillingLine data)
private void SyncData()
{
this.data = data ?? new BillingLine();
// sets durationValue & durationUnit
count = data.Count;
description = data.Description;
if (Data != null)
{
// set durationValue, durationUnit
Duration = Data.Duration;
// other redondant representation
count = Data.Count;
description = Data.Description;
unitaryCostText = Data.UnitaryCost.ToString("G", CultureInfo.InvariantCulture);
}
CheckCommand(Data, ViewModelState);
}
Duration = data.Duration;
unitaryCostText = data.UnitaryCost.ToString("G", CultureInfo.InvariantCulture);
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName=="Data")
{
SyncData();
}
}
private int count;
@ -33,7 +62,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<int>(ref count, value);
data.Count = count;
Data.Count = count;
}
}
private string description;
@ -47,9 +76,10 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<string>(ref description, value);
data.Description = description;
Data.Description = description;
}
}
decimal unitaryCost;
public decimal UnitaryCost
{
@ -61,7 +91,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<decimal>(ref unitaryCost, value);
data.UnitaryCost = unitaryCost;
Data.UnitaryCost = unitaryCost;
}
}
@ -76,7 +106,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
set
{
SetProperty<int>(ref durationValue, value, "DurationValue");
data.Duration = this.Duration;
Data.Duration = this.Duration;
}
}
@ -99,7 +129,7 @@ pour décrire la quantité de travail associée à ce type de service")]
set
{
SetProperty<DurationUnits>(ref durationUnit, value, "DurationUnit");
data.Duration = this.Duration;
Data.Duration = this.Duration;
}
}
@ -125,7 +155,6 @@ pour décrire la quantité de travail associée à ce type de service")]
}
}
public ICommand ValidateCommand { set; get; }
public TimeSpan Duration
{

@ -5,18 +5,24 @@ namespace BookAStar.ViewModels.EstimateAndBilling
{
using Data;
using Model;
using System.Linq;
public class BookQueriesViewModel : XLabs.Forms.Mvvm.ViewModel
{
public BookQueriesViewModel()
{
queries = new ObservableCollection<BookQueryViewModel>
(DataManager.Instance.BookQueries.Select(
q =>
new BookQueryViewModel(q)));
}
private ObservableCollection<BookQueryViewModel> queries;
public ObservableCollection<BookQueryData> Queries
public ObservableCollection<BookQueryViewModel> Queries
{
get
{
return DataManager.Current.BookQueries;
return queries;
}
}

@ -6,20 +6,22 @@ using XLabs.Forms.Mvvm;
namespace BookAStar.ViewModels.EstimateAndBilling
{
using Data;
using Helpers;
using Interfaces;
using Model;
using Model.Social;
using Model.Workflow;
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.Forms;
class BookQueryViewModel : ViewModel, IBookQueryData
public class BookQueryViewModel : ViewModel, IBookQueryData
{
public BookQueryViewModel()
{
}
public BookQueryViewModel(BookQueryData data)
public BookQueryViewModel(BookQuery data)
{
Debug.Assert(data != null);
Client=data.Client;
@ -28,19 +30,33 @@ namespace BookAStar.ViewModels.EstimateAndBilling
Previsionnal = data.Previsionnal;
Id = data.Id;
estimates = new ObservableCollection<Estimate>(
DataManager.Current.Estimates.Where(
DataManager.Instance.Estimates.Where(
e => e.Query.Id == Id
));
this.data = data;
}
private BookQueryData data;
public BookQueryData Data {
private BookQuery data;
public BookQuery Data {
get
{
return data;
}
}
public ClientProviderInfo Client { get; set; }
public ImageSource Avatar
{
get
{
return UserHelpers.Avatar(Client.Avatar);
}
}
public ImageSource SmallAvatar
{
get
{
return UserHelpers.SmallAvatar(Client.Avatar, Client.UserName);
}
}
public Location Location { get; set; }
public long Id { get; set; }
public DateTime EventDate { get; set; }
@ -49,7 +65,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
{
get
{
return DataManager.Current.EstimationCache.LocalGet(this.Id);
return DataManager.Instance.EstimationCache.LocalGet(this.Id);
}
}
private ObservableCollection<Estimate> estimates;

@ -1,55 +1,78 @@
using System.Collections.Generic;
using BookAStar.Model.Workflow;
using System.Collections.ObjectModel;
using BookAStar.Model;
using Xamarin.Forms;
using BookAStar.Data;
using Newtonsoft.Json;
using System.Linq;
using System.ComponentModel;
namespace BookAStar.ViewModels.EstimateAndBilling
{
public class EditEstimateViewModel : EditingViewModel
using Model;
using Model.Workflow;
using Model.Social;
using Validation;
public class EditEstimateViewModel : EditingViewModel<Estimate>
{
/// <summary>
/// For deserialization
/// </summary>
public EditEstimateViewModel()
{
}
/// <summary>
/// Builds a new view model on estimate,
/// sets <c>Data</c> with given value parameter
/// </summary>
/// <param name="data"></param>
/// <param name="localState"></param>
public EditEstimateViewModel(Estimate data)
public EditEstimateViewModel(Estimate data) : base(data)
{
SyncData();
}
public override void OnViewAppearing()
{
base.OnViewAppearing();
SyncData();
}
/// <summary>
/// Called to synchronyze this view on target model,
/// at accepting a new representation for this model
/// </summary>
private void SyncData()
{
Data = data;
if (Data.AttachedFiles == null) Data.AttachedFiles = new List<string>();
if (Data.AttachedGraphics == null) Data.AttachedGraphics = new List<string>();
if (Data.Bill == null) Data.Bill = new List<BillingLine>();
AttachedFiles = new ObservableCollection<string>(Data.AttachedFiles);
AttachedGraphicList = new ObservableCollection<string>(Data.AttachedGraphics);
Bill = new ObservableCollection<BillingLineViewModel>(Data.Bill.Select(
l => new BillingLineViewModel(l)
));
Bill.CollectionChanged += Bill_CollectionChanged;
Title = Data.Title;
Description = Data.Description;
NotifyPropertyChanged("FormattedTotal");
NotifyPropertyChanged("Query");
NotifyPropertyChanged("CLient");
NotifyPropertyChanged("ModelState");
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName.StartsWith("Data"))
{
SyncData();
}
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Bill_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Data.Bill = new List<BillingLine>( Bill );
Data.Bill = Bill.Select(l => l.Data).ToList();
NotifyPropertyChanged("FormattedTotal");
NotifyPropertyChanged("Bill");
NotifyPropertyChanged("ViewModelState");
}
private Estimate data;
public Estimate Data { get { return data; } set {
SetProperty<Estimate>(ref data, value);
if (data.AttachedFiles == null) data.AttachedFiles = new List<string>();
if (data.AttachedGraphics == null) data.AttachedGraphics = new List<string>();
if (data.Bill == null) data.Bill = new List<BillingLine>();
AttachedFiles = new ObservableCollection<string>(data.AttachedFiles);
AttachedGraphicList = new ObservableCollection<string>(data.AttachedGraphics);
Bill = new ObservableCollection<BillingLine>(data.Bill);
Bill.CollectionChanged += Bill_CollectionChanged;
Title = Data.Title;
Description = Data.Description;
NotifyPropertyChanged("FormattedTotal");
NotifyPropertyChanged("Query");
NotifyPropertyChanged("CLient");
} }
[JsonIgnore]
public ObservableCollection<string> AttachedFiles
@ -64,7 +87,7 @@ namespace BookAStar.ViewModels.EstimateAndBilling
}
[JsonIgnore]
public ObservableCollection<BillingLine> Bill
public ObservableCollection<BillingLineViewModel> Bill
{
get; protected set;
}
@ -107,25 +130,27 @@ namespace BookAStar.ViewModels.EstimateAndBilling
public ClientProviderInfo Client { get { return Data.Client; } }
[JsonIgnore]
public BookQueryData Query { get { return Data.Query; } }
public BookQuery Query { get { return Data.Query; } }
[JsonIgnore]
public FormattedString FormattedTotal
{
get
{
OnPlatform<Font> lfs = (OnPlatform<Font>)App.Current.Resources["LargeFontSize"];
OnPlatform<Color> etc = (OnPlatform<Color>)App.Current.Resources["EmphasisTextColor"];
/*
OnPlatform<Font> lfs = (OnPlatform<Font>)App.Current.Resources["MediumFontSize"];
*/
OnPlatform<double> mfs = (OnPlatform < double > ) App.Current.Resources["MediumFontSize"];
Color etc = (Color) App.Current.Resources["EmphasisTextColor"];
return new FormattedString
{
Spans = {
new Span { Text = "Total TTC: " },
new Span { Text = Data.Total.ToString(),
ForegroundColor = etc.Android ,
FontSize = (double) lfs.Android.FontSize },
new Span { Text = "€", FontSize = (double) lfs.Android.FontSize }
ForegroundColor = etc,
FontSize = mfs },
new Span { Text = "€", FontSize = mfs }
}
};
}

@ -0,0 +1,14 @@
using XLabs.Forms.Mvvm;
namespace BookAStar.ViewModels
{
using EstimateAndBilling;
using UserProfile;
public class HomeViewModel : ViewModel
{
public BookQueriesViewModel BookQueries { get; set; }
public UserProfileViewModel UserProfile { get; set; }
}
}

@ -0,0 +1,46 @@
using BookAStar.Data;
using BookAStar.Model.Social.Chat;
using BookAStar.Model.Social.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace BookAStar.ViewModels.Messaging
{
public class ChatUserCollection : RemoteEntityRO<ChatUserInfo, string>
{
public ChatUserCollection() : base ("chat/users", u=>u.UserId)
{
}
public void OnPrivateMessage(ChatMessage msg)
{
var sender = this.FirstOrDefault(user => user.UserName == msg.SenderId);
if (sender != null)
{
sender.PrivateMessages.Add(msg);
} else
{
// TODO alert? or else get chat user info
// or else just display this message ...
}
}
public override void Merge(ChatUserInfo item)
{
var key = GetKey(item);
var existent = this.FirstOrDefault(u => u.UserId == key);
if (existent != null) {
existent.UserName = item.UserName;
existent.Roles = item.Roles;
existent.Avatar = item.Avatar;
existent.Connections = item.Connections;
}
else Add(item);
}
}
}

@ -0,0 +1,196 @@

using BookAStar.Helpers;
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.Forms;
using XLabs.Forms.Mvvm;
using YavscLib;
using System;
using Newtonsoft.Json;
using BookAStar.Model.Social.Messaging;
namespace BookAStar.Model.Social.Chat
{
public class ChatUserInfo : ViewModel, IChatUserInfo
{
public ChatUserInfo()
{
PrivateMessages.CollectionChanged += PrivateMessages_CollectionChanged;
}
private void PrivateMessages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Unread");
}
public string avatar;
public string Avatar
{
get
{
return avatar;
}
set
{
var newSource = UserHelpers.Avatar(value);
SetProperty<string>(ref avatar, value);
SetProperty<ImageSource>(ref avatarSource, newSource, "AvatarSource");
}
}
ImageSource avatarSource;
[JsonIgnore]
public ImageSource AvatarSource
{
get
{
return avatarSource;
}
}
public Connection [] Connections
{
get
{
return ObservableConnections?.ToArray();
}
set
{
ObservableConnections = new ObservableCollection<Connection>(value);
}
}
ObservableCollection<Connection> connections;
[JsonIgnore]
public ObservableCollection<Connection> ObservableConnections
{
get
{
return connections;
}
set
{
SetProperty<ObservableCollection<Connection>>(ref connections, value);
}
}
string[] roles;
public string[] Roles
{
get
{
return roles;
}
set
{
SetProperty<string[]>(ref roles, value);
NotifyPropertyChanged("RolesAsAString");
}
}
[JsonIgnore]
public string RolesAsAString
{
get
{
return Roles == null? "": string.Join(", ", Roles);
}
}
string userId;
public string UserId
{
get
{
return userId;
}
set
{
SetProperty<string>(ref userId, value);
}
}
string userName;
public string UserName
{
get
{
return userName;
}
set
{
SetProperty<string>(ref userName, value);
}
}
public bool IsConnected { get
{
return Connections.Length > 0;
} }
[JsonIgnore]
IConnection[] IChatUserInfo.Connections
{
get
{
return Connections;
}
set
{
throw new NotImplementedException();
}
}
ObservableCollection<ChatMessage> privateMessages = new ObservableCollection<ChatMessage>();
[JsonIgnore]
public ObservableCollection<ChatMessage> PrivateMessages
{
get
{
return privateMessages;
}
}
[JsonIgnore]
public bool Unread
{
get
{
return PrivateMessages==null?false: PrivateMessages.Any(
m => !m.Read);
}
}
[JsonIgnore]
public ImageSource MessagesBadge
{
get
{
return Unread ? ImageSource.FromResource("BookAStar.Images.Chat.talk.png") :null;
}
}
public void OnConnected(string cxId)
{
// We do assume this cxId dosn't already exist in this list.
var cx = new Connection { ConnectionId = cxId, Connected = true };
if (ObservableConnections == null)
ObservableConnections = new ObservableCollection<Connection>();
ObservableConnections.Add(cx);
if (this.ObservableConnections.Count == 1)
NotifyPropertyChanged("IsConnected");
}
public void OnDisconnected(string cxId)
{
var existentcx = Connections.FirstOrDefault(cx => cx.ConnectionId == cxId);
if (existentcx != null)
{
this.ObservableConnections.Remove(existentcx);
if (this.ObservableConnections.Count == 0)
NotifyPropertyChanged("IsConnected");
}
}
}
}

@ -7,27 +7,15 @@ using XLabs.Forms.Mvvm;
namespace BookAStar.ViewModels.Messaging
{
using Data;
using Model;
using Model.Social.Chat;
using Model.Social.Messaging;
class ChatViewModel: ViewModel
public class ChatViewModel: ViewModel
{
public ObservableCollection<ChatMessage> Messages { get; set; }
public ObservableCollection<ChatMessage> Notifs { get; set; }
public ObservableCollection<ChatMessage> PVs { get; set; }
public ObservableCollection<ClientProviderInfo> Contacts { get; set; }
private string chatUser;
public string ChatUser
{
get
{
return chatUser;
}
set
{
SetProperty<string>(ref chatUser, value);
}
}
public ChatUserCollection ChatUsers { get; set; }
private ConnectionState state;
public ConnectionState State
{
@ -40,8 +28,7 @@ namespace BookAStar.ViewModels.Messaging
MainSettings.UserChanged += MainSettings_UserChanged;
Messages = new ObservableCollection<ChatMessage>();
Notifs = new ObservableCollection<ChatMessage>();
PVs = DataManager.Current.PrivateMessages;
Contacts = DataManager.Current.Contacts;
ChatUsers = DataManager.Instance.ChatUsers;
App.ChatHubProxy.On<string, string>("addMessage", (n, m) =>
{
Messages.Add(new ChatMessage
@ -54,45 +41,53 @@ namespace BookAStar.ViewModels.Messaging
App.ChatHubProxy.On<string, string, string>("notify", (eventId, cxId, userName) =>
{
var msg = new ChatMessage
{
Message = eventId,
SenderId = userName,
Date = DateTime.Now
};
// TODO make admin possible
// by assigning a server side username to anonymous.
// From now, don't log anonymous
if (!string.IsNullOrEmpty(userName))
if (string.IsNullOrEmpty(userName))
{
Notifs.Add(new ChatMessage
{
Message = eventId,
SenderId = userName,
Date = DateTime.Now
});
if (eventId == "connected")
OnUserConnected(cxId, userName);
else if (eventId == "disconnected")
OnUserDisconnected(userName);
msg.SenderId = $"({cxId})";
}
Notifs.Add(msg);
if (eventId == "connected")
OnUserConnected(cxId, userName);
else if (eventId == "disconnected")
OnUserDisconnected(cxId, userName);
});
ChatUser = MainSettings.UserName;
}
private void OnUserConnected(string cxId, string userName)
{
var user = Contacts.SingleOrDefault(
var user = ChatUsers.SingleOrDefault(
c => c.UserName == userName);
if (user != null)
user.ChatHubConnectionId = cxId;
if (user == null)
{
user = new ChatUserInfo {
UserName = userName
};
ChatUsers.Add(user);
}
user.OnConnected(cxId);
}
private void OnUserDisconnected (string userName)
private void OnUserDisconnected (string cxId, string userName)
{
var user = Contacts.SingleOrDefault(
var user = ChatUsers.SingleOrDefault(
c => c.UserName == userName);
if (user != null)
user.ChatHubConnectionId = null;
if (user == null)
{
return;
}
user.OnDisconnected(cxId);
}
private void MainSettings_UserChanged(object sender, EventArgs e)
{
ChatUser = MainSettings.UserName;
}
private void ChatHubConnection_StateChanged(StateChange obj)

@ -1,6 +1,6 @@
using XLabs.Forms.Mvvm;
namespace BookAStar.Model.UI
namespace BookAStar.ViewModels
{
internal class PageState
{

Some files were not shown because too many files have changed in this diff Show More

Loading…