From 4f09c9e33047c8d118036ef9b7ef1ca3ffdccdc4 Mon Sep 17 00:00:00 2001 From: Sanders Lauture Date: Sun, 15 Oct 2017 00:38:48 -0700 Subject: [PATCH 1/9] "Finish" redesign of AddContactPage It looks kind of jank but it's way better --- .../Controls/AddContactListElement.xaml | 8 +- .../Controls/AddContactListElement.xaml.cs | 12 + Signal-Windows/Models/PhoneContact.cs | 1 + Signal-Windows/Utils.cs | 9 +- .../ViewModels/AddContactPageViewModel.cs | 285 ++++++++++++------ Signal-Windows/Views/AddContactPage.xaml | 42 +-- Signal-Windows/Views/AddContactPage.xaml.cs | 23 +- 7 files changed, 252 insertions(+), 128 deletions(-) diff --git a/Signal-Windows/Controls/AddContactListElement.xaml b/Signal-Windows/Controls/AddContactListElement.xaml index cba054e..0968212 100644 --- a/Signal-Windows/Controls/AddContactListElement.xaml +++ b/Signal-Windows/Controls/AddContactListElement.xaml @@ -13,15 +13,17 @@ + - + - - + + + diff --git a/Signal-Windows/Controls/AddContactListElement.xaml.cs b/Signal-Windows/Controls/AddContactListElement.xaml.cs index 879c9b2..7e75722 100644 --- a/Signal-Windows/Controls/AddContactListElement.xaml.cs +++ b/Signal-Windows/Controls/AddContactListElement.xaml.cs @@ -62,6 +62,17 @@ public ImageSource ContactPhoto } } + public bool _OnSignal; + public bool OnSignal + { + get { return _OnSignal; } + set + { + _OnSignal = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OnSignal))); + } + } + public PhoneContact Model { get @@ -82,6 +93,7 @@ private void AddContactListElement_DataContextChanged(FrameworkElement sender, D DisplayName = Model.Name; PhoneNumber = Model.PhoneNumber; ContactPhoto = Model.Photo; + OnSignal = Model.OnSignal; } } } diff --git a/Signal-Windows/Models/PhoneContact.cs b/Signal-Windows/Models/PhoneContact.cs index 851d096..9fbd126 100644 --- a/Signal-Windows/Models/PhoneContact.cs +++ b/Signal-Windows/Models/PhoneContact.cs @@ -13,6 +13,7 @@ public class PhoneContact public string Name { get; set; } public string PhoneNumber { get; set; } public ImageSource Photo { get; set; } + public bool OnSignal { get; set; } public AddContactListElement View; } } diff --git a/Signal-Windows/Utils.cs b/Signal-Windows/Utils.cs index d46245d..58eafb3 100644 --- a/Signal-Windows/Utils.cs +++ b/Signal-Windows/Utils.cs @@ -12,7 +12,7 @@ namespace Signal_Windows { - public class Utils + public static class Utils { public static string[] Colors = { "red", @@ -153,12 +153,17 @@ public static PageStyle GetViewStyle(Size s) } } - private string GetCountryCode() + public static string GetCountryCode() { var c = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; return GetCountryCode(c.ToUpper()); } + public static bool ContainsCaseInsensitive(this string str, string value) + { + return CultureInfo.InvariantCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0; + } + public static string GetCountryCode(string ISO3166) //https://stackoverflow.com/questions/34837436/uwp-get-country-phone-number-prefix { var dictionary = new Dictionary(); diff --git a/Signal-Windows/ViewModels/AddContactPageViewModel.cs b/Signal-Windows/ViewModels/AddContactPageViewModel.cs index c5de0bd..065f850 100644 --- a/Signal-Windows/ViewModels/AddContactPageViewModel.cs +++ b/Signal-Windows/ViewModels/AddContactPageViewModel.cs @@ -14,23 +14,48 @@ using Windows.UI.Xaml.Media.Imaging; using System.Collections.ObjectModel; using Windows.ApplicationModel.Core; +using libsignalservice; +using PhoneNumbers; +using System.Collections.Generic; +using System.Linq; +using Windows.UI.Xaml.Controls; +using System.Globalization; namespace Signal_Windows.ViewModels { public class AddContactPageViewModel : ViewModelBase { public ObservableCollection Contacts; + private List contactList; + private PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.GetInstance(); + + private string _ContactName = ""; + public string ContactName + { + get { return _ContactName; } + set { _ContactName = value; RaisePropertyChanged(nameof(ContactName)); } + } + + private string _ContactNumber = ""; + public string ContactNumber + { + get { return _ContactNumber; } + set { _ContactNumber = value; RaisePropertyChanged(nameof(ContactNumber)); } + } public AddContactPageViewModel() { Contacts = new ObservableCollection(); + contactList = new List(); } public async Task OnNavigatedTo() { + SignalServiceAccountManager accountManager = new SignalServiceAccountManager(App.ServiceUrls, App.Store.Username, App.Store.Password, (int)App.Store.DeviceId, App.USER_AGENT); ContactStore contactStore = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AllContactsReadOnly); if (contactStore != null) { + HashSet seenNumbers = new HashSet(); var contacts = await contactStore.FindContactsAsync(); foreach (var contact in contacts) { @@ -39,29 +64,50 @@ public async Task OnNavigatedTo() { if (phone.Kind == ContactPhoneKind.Mobile) { - PhoneContact phoneContact = new PhoneContact + string formattedNumber = null; + try + { + formattedNumber = ParsePhoneNumber(phone.Number); + } + catch (NumberParseException) { - Name = contact.FullName, - PhoneNumber = phone.Number - }; - if (contact.SourceDisplayPicture != null) + Debug.WriteLine($"Couldn't parse {phone.Number}"); + continue; + } + if (!seenNumbers.Contains(formattedNumber)) { - using (var stream = await contact.SourceDisplayPicture.OpenReadAsync()) + seenNumbers.Add(formattedNumber); + PhoneContact phoneContact = new PhoneContact { - BitmapImage bitmapImage = new BitmapImage(); - await bitmapImage.SetSourceAsync(stream); - phoneContact.Photo = bitmapImage; - + Name = contact.FullName, + PhoneNumber = formattedNumber, + OnSignal = false + }; + if (contact.SourceDisplayPicture != null) + { + using (var stream = await contact.SourceDisplayPicture.OpenReadAsync()) + { + BitmapImage bitmapImage = new BitmapImage(); + await bitmapImage.SetSourceAsync(stream); + phoneContact.Photo = bitmapImage; + } } + contactList.Add(phoneContact); } - this.Contacts.Add(phoneContact); - //await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - //{ - - //}); } } } + + var signalContactDetails = accountManager.getContacts(contactList.Select(c => c.PhoneNumber).ToList()); + foreach (var contact in contactList) + { + var foundContact = signalContactDetails.FirstOrDefault(c => c.getNumber() == contact.PhoneNumber); + if (foundContact != null) + { + contact.OnSignal = true; + } + Contacts.Add(contact); + } } else { @@ -73,13 +119,46 @@ public async Task OnNavigatedTo() public AddContactPage View; private bool _UIEnabled = true; - public bool UIEnabled { get { return _UIEnabled; } set { _UIEnabled = value; RaisePropertyChanged(nameof(UIEnabled)); } } + private bool _AddEnabled = false; + public bool AddEnabled + { + get { return _AddEnabled; } + set { _AddEnabled = value; RaisePropertyChanged(nameof(AddEnabled)); } + } + + private bool validName = false; + private bool ValidName + { + get { return validName; } + set + { + validName = value; + SetAddEnabled(); + } + } + + private bool validNumber = false; + private bool ValidNumber + { + get { return validNumber; } + set + { + validNumber = value; + SetAddEnabled(); + } + } + + private void SetAddEnabled() + { + AddEnabled = ValidName && ValidNumber && UIEnabled; + } + internal void BackButton_Click(object sender, BackRequestedEventArgs e) { if (UIEnabled) @@ -89,89 +168,117 @@ internal void BackButton_Click(object sender, BackRequestedEventArgs e) } } - internal async void AddButton_Click(object sender, RoutedEventArgs e) + internal void searchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + { + string text = sender.Text; + var validContacts = contactList.Where( + c => c.Name.ContainsCaseInsensitive(text) || + c.PhoneNumber.ContainsCaseInsensitive(text)); + Contacts.Clear(); + foreach (var contact in validContacts) + { + Contacts.Add(contact); + } + } + } + + internal void ContactNameTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + TextBox textBox = sender as TextBox; + string text = textBox.Text; + if (string.IsNullOrEmpty(text)) + { + ValidName = false; + } + else + { + ValidName = true; + } + } + + internal void ContactNumberTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + // TODO: See the TODO for AddButton_Click + TextBox textBox = sender as TextBox; + string text = textBox.Text; + if (string.IsNullOrEmpty(text)) + { + ValidNumber = false; + } + else + { + ValidNumber = true; + } + } + + // TODO: use the AsYouTypeFormatter when typing into the ContactNumber box so we don't have to validate here + // we need to be sure that the number here is valid + internal async Task AddButton_Click(object sender, RoutedEventArgs e) { if (UIEnabled) { - //UIEnabled = false; - //Debug.WriteLine("creating contact {0} ({1})", ContactName, ContactNumber); - //SignalContact contact = new SignalContact() - //{ - // ThreadDisplayName = ContactName, - // ThreadId = ContactNumber, - // CanReceive = true, - // AvatarFile = null, - // LastActiveTimestamp = 0, - // Draft = null, - // Color = "red", - // UnreadCount = 0 - //}; - //ContactName = ""; - //ContactNumber = ""; - //await Task.Run(() => - //{ - // SignalDBContext.InsertOrUpdateContactLocked(contact, MainPageVM); - //}); - //UIEnabled = true; + UIEnabled = false; + string formattedPhoneNumber = null; + try + { + formattedPhoneNumber = ParsePhoneNumber(ContactNumber); + } + catch (NumberParseException) + { + MessageDialog message = new MessageDialog("Please format the number in E.164 format.", "Could not format number"); + await message.ShowAsync(); + return; + } + await AddContact(ContactName, formattedPhoneNumber); + UIEnabled = true; } } - internal async void PickButton_Click(object sender, RoutedEventArgs e) + internal async Task ContactsList_ItemClick(object sender, ItemClickEventArgs e) { if (UIEnabled) { - //UIEnabled = false; - //ContactPicker contactPicker = new ContactPicker(); - //contactPicker.SelectionMode = ContactSelectionMode.Fields; - //contactPicker.DesiredFieldsWithContactFieldType.Add(ContactFieldType.PhoneNumber); - //var contact = await contactPicker.PickContactAsync(); - //if (contact != null) - //{ - // // The contact we just got doesn't contain the contact picture so we need to fetch it - // // see https://stackoverflow.com/questions/33401625/cant-get-contact-profile-images-in-uwp - // ContactStore contactStore = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AllContactsReadOnly); - // // If we do not have access to contacts the ContactStore will be null, we can still use the contact the user - // // seleceted however - // if (contactStore != null) - // { - // Contact realContact = await contactStore.GetContactAsync(contact.Id); - // if (realContact.SourceDisplayPicture != null) - // { - // using (var stream = await realContact.SourceDisplayPicture.OpenReadAsync()) - // { - // BitmapImage bitmapImage = new BitmapImage(); - // await bitmapImage.SetSourceAsync(stream); - // ContactPhoto = bitmapImage; - // } - // } - // else - // { - // ContactPhoto = null; - // } - // } - // ContactName = contact.Name; - // if (contact.Phones.Count > 0) - // { - // var originalNumber = contact.Phones[0].Number; - // if (originalNumber[0] != '+') - // { - // // need a better way of determining the "default" country code here - // var formattedPhoneNumber = PhoneNumberFormatter.FormatE164("1", originalNumber); - // if (string.IsNullOrEmpty(formattedPhoneNumber)) - // { - // ContactNumber = originalNumber; - // MessageDialog message = new MessageDialog("Please format the number in E.164 format.", "Could not format number"); - // await message.ShowAsync(); - // } - // else - // { - // ContactNumber = formattedPhoneNumber; - // } - // } - // } - //} - //UIEnabled = true; + UIEnabled = false; + PhoneContact phoneContact = e.ClickedItem as PhoneContact; + await AddContact(phoneContact.Name, phoneContact.PhoneNumber); + UIEnabled = true; } } + + private async Task AddContact(string name, string number) + { + Debug.WriteLine("creating contact {0} ({1})", name, number); + SignalContact contact = new SignalContact() + { + ThreadDisplayName = name, + ThreadId = number, + CanReceive = true, + AvatarFile = null, + LastActiveTimestamp = 0, + Draft = null, + Color = "red", + UnreadCount = 0 + }; + await Task.Run(() => + { + SignalDBContext.InsertOrUpdateContactLocked(contact, MainPageVM); + }); + } + + /// + /// Parses and formats a number in E164 format + /// + /// The number to parse + /// + /// A number in E164 format + private string ParsePhoneNumber(string number) + { + // on phone we should try to get their SIM country code + // otherwise we should try to use the user's location? + PhoneNumber phoneNumber = phoneNumberUtil.Parse(number, "US"); + return phoneNumberUtil.Format(phoneNumber, PhoneNumberFormat.E164); + } } } \ No newline at end of file diff --git a/Signal-Windows/Views/AddContactPage.xaml b/Signal-Windows/Views/AddContactPage.xaml index d01a1a6..0009af3 100644 --- a/Signal-Windows/Views/AddContactPage.xaml +++ b/Signal-Windows/Views/AddContactPage.xaml @@ -11,38 +11,26 @@ DataContext="{Binding AddContactPageInstance, Source={StaticResource Locator}}"> - + + + + + + + + + + + +