Initial nativescript testing
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 2m24s

This commit is contained in:
Broks Randolfs Gailītis 2025-03-02 15:05:30 +02:00
parent fdb179d183
commit 4c21600576
13 changed files with 270 additions and 30 deletions

View File

@ -8,7 +8,6 @@ platforms/
*.js *.js
!eslint.config.js !eslint.config.js
!webpack.config.js !webpack.config.js
!tailwind.config.js
# Logs # Logs
logs logs

View File

@ -14,6 +14,9 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application <application
android:name="com.tns.NativeScriptApplication" android:name="com.tns.NativeScriptApplication"

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="_amenity_restaurant_CYImm">"Restaurant"</string>
<string name="_amenity_pub_hVjSx">"Pub"</string>
<string name="_amenity_ice_cream_12K4Gu">"Ice Cream"</string>
<string name="_amenity_food_court_Z2qUk6P">"Food Court"</string>
<string name="_amenity_fast_food_Z1zDs8J">"Fast Food"</string>
<string name="_amenity_cafe_Z13hX6o">"Cafe"</string>
<string name="_amenity_biergarten_zXiC4">"Beer Garden"</string>
<string name="_amenity_bar_hVg75">"Bar"</string>
<string name="_app_name_1k3Sbz">"Blakus"</string>
<string name="app_name">"Blakus"</string>
<string name="title_activity_kimera">"Blakus"</string>
</resources>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="_amenity_restaurant_CYImm">"Restorāns"</string>
<string name="_amenity_pub_hVjSx">"Krogs"</string>
<string name="_amenity_ice_cream_12K4Gu">"Saldējums"</string>
<string name="_amenity_food_court_Z2qUk6P">"Ēdināšanas laukums"</string>
<string name="_amenity_fast_food_Z1zDs8J">"Ātras uzkodas"</string>
<string name="_amenity_cafe_Z13hX6o">"Kafejnīca"</string>
<string name="_amenity_biergarten_zXiC4">"Biergarten"</string>
<string name="_amenity_bar_hVg75">"Bārs"</string>
<string name="_app_name_1k3Sbz">"Blakus"</string>
<string name="app_name">"Blakus"</string>
<string name="title_activity_kimera">"Blakus"</string>
</resources>

View File

@ -4,11 +4,12 @@
"license": "SEE LICENSE IN <your-license-filename>", "license": "SEE LICENSE IN <your-license-filename>",
"repository": "<fill-your-repository-here>", "repository": "<fill-your-repository-here>",
"dependencies": { "dependencies": {
"@nativescript/core": "*" "@nativescript/core": "*",
"@nativescript/geolocation": "^9.0.0",
"@nativescript/localize": "^5.2.0"
}, },
"devDependencies": { "devDependencies": {
"@nativescript/android": "~8.8.0", "@nativescript/android": "~8.8.0",
"@nativescript/ios": "~8.8.0", "@nativescript/ios": "~8.8.0"
"@nativescript/tailwind": "^2.1.0"
} }
} }

View File

@ -1 +1,7 @@
 .ns-root {
--base-color: red;
}
Button {
background-color: var(--base-color);
}

View File

@ -1,3 +1,7 @@
import { Application } from '@nativescript/core'; import { Application } from '@nativescript/core';
import { localize } from '@nativescript/localize';
Application.setResources({ L: localize })
Application.run({ moduleName: 'app-root' }); Application.run({ moduleName: 'app-root' });
console.log(localize('app.name'));

View File

@ -0,0 +1,13 @@
{
"app.name": "Blakus",
"amenity": {
"bar": "Bar",
"biergarten": "Beer Garden",
"cafe": "Cafe",
"fast_food": "Fast Food",
"food_court": "Food Court",
"ice_cream": "Ice Cream",
"pub": "Pub",
"restaurant": "Restaurant"
}
}

View File

