diff --git a/AuthenticatorPro.Droid.Shared/AuthenticatorPro.Droid.Shared.csproj b/AuthenticatorPro.Droid.Shared/AuthenticatorPro.Droid.Shared.csproj
index 9ac84cc527..c77ebe75ab 100644
--- a/AuthenticatorPro.Droid.Shared/AuthenticatorPro.Droid.Shared.csproj
+++ b/AuthenticatorPro.Droid.Shared/AuthenticatorPro.Droid.Shared.csproj
@@ -50,6 +50,7 @@
+
diff --git a/AuthenticatorPro.Droid.Shared/Source/Query/WearPreferences.cs b/AuthenticatorPro.Droid.Shared/Source/Query/WearPreferences.cs
new file mode 100644
index 0000000000..18a7a160c9
--- /dev/null
+++ b/AuthenticatorPro.Droid.Shared/Source/Query/WearPreferences.cs
@@ -0,0 +1,12 @@
+namespace AuthenticatorPro.Droid.Shared.Query
+{
+ public class WearPreferences
+ {
+ public readonly string DefaultCategory;
+
+ public WearPreferences(string defaultCategory)
+ {
+ DefaultCategory = defaultCategory;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AuthenticatorPro.Droid/Resources/values/wear.xml b/AuthenticatorPro.Droid/Resources/values/wear.xml
index a7258cf2bb..e86c79221d 100644
--- a/AuthenticatorPro.Droid/Resources/values/wear.xml
+++ b/AuthenticatorPro.Droid/Resources/values/wear.xml
@@ -1,6 +1,6 @@
- - protocol_v2
+ - protocol_v2.1
diff --git a/AuthenticatorPro.Droid/Source/Service/WearQueryService.cs b/AuthenticatorPro.Droid/Source/Service/WearQueryService.cs
index f6187182ed..195649b814 100644
--- a/AuthenticatorPro.Droid/Source/Service/WearQueryService.cs
+++ b/AuthenticatorPro.Droid/Source/Service/WearQueryService.cs
@@ -27,6 +27,7 @@ internal class WearQueryService : WearableListenerService
private const string ListCategoriesCapability = "list_categories";
private const string ListCustomIconsCapability = "list_custom_icons";
private const string GetCustomIconCapability = "get_custom_icon";
+ private const string GetPreferencesCapability = "get_preferences";
private readonly Lazy _initTask;
@@ -127,6 +128,17 @@ await WearableClass.GetMessageClient(this)
.SendMessageAsync(nodeId, GetCustomIconCapability, data);
}
+ private async Task GetPreferences(string nodeId)
+ {
+ var preferences = new PreferenceWrapper(this);
+ var settings = new WearPreferences(preferences.DefaultCategory);
+ var json = JsonConvert.SerializeObject(settings);
+ var data = Encoding.UTF8.GetBytes(json);
+
+ await WearableClass.GetMessageClient(this)
+ .SendMessageAsync(nodeId, GetPreferencesCapability, data);
+ }
+
public override async void OnMessageReceived(IMessageEvent messageEvent)
{
await _initTask.Value;
@@ -151,6 +163,10 @@ public override async void OnMessageReceived(IMessageEvent messageEvent)
await GetCustomIcon(id, messageEvent.SourceNodeId);
break;
}
+
+ case GetPreferencesCapability:
+ await GetPreferences(messageEvent.SourceNodeId);
+ break;
}
}
}
diff --git a/AuthenticatorPro.WearOS/AuthenticatorPro.WearOS.csproj b/AuthenticatorPro.WearOS/AuthenticatorPro.WearOS.csproj
index a5de5dba64..b1f78cf040 100644
--- a/AuthenticatorPro.WearOS/AuthenticatorPro.WearOS.csproj
+++ b/AuthenticatorPro.WearOS/AuthenticatorPro.WearOS.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -85,6 +85,7 @@
+
@@ -124,6 +125,9 @@
2.2.0.4
+
+ 1.1.1.6
+
1.1.0.1
diff --git a/AuthenticatorPro.WearOS/Source/Activity/MainActivity.cs b/AuthenticatorPro.WearOS/Source/Activity/MainActivity.cs
index 63ae7a1b40..f4df97eb4e 100644
--- a/AuthenticatorPro.WearOS/Source/Activity/MainActivity.cs
+++ b/AuthenticatorPro.WearOS/Source/Activity/MainActivity.cs
@@ -19,6 +19,7 @@
using AuthenticatorPro.WearOS.Cache;
using AuthenticatorPro.WearOS.Data;
using AuthenticatorPro.WearOS.List;
+using AuthenticatorPro.WearOS.Util;
using Newtonsoft.Json;
@@ -28,11 +29,12 @@ namespace AuthenticatorPro.WearOS.Activity
internal class MainActivity : AppCompatActivity, MessageClient.IOnMessageReceivedListener
{
// Query Paths
- private const string ProtocolVersion = "protocol_v2";
+ private const string ProtocolVersion = "protocol_v2.1";
private const string ListAuthenticatorsCapability = "list_authenticators";
private const string ListCategoriesCapability = "list_categories";
private const string ListCustomIconsCapability = "list_custom_icons";
private const string GetCustomIconCapability = "get_custom_icon";
+ private const string GetPreferencesCapability = "get_preferences";
private const string RefreshCapability = "refresh";
// Cache Names
@@ -44,12 +46,14 @@ internal class MainActivity : AppCompatActivity, MessageClient.IOnMessageReceive
private RelativeLayout _loadingLayout;
private RelativeLayout _emptyLayout;
private WearableRecyclerView _authList;
+ private WearableNavigationDrawerView _categoryList;
// Data
private AuthenticatorSource _authSource;
private ListCache _authCache;
private ListCache _categoryCache;
private CustomIconCache _customIconCache;
+ private PreferenceWrapper _preferences;
private AuthenticatorListAdapter _authListAdapter;
private CategoryListAdapter _categoryListAdapter;
@@ -61,11 +65,13 @@ internal class MainActivity : AppCompatActivity, MessageClient.IOnMessageReceive
// Lifecycle Synchronisation
private readonly SemaphoreSlim _onCreateLock;
+ private readonly SemaphoreSlim _refreshLock;
public MainActivity()
{
- _onCreateLock = new SemaphoreSlim(1, 1);
+ _onCreateLock = new SemaphoreSlim(1, 1);
+ _refreshLock = new SemaphoreSlim(1, 1);
}
#region Activity Lifecycle
@@ -76,6 +82,8 @@ protected override async void OnCreate(Bundle bundle)
await _onCreateLock.WaitAsync();
SetContentView(Resource.Layout.activityMain);
+ _preferences = new PreferenceWrapper(this);
+
_authCache = new ListCache(AuthenticatorCacheName, this);
_categoryCache = new ListCache(CategoryCacheName, this);
_customIconCache = new CustomIconCache(this);
@@ -103,12 +111,36 @@ protected override async void OnResume()
}
catch(ApiException)
{
- RunOnUiThread(UpdateViewState);
+ RunOnUiThread(CheckOfflineState);
return;
}
- RunOnUiThread(UpdateViewState);
+ RunOnUiThread(CheckOfflineState);
await Refresh();
+
+ await _refreshLock.WaitAsync();
+ _refreshLock.Release();
+
+ var defaultCategory = _preferences.DefaultCategory;
+
+ if(defaultCategory == null)
+ {
+ RunOnUiThread(CheckEmptyState);
+ return;
+ }
+
+ _authSource.SetCategory(defaultCategory);
+ var position = _categoryCache.FindIndex(c => c.Id == defaultCategory) + 1;
+
+ if(position < 0)
+ return;
+
+ RunOnUiThread(delegate
+ {
+ _categoryList.SetCurrentItem(position, false);
+ _authListAdapter.NotifyDataSetChanged();
+ CheckEmptyState();
+ });
}
protected override async void OnPause()
@@ -144,17 +176,17 @@ private void InitViews()
_authListAdapter.HasStableIds = true;
_authList.SetAdapter(_authListAdapter);
- var categoriesDrawer = FindViewById(Resource.Id.drawerCategories);
+ _categoryList = FindViewById(Resource.Id.drawerCategories);
_categoryListAdapter = new CategoryListAdapter(this, _categoryCache);
- categoriesDrawer.SetAdapter(_categoryListAdapter);
- categoriesDrawer.ItemSelected += OnCategorySelected;
+ _categoryList.SetAdapter(_categoryListAdapter);
+ _categoryList.ItemSelected += OnCategorySelected;
}
private void OnCategorySelected(object sender, WearableNavigationDrawerView.ItemSelectedEventArgs e)
{
if(e.Pos > 0)
{
- var category = _categoryCache.Get(e.Pos - 1);
+ var category = _categoryCache[e.Pos - 1];
if(category == null)
return;
@@ -165,20 +197,27 @@ private void OnCategorySelected(object sender, WearableNavigationDrawerView.Item
_authSource.SetCategory(null);
_authListAdapter.NotifyDataSetChanged();
- UpdateViewState();
+ CheckEmptyState();
+ }
+
+ private void CheckOfflineState()
+ {
+ if(_serverNode == null)
+ {
+ AnimUtil.FadeOutView(_loadingLayout, AnimUtil.LengthShort);
+ _offlineLayout.Visibility = ViewStates.Visible;
+ }
+ else
+ _offlineLayout.Visibility = ViewStates.Invisible;
}
- private void UpdateViewState()
+ private void CheckEmptyState()
{
if(_loadingLayout.Visibility == ViewStates.Visible)
AnimUtil.FadeOutView(_loadingLayout, AnimUtil.LengthShort);
_emptyLayout.Visibility = ViewStates.Gone;
- _offlineLayout.Visibility = _serverNode == null
- ? ViewStates.Visible
- : ViewStates.Gone;
-
if(_authSource.GetView().Count == 0)
_emptyLayout.Visibility = ViewStates.Visible;
else
@@ -263,18 +302,23 @@ private async Task Refresh()
{
if(_serverNode == null)
return;
+
+ await _refreshLock.WaitAsync();
Interlocked.Exchange(ref _responsesReceived, 0);
- Interlocked.Exchange(ref _responsesRequired, 3);
+ Interlocked.Exchange(ref _responsesRequired, 4);
- await WearableClass.GetMessageClient(this)
- .SendMessageAsync(_serverNode.Id, ListAuthenticatorsCapability, new byte[] { });
-
- await WearableClass.GetMessageClient(this)
- .SendMessageAsync(_serverNode.Id, ListCategoriesCapability, new byte[] { });
+ var client = WearableClass.GetMessageClient(this);
- await WearableClass.GetMessageClient(this)
- .SendMessageAsync(_serverNode.Id, ListCustomIconsCapability, new byte[] { });
+ async Task MakeRequest(string capability)
+ {
+ await client.SendMessageAsync(_serverNode.Id, capability, new byte[] { });
+ }
+
+ await MakeRequest(ListAuthenticatorsCapability);
+ await MakeRequest(ListCategoriesCapability);
+ await MakeRequest(ListCustomIconsCapability);
+ await MakeRequest(GetPreferencesCapability);
}
private async Task OnAuthenticatorListReceived(byte[] data)
@@ -330,6 +374,14 @@ private async Task OnCustomIconReceived(byte[] data)
await _customIconCache.Add(icon.Id, icon.Data);
}
+
+ private void OnPreferencesReceived(byte[] data)
+ {
+ var json = Encoding.UTF8.GetString(data);
+ var prefs = JsonConvert.DeserializeObject(json);
+
+ _preferences.DefaultCategory = prefs.DefaultCategory;
+ }
public async void OnMessageReceived(IMessageEvent messageEvent)
{
@@ -350,6 +402,10 @@ public async void OnMessageReceived(IMessageEvent messageEvent)
case GetCustomIconCapability:
await OnCustomIconReceived(messageEvent.GetData());
break;
+
+ case GetPreferencesCapability:
+ OnPreferencesReceived(messageEvent.GetData());
+ break;
case RefreshCapability:
await Refresh();
@@ -362,7 +418,7 @@ public async void OnMessageReceived(IMessageEvent messageEvent)
var required = Interlocked.CompareExchange(ref _responsesRequired, 0, 0);
if(received == required)
- RunOnUiThread(UpdateViewState);
+ _refreshLock.Release();
}
#endregion
}
diff --git a/AuthenticatorPro.WearOS/Source/Cache/ListCache.cs b/AuthenticatorPro.WearOS/Source/Cache/ListCache.cs
index d63416f424..617e7147f9 100644
--- a/AuthenticatorPro.WearOS/Source/Cache/ListCache.cs
+++ b/AuthenticatorPro.WearOS/Source/Cache/ListCache.cs
@@ -1,4 +1,6 @@
-using Android.Content;
+using System;
+using System.Collections;
+using Android.Content;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -9,13 +11,13 @@
namespace AuthenticatorPro.WearOS.Cache
{
- internal class ListCache
+ internal class ListCache : IEnumerable
{
private readonly string _name;
private readonly Context _context;
private readonly SemaphoreSlim _flushLock;
-
private List _items;
+
public int Count => _items.Count;
public ListCache(string name, Context context)
@@ -48,7 +50,7 @@ public async Task Replace(List items)
await Flush();
}
- public bool Dirty(List items, IEqualityComparer comparer = null)
+ public bool Dirty(IEnumerable items, IEqualityComparer comparer = null)
{
return comparer != null
? !_items.SequenceEqual(items, comparer)
@@ -70,14 +72,35 @@ private async Task Flush()
}
}
- public T Get(int position)
+ public List GetItems()
{
- return _items.ElementAtOrDefault(position);
+ return _items;
}
- public List GetItems()
+ public bool Contains(T item)
{
- return _items;
+ return _items.Contains(item);
+ }
+
+ private IEnumerator GetEnumerator()
+ {
+ return _items.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public int FindIndex(Predicate predicate)
+ {
+ return _items.FindIndex(predicate);
+ }
+
+ public T this[int index]
+ {
+ get => _items[index];
+ set => _items[index] = value;
}
}
}
\ No newline at end of file
diff --git a/AuthenticatorPro.WearOS/Source/List/CategoryListAdapter.cs b/AuthenticatorPro.WearOS/Source/List/CategoryListAdapter.cs
index dda1d157a9..e3057f1d7e 100644
--- a/AuthenticatorPro.WearOS/Source/List/CategoryListAdapter.cs
+++ b/AuthenticatorPro.WearOS/Source/List/CategoryListAdapter.cs
@@ -29,7 +29,7 @@ public override ICharSequence GetItemTextFormatted(int pos)
if(pos == 0)
return new String(_context.GetString(Resource.String.categoryAll));
- var item = _cache.Get(pos - 1);
+ var item = _cache[pos - 1];
return item == null
? new String()
diff --git a/AuthenticatorPro.WearOS/Source/Util/PreferenceWrapper.cs b/AuthenticatorPro.WearOS/Source/Util/PreferenceWrapper.cs
new file mode 100644
index 0000000000..0ce3aac7d1
--- /dev/null
+++ b/AuthenticatorPro.WearOS/Source/Util/PreferenceWrapper.cs
@@ -0,0 +1,27 @@
+using Android.Content;
+using AndroidX.Preference;
+
+namespace AuthenticatorPro.WearOS.Util
+{
+ internal class PreferenceWrapper
+ {
+ private const string DefaultCategoryKey = "defaultCategory";
+ private const string DefaultCategoryDefault = null;
+ public string DefaultCategory
+ {
+ get => _preferences.GetString(DefaultCategoryKey, DefaultCategoryDefault);
+ set => SetPreference(DefaultCategoryKey, value);
+ }
+
+ private readonly ISharedPreferences _preferences;
+
+ public PreferenceWrapper(Context context)
+ {
+ _preferences = PreferenceManager.GetDefaultSharedPreferences(context);
+ }
+ private void SetPreference(string key, string value)
+ {
+ _preferences.Edit().PutString(key, value).Commit();
+ }
+ }
+}
\ No newline at end of file