Skip to content

Commit 2469039

Browse files
committed
Merge remote-tracking branch 'remotes/origin/jaka/feat/mini-apps-UI-screens' into development
2 parents 580fb75 + 24ef820 commit 2469039

30 files changed

+2222
-357
lines changed

Spixi/MiniApps/MiniApp.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ public class MiniApp
2424
public string image = "";
2525
public string url = "";
2626
public string contentUrl = "";
27+
public long contentSize = 0;
2728
public string checksum = "";
2829
public byte[] publicKey = null;
29-
public byte[] signature = null;
30+
public byte[] signature = null;
3031
public Dictionary<MiniAppCapabilities, bool> capabilities = null;
3132

3233
public MiniApp(string[] app_info)
@@ -86,6 +87,13 @@ public MiniApp(string[] app_info)
8687
contentUrl = value;
8788
break;
8889

90+
case "contentSize":
91+
if (long.TryParse(value, out long size))
92+
{
93+
contentSize = size;
94+
}
95+
break;
96+
8997
case "checksum":
9098
checksum = value;
9199
break;
@@ -179,6 +187,7 @@ public void writeAppInfoFile(string filePath)
179187
sb.AppendLine($"image = {image}");
180188
sb.AppendLine($"url = {url}");
181189
sb.AppendLine($"contentUrl = {contentUrl}");
190+
sb.AppendLine($"contentSize = {contentSize}");
182191
sb.AppendLine($"checksum = {checksum}");
183192

184193
File.WriteAllText(filePath, sb.ToString());

Spixi/MiniApps/MiniAppManager.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public void stop()
7575
}
7676
}
7777