@ -0,0 +1,13 @@
{
"app.name": "Blakus",
"amenity": {
"bar": "Bārs",
"biergarten": "Biergarten",
"cafe": "Kafejnīca",
"fast_food": "Ātras uzkodas",
"food_court": "Ēdināšanas laukums",
"ice_cream": "Saldējums",
"pub": "Krogs",
"restaurant": "Restorāns"
}
}

View File

@ -0,0 +1,32 @@
import * as geolocation from '@nativescript/geolocation';
import { CoreTypes } from '@nativescript/core'
export class DeviceLocation {
static async getDeviceLocation() {
try {
// Enable location services
await geolocation.enableLocationRequest(false, true);
const enabled = await geolocation.isEnabled();
console.log('what do we got?', enabled)
// Get the current location
const location = await geolocation.getCurrentLocation({
desiredAccuracy: CoreTypes.Accuracy.high,
maximumAge: 5000,
timeout: 20000,
});
if (location) {
console.log(`Latitude: ${location.latitude}, Longitude: ${location.longitude}`);
return location;
} else {
throw new Error('Could not get the location.');
}
} catch (error) {
console.error('Error obtaining location:', error);
}
}
}

View File

@ -1,11 +1,22 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page"> <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
<Page.actionBar> <Page.actionBar>
<ActionBar title="My App" icon="" class="action-bar"> <ActionBar title="Blakus" icon="" class="action-bar">
</ActionBar> </ActionBar>
</Page.actionBar> </Page.actionBar>
<StackLayout class="p-20"> <StackLayout class="p-20">
<Label text="Tap the button" class="h1 text-center"/> <Label text="Tap the button" class="h1 text-center"/>
<Button text="TAP" tap="{{ onTap }}" class="btn btn-primary btn-active"/> <Button text="TAP" tap="{{ onTap }}" />
<Button text="Location" tap="{{ onLocationTap }}" />
<Label text="{{ message }}" class="h2 text-center" textWrap="true"/> <Label text="{{ message }}" class="h2 text-center" textWrap="true"/>
<ListView items="{{ items }}" itemTap="{{ onItemTap }}">
<ListView.itemTemplate>
<!-- The item template can only have a single root element -->
<GridLayout padding="16" columns="20, *, *">
<ContentView width="20" height="20" borderRadius="20" backgroundColor="#65adf1" />
<Label text="{{ title }}" col="1" textWrap="true" marginLeft="8" />
<Label text="{{ L(`amenity.${amenity}`) }}" col="2" />
</GridLayout>
</ListView.itemTemplate>
</ListView>
</StackLayout> </StackLayout>
</Page> </Page>

View File

