Skip to content

Commit 41c9cb4

Browse files
authored
Merge pull request #13 from modos189/feat/share
feat: implement geolocation sharing
2 parents 367456b + 3c74b00 commit 41c9cb4

File tree

8 files changed

+267
-10
lines changed

8 files changed

+267
-10
lines changed

App_Resources/Android/src/main/res/values-v21/colors.xml

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3+
<!-- Main colors from your design system -->
34
<color name="ns_primary">#0e3d4e</color>
4-
<color name="ns_primaryDark">#0e3d4e</color>
5-
<color name="ns_accent">#0b303e</color>
6-
<color name="ns_blue">#272734</color>
5+
<color name="ns_primaryDark">#0b303e</color>
6+
<color name="ns_accent">#4a9299</color>
7+
8+
<!-- Additional colors -->
9+
<color name="ns_surface_bright">#275160</color>
10+
<color name="ns_primary_light">#56b0b9</color>
11+
<color name="ns_text">#ffffff</color>
712
</resources>

App_Resources/Android/src/main/res/values/styles.xml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<resources xmlns:android="http://schemas.android.com/apk/res/android">
33

44
<!-- theme to use FOR launch screen-->
5-
<style name="LaunchScreenThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
5+
<style name="LaunchScreenThemeBase" parent="Theme.MaterialComponents.Light.NoActionBar">
66
<item name="toolbarStyle">@style/NativeScriptToolbarStyle</item>
77

88
<item name="colorPrimary">@color/ns_primary</item>
@@ -26,11 +26,35 @@
2626
<item name="colorPrimary">@color/ns_primary</item>
2727
<item name="colorPrimaryDark">@color/ns_primaryDark</item>
2828
<item name="colorAccent">@color/ns_accent</item>
29+
30+
<!-- Добавляем настройки для элементов диалогов -->
31+
<item name="materialButtonStyle">@style/AppButtonStyle</item>
32+
<item name="alertDialogTheme">@style/AppAlertDialogTheme</item>
33+
<item name="materialAlertDialogTheme">@style/AppAlertDialogTheme</item>
2934
</style>
3035

3136
<style name="AppTheme" parent="AppThemeBase">
3237
</style>
3338

39+
<!-- Custom button style -->
40+
<style name="AppButtonStyle" parent="Widget.MaterialComponents.Button">
41+
<item name="backgroundTint">@color/ns_accent</item>
42+
<item name="android:textColor">@color/ns_text</item>
43+
</style>
44+
45+
<!-- Custom dialog style -->
46+
<style name="AppAlertDialogTheme" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
47+
<item name="colorPrimary">@color/ns_accent</item>
48+
<item name="colorAccent">@color/ns_primary_light</item>
49+
<item name="buttonBarPositiveButtonStyle">@style/AppDialogButtonStyle</item>
50+
<item name="buttonBarNegativeButtonStyle">@style/AppDialogButtonStyle</item>
51+
<item name="buttonBarNeutralButtonStyle">@style/AppDialogButtonStyle</item>
52+
</style>
53+
54+
<style name="AppDialogButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
55+
<item name="android:textColor">@color/ns_primary_light</item>
56+
</style>
57+
3458
<!-- theme for action-bar -->
3559
<style name="NativeScriptToolbarStyleBase" parent="Widget.AppCompat.Toolbar">
3660
<item name="android:background">@color/ns_primary</item>

app/utils/bridge.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3
22

