mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Merge pull request #1380 from RoboSats/android-notification-system
Android notification system
This commit is contained in:
commit
e0bd103c4f
@ -24,7 +24,11 @@ const App = (): JSX.Element => {
|
|||||||
<GarageContextProvider>
|
<GarageContextProvider>
|
||||||
<FederationContextProvider>
|
<FederationContextProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{(window.NativeRobosats === undefined && window.RobosatsClient === undefined )? <HostAlert /> : <TorConnectionBadge />}
|
{window.NativeRobosats === undefined && window.RobosatsClient === undefined ? (
|
||||||
|
<HostAlert />
|
||||||
|
) : (
|
||||||
|
<TorConnectionBadge />
|
||||||
|
)}
|
||||||
<Main />
|
<Main />
|
||||||
</FederationContextProvider>
|
</FederationContextProvider>
|
||||||
</GarageContextProvider>
|
</GarageContextProvider>
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { MemoryRouter,HashRouter ,BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { MemoryRouter, HashRouter, BrowserRouter } from 'react-router-dom';
|
||||||
import { Box, Slide, Typography, styled } from '@mui/material';
|
import { Box, Typography, styled } from '@mui/material';
|
||||||
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
||||||
|
|
||||||
import { RobotPage, MakerPage, BookPage, OrderPage, SettingsPage, NavBar, MainDialogs } from './';
|
import { NavBar, MainDialogs } from './';
|
||||||
import RobotAvatar from '../components/RobotAvatar';
|
import RobotAvatar from '../components/RobotAvatar';
|
||||||
import Notifications from '../components/Notifications';
|
import Notifications from '../components/Notifications';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
|
||||||
|
import Routes from './Routes';
|
||||||
|
|
||||||
function getRouter() {
|
const getRouter = (): any => {
|
||||||
if (window.NativeRobosats === undefined && window.RobosatsClient === undefined) {
|
if (window.NativeRobosats === undefined && window.RobosatsClient === undefined) {
|
||||||
return BrowserRouter;
|
return BrowserRouter;
|
||||||
} else if (window.RobosatsClient === 'desktop-app') {
|
} else if (window.RobosatsClient === 'desktop-app') {
|
||||||
@ -18,7 +19,7 @@ function getRouter() {
|
|||||||
} else {
|
} else {
|
||||||
return MemoryRouter;
|
return MemoryRouter;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const Router = getRouter();
|
const Router = getRouter();
|
||||||
|
|
||||||
const TestnetTypography = styled(Typography)({
|
const TestnetTypography = styled(Typography)({
|
||||||
@ -38,7 +39,7 @@ const MainBox = styled(Box)<MainBoxProps>((props) => ({
|
|||||||
|
|
||||||
const Main: React.FC = () => {
|
const Main: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { settings, page, slideDirection, setOpen, windowSize, navbarHeight } =
|
const { settings, page, setOpen, windowSize, navbarHeight } =
|
||||||
useContext<UseAppStoreType>(AppContext);
|
useContext<UseAppStoreType>(AppContext);
|
||||||
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
@ -62,88 +63,7 @@ const Main: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<MainBox navbarHeight={navbarHeight}>
|
<MainBox navbarHeight={navbarHeight}>
|
||||||
|
<Routes />
|
||||||
<Routes>
|
|
||||||
{['/robot/:token?', '/', ''].map((path, index) => {
|
|
||||||
return (
|
|
||||||
<Route
|
|
||||||
path={path}
|
|
||||||
element={
|
|
||||||
<Slide
|
|
||||||
direction={page === 'robot' ? slideDirection.in : slideDirection.out}
|
|
||||||
in={page === 'robot'}
|
|
||||||
appear={slideDirection.in !== undefined}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<RobotPage />
|
|
||||||
</div>
|
|
||||||
</Slide>
|
|
||||||
}
|
|
||||||
key={index}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path={'/offers'}
|
|
||||||
element={
|
|
||||||
<Slide
|
|
||||||
direction={page === 'offers' ? slideDirection.in : slideDirection.out}
|
|
||||||
in={page === 'offers'}
|
|
||||||
appear={slideDirection.in !== undefined}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<BookPage />
|
|
||||||
</div>
|
|
||||||
</Slide>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path='/create'
|
|
||||||
element={
|
|
||||||
<Slide
|
|
||||||
direction={page === 'create' ? slideDirection.in : slideDirection.out}
|
|
||||||
in={page === 'create'}
|
|
||||||
appear={slideDirection.in !== undefined}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<MakerPage />
|
|
||||||
</div>
|
|
||||||
</Slide>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path='/order/:shortAlias/:orderId'
|
|
||||||
element={
|
|
||||||
<Slide
|
|
||||||
direction={page === 'order' ? slideDirection.in : slideDirection.out}
|
|
||||||
in={page === 'order'}
|
|
||||||
appear={slideDirection.in !== undefined}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<OrderPage />
|
|
||||||
</div>
|
|
||||||
</Slide>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path='/settings'
|
|
||||||
element={
|
|
||||||
<Slide
|
|
||||||
direction={page === 'settings' ? slideDirection.in : slideDirection.out}
|
|
||||||
in={page === 'settings'}
|
|
||||||
appear={slideDirection.in !== undefined}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<SettingsPage />
|
|
||||||
</div>
|
|
||||||
</Slide>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</MainBox>
|
</MainBox>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<MainDialogs />
|
<MainDialogs />
|
||||||
|
114
frontend/src/basic/Routes.tsx
Normal file
114
frontend/src/basic/Routes.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import React, { useContext, useEffect } from 'react';
|
||||||
|
import { Routes as DomRoutes, Route, useNavigate } from 'react-router-dom';
|
||||||
|
import { Slide } from '@mui/material';
|
||||||
|
import { type UseAppStoreType, AppContext } from '../contexts/AppContext';
|
||||||
|
|
||||||
|
import { RobotPage, MakerPage, BookPage, OrderPage, SettingsPage } from '.';
|
||||||
|
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
|
||||||
|
|
||||||
|
const Routes: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
const { page, slideDirection } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('navigateToPage', (event) => {
|
||||||
|
console.log('navigateToPage', JSON.stringify(event));
|
||||||
|
const orderId: string = event?.detail?.order_id;
|
||||||
|
const coordinator: string = event?.detail?.coordinator;
|
||||||
|
if (orderId && coordinator) {
|
||||||
|
const slot = garage.getSlotByOrder(coordinator, orderId);
|
||||||
|
if (slot?.token) {
|
||||||
|
garage.setCurrentSlot(slot?.token);
|
||||||
|
navigate(`/order/${coordinator}/${orderId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DomRoutes>
|
||||||
|
{['/robot/:token?', '/', ''].map((path, index) => {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
path={path}
|
||||||
|
element={
|
||||||
|
<Slide
|
||||||
|
direction={page === 'robot' ? slideDirection.in : slideDirection.out}
|
||||||
|
in={page === 'robot'}
|
||||||
|
appear={slideDirection.in !== undefined}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<RobotPage />
|
||||||
|
</div>
|
||||||
|
</Slide>
|
||||||
|
}
|
||||||
|
key={index}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={'/offers'}
|
||||||
|
element={
|
||||||
|
<Slide
|
||||||
|
direction={page === 'offers' ? slideDirection.in : slideDirection.out}
|
||||||
|
in={page === 'offers'}
|
||||||
|
appear={slideDirection.in !== undefined}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<BookPage />
|
||||||
|
</div>
|
||||||
|
</Slide>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path='/create'
|
||||||
|
element={
|
||||||
|
<Slide
|
||||||
|
direction={page === 'create' ? slideDirection.in : slideDirection.out}
|
||||||
|
in={page === 'create'}
|
||||||
|
appear={slideDirection.in !== undefined}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<MakerPage />
|
||||||
|
</div>
|
||||||
|
</Slide>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path='/order/:shortAlias/:orderId'
|
||||||
|
element={
|
||||||
|
<Slide
|
||||||
|
direction={page === 'order' ? slideDirection.in : slideDirection.out}
|
||||||
|
in={page === 'order'}
|
||||||
|
appear={slideDirection.in !== undefined}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<OrderPage />
|
||||||
|
</div>
|
||||||
|
</Slide>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path='/settings'
|
||||||
|
element={
|
||||||
|
<Slide
|
||||||
|
direction={page === 'settings' ? slideDirection.in : slideDirection.out}
|
||||||
|
in={page === 'settings'}
|
||||||
|
appear={slideDirection.in !== undefined}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<SettingsPage />
|
||||||
|
</div>
|
||||||
|
</Slide>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DomRoutes>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Routes;
|
@ -174,8 +174,8 @@ const OrderDetails = ({
|
|||||||
|
|
||||||
const isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
|
const isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
|
||||||
const tradeFee = order.is_maker
|
const tradeFee = order.is_maker
|
||||||
? (coordinator.info?.maker_fee ?? 0)
|
? coordinator.info?.maker_fee ?? 0
|
||||||
: (coordinator.info?.taker_fee ?? 0);
|
: coordinator.info?.taker_fee ?? 0;
|
||||||
const defaultRoutingBudget = 0.001;
|
const defaultRoutingBudget = 0.001;
|
||||||
const btc_now = order.satoshis_now / 100000000;
|
const btc_now = order.satoshis_now / 100000000;
|
||||||
const rate = Number(order.max_amount ?? order.amount) / btc_now;
|
const rate = Number(order.max_amount ?? order.amount) / btc_now;
|
||||||
|
@ -76,20 +76,13 @@ const makeTheme = function (settings: Settings): Theme {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getHostUrl = (network = 'mainnet'): string => {
|
const getHostUrl = (network = 'mainnet'): string => {
|
||||||
let host = '';
|
let host = defaultFederation.exp[network].onion;
|
||||||
let protocol = '';
|
let protocol = 'http:';
|
||||||
if(isDesktopRoboSats){
|
if (window.NativeRobosats === undefined) {
|
||||||
host = defaultFederation.exp[network].onion;
|
|
||||||
protocol = 'http:';
|
|
||||||
}
|
|
||||||
else if (window.NativeRobosats === undefined) {
|
|
||||||
host = getHost();
|
host = getHost();
|
||||||
protocol = location.protocol;
|
protocol = location.protocol;
|
||||||
} else {
|
|
||||||
host = defaultFederation.exp[network].onion;
|
|
||||||
protocol = 'http:';
|
|
||||||
}
|
}
|
||||||
const hostUrl = `${host}`;
|
const hostUrl = `${protocol}//${host}`;
|
||||||
return hostUrl;
|
return hostUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
defaultExchange,
|
defaultExchange,
|
||||||
} from '.';
|
} from '.';
|
||||||
import defaultFederation from '../../static/federation.json';
|
import defaultFederation from '../../static/federation.json';
|
||||||
|
import { systemClient } from '../services/System';
|
||||||
import { getHost } from '../utils';
|
import { getHost } from '../utils';
|
||||||
import { coordinatorDefaultValues } from './Coordinator.model';
|
import { coordinatorDefaultValues } from './Coordinator.model';
|
||||||
import { updateExchangeInfo } from './Exchange.model';
|
import { updateExchangeInfo } from './Exchange.model';
|
||||||
@ -105,9 +106,12 @@ export class Federation {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateUrl = async (origin: Origin, settings: Settings, hostUrl: string): Promise<void> => {
|
updateUrl = async (origin: Origin, settings: Settings, hostUrl: string): Promise<void> => {
|
||||||
|
const federationUrls = {};
|
||||||
for (const coor of Object.values(this.coordinators)) {
|
for (const coor of Object.values(this.coordinators)) {
|
||||||
coor.updateUrl(origin, settings, hostUrl);
|
coor.updateUrl(origin, settings, hostUrl);
|
||||||
|
federationUrls[coor.shortAlias] = coor.url;
|
||||||
}
|
}
|
||||||
|
systemClient.setCookie('federation', JSON.stringify(federationUrls));
|
||||||
};
|
};
|
||||||
|
|
||||||
update = async (): Promise<void> => {
|
update = async (): Promise<void> => {
|
||||||
|
@ -83,7 +83,7 @@ class Garage {
|
|||||||
// Slots
|
// Slots
|
||||||
getSlot: (token?: string) => Slot | null = (token) => {
|
getSlot: (token?: string) => Slot | null = (token) => {
|
||||||
const currentToken = token ?? this.currentSlot;
|
const currentToken = token ?? this.currentSlot;
|
||||||
return currentToken ? (this.slots[currentToken] ?? null) : null;
|
return currentToken ? this.slots[currentToken] ?? null : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteSlot: (token?: string) => void = (token) => {
|
deleteSlot: (token?: string) => void = (token) => {
|
||||||
@ -104,6 +104,7 @@ class Garage {
|
|||||||
const slot = this.getSlot(token);
|
const slot = this.getSlot(token);
|
||||||
if (attributes) {
|
if (attributes) {
|
||||||
if (attributes.copiedToken !== undefined) slot?.setCopiedToken(attributes.copiedToken);
|
if (attributes.copiedToken !== undefined) slot?.setCopiedToken(attributes.copiedToken);
|
||||||
|
this.save();
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onRobotUpdate');
|
||||||
}
|
}
|
||||||
return slot;
|
return slot;
|
||||||
@ -111,9 +112,22 @@ class Garage {
|
|||||||
|
|
||||||
setCurrentSlot: (currentSlot: string) => void = (currentSlot) => {
|
setCurrentSlot: (currentSlot: string) => void = (currentSlot) => {
|
||||||
this.currentSlot = currentSlot;
|
this.currentSlot = currentSlot;
|
||||||
|
this.save();
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onRobotUpdate');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getSlotByOrder: (coordinator: string, orderID: number) => Slot | null = (
|
||||||
|
coordinator,
|
||||||
|
orderID,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
Object.values(this.slots).find((slot) => {
|
||||||
|
const robot = slot.getRobot(coordinator);
|
||||||
|
return slot.activeShortAlias === coordinator && robot?.activeOrderId === orderID;
|
||||||
|
}) ?? null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Robots
|
// Robots
|
||||||
createRobot: (token: string, shortAliases: string[], attributes: Record<any, any>) => void = (
|
createRobot: (token: string, shortAliases: string[], attributes: Record<any, any>) => void = (
|
||||||
token,
|
token,
|
||||||
@ -174,6 +188,7 @@ class Garage {
|
|||||||
Object.values(this.slots).forEach((slot) => {
|
Object.values(this.slots).forEach((slot) => {
|
||||||
slot.syncCoordinator(coordinator, this);
|
slot.syncCoordinator(coordinator, this);
|
||||||
});
|
});
|
||||||
|
this.save();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
frontend/src/services/Native/index.d.ts
vendored
8
frontend/src/services/Native/index.d.ts
vendored
@ -26,7 +26,13 @@ export interface NativeWebViewMessageHttp {
|
|||||||
export interface NativeWebViewMessageSystem {
|
export interface NativeWebViewMessageSystem {
|
||||||
id?: number;
|
id?: number;
|
||||||
category: 'system';
|
category: 'system';
|
||||||
type: 'init' | 'torStatus' | 'copyToClipboardString' | 'setCookie' | 'deleteCookie';
|
type:
|
||||||
|
| 'init'
|
||||||
|
| 'torStatus'
|
||||||
|
| 'copyToClipboardString'
|
||||||
|
| 'setCookie'
|
||||||
|
| 'deleteCookie'
|
||||||
|
| 'navigateToPage';
|
||||||
key?: string;
|
key?: string;
|
||||||
detail?: string;
|
detail?: string;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@ class NativeRobosats {
|
|||||||
if (message.key !== undefined) {
|
if (message.key !== undefined) {
|
||||||
this.cookies[message.key] = String(message.detail);
|
this.cookies[message.key] = String(message.detail);
|
||||||
}
|
}
|
||||||
|
} else if (message.type === 'navigateToPage') {
|
||||||
|
window.dispatchEvent(new CustomEvent('navigateToPage', { detail: message?.detail }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@
|
|||||||
"Tell us about a new feature or a bug": "Proposa funcionalitats o notifica errors",
|
"Tell us about a new feature or a bug": "Proposa funcionalitats o notifica errors",
|
||||||
"We are abandoning Telegram! Our old TG groups": "Estem deixant Telegram! Els nostres grups antics de TG",
|
"We are abandoning Telegram! Our old TG groups": "Estem deixant Telegram! Els nostres grups antics de TG",
|
||||||
"X Official Account": "Compte oficial a X",
|
"X Official Account": "Compte oficial a X",
|
||||||
"#23": "Phrases in components/Dialogs/Coordinator.tsx",
|
"#24": "Phrases in components/Dialogs/Coordinator.tsx",
|
||||||
"...Opening on Nostr gateway. Pubkey copied!": "...Obrint la passarel·la Nostr. Clau pública copiada!",
|
"...Opening on Nostr gateway. Pubkey copied!": "...Obrint la passarel·la Nostr. Clau pública copiada!",
|
||||||
"24h contracted volume": "Volum contractat en 24h",
|
"24h contracted volume": "Volum contractat en 24h",
|
||||||
"24h non-KYC bitcoin premium": "Prima de bitcoin sense KYC en 24h",
|
"24h non-KYC bitcoin premium": "Prima de bitcoin sense KYC en 24h",
|
||||||
@ -475,7 +475,7 @@
|
|||||||
"You send via Lightning {{amount}} Sats (Approx)": "Tu envies via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "Tu envies via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "Envies via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "Envies via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
|
||||||
"#42": "Phrases in components/RobotInfo/index.tsx",
|
"#43": "Phrases in components/RobotInfo/index.tsx",
|
||||||
"Active order!": "Active order!",
|
"Active order!": "Active order!",
|
||||||
"Claim": "Retirar",
|
"Claim": "Retirar",
|
||||||
"Claim Sats!": "Reclamar Sats!",
|
"Claim Sats!": "Reclamar Sats!",
|
||||||
|
@ -7,6 +7,8 @@ import EncryptedStorage from 'react-native-encrypted-storage';
|
|||||||
import { name as app_name, version as app_version } from './package.json';
|
import { name as app_name, version as app_version } from './package.json';
|
||||||
import TorModule from './native/TorModule';
|
import TorModule from './native/TorModule';
|
||||||
import RoboIdentitiesModule from './native/RoboIdentitiesModule';
|
import RoboIdentitiesModule from './native/RoboIdentitiesModule';
|
||||||
|
import NotificationsModule from './native/NotificationsModule';
|
||||||
|
import SystemModule from './native/SystemModule';
|
||||||
|
|
||||||
const backgroundColors = {
|
const backgroundColors = {
|
||||||
light: 'white',
|
light: 'white',
|
||||||
@ -23,6 +25,14 @@ const App = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
TorModule.start();
|
TorModule.start();
|
||||||
|
DeviceEventEmitter.addListener('navigateToPage', (payload) => {
|
||||||
|
window.navigateToPage = payload;
|
||||||
|
injectMessage({
|
||||||
|
category: 'system',
|
||||||
|
type: 'navigateToPage',
|
||||||
|
detail: payload,
|
||||||
|
});
|
||||||
|
});
|
||||||
DeviceEventEmitter.addListener('TorStatus', (payload) => {
|
DeviceEventEmitter.addListener('TorStatus', (payload) => {
|
||||||
if (payload.torStatus === 'OFF') TorModule.restart();
|
if (payload.torStatus === 'OFF') TorModule.restart();
|
||||||
injectMessage({
|
injectMessage({
|
||||||
@ -54,6 +64,17 @@ const App = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onLoadEnd = () => {
|
||||||
|
if (window.navigateToPage) {
|
||||||
|
injectMessage({
|
||||||
|
category: 'system',
|
||||||
|
type: 'navigateToPage',
|
||||||
|
detail: window.navigateToPage,
|
||||||
|
});
|
||||||
|
window.navigateToPage = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const init = (responseId: string) => {
|
const init = (responseId: string) => {
|
||||||
const loadCookie = async (key: string) => {
|
const loadCookie = async (key: string) => {
|
||||||
return await EncryptedStorage.getItem(key).then((value) => {
|
return await EncryptedStorage.getItem(key).then((value) => {
|
||||||
@ -62,6 +83,7 @@ const App = () => {
|
|||||||
webViewRef.current?.injectJavaScript(
|
webViewRef.current?.injectJavaScript(
|
||||||
`(function() {window.NativeRobosats?.loadCookie(${json});})();`,
|
`(function() {window.NativeRobosats?.loadCookie(${json});})();`,
|
||||||
);
|
);
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -71,8 +93,13 @@ const App = () => {
|
|||||||
loadCookie('settings_mode');
|
loadCookie('settings_mode');
|
||||||
loadCookie('settings_light_qr');
|
loadCookie('settings_light_qr');
|
||||||
loadCookie('settings_network');
|
loadCookie('settings_network');
|
||||||
loadCookie('settings_use_proxy');
|
loadCookie('settings_use_proxy').then((useProxy) => {
|
||||||
loadCookie('garage_slots').then(() => injectMessageResolve(responseId));
|
SystemModule.useProxy(useProxy ?? 'true');
|
||||||
|
});
|
||||||
|
loadCookie('garage_slots').then((slots) => {
|
||||||
|
NotificationsModule.monitorOrders(slots ?? '{}');
|
||||||
|
injectMessageResolve(responseId);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCatch = (dataId: string, event: any) => {
|
const onCatch = (dataId: string, event: any) => {
|
||||||
@ -128,6 +155,13 @@ const App = () => {
|
|||||||
Clipboard.setString(data.detail);
|
Clipboard.setString(data.detail);
|
||||||
} else if (data.type === 'setCookie') {
|
} else if (data.type === 'setCookie') {
|
||||||
setCookie(data.key, data.detail);
|
setCookie(data.key, data.detail);
|
||||||
|
if (data.key === 'federation') {
|
||||||
|
SystemModule.setFederation(data.detail ?? '{}');
|
||||||
|
} else if (data.key === 'garage_slots') {
|
||||||
|
NotificationsModule.monitorOrders(data.detail ?? '{}');
|
||||||
|
} else if (data.key === 'settings_use_proxy') {
|
||||||
|
SystemModule.useProxy(data.detail ?? 'true');
|
||||||
|
}
|
||||||
} else if (data.type === 'deleteCookie') {
|
} else if (data.type === 'deleteCookie') {
|
||||||
EncryptedStorage.removeItem(data.key);
|
EncryptedStorage.removeItem(data.key);
|
||||||
}
|
}
|
||||||
@ -185,6 +219,7 @@ const App = () => {
|
|||||||
allowsLinkPreview={false}
|
allowsLinkPreview={false}
|
||||||
renderLoading={() => <Text></Text>}
|
renderLoading={() => <Text></Text>}
|
||||||
onError={(syntheticEvent) => <Text>{syntheticEvent.type}</Text>}
|
onError={(syntheticEvent) => <Text>{syntheticEvent.type}</Text>}
|
||||||
|
onLoadEnd={() => setTimeout(onLoadEnd, 3000)}
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@ -13,6 +16,7 @@
|
|||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
>
|
>
|
||||||
|
<service android:name=".NotificationsService" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -1,10 +1,55 @@
|
|||||||
package com.robosats;
|
package com.robosats;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.facebook.react.ReactActivity;
|
import com.facebook.react.ReactActivity;
|
||||||
import com.facebook.react.ReactActivityDelegate;
|
import com.facebook.react.ReactActivityDelegate;
|
||||||
import com.facebook.react.ReactRootView;
|
import com.facebook.react.ReactRootView;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
|
|
||||||
public class MainActivity extends ReactActivity {
|
public class MainActivity extends ReactActivity {
|
||||||
|
private static final int REQUEST_CODE_POST_NOTIFICATIONS = 1;
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_CODE_POST_NOTIFICATIONS);
|
||||||
|
} else {
|
||||||
|
Intent serviceIntent = new Intent(getApplicationContext(), NotificationsService.class);
|
||||||
|
getApplicationContext().startService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent != null) {
|
||||||
|
String coordinator = intent.getStringExtra("coordinator");
|
||||||
|
int order_id = intent.getIntExtra("order_id", 0);
|
||||||
|
if (order_id > 0) {
|
||||||
|
navigateToPage(coordinator, order_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
if (intent != null) {
|
||||||
|
String coordinator = intent.getStringExtra("coordinator");
|
||||||
|
int order_id = intent.getIntExtra("order_id", 0);
|
||||||
|
if (order_id > 0) {
|
||||||
|
navigateToPage(coordinator, order_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
||||||
@ -25,6 +70,31 @@ public class MainActivity extends ReactActivity {
|
|||||||
return new MainActivityDelegate(this, getMainComponentName());
|
return new MainActivityDelegate(this, getMainComponentName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
if (requestCode == REQUEST_CODE_POST_NOTIFICATIONS) {
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Intent serviceIntent = new Intent(getApplicationContext(), NotificationsService.class);
|
||||||
|
getApplicationContext().startService(serviceIntent);
|
||||||
|
} else {
|
||||||
|
// Permission denied, handle accordingly
|
||||||
|
// Maybe show a message to the user explaining why the permission is necessary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateToPage(String coordinator, Integer order_id) {
|
||||||
|
ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
|
||||||
|
if (reactContext != null) {
|
||||||
|
WritableMap payload = Arguments.createMap();
|
||||||
|
payload.putString("coordinator", coordinator);
|
||||||
|
payload.putInt("order_id", order_id);
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("navigateToPage", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class MainActivityDelegate extends ReactActivityDelegate {
|
public static class MainActivityDelegate extends ReactActivityDelegate {
|
||||||
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
|
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
|
||||||
super(activity, mainComponentName);
|
super(activity, mainComponentName);
|
||||||
|
@ -0,0 +1,301 @@
|
|||||||
|
package com.robosats;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.robosats.tor.TorKmp;
|
||||||
|
import com.robosats.tor.TorKmpManager;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import kotlin.UninitializedPropertyAccessException;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class NotificationsService extends Service {
|
||||||
|
private Handler handler;
|
||||||
|
private Runnable periodicTask;
|
||||||
|
private static final String CHANNEL_ID = "robosats_notifications";
|
||||||
|
private static final int NOTIFICATION_ID = 76453;
|
||||||
|
private static final long INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
||||||
|
private static final String PREFS_NAME_NOTIFICATION = "Notifications";
|
||||||
|
private static final String PREFS_NAME_SYSTEM = "System";
|
||||||
|
private static final String KEY_DATA_SLOTS = "Slots";
|
||||||
|
private static final String KEY_DATA_PROXY = "UsePoxy";
|
||||||
|
private static final String KEY_DATA_FEDERATION = "Federation";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
createNotificationChannel();
|
||||||
|
startForeground(NOTIFICATION_ID, buildServiceNotification());
|
||||||
|
|
||||||
|
handler = new Handler();
|
||||||
|
periodicTask = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.d("NotificationsService", "Running periodic task");
|
||||||
|
executeBackgroundTask();
|
||||||
|
handler.postDelayed(periodicTask, INTERVAL_MS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.d("NotificationsService", "Squeduling periodic task");
|
||||||
|
handler.postDelayed(periodicTask, 5000);
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (handler != null && periodicTask != null) {
|
||||||
|
handler.removeCallbacks(periodicTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopForeground(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
"Robosats",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
);
|
||||||
|
manager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeBackgroundTask() {
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
executor.submit(this::checkNotifications);
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification buildServiceNotification() {
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
|
.setContentTitle("Tor Notifications")
|
||||||
|
.setContentText("Running in the background every 5 minutes to check for notifications.")
|
||||||
|
.setSmallIcon(R.mipmap.ic_icon)
|
||||||
|
.setTicker("Robosats")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setAutoCancel(false)
|
||||||
|
.setContentIntent(pendingIntent);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkNotifications() {
|
||||||
|
Log.d("NotificationsService", "checkNotifications");
|
||||||
|
SharedPreferences sharedPreferences =
|
||||||
|
getApplicationContext()
|
||||||
|
.getSharedPreferences(PREFS_NAME_NOTIFICATION, ReactApplicationContext.MODE_PRIVATE);
|
||||||
|
String slotsJson = sharedPreferences.getString(KEY_DATA_SLOTS, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert slotsJson != null;
|
||||||
|
JSONObject slots = new JSONObject(slotsJson);
|
||||||
|
Iterator<String> it = slots.keys();
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
String robotToken = it.next();
|
||||||
|
JSONObject slot = (JSONObject) slots.get(robotToken);
|
||||||
|
JSONObject robots = slot.getJSONObject("robots");
|
||||||
|
JSONObject coordinatorRobot;
|
||||||
|
String shortAlias = slot.getString("activeShortAlias");
|
||||||
|
coordinatorRobot = robots.getJSONObject(shortAlias);
|
||||||
|
fetchNotifications(coordinatorRobot, shortAlias);
|
||||||
|
}
|
||||||
|
} catch (JSONException | InterruptedException e) {
|
||||||
|
Log.d("NotificationsService", "Error reading garage: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchNotifications(JSONObject robot, String coordinator) throws JSONException, InterruptedException {
|
||||||
|
String token = robot.getString("tokenSHA256");
|
||||||
|
SharedPreferences sharedPreferences =
|
||||||
|
getApplicationContext()
|
||||||
|
.getSharedPreferences(PREFS_NAME_SYSTEM, ReactApplicationContext.MODE_PRIVATE);
|
||||||
|
boolean useProxy = Objects.equals(sharedPreferences.getString(KEY_DATA_PROXY, null), "true");
|
||||||
|
JSONObject federation = new JSONObject(sharedPreferences.getString(KEY_DATA_FEDERATION, "{}"));
|
||||||
|
long unix_time_millis = sharedPreferences.getLong(token, 0);
|
||||||
|
String url = federation.getString(coordinator) + "/api/notifications";
|
||||||
|
if (unix_time_millis > 0) {
|
||||||
|
String last_created_at = String
|
||||||
|
.valueOf(LocalDateTime.ofInstant(Instant.ofEpochMilli(unix_time_millis), ZoneId.of("UTC")));
|
||||||
|
url += "?created_at=" + last_created_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS); // Set read timeout
|
||||||
|
|
||||||
|
if (useProxy) {
|
||||||
|
TorKmp tor = this.getTorKmp();
|
||||||
|
builder.proxy(tor.getProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
OkHttpClient client = builder.build();
|
||||||
|
Request.Builder requestBuilder = new Request.Builder().url(url);
|
||||||
|
|
||||||
|
requestBuilder
|
||||||
|
.addHeader("Authorization", "Token " + token);
|
||||||
|
|
||||||
|
requestBuilder.get();
|
||||||
|
Request request = requestBuilder.build();
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
displayErrorNotification();
|
||||||
|
Log.d("NotificationsService", "Error fetching coordinator: " + e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) throws IOException {
|
||||||
|
String body = response.body() != null ? response.body().string() : "{}";
|
||||||
|
JSONObject headersJson = new JSONObject();
|
||||||
|
response.headers().names().forEach(name -> {
|
||||||
|
try {
|
||||||
|
headersJson.put(name, response.header(name));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
JSONArray results = new JSONArray(body);
|
||||||
|
for (int i=0; i < results.length(); i++) {
|
||||||
|
JSONObject notification = results.getJSONObject(i);
|
||||||
|
Integer order_id = notification.getInt("order_id");
|
||||||
|
String title = notification.getString("title");
|
||||||
|
|
||||||
|
if (title.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayOrderNotification(order_id, title, coordinator);
|
||||||
|
|
||||||
|
long milliseconds;
|
||||||
|
try {
|
||||||
|
String created_at = notification.getString("created_at");
|
||||||
|
LocalDateTime datetime = LocalDateTime.parse(created_at, DateTimeFormatter.ISO_DATE_TIME);
|
||||||
|
milliseconds = datetime.toInstant(ZoneOffset.UTC).toEpochMilli() + 1000;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
milliseconds = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putLong(token, milliseconds);
|
||||||
|
editor.apply();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayOrderNotification(Integer order_id, String message, String coordinator) {
|
||||||
|
NotificationManager notificationManager = (NotificationManager)
|
||||||
|
getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
Intent intent = new Intent(this.getApplicationContext(), MainActivity.class);
|
||||||
|
intent.putExtra("coordinator", coordinator);
|
||||||
|
intent.putExtra("order_id", order_id);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(this.getApplicationContext(), 0,
|
||||||
|
intent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder =
|
||||||
|
new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||||
|
.setContentTitle("Order #" + order_id)
|
||||||
|
.setContentText(message)
|
||||||
|
.setSmallIcon(R.mipmap.ic_icon)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setAutoCancel(true);
|
||||||
|
|
||||||
|
notificationManager.notify(order_id, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayErrorNotification() {
|
||||||
|
NotificationManager notificationManager = (NotificationManager)
|
||||||
|
getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder =
|
||||||
|
new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||||
|
.setContentTitle("Connection Error")
|
||||||
|
.setContentText("There was an error while connecting to the Tor network.")
|
||||||
|
.setSmallIcon(R.mipmap.ic_icon)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true);
|
||||||
|
|
||||||
|
notificationManager.notify(0, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private TorKmp getTorKmp() throws InterruptedException {
|
||||||
|
TorKmp torKmp;
|
||||||
|
try {
|
||||||
|
torKmp = TorKmpManager.INSTANCE.getTorKmpObject();
|
||||||
|
} catch (UninitializedPropertyAccessException e) {
|
||||||
|
torKmp = new TorKmp((Application) this.getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
int retires = 0;
|
||||||
|
while (!torKmp.isConnected() && retires < 15) {
|
||||||
|
if (!torKmp.isStarting()) {
|
||||||
|
torKmp.getTorOperationManager().startQuietly();
|
||||||
|
}
|
||||||
|
Thread.sleep(2000);
|
||||||
|
retires += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!torKmp.isConnected()) {
|
||||||
|
displayErrorNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
return torKmp;
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,9 @@ import com.facebook.react.ReactPackage;
|
|||||||
import com.facebook.react.bridge.NativeModule;
|
import com.facebook.react.bridge.NativeModule;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.uimanager.ViewManager;
|
import com.facebook.react.uimanager.ViewManager;
|
||||||
|
import com.robosats.modules.NotificationsModule;
|
||||||
import com.robosats.modules.RoboIdentitiesModule;
|
import com.robosats.modules.RoboIdentitiesModule;
|
||||||
|
import com.robosats.modules.SystemModule;
|
||||||
import com.robosats.modules.TorModule;
|
import com.robosats.modules.TorModule;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -22,7 +24,9 @@ public class RobosatsPackage implements ReactPackage {
|
|||||||
ReactApplicationContext reactContext) {
|
ReactApplicationContext reactContext) {
|
||||||
List<NativeModule> modules = new ArrayList<>();
|
List<NativeModule> modules = new ArrayList<>();
|
||||||
|
|
||||||
|
modules.add(new SystemModule(reactContext));
|
||||||
modules.add(new TorModule(reactContext));
|
modules.add(new TorModule(reactContext));
|
||||||
|
modules.add(new NotificationsModule(reactContext));
|
||||||
modules.add(new RoboIdentitiesModule(reactContext));
|
modules.add(new RoboIdentitiesModule(reactContext));
|
||||||
|
|
||||||
return modules;
|
return modules;
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.robosats.modules;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
public class NotificationsModule extends ReactContextBaseJavaModule {
|
||||||
|
public NotificationsModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "NotificationsModule";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void monitorOrders(String slots_json) {
|
||||||
|
String PREFS_NAME = "Notifications";
|
||||||
|
String KEY_DATA = "Slots";
|
||||||
|
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(PREFS_NAME, ReactApplicationContext.MODE_PRIVATE);
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putString(KEY_DATA, slots_json);
|
||||||
|
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.robosats.modules;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
public class SystemModule extends ReactContextBaseJavaModule {
|
||||||
|
public SystemModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "SystemModule";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void useProxy(String use_proxy) {
|
||||||
|
String PREFS_NAME = "System";
|
||||||
|
String KEY_DATA = "UsePoxy";
|
||||||
|
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(PREFS_NAME, ReactApplicationContext.MODE_PRIVATE);
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putString(KEY_DATA, use_proxy);
|
||||||
|
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
@ReactMethod
|
||||||
|
public void setFederation(String use_proxy) {
|
||||||
|
String PREFS_NAME = "System";
|
||||||
|
String KEY_DATA = "Federation";
|
||||||
|
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(PREFS_NAME, ReactApplicationContext.MODE_PRIVATE);
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putString(KEY_DATA, use_proxy);
|
||||||
|
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package com.robosats.modules;
|
package com.robosats.modules;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -11,6 +12,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|||||||
import com.facebook.react.bridge.ReactMethod;
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
|
import com.robosats.tor.TorKmp;
|
||||||
import com.robosats.tor.TorKmpManager;
|
import com.robosats.tor.TorKmpManager;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -20,6 +22,7 @@ import java.io.IOException;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import kotlin.UninitializedPropertyAccessException;
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.Callback;
|
import okhttp3.Callback;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
@ -30,10 +33,11 @@ import okhttp3.Response;
|
|||||||
|
|
||||||
|
|
||||||
public class TorModule extends ReactContextBaseJavaModule {
|
public class TorModule extends ReactContextBaseJavaModule {
|
||||||
private TorKmpManager torKmpManager;
|
|
||||||
private ReactApplicationContext context;
|
private ReactApplicationContext context;
|
||||||
public TorModule(ReactApplicationContext reactContext) {
|
public TorModule(ReactApplicationContext reactContext) {
|
||||||
context = reactContext;
|
context = reactContext;
|
||||||
|
TorKmp torKmpManager = new TorKmp((Application) context.getApplicationContext());
|
||||||
|
TorKmpManager.INSTANCE.updateTorKmpObject(torKmpManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -42,11 +46,12 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void sendRequest(String action, String url, String headers, String body, final Promise promise) throws JSONException {
|
public void sendRequest(String action, String url, String headers, String body, final Promise promise) throws JSONException, UninitializedPropertyAccessException {
|
||||||
OkHttpClient client = new OkHttpClient.Builder()
|
OkHttpClient client = new OkHttpClient.Builder()
|
||||||
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
||||||
.readTimeout(30, TimeUnit.SECONDS) // Set read timeout
|
.readTimeout(30, TimeUnit.SECONDS) // Set read timeout
|
||||||
.proxy(torKmpManager.getProxy()).build();
|
.proxy(TorKmpManager.INSTANCE.getTorKmpObject().getProxy())
|
||||||
|
.build();
|
||||||
|
|
||||||
Request.Builder requestBuilder = new Request.Builder().url(url);
|
Request.Builder requestBuilder = new Request.Builder().url(url);
|
||||||
|
|
||||||
@ -89,8 +94,8 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void getTorStatus() {
|
public void getTorStatus() throws UninitializedPropertyAccessException {
|
||||||
String torState = torKmpManager.getTorState().getState().name();
|
String torState = TorKmpManager.INSTANCE.getTorKmpObject().getTorState().getState().name();
|
||||||
WritableMap payload = Arguments.createMap();
|
WritableMap payload = Arguments.createMap();
|
||||||
payload.putString("torStatus", torState);
|
payload.putString("torStatus", torState);
|
||||||
context
|
context
|
||||||
@ -99,8 +104,8 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void isConnected() {
|
public void isConnected() throws UninitializedPropertyAccessException {
|
||||||
String isConnected = String.valueOf(torKmpManager.isConnected());
|
String isConnected = String.valueOf(TorKmpManager.INSTANCE.getTorKmpObject().isConnected());
|
||||||
WritableMap payload = Arguments.createMap();
|
WritableMap payload = Arguments.createMap();
|
||||||
payload.putString("isConnected", isConnected);
|
payload.putString("isConnected", isConnected);
|
||||||
context
|
context
|
||||||
@ -109,8 +114,8 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void isStarting() {
|
public void isStarting() throws UninitializedPropertyAccessException {
|
||||||
String isStarting = String.valueOf(torKmpManager.isStarting());
|
String isStarting = String.valueOf(TorKmpManager.INSTANCE.getTorKmpObject().isStarting());
|
||||||
WritableMap payload = Arguments.createMap();
|
WritableMap payload = Arguments.createMap();
|
||||||
payload.putString("isStarting", isStarting);
|
payload.putString("isStarting", isStarting);
|
||||||
context
|
context
|
||||||
@ -119,8 +124,8 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void stop() {
|
public void stop() throws UninitializedPropertyAccessException {
|
||||||
torKmpManager.getTorOperationManager().stopQuietly();
|
TorKmpManager.INSTANCE.getTorKmpObject().getTorOperationManager().stopQuietly();
|
||||||
WritableMap payload = Arguments.createMap();
|
WritableMap payload = Arguments.createMap();
|
||||||
context
|
context
|
||||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
@ -128,9 +133,8 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void start() {
|
public void start() throws InterruptedException, UninitializedPropertyAccessException {
|
||||||
torKmpManager = new TorKmpManager(context.getCurrentActivity().getApplication());
|
TorKmpManager.INSTANCE.getTorKmpObject().getTorOperationManager().startQuietly();
|
||||||
torKmpManager.getTorOperationManager().startQuietly();
|
|
||||||
WritableMap payload = Arguments.createMap();
|
WritableMap payload = Arguments.createMap();
|
||||||
context
|
context
|
||||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
@ -138,9 +142,10 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void restart() {
|
public void restart() throws UninitializedPropertyAccessException {
|
||||||
torKmpManager = new TorKmpManager(context.getCurrentActivity().getApplication());
|
TorKmp torKmp = new TorKmp(context.getCurrentActivity().getApplication());
|
||||||
torKmpManager.getTorOperationManager().restartQuietly();
|
TorKmpManager.INSTANCE.updateTorKmpObject(torKmp);
|
||||||
|
TorKmpManager.INSTANCE.getTorKmpObject().getTorOperationManager().restartQuietly();
|
||||||
WritableMap payload = Arguments.createMap();
|
WritableMap payload = Arguments.createMap();
|
||||||
context
|
context
|
||||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
@ -148,8 +153,8 @@ public class TorModule extends ReactContextBaseJavaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void newIdentity() {
|
public void newIdentity() throws UninitializedPropertyAccessException {
|
||||||
torKmpManager.newIdentity(context.getCurrentActivity().getApplication());
|
TorKmpManager.INSTANCE.getTorKmpObject().newIdentity(context.getCurrentActivity().getApplication());
|
||||||
WritableMap payload = Arguments.createMap();
|
WritableMap payload = Arguments.createMap();
|
||||||
context
|
context
|
||||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
@ -27,8 +27,9 @@ import io.matthewnelson.kmp.tor.manager.R
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
class TorKmpManager(application : Application) {
|
class TorKmp(application : Application) {
|
||||||
|
|
||||||
private val TAG = "TorListener"
|
private val TAG = "TorListener"
|
||||||
|
|
||||||
@ -387,3 +388,16 @@ class TorKmpManager(application : Application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object TorKmpManager {
|
||||||
|
private lateinit var torKmp: TorKmp
|
||||||
|
|
||||||
|
@Throws(UninitializedPropertyAccessException::class)
|
||||||
|
fun getTorKmpObject(): TorKmp {
|
||||||
|
return torKmp
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateTorKmpObject(newKmpObject: TorKmp) {
|
||||||
|
torKmp = newKmpObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
mobile/android/app/src/main/jniLibs/x86_64/librobohash.so
Executable file
BIN
mobile/android/app/src/main/jniLibs/x86_64/librobohash.so
Executable file
Binary file not shown.
BIN
mobile/android/app/src/main/jniLibs/x86_64/librobonames.so
Executable file
BIN
mobile/android/app/src/main/jniLibs/x86_64/librobonames.so
Executable file
Binary file not shown.
BIN
mobile/android/app/src/main/res/mipmap-hdpi/ic_icon.png
Normal file
BIN
mobile/android/app/src/main/res/mipmap-hdpi/ic_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
10
mobile/native/NotificationsModule.ts
Normal file
10
mobile/native/NotificationsModule.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { NativeModules } from 'react-native';
|
||||||
|
const { NotificationsModule } = NativeModules;
|
||||||
|
|
||||||
|
interface NotificationsModuleInterface {
|
||||||
|
monitorOrders: (slotsJson: string) => void;
|
||||||
|
useProxy: (useProxy: string) => void;
|
||||||
|
setFederation: (federation: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotificationsModule as NotificationsModuleInterface;
|
@ -2,8 +2,8 @@ import { NativeModules } from 'react-native';
|
|||||||
const { RoboIdentitiesModule } = NativeModules;
|
const { RoboIdentitiesModule } = NativeModules;
|
||||||
|
|
||||||
interface RoboIdentitiesModuleInterface {
|
interface RoboIdentitiesModuleInterface {
|
||||||
generateRoboname: (initialString: String) => Promise<string>;
|
generateRoboname: (initialString: string) => Promise<string>;
|
||||||
generateRobohash: (initialString: String) => Promise<string>;
|
generateRobohash: (initialString: string) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RoboIdentitiesModule as RoboIdentitiesModuleInterface;
|
export default RoboIdentitiesModule as RoboIdentitiesModuleInterface;
|
||||||
|
9
mobile/native/SystemModule.ts
Normal file
9
mobile/native/SystemModule.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { NativeModules } from 'react-native';
|
||||||
|
const { SystemModule } = NativeModules;
|
||||||
|
|
||||||
|
interface SystemModuleInterface {
|
||||||
|
useProxy: (useProxy: string) => void;
|
||||||
|
setFederation: (federation: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemModule as SystemModuleInterface;
|
@ -1,132 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
|
|
||||||
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['Tor_kotlinVersion']
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
|
||||||
// noinspection DifferentKotlinGradleVersion
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
def getExtOrDefault(name) {
|
|
||||||
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['Tor_' + name]
|
|
||||||
}
|
|
||||||
|
|
||||||
def getExtOrIntegerDefault(name) {
|
|
||||||
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['Tor_' + name]).toInteger()
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
|
|
||||||
buildToolsVersion getExtOrDefault('buildToolsVersion')
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion rootProject.ext.has('minSdkVersion') ? rootProject.ext.minSdkVersion : 16
|
|
||||||
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lintOptions {
|
|
||||||
disable 'GradleCompatible'
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
|
|
||||||
def found = false
|
|
||||||
def defaultDir = null
|
|
||||||
def androidSourcesName = 'React Native sources'
|
|
||||||
|
|
||||||
if (rootProject.ext.has('reactNativeAndroidRoot')) {
|
|
||||||
defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
|
|
||||||
} else {
|
|
||||||
defaultDir = new File(
|
|
||||||
projectDir,
|
|
||||||
'/../../../node_modules/react-native/android'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultDir.exists()) {
|
|
||||||
maven {
|
|
||||||
url defaultDir.toString()
|
|
||||||
name androidSourcesName
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
|
|
||||||
found = true
|
|
||||||
} else {
|
|
||||||
def parentDir = rootProject.projectDir
|
|
||||||
|
|
||||||
1.upto(5, {
|
|
||||||
if (found) return true
|
|
||||||
parentDir = parentDir.parentFile
|
|
||||||
|
|
||||||
def androidSourcesDir = new File(
|
|
||||||
parentDir,
|
|
||||||
'node_modules/react-native'
|
|
||||||
)
|
|
||||||
|
|
||||||
def androidPrebuiltBinaryDir = new File(
|
|
||||||
parentDir,
|
|
||||||
'node_modules/react-native/android'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (androidPrebuiltBinaryDir.exists()) {
|
|
||||||
maven {
|
|
||||||
url androidPrebuiltBinaryDir.toString()
|
|
||||||
name androidSourcesName
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
|
|
||||||
found = true
|
|
||||||
} else if (androidSourcesDir.exists()) {
|
|
||||||
maven {
|
|
||||||
url androidSourcesDir.toString()
|
|
||||||
name androidSourcesName
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
throw new GradleException(
|
|
||||||
"${project.name}: unable to locate React Native android sources. " +
|
|
||||||
"Ensure you have you installed React Native as a dependency in your project and try again."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def kotlin_version = getExtOrDefault('kotlinVersion')
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// noinspection GradleDynamicVersion
|
|
||||||
api 'com.facebook.react:react-native:+'
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
||||||
compileOnly files('libs/sifir_android.aar')
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user