@ -1,8 +1,53 @@
import { Observable } from '@nativescript/core'; import { Observable, Http, type ItemEventData, ListView } from '@nativescript/core';
import { DeviceLocation } from './location';
import type { Location } from '@nativescript/geolocation';
import { localize } from '@nativescript/localize';
type ReverseGeocodeResult = {
valsts: string;
admin_vien: string;
terit_vien: string;
apdz_vieta: string;
iela: string;
maja: string;
index: string;
korpus: string;
vzd_id: string;
distance: number;
x: string;
y: string;
lon: number;
lat: number;
adrese: string;
}
type BlakusItem = {
title: string;
amenity: string;
tags: Record<string, string>;
}
type OverpassResult = {
version: number;
generator: string;
osm3s: Record<string, string>;
elements: {
type: string,
id: number,
tags: Record<string, string>;
}[]
}
async function aWait(timeout: number) {
return new Promise(resolve => setTimeout(resolve, timeout));
}
export class HelloWorldModel extends Observable { export class HelloWorldModel extends Observable {
private _counter: number; private _counter: number;
private _message: string; private _message: string;
private _items: BlakusItem[];
private _locationServiceBaseURL = 'https://api.kartes.lv/v3/KVDM_mwwKi/'
private _overpassBaseURL = 'https://overpass-api.de/api/';
constructor() { constructor() {
super(); super();
@ -23,17 +68,115 @@ export class HelloWorldModel extends Observable {
} }
} }
onTap() { get items(): BlakusItem[] {
this._counter--; return this._items || [];
this.updateMessage();
} }
private updateMessage() { set items(value: BlakusItem[]) {
if (this._items !== value) {
this._items = value;
this.notifyPropertyChange('items', value);
}
}
async reverseGeocode({latitude, longitude}: Location) {
const params = new URLSearchParams();
params.set('lat', latitude.toString());
params.set('lon', longitude.toString());
this.message = [latitude, longitude].join(', ');
const url = new URL(`reverse_geocoding?${params}`, this._locationServiceBaseURL).toString();
const result = await Http.getJSON<ReverseGeocodeResult>(url);
console.log(result);
return result;
}
overpassQuery(query: string) {
const url = new URL(`interpreter?data=${encodeURIComponent(query)}`, this._overpassBaseURL).toString();
console.log('overpass query', url)
return Http.getJSON<OverpassResult>(url)
}
async getOverpassAmenities({latitude, longitude}: Location) {
const radius = 1000; // meters
const query = `
[out:json][timeout:25];
node["amenity"](around:${radius}, ${latitude}, ${longitude});
out tags;
`;
const result = await this.overpassQuery(query);
console.log('yep');
console.log(Array.from(new Set(result.elements.map(el => el.tags['amenity']))));
}
async getOverpassInfo({latitude, longitude}: Location) {
const radius = 1000; // meters
const amenities = ['restaurant', 'cafe', 'bar', 'pub', 'biergarten', 'fast_food', 'food_court', 'ice_cream'];
const query = `
[out:json];
node(around:${radius}, ${latitude}, ${longitude})["amenity"~"${amenities.join('|')}"];
out body;
>;
out skel qt;
`;
const result = await this.overpassQuery(query);
this.items = result.elements
.map(({ tags }) => ({ title: tags.name, amenity: tags.amenity, tags}))
.filter(i => amenities.includes(i.amenity)); // query returns some random results. need to look into this
console.log(localize('app.name'))
console.log(result.elements);
}
async postalPolygons(country = 'LV', code: string, mode: 'isolate' | 'collect' | 'union' = 'union') {
await aWait(1000);
const params = new URLSearchParams();
if (code.length === 4) params.set('search', `${country}-${code}`);
if (code.length == 2) params.set('groups', `${country}-${code}`)
params.set('union_mode', mode);
params.set('wgs84', '');
// params.set('wkt', '');
const url = new URL(`postal_codes?${params}`, this._locationServiceBaseURL).toString();
const result = await Http.getJSON(url);
console.log(url);
console.log(result);
}
async onLocationTap() {
const location = await DeviceLocation.getDeviceLocation();
await this.getOverpassInfo(location);
// await this.getOverpassAmenities(location);
// const { index } = await this.reverseGeocode(location);
// const [country, code] = index.split('-');
// await this.postalPolygons(country, code);
}
onTap() {
this._counter--;
if (this._counter <= 0) { if (this._counter <= 0) {
this.message = this.updateMessage('Hoorraaay! You unlocked the NativeScript clicker achievement!');
'Hoorraaay! You unlocked the NativeScript clicker achievement!';
} else { } else {
this.message = `${this._counter} taps left`; this.updateMessage(`${this._counter} taps left`);
} }
} }
onItemTap(args: ItemEventData) {
const listView = args.object as ListView
console.log('Tapped item', listView.items[args.index])
}
private updateMessage(message = "") {
this.message = message;
}
} }

View File

@ -1,13 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{css,xml,html,vue,svelte,ts,tsx}'],
// use the .ns-dark class to control dark mode (applied by NativeScript) - since 'media' (default) is not supported.
darkMode: ['class', '.ns-dark'],
theme: {
extend: {},
},
plugins: [],
corePlugins: {
preflight: false, // disables browser-specific resets
},
};