33
import {
4+
sharePosition,
45
switchToPane,
56
bootFinished,
67
getVersionName,
@@ -18,6 +19,16 @@ export const router = async (event) => {
1819
const [eventName, eventData] = event;
1920

2021
switch (eventName) {
22+
case "intentPosLink":
23+
await sharePosition(
24+
eventData.lat,
25+
eventData.lng,
26+
eventData.zoom,
27+
eventData.title,
28+
eventData.isPortal,
29+
eventData.guid || null
30+
);
31+
break;
2132
case "switchToPane":
2233
await switchToPane(eventData.id);
2334
break;
@@ -60,7 +71,7 @@ export const router = async (event) => {
6071
export const injectBridgeIITC = async (webview) => {
6172
let bridge = "window.app = window.nsWebViewBridge;";
6273
const events = {
63-
intentPosLink: ["lat", "lng", "zoom", "title", "isPortal"],
74+
intentPosLink: ["lat", "lng", "zoom", "title", "isPortal", "guid"],
6475
shareString: ["str"],
6576
spinnerEnabled: ["en"],
6677
copy: ["s"],

app/utils/events-from-iitc.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3
22

33
import store from "@/store";
4+
import { showLocationShareOptions } from "./share";
5+
6+
7+
/**
8+
* Handles request to share geographic position
9+
* @param {number} lat - Latitude
10+
* @param {number} lng - Longitude
11+
* @param {number} zoom - Map zoom level
12+
* @param {string} title - Location title
13+
* @param {boolean} isPortal - Whether this is a portal location
14+
* @param {string} guid - Portal's globally unique identifier
15+
* @returns {Promise<boolean>} Success status
16+
*/
17+
export const sharePosition = (lat, lng, zoom, title, isPortal, guid) => {
18+
return showLocationShareOptions(lat, lng, title, isPortal, guid);
19+
}
420

521
/**
622
* Returns the versionName of mobile app

app/utils/platform.js

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import {Application, Utils} from "@nativescript/core";
1+
//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3
2+
3+
import {Application, Utils, isAndroid, isIOS, Dialogs} from "@nativescript/core";
24

35
export const getStatusBarHeight = () => {
46
let result = 0;
@@ -25,3 +27,100 @@ export const getNavigationBarHeight = () => {
2527
}
2628
return result;
2729
}
30+
31+
/**
32+
* Universal sharing function for different content types
33+
* @param {any} content - Content to share (object for geo, string for text/url)
34+
* @param {string} contentType - Type of content ('geo', 'text', 'url', 'prime')
35+
* @param {string} title - Optional title or description
36+
* @returns {boolean} Success status
37+
*/
38+
export const shareContent = (content, contentType, title = '') => {
39+
try {
40+
if (isAndroid) {
41+
const activity = Application.android.foregroundActivity || Application.android.startActivity;
42+
if (!activity) return false;
43+
44+
const intent = new android.content.Intent();
45+
46+
if (contentType === "geo") {
47+
// Share as geo-coordinates
48+
const lat = content.lat;
49+
const lng = content.lng;
50+
const geoUri = `geo:${lat},${lng}?q=${lat},${lng}${title ? `(${encodeURIComponent(title)})` : ''}`;
51+
52+
intent.setAction(android.content.Intent.ACTION_VIEW);
53+
intent.setData(android.net.Uri.parse(geoUri));
54+
}
55+
else if (contentType === "url" || contentType === "text") {
56+
// Share as text or URL
57+
intent.setAction(android.content.Intent.ACTION_SEND);
58+
intent.setType("text/plain");
59+
intent.putExtra(android.content.Intent.EXTRA_TEXT, content);
60+
if (title) {
61+
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, title);
62+
}
63+
}
64+
else if (contentType === "prime") {
65+
// Open in Ingress Prime
66+
intent.setAction(android.content.Intent.ACTION_VIEW);
67+
intent.setData(android.net.Uri.parse(content));
68+
}
69+
70+
// Show app chooser dialog
71+
const chooserTitle = {
72+
geo: "Open in maps",
73+
url: "Open URL",
74+
text: "Share text",
75+
prime: "Open in Ingress Prime"
76+
}[contentType] || "Share via";
77+
78+
const chooser = android.content.Intent.createChooser(intent, chooserTitle);
79+
activity.startActivity(chooser);
80+
81+
return true;
82+
}
83+
else if (isIOS) {
84+
const shareItems = [];
85+
86+
if (contentType === "geo") {
87+
// Share as geo location with URL schemes
88+
const lat = content.lat;
89+
const lng = content.lng;
90+
const locationTitle = title || `${lat},${lng}`;
91+
92+
// Add text and multiple URL schemes for map apps
93+
shareItems.push(`${locationTitle}\nCoordinates: ${lat},${lng}`);
94+
shareItems.push(`maps://?ll=${lat},${lng}&q=${encodeURIComponent(locationTitle)}`);
95+
shareItems.push(`comgooglemaps://?q=${lat},${lng}`);
96+
shareItems.push(`https://www.google.com/maps/search/?api=1&query=${lat},${lng}`);
97+
}
98+
else if (contentType === "url" || contentType === "text") {
99+
// Share as text or URL
100+
shareItems.push(content);
101+
}
102+
else if (contentType === "prime") {
103+
// Open directly in Ingress Prime
104+
const url = NSURL.URLWithString(content);
105+
UIApplication.sharedApplication.openURL(url);
106+
return true;
107+
}
108+
109+
// Create and show UIActivityViewController
110+
const controller = UIActivityViewController.alloc().initWithActivityItemsApplicationActivities(
111+
shareItems, null
112+
);
113+
114+
const rootController = UIApplication.sharedApplication.keyWindow.rootViewController;
115+
rootController.presentViewControllerAnimatedCompletion(controller, true, null);
116+
117+
return true;
118+
}
119+
120+
return false;
121+
} catch (error) {
122+
console.error("Error sharing content:", error);
123+
return false;
124+
}
125+
};
126+

app/utils/share.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3
2+
3+
import { action } from "@nativescript-community/ui-material-dialogs";
4+
import { shareContent } from "~/utils/platform";
5+
6+
/**
7+
* Creates a link to open a specific portal in Ingress Prime.
8+
* Uses Firebase's Dynamic Links feature.
9+
* https://firebase.google.com/docs/dynamic-links/create-manually
10+
*
11+
* Based on approach from:
12+
* https://github.com/IITC-CE/ingress-intel-total-conversion/pull/817
13+
*
14+
* @param {string} guid - Portal's globally unique identifier
15+
* @param {number} lat - Latitude of the portal
16+
* @param {number} lng - Longitude of the portal
17+
* @returns {string} URL that opens Ingress Prime with portal details
18+
*/
19+
const makePrimeLink = (guid, lat, lng) => {
20+
const base = 'https://link.ingress.com/';
21+
22+
// Define URL components
23+
const link = {
24+
'link': `https://intel.ingress.com/portal/${guid || ''}`
25+
};
26+
const android = {
27+
'apn': 'com.nianticproject.ingress'
28+
};
29+
const ios = {
30+
'isi': '576505181',
31+
'ibi': 'com.google.ingress',
32+
'ifl': 'https://apps.apple.com/app/ingress/id576505181'
33+
};
34+
const other = {
35+
'ofl': `https://intel.ingress.com/intel?pll=${lat},${lng}`
36+
};
37+
38+
// Construct URL with all parameters
39+
const url = new URL(base);
40+
for (const [key, value] of Object.entries({...link, ...android, ...ios, ...other})) {
41+
url.searchParams.set(key, value);
42+
}
43+
44+
return url.toString();
45+
};
46+
47+
/**
48+
* Shows a dialog with location sharing options
49+
* @param {number} lat - Latitude
50+
* @param {number} lng - Longitude
51+
* @param {string} title - Optional location title
52+
* @param {boolean} isPortal - Whether this location is a portal
53+
* @param {string} guid - Portal's globally unique identifier
54+
* @returns {Promise<boolean>} Success status
55+
*/
56+
export const showLocationShareOptions = (lat, lng, title = '', isPortal = false, guid = '') => {
57+
try {
58+
// Prepare options for the dialog
59+
const options = {
60+
title: "Share Location",
61+
message: "Choose how to share",
62+
cancelButtonText: "Cancel",
63+
actions: ["Share as text", "Open in maps", "Share link"]
64+
};
65+
66+
// Add Ingress Prime option if it's a portal or has guid
67+
if (isPortal || guid) {
68+
options.actions.push("Open in Ingress Prime");
69+
}
70+
71+
// Show dialog with options
72+
return action(options).then(result => {
73+
// User cancelled if result is undefined or equals cancelButtonText
74+
if (!result || result === options.cancelButtonText) return false;
75+
76+
const index = options.actions.indexOf(result);
77+
if (index === -1) return false;
78+
79+
switch (index) {
80+
case 0: // Share as text
81+
const textContent = `${title ? title + '\n' : ''}Location: ${lat},${lng}`;
82+
return shareContent(textContent, "text", title || "Location");
83+
84+
case 1: // Open in maps
85+
return shareContent({lat, lng}, "geo", title);
86+
87+
case 2: // Share link
88+
const url = `https://intel.ingress.com/?ll=${lat},${lng}&z=17${isPortal ? `&pll=${lat},${lng}` : ''}`;
89+
return shareContent(url, "url", title || "Intel Map");
90+
91+
case 3: // Open in Ingress Prime (if available)
92+
if (options.actions.length > 3) {
93+
const primeUrl = makePrimeLink(guid, lat, lng);
94+
return shareContent(primeUrl, "prime");
95+
}
96+
return false;
97+
}
98+
99+
return false;
100+
});
101+
} catch (error) {
102+
console.error("Error showing location share options:", error);
103+
return Promise.resolve(false);
104+
}
105+
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@nativescript-community/ui-label": "^1.3.33",
1616
"@nativescript-community/ui-material-bottomsheet": "^7.2.66",
1717
"@nativescript-community/ui-material-button": "^7.2.66",
18+
"@nativescript-community/ui-material-dialogs": "^7.2.71",
1819
"@nativescript-community/ui-material-ripple": "^7.2.71",
1920
"@nativescript-community/ui-svg": "^0.2.6",
2021
"@nativescript-community/ui-webview": "^1.5.0",

0 commit comments

Comments
 (0)