78-
public MiniApp? fetch(string url, long maxSizeBytes = 1 * 1024 * 1024) // 1 MB default limit
78+
public async Task<MiniApp?> fetch(string url, long maxSizeBytes = 1 * 1024 * 1024) // 1 MB default limit
7979
{
8080
if (string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out Uri uri) || uri.Scheme != Uri.UriSchemeHttps)
8181
{
@@ -85,17 +85,18 @@ public void stop()
8585

8686
try
8787
{
88-
HttpResponseMessage headResponse = httpClient.Send(new HttpRequestMessage(HttpMethod.Head, url));
88+
using var headRequest = new HttpRequestMessage(HttpMethod.Head, url);
89+
using HttpResponseMessage headResponse = await httpClient.SendAsync(headRequest);
8990
headResponse.EnsureSuccessStatusCode();
90-
long contentLength = headResponse.Content.Headers.ContentLength ?? 0;
9191

92+
long contentLength = headResponse.Content.Headers.ContentLength ?? 0;
9293
if (contentLength > maxSizeBytes)
9394
{
9495
Logging.error("App content size exceeds limit: " + contentLength + " bytes");
9596
return null;
9697
}
9798

98-
byte[] data = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
99+
byte[] data = await httpClient.GetByteArrayAsync(url);
99100
if (data.Length != contentLength)
100101
{
101102
Logging.error("Downloaded app data size mismatch: expected " + contentLength + " bytes, but got " + data.Length + " bytes");
@@ -136,6 +137,7 @@ public string install(MiniApp fetchedAppInfo)
136137
try
137138
{
138139
File.WriteAllBytes(source_app_file_path, client.GetByteArrayAsync(fetchedAppInfo.contentUrl).Result);
140+
fetchedAppInfo.contentSize = new FileInfo(source_app_file_path).Length;
139141
}
140142
catch (Exception e)
141143
{
@@ -376,6 +378,26 @@ public string getAppIconPath(string app_id)
376378
return null;
377379
}
378380

381+
public string getAppInstallURL(string app_id)
382+
{
383+
MiniApp mini_app = getApp(app_id);
384+
if (mini_app != null)
385+
{
386+
return mini_app.url;
387+
}
388+
return null;
389+
}
390+
391+
public string getAppName(string app_id)
392+
{
393+
MiniApp mini_app = getApp(app_id);
394+
if (mini_app != null)
395+
{
396+
return mini_app.name;
397+
}
398+
return null;
399+
}
400+
379401
public Dictionary<string, MiniApp> getInstalledApps()
380402
{
381403
return appList;

Spixi/Network/StreamProcessor.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,9 +1145,13 @@ private static void handleAppData(Address sender_address, byte[] app_data_raw)
11451145
public static void sendAppRequest(Friend friend, string app_id, byte[] session_id, byte[] data)
11461146
{
11471147
// TODO use channels and drop SpixiAppData
1148+
string app_install_url = Node.MiniAppManager.getAppInstallURL(app_id);
1149+
string app_name = Node.MiniAppManager.getAppName(app_id);
1150+
string app_info = $"{app_id}||{app_install_url}||{app_name}"; // TODO pack this information better
1151+
11481152
SpixiMessage spixi_msg = new SpixiMessage();
11491153
spixi_msg.type = SpixiMessageCode.appRequest;
1150-
spixi_msg.data = new SpixiAppData(session_id, data, app_id).getBytes();
1154+
spixi_msg.data = new SpixiAppData(session_id, data, app_info).getBytes();
11511155

11521156
StreamMessage new_msg = new StreamMessage();
11531157
new_msg.type = StreamMessageCode.data;
@@ -1259,7 +1263,16 @@ private static void handleAppRequest(Address sender_address, Address recipient_a
12591263
return;
12601264
}
12611265

1262-
string app_id = app_data.appId;
1266+
if (string.IsNullOrEmpty(app_data.appId))
1267+
{
1268+
Logging.error("App with session id: {0} has no provided app id.", Crypto.hashToString(app_data.sessionId));
1269+
return;
1270+
}
1271+
1272+
string[] appid_data = app_data.appId.Split("||");
1273+
string app_id = appid_data[0];
1274+
string? app_install_url = appid_data.Length > 1 ? appid_data[1] : null;
1275+
12631276

12641277
app_page = am.getAppPage(sender_address, app_id);
12651278
if (app_page != null)
@@ -1291,19 +1304,10 @@ private static void handleAppRequest(Address sender_address, Address recipient_a
12911304
{
12921305
// app doesn't exist
12931306
Logging.error("App with id {0} is not installed.", app_id);
1294-
return;
12951307
}
12961308
}
1297-
if (FriendList.addMessageWithType(app_data.sessionId, FriendMessageType.appSession, sender_address, 0, app.id) != null)
1298-
{
1299-
app_page = new MiniAppPage(app_id, sender_address, user_addresses, am.getAppEntryPoint(app_id));
1300-
app_page.myRequestAddress = recipient_address;
1301-
app_page.requestedByAddress = sender_address;
1302-
app_page.sessionId = app_data.sessionId;
1303-
am.addAppPage(app_page);
1309+
FriendList.addMessageWithType(app_data.sessionId, FriendMessageType.appSession, sender_address, 0, app_data.appId);
13041310

1305-
Node.refreshAppRequests = true;
1306-
}
13071311
});
13081312
}
13091313

Spixi/Pages/Chat/SingleChatPage.xaml.cs

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,16 @@ private void onNavigating(object sender, WebNavigatingEventArgs e)
195195
string app_id = current_url.Substring("ixian:app:".Length);
196196
onApp(app_id);
197197
}
198+
else if (current_url.StartsWith("ixian:installApp:"))
199+
{
200+
string app_url = current_url.Substring("ixian:installApp:".Length);
201+
onInstallApp(app_url);
202+
}
203+
else if (current_url.StartsWith("ixian:joinApp:"))
204+
{
205+
string app_id = current_url.Substring("ixian:joinApp:".Length);
206+
onJoinApp(app_id);
207+
}
198208
else if (current_url.StartsWith("ixian:loadContacts"))
199209
{
200210
loadContacts();
@@ -735,17 +745,75 @@ public void onConfirmPaymentRequest(FriendMessage msg, string amount)
735745
public void onApp(string app_id)
736746
{
737747
Address[] user_addresses = new Address[] { friend.walletAddress };
738-
MiniAppPage custom_app_page = new MiniAppPage(app_id, IxianHandler.getWalletStorage().getPrimaryAddress(), user_addresses, Node.MiniAppManager.getAppEntryPoint(app_id));
739-
custom_app_page.accepted = true;
740-
Node.MiniAppManager.addAppPage(custom_app_page);
748+
749+
byte[]? session_id = null;
750+
if (homePage != null)
751+
{
752+
session_id = homePage.onJoinApp(app_id, user_addresses);
753+
}
754+
else
755+
{
756+
MiniAppPage custom_app_page = new MiniAppPage(app_id, IxianHandler.getWalletStorage().getPrimaryAddress(), user_addresses, Node.MiniAppManager.getAppEntryPoint(app_id));
757+
custom_app_page.accepted = true;
758+
Node.MiniAppManager.addAppPage(custom_app_page);
759+
session_id = custom_app_page.sessionId;
760+
MainThread.BeginInvokeOnMainThread(() =>
761+
{
762+
Navigation.PushAsync(custom_app_page, Config.defaultXamarinAnimations);
763+
});
764+
}
765+
766+
767+
if(session_id == null)
768+
{
769+
return;
770+
}
771+
772+
FriendList.addMessageWithType(session_id, FriendMessageType.appSession, friend.walletAddress, 0, app_id, true, null, 0, false);
773+
StreamProcessor.sendAppRequest(friend, app_id, session_id, null);
774+
}
775+
776+
public void onJoinApp(string app_id)
777+
{
778+
779+
Address[] user_addresses = new Address[] { friend.walletAddress };
780+
if (homePage != null)
781+
{
782+
homePage.onJoinApp(app_id, user_addresses);
783+
return;
784+
}
785+
786+
MiniAppPage miniAppPage = new MiniAppPage(app_id, IxianHandler.getWalletStorage().getPrimaryAddress(), user_addresses, Node.MiniAppManager.getAppEntryPoint(app_id));
787+
miniAppPage.accepted = true;
788+
Node.MiniAppManager.addAppPage(miniAppPage);
741789

742790
MainThread.BeginInvokeOnMainThread(() =>
743791
{
744-
Navigation.PushAsync(custom_app_page, Config.defaultXamarinAnimations);
792+
Navigation.PushAsync(miniAppPage, Config.defaultXamarinAnimations);
745793
});
746794

747-
FriendList.addMessageWithType(custom_app_page.sessionId, FriendMessageType.appSession, friend.walletAddress, 0, app_id, true, null, 0, false);
748-
StreamProcessor.sendAppRequest(friend, app_id, custom_app_page.sessionId, null);
795+
}
796+
797+
public async void onInstallApp(string app_url)
798+
{
799+
if (homePage != null)
800+
{
801+
homePage.onInstallApp(app_url);
802+
return;
803+
}
804+
805+
MiniApp? app = await Node.MiniAppManager.fetch(app_url);
806+
if (app == null)
807+
{
808+
return;
809+
}
810+
811+
app.url = app_url;
812+
813+
MainThread.BeginInvokeOnMainThread(() =>
814+
{
815+
Navigation.PushAsync(new AppDetailsPage(app), Config.defaultXamarinAnimations);
816+
});
749817
}
750818

751819
private void onKickUser(Address address)
@@ -1157,8 +1225,51 @@ public void insertMessage(FriendMessage message, int channel)
11571225
Utils.sendUiCommand(this, "addFile", Crypto.hashToString(message.id), address, nick, avatar, uid, name, message.timestamp.ToString(), message.localSender.ToString(), message.confirmed.ToString(), message.read.ToString(), progress, message.completed.ToString(), paid.ToString());
11581226
}
11591227
}
1160-
1161-
if(message.type == FriendMessageType.standard)
1228+
1229+
if (message.type == FriendMessageType.appSession)
1230+
{
1231+
MiniAppManager am = Node.MiniAppManager;
1232+
1233+
string app_id;
1234+
string app_install_url = "";
1235+
string app_name = "";
1236+
if (message.message.Contains("||"))
1237+
{
1238+
string[] app_id_data = message.message.Split(new[] { "||" }, StringSplitOptions.None);
1239+
app_id = app_id_data[0];
1240+
app_install_url = app_id_data.Length > 1 ? app_id_data[1] : "";
1241+
app_name = app_id_data.Length > 2 ? app_id_data[2] : "";
1242+
}
1243+
else
1244+
{
1245+
app_id = message.message;
1246+
}
1247+
1248+
1249+
MiniApp app = am.getApp(app_id);
1250+
string app_state = "";
1251+
1252+
if (app == null)
1253+
{
1254+
app_state = "Missing";
1255+
}
1256+
else
1257+
{
1258+
app_name = app.name;
1259+
}
1260+
1261+
1262+
if (message.localSender)
1263+
{
1264+
Utils.sendUiCommand(this, "addAppRequest", Crypto.hashToString(message.id), app_id, app_name, address, nick, avatar, message.timestamp.ToString(), message.localSender.ToString(), message.confirmed.ToString(), message.read.ToString(), app_state, app_install_url);
1265+
}
1266+
else
1267+
{
1268+
Utils.sendUiCommand(this, "addAppRequest", Crypto.hashToString(message.id), app_id, app_name, address, nick, avatar, message.timestamp.ToString(), message.localSender.ToString(), message.confirmed.ToString(), message.read.ToString(), app_state, app_install_url);
1269+
}
1270+
}
1271+
1272+
if (message.type == FriendMessageType.standard)
11621273
{
11631274
// Normal chat message
11641275
// Call webview methods on the main UI thread only

Spixi/Pages/Home/HomePage.xaml.cs

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,17 @@ public void loadChats()
814814
excerpt = SpixiLocalization._SL("index-excerpt-payment-received");
815815
}
816816
}
817+
else if (lastmsg.type == FriendMessageType.appSession)
818+
{
819+
if (lastmsg.localSender)
820+
{
821+
excerpt = SpixiLocalization._SL("chat-app-invite-sent");
822+
}
823+
else
824+
{
825+
excerpt = SpixiLocalization._SL("chat-app-invite-received");
826+
}
827+
}
817828
else if (lastmsg.type == FriendMessageType.requestAdd)
818829
{
819830
if (friend.approved)
@@ -1299,22 +1310,47 @@ private async void HandlePickAppMultiUserSucceeded(object sender, SPIXI.EventArg
12991310
try
13001311
{
13011312
await Navigation.PopAsync(Config.defaultXamarinAnimations);
1302-
1303-
MiniAppPage miniAppPage = new MiniAppPage(appId, IxianHandler.getWalletStorage().getPrimaryAddress(), new Address[] { id_bytes }, Node.MiniAppManager.getAppEntryPoint(appId));
1304-
miniAppPage.accepted = true;
1305-
Node.MiniAppManager.addAppPage(miniAppPage);
13061313

1307-
MainThread.BeginInvokeOnMainThread(() =>
1308-
{
1309-
Navigation.PushAsync(miniAppPage, Config.defaultXamarinAnimations);
1310-
});
1314+
byte[] session_id = onJoinApp(appId, new Address[] { id_bytes });
1315+
1316+
string install_url = Node.MiniAppManager.getAppInstallURL(appId);
1317+
1318+
FriendList.addMessageWithType(session_id, FriendMessageType.appSession, friend.walletAddress, 0, appId, true, null, 0, false);
1319+
StreamProcessor.sendAppRequest(friend, appId, session_id, null);
13111320
}
13121321
catch (Exception ex)
13131322
{
13141323
Logging.error("Navigation failed: " + ex.Message);
13151324
}
13161325
}
13171326

1327+
public byte[] onJoinApp(string appId, Address[] userAddresses)
1328+
{
1329+
MiniAppPage miniAppPage = new MiniAppPage(appId, IxianHandler.getWalletStorage().getPrimaryAddress(), userAddresses, Node.MiniAppManager.getAppEntryPoint(appId));
1330+
miniAppPage.accepted = true;
1331+
Node.MiniAppManager.addAppPage(miniAppPage);
1332+
1333+
MainThread.BeginInvokeOnMainThread(() =>
1334+
{
1335+
Navigation.PushAsync(miniAppPage, Config.defaultXamarinAnimations);
1336+
});
1337+
return miniAppPage.sessionId;
1338+
}
1339+
public async void onInstallApp(string appUrl)
1340+
{
1341+
MiniApp? app = await Node.MiniAppManager.fetch(appUrl);
1342+
if (app == null)
1343+
{
1344+
return;
1345+
}
1346+
1347+
app.url = appUrl;
1348+
1349+
MainThread.BeginInvokeOnMainThread(() =>
1350+
{
1351+
Navigation.PushAsync(new AppDetailsPage(app), Config.defaultXamarinAnimations);
1352+
});
1353+
}
13181354
private void onAppDetails(string appId)
13191355
{
13201356
Navigation.PushAsync(new AppDetailsPage(appId), Config.defaultXamarinAnimations);

0 commit comments

Comments
 (0)