diff --git a/Chiota/Chiota.Android/Chiota.Android.csproj b/Chiota/Chiota.Android/Chiota.Android.csproj index 70229e0..4c2e8db 100644 --- a/Chiota/Chiota.Android/Chiota.Android.csproj +++ b/Chiota/Chiota.Android/Chiota.Android.csproj @@ -94,9 +94,6 @@ 2.0.2 - - 3.0.0-beta14 - @@ -114,8 +111,8 @@ - - + + diff --git a/Chiota/Chiota.Android/MainActivity.cs b/Chiota/Chiota.Android/MainActivity.cs index 3909f94..bfd01e2 100644 --- a/Chiota/Chiota.Android/MainActivity.cs +++ b/Chiota/Chiota.Android/MainActivity.cs @@ -1,24 +1,31 @@ namespace Chiota.Droid { using Android.App; + using Android.App.Job; using Android.Content; using Android.Content.PM; using Android.OS; using Chiota; using Chiota.Droid.Services; - using Chiota.Messages; - using ImageCircle.Forms.Plugin.Droid; - using Plugin.LocalNotifications; + using ImageCircle.Forms.Plugin.Droid; using Plugin.Permissions; using Xamarin.Forms; - [Activity(Label = "FlorenceApp", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = false, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] + [Activity(Label = "Chiota", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = false, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity { + private JobScheduler jobScheduler; + + public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) + { + ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults); + } + protected override void OnCreate(Bundle bundle) { TabLayoutResource = Resource.Layout.Tabbar; @@ -27,45 +34,26 @@ protected override void OnCreate(Bundle bundle) // ToolbarResource = Resource.Id.toolbar; base.OnCreate(bundle); - Xamarin.Forms.Forms.Init(this, bundle); + Forms.Init(this, bundle); + + this.jobScheduler = (JobScheduler)this.GetSystemService(JobSchedulerService); ZXing.Net.Mobile.Forms.Android.Platform.Init(); ImageCircleRenderer.Init(); - // Changes the notification icon - LocalNotificationsImplementation.NotificationIconId = Resource.Drawable.reminder; - this.LoadApplication(new App()); this.WireUpLongRunningTask(); } - // https://github.com/jamesmontemagno/MediaPlugin#important-permission-information - public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) + private void WireUpLongRunningTask() { - ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults); - PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults); - } - - public void WireUpLongRunningTask() - { - MessagingCenter.Subscribe( - this, - "StartLongRunningTaskMessage", - message => - { - var intent = new Intent(this, typeof(PeriodicTaskService)); - this.StartService(intent); - }); - - MessagingCenter.Subscribe( - this, - "StopLongRunningTaskMessage", - message => - { - var intent = new Intent(this, typeof(PeriodicTaskService)); - this.StopService(intent); - }); + var javaClass = Java.Lang.Class.FromType(typeof(PeriodicJob)); + var compName = new ComponentName(this, javaClass); + var jobInfo = new JobInfo.Builder(1, compName) + .SetRequiredNetworkType(NetworkType.Any) + .SetPeriodic(1000 * 60 * 15).Build(); + var result = this.jobScheduler.Schedule(jobInfo); } } } \ No newline at end of file diff --git a/Chiota/Chiota.Android/Properties/AndroidManifest.xml b/Chiota/Chiota.Android/Properties/AndroidManifest.xml index c7bb1ae..3f249c7 100644 --- a/Chiota/Chiota.Android/Properties/AndroidManifest.xml +++ b/Chiota/Chiota.Android/Properties/AndroidManifest.xml @@ -1,6 +1,6 @@  - - + + diff --git a/Chiota/Chiota.Android/Resources/Resource.designer.cs b/Chiota/Chiota.Android/Resources/Resource.designer.cs index 2d99489..556e0d1 100644 --- a/Chiota/Chiota.Android/Resources/Resource.designer.cs +++ b/Chiota/Chiota.Android/Resources/Resource.designer.cs @@ -26,7 +26,6 @@ static Resource() public static void UpdateIdValues() { - global::Plugin.LocalNotifications.Resource.Drawable.plugin_lc_smallicon = global::Chiota.Droid.Resource.Drawable.plugin_lc_smallicon; global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSize = global::Chiota.Droid.Resource.Attribute.actionBarSize; global::ZXing.Net.Mobile.Forms.Android.Resource.Layout.zxingscanneractivitylayout = global::Chiota.Droid.Resource.Layout.zxingscanneractivitylayout; global::ZXing.Net.Mobile.Forms.Android.Resource.Layout.zxingscannerfragmentlayout = global::Chiota.Droid.Resource.Layout.zxingscannerfragmentlayout; @@ -2355,26 +2354,26 @@ public partial class Drawable // aapt resource value: 0x7f020053 public const int avd_hide_password = 2130837587; + // aapt resource value: 0x7f020135 + public const int avd_hide_password_1 = 2130837813; + // aapt resource value: 0x7f020136 - public const int avd_hide_password_1 = 2130837814; + public const int avd_hide_password_2 = 2130837814; // aapt resource value: 0x7f020137 - public const int avd_hide_password_2 = 2130837815; - - // aapt resource value: 0x7f020138 - public const int avd_hide_password_3 = 2130837816; + public const int avd_hide_password_3 = 2130837815; // aapt resource value: 0x7f020054 public const int avd_show_password = 2130837588; + // aapt resource value: 0x7f020138 + public const int avd_show_password_1 = 2130837816; + // aapt resource value: 0x7f020139 - public const int avd_show_password_1 = 2130837817; + public const int avd_show_password_2 = 2130837817; // aapt resource value: 0x7f02013a - public const int avd_show_password_2 = 2130837818; - - // aapt resource value: 0x7f02013b - public const int avd_show_password_3 = 2130837819; + public const int avd_show_password_3 = 2130837818; // aapt resource value: 0x7f020055 public const int design_bottom_navigation_item_background = 2130837589; @@ -3021,11 +3020,11 @@ public partial class Drawable // aapt resource value: 0x7f02012b public const int notification_icon_background = 2130837803; - // aapt resource value: 0x7f020134 - public const int notification_template_icon_bg = 2130837812; + // aapt resource value: 0x7f020133 + public const int notification_template_icon_bg = 2130837811; - // aapt resource value: 0x7f020135 - public const int notification_template_icon_low_bg = 2130837813; + // aapt resource value: 0x7f020134 + public const int notification_template_icon_low_bg = 2130837812; // aapt resource value: 0x7f02012c public const int notification_tile_bg = 2130837804; @@ -3034,22 +3033,19 @@ public partial class Drawable public const int notify_panel_notification_icon_bg = 2130837805; // aapt resource value: 0x7f02012e - public const int plugin_lc_smallicon = 2130837806; + public const int plus = 2130837806; // aapt resource value: 0x7f02012f - public const int plus = 2130837807; + public const int reminder = 2130837807; // aapt resource value: 0x7f020130 - public const int reminder = 2130837808; + public const int splash_screen = 2130837808; // aapt resource value: 0x7f020131 - public const int splash_screen = 2130837809; + public const int tooltip_frame_dark = 2130837809; // aapt resource value: 0x7f020132 - public const int tooltip_frame_dark = 2130837810; - - // aapt resource value: 0x7f020133 - public const int tooltip_frame_light = 2130837811; + public const int tooltip_frame_light = 2130837810; static Drawable() { diff --git a/Chiota/Chiota.Android/Services/BackgroundReceiver.cs b/Chiota/Chiota.Android/Services/BackgroundReceiver.cs deleted file mode 100644 index 02bd003..0000000 --- a/Chiota/Chiota.Android/Services/BackgroundReceiver.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Chiota.Droid.Services -{ - using System.Linq; - - using Android.Content; - using Android.OS; - - using Chiota.Models; - using Chiota.Services; - - using Plugin.LocalNotifications; - - [BroadcastReceiver] - public class BackgroundReceiver : BroadcastReceiver - { - public override async void OnReceive(Context context, Intent intent) - { - var pm = (PowerManager)context.GetSystemService(Context.PowerService); - var wakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "BackgroundReceiver"); - wakeLock.Acquire(); - - // seed needs to be stored on device!! - var secureStorage = new SecureStorage(); - if (secureStorage.CheckUserStored()) - { - var user = await secureStorage.GetUser(); - if (user != null) - { - var contactApprovedList = await user.TangleMessenger.GetJsonMessageAsync>(user.ApprovedAddress); - - // Todo also message for a new contact request - foreach (var contact in contactApprovedList.Where(c => !c.Data.Rejected)) - { - var encryptedMessages = await user.TangleMessenger.GetMessagesAsync(contact.Data.ChatAdress); - foreach (var unused in encryptedMessages.Where(c => !c.Stored)) - { - CrossLocalNotifications.Current.Show(contact.Data.Name, "New Message from " + contact.Data.Name); - } - } - } - } - - wakeLock.Release(); - } - } -} \ No newline at end of file diff --git a/Chiota/Chiota.Android/Services/NotificationsTask.cs b/Chiota/Chiota.Android/Services/NotificationsTask.cs new file mode 100644 index 0000000..a0cb9ea --- /dev/null +++ b/Chiota/Chiota.Android/Services/NotificationsTask.cs @@ -0,0 +1,61 @@ +namespace Chiota.Droid.Services +{ + using System.Linq; + using System.Threading.Tasks; + + using Android.App; + using Android.Content; + using Android.Media; + using Android.OS; + using Android.Support.V4.App; + + using Chiota.Models; + using Chiota.Services; + using Java.Lang; + + using Resource = Resource; + + public class NotificationsTask : AsyncTask> + { + protected override async Task RunInBackground(params Void[] @params) + { + var finished = await this.LookForNewNotifications(); + return finished; + } + + private async Task LookForNewNotifications() + { + // seed needs to be stored on device!! + var secureStorage = new SecureStorage(); + if (secureStorage.CheckUserStored()) + { + var user = await secureStorage.GetUser(); + if (user != null) + { + var contactApprovedList = await user.TangleMessenger.GetJsonMessageAsync>(user.ApprovedAddress); + + // currently no messages for contact request due to perfomance issues + foreach (var contact in contactApprovedList.Where(c => !c.Data.Rejected)) + { + var encryptedMessages = await user.TangleMessenger.GetMessagesAsync(contact.Data.ChatAdress); + + foreach (var unused in encryptedMessages.Where(c => !c.Stored)) + { + var builder = new NotificationCompat.Builder(Application.Context) + .SetAutoCancel(true) // Dismiss from the notif. area when clicked + .SetContentTitle(contact.Data.Name) // Set its title + .SetContentText("New Message from " + contact.Data.Name) + .SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification)) + .SetSmallIcon(Resource.Drawable.reminder); + var notification = builder.Build(); + var notificationManager = Application.Context.GetSystemService(Context.NotificationService) as NotificationManager; + notificationManager?.Notify(0, notification); + } + } + } + } + + return true; + } + } +} diff --git a/Chiota/Chiota.Android/Services/PeriodicJob.cs b/Chiota/Chiota.Android/Services/PeriodicJob.cs new file mode 100644 index 0000000..13de8aa --- /dev/null +++ b/Chiota/Chiota.Android/Services/PeriodicJob.cs @@ -0,0 +1,63 @@ +namespace Chiota.Droid.Services +{ + using System.Threading; + using System.Threading.Tasks; + + using Android.App; + using Android.App.Job; + using Android.OS; + + using Chiota.Messages; + + using Xamarin.Forms; + + [Service(Name = "ChiotaApp.ChiotaApp.PeriodicJob", Permission = "android.permission.BIND_JOB_SERVICE")] + public class PeriodicJob : JobService + { + private CancellationTokenSource cts; + + public override bool OnStartJob(JobParameters jobParameters) + { + // Called by the operating system when starting the service. + // Start up a thread, do work on the thread. + this.cts = new CancellationTokenSource(); + + Task.Run( + () => + { + try + { + var notification = new NotificationsTask(); + notification.Execute(); + } + catch (OperationCanceledException) + { + } + finally + { + if (this.cts.IsCancellationRequested) + { + var message = new CancelledMessage(); + Device.BeginInvokeOnMainThread(() => MessagingCenter.Send(message, "CancelledMessage")); + } + } + }, + this.cts.Token); + + return true; + } + + public override bool OnStopJob(JobParameters jobParameters) + { + // Called by Android when it has to terminate a running service. + if (this.cts != null) + { + this.cts.Token.ThrowIfCancellationRequested(); + + this.cts.Cancel(); + } + + return true; // false don't reschedule the job. + } + } +} \ No newline at end of file diff --git a/Chiota/Chiota.Android/Services/PeriodicTaskService.cs b/Chiota/Chiota.Android/Services/PeriodicTaskService.cs deleted file mode 100644 index 4295962..0000000 --- a/Chiota/Chiota.Android/Services/PeriodicTaskService.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace Chiota.Droid.Services -{ - using System.Threading; - using System.Threading.Tasks; - - using Android.App; - using Android.Content; - using Android.OS; - using Android.Runtime; - - using Chiota.Messages; - - using Xamarin.Forms; - - //Todo https://blog.xamarin.com/replacing-services-jobs-android-oreo-8-0/ - [Service] - public class PeriodicTaskService : Service - { - private CancellationTokenSource cts; - - public override IBinder OnBind(Intent intent) - { - return null; - } - - public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) - { - this.cts = new CancellationTokenSource(); - - Task.Run( - () => - { - try - { - // start backgroundreceiver - var alarmIntent = new Intent(this, typeof(BackgroundReceiver)); - - var pending = PendingIntent.GetBroadcast(this, 0, alarmIntent, PendingIntentFlags.UpdateCurrent); - - var alarmManager = this.GetSystemService(AlarmService).JavaCast(); - - // alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 3 * 1000, pending); - alarmManager.SetRepeating( - AlarmType.RtcWakeup, - SystemClock.ElapsedRealtime(), - 1000 * 60 * 15, // updates every 15 minutes - pending); - } - catch (OperationCanceledException) - { - } - finally - { - if (this.cts.IsCancellationRequested) - { - var message = new CancelledMessage(); - Device.BeginInvokeOnMainThread(() => MessagingCenter.Send(message, "CancelledMessage")); - } - } - }, - this.cts.Token); - - return StartCommandResult.Sticky; - } - - public override void OnDestroy() - { - if (this.cts != null) - { - this.cts.Token.ThrowIfCancellationRequested(); - - this.cts.Cancel(); - } - - base.OnDestroy(); - } - } -} \ No newline at end of file diff --git a/Chiota/Chiota.UWP/Chiota.UWP.csproj b/Chiota/Chiota.UWP/Chiota.UWP.csproj index fce6780..04376bb 100644 --- a/Chiota/Chiota.UWP/Chiota.UWP.csproj +++ b/Chiota/Chiota.UWP/Chiota.UWP.csproj @@ -151,9 +151,6 @@ 2.0.2 - - 3.0.0-beta14 - 2.5.1.444934 diff --git a/Chiota/Chiota.UWP/MainPage.xaml.cs b/Chiota/Chiota.UWP/MainPage.xaml.cs index f50d6ec..291b205 100644 --- a/Chiota/Chiota.UWP/MainPage.xaml.cs +++ b/Chiota/Chiota.UWP/MainPage.xaml.cs @@ -1,45 +1,74 @@ namespace Chiota.UWP { using System; - using System.Linq; + using System.Diagnostics; using System.Threading.Tasks; - using Chiota.Models; - using Chiota.Services; - - using Plugin.LocalNotifications; - using Windows.ApplicationModel.Background; - using Windows.UI.Xaml; /// - /// An empty page that can be used on its own or navigated to within a Frame. + /// The main page. /// public sealed partial class MainPage { + private const string BackgroundTaskName = "MyBackgroundTask"; + public MainPage() { this.InitializeComponent(); + + // ApplicationView.PreferredLaunchViewSize = new Size(600, 850); + // ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize; + this.LoadApplication(new Chiota.App()); ZXing.Net.Mobile.Forms.WindowsUniversal.ZXingScannerViewRenderer.Init(); if (this.IsRegistered()) { - this.Deregister(); + this.Unregister(); } - this.Loaded += this.MainPageLoaded; + this.Register(); } - private async void MainPageLoaded(object sender, RoutedEventArgs e) + private async Task Register() { - await this.BackgroundTask(); + BackgroundExecutionManager.RemoveAccess(); + + await BackgroundExecutionManager.RequestAccessAsync(); + + var builder = new BackgroundTaskBuilder + { + Name = BackgroundTaskName, + TaskEntryPoint = + "UWPRuntimeComponent.BackgroundTask" + }; + + // builder.SetTrigger(new SystemTrigger(SystemTriggerType.InternetAvailable, false)); + builder.SetTrigger(new TimeTrigger(15, false)); + + var task = builder.Register(); + + task.Completed += this.Task_Completed; + + Debug.WriteLine("[PeriodicBackgroundService] Background task registered"); + } + + private async void Task_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args) + { + var settings = Windows.Storage.ApplicationData.Current.LocalSettings; + var key = BackgroundTaskName; + var message = settings.Values[key]; + + // Run your background task code here + // notification here + Debug.WriteLine("[PeriodicBackgroundService] Background task completed"); } - private void Deregister() + private void Unregister() { - var taskName = "BackgroundTask"; + var taskName = BackgroundTaskName; foreach (var task in BackgroundTaskRegistration.AllTasks) { @@ -52,7 +81,7 @@ private void Deregister() private bool IsRegistered() { - var taskName = "BackgroundTask"; + var taskName = BackgroundTaskName; foreach (var task in BackgroundTaskRegistration.AllTasks) { @@ -64,50 +93,5 @@ private bool IsRegistered() return false; } - - private async Task BackgroundTask() - { - BackgroundExecutionManager.RemoveAccess(); - - await BackgroundExecutionManager.RequestAccessAsync(); - - var builder = new BackgroundTaskBuilder - { - Name = "BackgroundTask", - TaskEntryPoint = "UWPRuntimeComponent.BackgroundTask" - }; - - builder.SetTrigger(new TimeTrigger(15, false)); - var task = builder.Register(); - task.Completed += this.TaskCompleted; - } - - private async void TaskCompleted(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args) - { - var settings = Windows.Storage.ApplicationData.Current.LocalSettings; - var key = "BackgroundTask"; - var message = settings.Values[key].ToString(); - - // Run your background task code here - var secureStorage = new SecureStorage(); - if (secureStorage.CheckUserStored()) - { - var user = await secureStorage.GetUser(); - if (user != null) - { - var contactApprovedList = await user.TangleMessenger.GetJsonMessageAsync>(user.ApprovedAddress); - - // Todo also message for a new contact request - foreach (var contact in contactApprovedList.Where(c => !c.Data.Rejected)) - { - var encryptedMessages = await user.TangleMessenger.GetMessagesAsync(contact.Data.ChatAdress); - foreach (var unused in encryptedMessages.Where(c => !c.Stored)) - { - CrossLocalNotifications.Current.Show(contact.Data.Name, "New Message from " + contact.Data.Name); - } - } - } - } - } } } diff --git a/Chiota/Chiota/App.xaml.cs b/Chiota/Chiota/App.xaml.cs index 0c4ae10..31395e7 100644 --- a/Chiota/Chiota/App.xaml.cs +++ b/Chiota/Chiota/App.xaml.cs @@ -4,7 +4,6 @@ namespace Chiota { - using Chiota.Messages; using Chiota.Services; using Chiota.Views; @@ -28,10 +27,6 @@ public App() protected override async void OnStart() { - // starts listening for messages - var messagestart = new StartLongRunningTaskMessage(); - MessagingCenter.Send(messagestart, "StartLongRunningTaskMessage"); - var secureStorage = new SecureStorage(); if (secureStorage.CheckUserStored()) { diff --git a/Chiota/Chiota/Chiota.csproj b/Chiota/Chiota/Chiota.csproj index dad5bc9..913fecb 100644 --- a/Chiota/Chiota/Chiota.csproj +++ b/Chiota/Chiota/Chiota.csproj @@ -30,7 +30,6 @@ - diff --git a/Chiota/Chiota/IOTAServices/IOTAHelper.cs b/Chiota/Chiota/IOTAServices/IOTAHelper.cs index 97d6a31..3aba0af 100644 --- a/Chiota/Chiota/IOTAServices/IOTAHelper.cs +++ b/Chiota/Chiota/IOTAServices/IOTAHelper.cs @@ -9,6 +9,8 @@ using Chiota.Services; using Chiota.ViewModels; + using Newtonsoft.Json; + using Tangle.Net.Entity; using VTDev.Libraries.CEXEngine.Crypto.Cipher.Asymmetric.Encrypt.NTRU; @@ -16,6 +18,12 @@ public class IotaHelper { + public static TryteString ObjectToTryteString(T data) + { + var serializeObject = JsonConvert.SerializeObject(data); + return TryteString.FromAsciiString(serializeObject); + } + public static bool CorrectSeedAdressChecker(string seed) { if (seed == null) @@ -59,6 +67,7 @@ public static async Task> GetNewMessages(IAsymmetricKeyPa var messages = new List(); var encryptedMessages = await tangle.GetMessagesAsync(contact.ChatAdress); var messageList = FilterChatMessages(encryptedMessages, keyPair); + if (messageList != null) { var sortedMessageList = messageList.OrderBy(o => o.Date).ToList(); @@ -68,7 +77,7 @@ public static async Task> GetNewMessages(IAsymmetricKeyPa { Text = message.Message, IsIncoming = message.Signature == contact.PublicKeyAdress.Substring(0, 30), - MessagDateTime = message.Date, + MessagDateTime = message.Date.ToLocalTime(), ProfileImage = contact.ImageUrl }); } diff --git a/Chiota/Chiota/IOTAServices/TangleMessenger.cs b/Chiota/Chiota/IOTAServices/TangleMessenger.cs index adb9cb8..a63d4a4 100644 --- a/Chiota/Chiota/IOTAServices/TangleMessenger.cs +++ b/Chiota/Chiota/IOTAServices/TangleMessenger.cs @@ -30,21 +30,27 @@ public TangleMessenger(Seed seed) public List ShorStorageHashes { get; set; } - public async Task SendMessageAsync(TryteString message, string address) + public async Task SendMessageAsync(TryteString message, string address, int retryNumber = 3) { - var bundle = new Bundle(); - bundle.AddTransfer(this.CreateTransfer(message, address)); + var roundNumber = 0; + while (roundNumber < retryNumber) + { + this.UpdateNode(roundNumber); - await Task.Factory.StartNew(() => this.repository.SendTransfer(this.seed, bundle, SecurityLevel.Medium, 27, 14)); - } + var bundle = new Bundle(); + bundle.AddTransfer(this.CreateTransfer(message, address)); - public async Task SendJsonMessageAsync(SentDataWrapper data, string address) - { - var serializeObject = JsonConvert.SerializeObject(data); - var bundle = new Bundle(); - bundle.AddTransfer(this.CreateTransfer(TryteString.FromAsciiString(serializeObject), address)); - - await Task.Factory.StartNew(() => this.repository.SendTransfer(this.seed, bundle, SecurityLevel.Medium, 27, 14)); + try + { + await Task.Factory.StartNew( + () => this.repository.SendTransfer(this.seed, bundle, SecurityLevel.Medium, 27, 14)); + break; + } + catch + { + roundNumber++; + } + } } public async Task> GetMessagesAsync(string adresse, int retryNumber = 1) @@ -53,11 +59,7 @@ public async Task> GetMessagesAsync(string adresse, int var messagesList = new List(); while (roundNumber < retryNumber && messagesList.Count == 0) { - // try to update the repository - if (roundNumber > 0) - { - this.repository = new RepositoryFactory().Create(); - } + this.UpdateNode(roundNumber); var adresses = new List
{ new Address(adresse) }; var transactions = await this.repository.FindTransactionsByAddressesAsync(adresses); @@ -83,9 +85,71 @@ public async Task> GetMessagesAsync(string adresse, int return messagesList; } - public TryteString GetMessages(Bundle bundle) + public async Task> GetJsonMessageAsync(string adresse, int retryNumber = 1) + { + var roundNumber = 0; + var messagesList = new List(); + + while (roundNumber < retryNumber && messagesList.Count == 0) + { + this.UpdateNode(roundNumber); + + var adresses = new List
{ new Address(adresse) }; + var transactions = await this.repository.FindTransactionsByAddressesAsync(adresses); + + // store hashes and load only new bundles + var newHashes = IotaHelper.GetNewHashes(transactions, this.ShorStorageHashes); + foreach (var transactionsHash in newHashes) + { + this.ShorStorageHashes.Add(transactionsHash); + + var hashString = transactionsHash.ToString(); + if (Application.Current.Properties.ContainsKey(hashString)) + { + var messageString = Application.Current.Properties[hashString] as string; + var deserializedObject = JsonConvert.DeserializeObject(messageString); + messagesList.Add(deserializedObject); + } + else + { + var bundle = await this.repository.GetBundleAsync(transactionsHash); + var messages = bundle.GetMessages(); + foreach (var message in messages) + { + try + { + var deserializedObject = JsonConvert.DeserializeObject(message); + messagesList.Add(deserializedObject); + Application.Current.Properties[hashString] = message; + await Application.Current.SavePropertiesAsync(); + } + catch + { + // ignored + } + } + } + } + + roundNumber++; + } + + return messagesList; + } + + private void UpdateNode(int roundNumber) + { + if (roundNumber > 0) + { + this.repository = new RepositoryFactory().Create(); + } + } + + private TryteString GetMessages(Bundle bundle) { var messageTrytes = string.Empty; + + // multiple message per bundle? foreach (var transaction in bundle.Transactions) { if (transaction.Value < 0) @@ -99,38 +163,19 @@ public TryteString GetMessages(Bundle bundle) } } - if (!messageTrytes.Contains("9ENDEGUTALLESGUT9")) + if (!messageTrytes.Contains(ChiotaIdentifier.End)) { return null; } - var index = messageTrytes.IndexOf("9ENDEGUTALLESGUT9", StringComparison.Ordinal); + var index = messageTrytes.IndexOf(ChiotaIdentifier.End, StringComparison.Ordinal); return new TryteString(messageTrytes.Substring(0, index)); } - public async Task> GetJsonMessageAsync(string adresse) - { - var adresses = new List
{ new Address(adresse) }; - var transactions = await this.repository.FindTransactionsByAddressesAsync(adresses); - var messagesList = new List(); - foreach (var transactionsHash in transactions.Hashes) - { - var bundle = this.repository.GetBundle(transactionsHash); - - var messages = bundle.GetMessages(); - foreach (var message in messages) - { - messagesList.Add(JsonConvert.DeserializeObject(message)); - } - } - - return messagesList; - } - private async Task MessageFromBundleOrStorage(Hash transactionsHash) { // todo upload them again after snapshot - TryteStringMessage message = new TryteStringMessage(); + var message = new TryteStringMessage(); var hashString = transactionsHash.ToString(); if (Application.Current.Properties.ContainsKey(hashString)) { @@ -158,7 +203,7 @@ private Transfer CreateTransfer(TryteString message, string adress) { Address = new Address(adress), Message = message, - Tag = new Tag("CHIOTAYOURIOTACHATAPP"), + Tag = new Tag(ChiotaIdentifier.Tag), Timestamp = Timestamp.UnixSecondsTimestamp }; } diff --git a/Chiota/Chiota/Models/ChiotaIdentifier.cs b/Chiota/Chiota/Models/ChiotaIdentifier.cs index 4d3e111..f40fc52 100644 --- a/Chiota/Chiota/Models/ChiotaIdentifier.cs +++ b/Chiota/Chiota/Models/ChiotaIdentifier.cs @@ -9,5 +9,7 @@ public class ChiotaIdentifier public static string FirstBreak => "9CHIOTAYOUR9"; public static string SecondBreak => "9IOTACHATAPP9"; + + public static string Tag => "CHIOTAYOURIOTACHATAPP"; } } diff --git a/Chiota/Chiota/ViewModels/AddContactViewModel.cs b/Chiota/Chiota/ViewModels/AddContactViewModel.cs index 0fb6025..f17fd0f 100644 --- a/Chiota/Chiota/ViewModels/AddContactViewModel.cs +++ b/Chiota/Chiota/ViewModels/AddContactViewModel.cs @@ -8,6 +8,8 @@ using Chiota.Models; using Chiota.Services; + using Newtonsoft.Json; + using Tangle.Net.Entity; using Xamarin.Forms; @@ -118,7 +120,8 @@ private async Task AddContact() }; // encrypt contact request? too much infos needed here for one message needs to get request adress plus chatadress - await this.user.TangleMessenger.SendJsonMessageAsync(new SentDataWrapper { Data = requestContact, Sender = this.user.Name }, contacts[0].ContactAdress); + var sentData = new SentDataWrapper { Data = requestContact, Sender = this.user.Name }; + await this.user.TangleMessenger.SendMessageAsync(IotaHelper.ObjectToTryteString(sentData), contacts[0].ContactAdress); this.SuccessfulRequestPrompt(); } } diff --git a/Chiota/Chiota/ViewModels/ChatViewModel.cs b/Chiota/Chiota/ViewModels/ChatViewModel.cs index 0427727..4085165 100644 --- a/Chiota/Chiota/ViewModels/ChatViewModel.cs +++ b/Chiota/Chiota/ViewModels/ChatViewModel.cs @@ -9,7 +9,6 @@ using System.Windows.Input; using Chiota.IOTAServices; - using Chiota.Messages; using Chiota.Models; using Chiota.Services; @@ -26,6 +25,8 @@ public class ChatViewModel : BaseViewModel public Action DisplayInvalidPublicKeyPrompt; + private const int CharacterLimit = 105; + private readonly User user; private readonly Contact contact; @@ -92,15 +93,18 @@ public async void OnAppearing() { this.GetMessagesAsync(this.Messages); } - - var messagestart = new StartLongRunningTaskMessage(); - MessagingCenter.Send(messagestart, "StopLongRunningTaskMessage"); } - public void OnDisappearing() + public void MessageRestriction(Entry entry) { - var messagestart = new StartLongRunningTaskMessage(); - MessagingCenter.Send(messagestart, "StartLongRunningTaskMessage"); + var val = entry.Text; + + if (val?.Length > CharacterLimit) + { + val = val.Remove(val.Length - 1); + entry.Text = val; + this.DisplayMessageTooLong(); + } } private async Task GetContactPublicKey() @@ -119,8 +123,7 @@ private async Task SendMessage() { this.IsBusy = true; - // No json object, because of the 106 character limit - if (this.OutGoingText.Length > 105) + if (this.OutGoingText.Length > CharacterLimit) { this.DisplayMessageTooLong(); } @@ -140,6 +143,7 @@ private async Task SendMessage() var tryteUser = new TryteString(encryptedForUser.ToTrytes() + ChiotaIdentifier.FirstBreak + signature + ChiotaIdentifier.SecondBreak + trytesDate + ChiotaIdentifier.End); await this.SendParallelAsync(tryteContact, tryteUser); + await this.AddNewMessagesAsync(this.Messages); } this.IsBusy = false; diff --git a/Chiota/Chiota/ViewModels/ContactListViewModel.cs b/Chiota/Chiota/ViewModels/ContactListViewModel.cs index 6ca2aef..d5e1aac 100644 --- a/Chiota/Chiota/ViewModels/ContactListViewModel.cs +++ b/Chiota/Chiota/ViewModels/ContactListViewModel.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using System.Windows.Input; + using Chiota.IOTAServices; using Chiota.Models; using Xamarin.Forms; @@ -43,7 +44,8 @@ private async void OnDecline() }; // store as rejected on approved contact adress - await this.user.TangleMessenger.SendJsonMessageAsync(new SentDataWrapper { Data = contact, Sender = this.user.Name }, this.user.ApprovedAddress); + var sentData = new SentDataWrapper { Data = contact, Sender = this.user.Name }; + await this.user.TangleMessenger.SendMessageAsync(IotaHelper.ObjectToTryteString(sentData), this.user.ApprovedAddress); this.viewCellObject.RefreshContacts = true; this.isClicked = false; } @@ -77,7 +79,8 @@ private async void OnAccept() private Task SendParallelAsync(Contact contact) { // store as approved on own adress - var firstTransaction = this.user.TangleMessenger.SendJsonMessageAsync(new SentDataWrapper { Data = contact, Sender = this.user.Name }, this.user.ApprovedAddress); + var sentData = new SentDataWrapper { Data = contact, Sender = this.user.Name }; + var firstTransaction = this.user.TangleMessenger.SendMessageAsync(IotaHelper.ObjectToTryteString(sentData), this.user.ApprovedAddress); contact.Name = this.user.Name; contact.ImageUrl = this.user.ImageUrl; @@ -85,7 +88,8 @@ private Task SendParallelAsync(Contact contact) contact.PublicKeyAdress = this.user.PublicKeyAddress; // store on other users approved contact address - var secondTransaction = this.user.TangleMessenger.SendJsonMessageAsync(new SentDataWrapper { Data = contact, Sender = this.user.Name }, this.ContactAdress); + sentData = new SentDataWrapper { Data = contact, Sender = this.user.Name }; + var secondTransaction = this.user.TangleMessenger.SendMessageAsync(IotaHelper.ObjectToTryteString(sentData), this.ContactAdress); return Task.WhenAll(firstTransaction, secondTransaction); } } diff --git a/Chiota/Chiota/ViewModels/ContactViewModel.cs b/Chiota/Chiota/ViewModels/ContactViewModel.cs index e09d2bf..50a2cd4 100644 --- a/Chiota/Chiota/ViewModels/ContactViewModel.cs +++ b/Chiota/Chiota/ViewModels/ContactViewModel.cs @@ -90,9 +90,9 @@ private async Task> GetConctacts(stri // right now people can add themselfs to your contacts list, when they know your public key adress and approved contact adress // in future store approved contacts with MAM - var contactRequestList = await this.user.TangleMessenger.GetJsonMessageAsync>(this.user.RequestAddress); + var contactRequestList = await this.user.TangleMessenger.GetJsonMessageAsync>(this.user.RequestAddress, 3); - var contactApprovedList = await this.user.TangleMessenger.GetJsonMessageAsync>(this.user.ApprovedAddress); + var contactApprovedList = await this.user.TangleMessenger.GetJsonMessageAsync>(this.user.ApprovedAddress, 3); var contactsWithoutResponse = contactRequestList.Except(contactApprovedList, new ChatAdressComparer()).ToList(); diff --git a/Chiota/Chiota/Views/ChatPage.xaml b/Chiota/Chiota/Views/ChatPage.xaml index 8dcd225..9407975 100644 --- a/Chiota/Chiota/Views/ChatPage.xaml +++ b/Chiota/Chiota/Views/ChatPage.xaml @@ -36,19 +36,20 @@ - diff --git a/Chiota/Chiota/Views/ChatPage.xaml.cs b/Chiota/Chiota/Views/ChatPage.xaml.cs index b95a6ff..377d9e5 100644 --- a/Chiota/Chiota/Views/ChatPage.xaml.cs +++ b/Chiota/Chiota/Views/ChatPage.xaml.cs @@ -25,6 +25,7 @@ public ChatPage(Contact contact, User user) } this.Title = contact.Name; + (this.MessageEntry as Entry).TextChanged += this.OnTextChanged; this.vm = new ChatViewModel(MessagesListView, contact, user) { Navigation = this.Navigation }; this.vm.DisplayMessageTooLong += () => this.DisplayAlert("Error", "Sorry, only 105 characters per message are allowed!", "OK"); this.vm.DisplayInvalidPublicKeyPrompt += () => this.DisplayAlert("Error", "Invalid public key! You contact needs to give you a new contact address.", "OK"); @@ -39,12 +40,19 @@ protected override void OnAppearing() protected override void OnDisappearing() { - this.vm.OnDisappearing(); this.vm.PageIsShown = false; this.vm = null; this.Navigation.PopAsync(); } + private void OnTextChanged(object sender, EventArgs e) + { + if (sender is Entry entry) + { + (this.BindingContext as ChatViewModel)?.MessageRestriction(entry); + } + } + private void Handle_Completed(object sender, EventArgs e) { (this.BindingContext as ChatViewModel)?.SendCommand.Execute(null); diff --git a/README.md b/README.md index b41324b..4bd5685 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,12 @@ Currently, there are the following points on my to-do list: - [x] Qr codes for address sharing - [x] Performance improvements - [x] Check for unique Address public key combination -- [ ] Improve Notifications/Background Tasks (Android 8.0, Sound, Windows, etc.) +- [x] Notifications +- [ ] Chatbot integration - [ ] Recovery after snapshot - [ ] Mam Integration - [ ] Improve/check NTRU solution or switch to SIDH (key only 564 bytes) for key exchange -- [ ] iOS App +- [ ] Windows/iOS App - [ ] Unit testing - [ ] Code refactoring diff --git a/UWPRuntimeComponent/BackgroundTask.cs b/UWPRuntimeComponent/BackgroundTask.cs index f1b7d77..50dc4bd 100644 --- a/UWPRuntimeComponent/BackgroundTask.cs +++ b/UWPRuntimeComponent/BackgroundTask.cs @@ -6,21 +6,11 @@ public sealed class BackgroundTask : IBackgroundTask { private BackgroundTaskDeferral deferral; - public void Run(IBackgroundTaskInstance taskInstance) + public async void Run(IBackgroundTaskInstance taskInstance) { this.deferral = taskInstance.GetDeferral(); // Run your background task code here - try - { - var settings = Windows.Storage.ApplicationData.Current.LocalSettings; - - settings.Values.Add("BackgroundTask", "Hello from UWP"); - } - catch - { - // ignored - } this.deferral.Complete(); }