Verified Commit ef476413 authored by staltz's avatar staltz

UI show bluetooth indicator enabled/disabled

parent 8410727e
......@@ -170,6 +170,7 @@ dependencies {
implementation "ch.acra:acra-dialog:5.0.0"
implementation project(':nodejs-mobile-react-native')
implementation project(':react-native-bluetooth-socket-bridge')
implementation project(':react-native-bluetooth-status')
implementation project(':react-native-dialogs')
implementation project(':react-native-vector-icons')
implementation project(':react-native-os')
......
......@@ -17,6 +17,7 @@ import com.bitgo.randombytes.RandomBytesPackage;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.reactnativenavigation.NavigationApplication;
import com.solinor.bluetoothstatus.RNBluetoothManagerPackage;
import com.scuttlebutt.bluetoothbridge.BluetoothSocketBridgeConfiguration;
import com.scuttlebutt.bluetoothbridge.BluetoothSocketBridgePackage;
import com.staltz.reactnativeandroidlocalnotification.NotificationPackage;
......@@ -65,6 +66,7 @@ public class MainApplication extends NavigationApplication {
// Add additional packages you require here
// No need to add RnnPackage and MainReactPackage
return Arrays.<ReactPackage>asList(new MainReactPackage(),
new RNBluetoothManagerPackage(),
new BluetoothSocketBridgePackage(bluetoothConfig),
new PickerPackage(),
new HasInternetPackage(),
......
rootProject.name = 'Manyverse'
include ':react-native-bluetooth-status'
project(':react-native-bluetooth-status').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-bluetooth-status/android')
include ':react-native-bluetooth-socket-bridge'
project(':react-native-bluetooth-socket-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-bluetooth-socket-bridge/android')
include ':react-native-image-crop-picker'
......
......@@ -57,6 +57,7 @@
08553A5F6E9A4A9DA315D8D1 /* nodejs-project in Resources */ = {isa = PBXBuildFile; fileRef = 8671363C471C406E89C6ECCB /* nodejs-project */; };
F2EA81708D02402E96A26723 /* builtin_modules in Resources */ = {isa = PBXBuildFile; fileRef = B168DD7A511648E0B1415628 /* builtin_modules */; };
46AB3840219C446C98FABDA6 /* libimageCropPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A9CD13730515442BA6FC3EA4 /* libimageCropPicker.a */; };
5E0C8EFBCBEA458F9FB8DB6D /* libRNBluetoothManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D7FB5DE71C54FE9985BD965 /* libRNBluetoothManager.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
......@@ -311,6 +312,8 @@
B168DD7A511648E0B1415628 /* builtin_modules */ = {isa = PBXFileReference; name = "builtin_modules"; path = "../node_modules/nodejs-mobile-react-native/install/resources/nodejs-modules/builtin_modules"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
C70A1A3736F24275994AE454 /* imageCropPicker.xcodeproj */ = {isa = PBXFileReference; name = "imageCropPicker.xcodeproj"; path = "../node_modules/react-native-image-crop-picker/ios/imageCropPicker.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
A9CD13730515442BA6FC3EA4 /* libimageCropPicker.a */ = {isa = PBXFileReference; name = "libimageCropPicker.a"; path = "libimageCropPicker.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
A95598164FE44CC2A24C5BD2 /* RNBluetoothManager.xcodeproj */ = {isa = PBXFileReference; name = "RNBluetoothManager.xcodeproj"; path = "../node_modules/react-native-bluetooth-status/ios/RNBluetoothManager.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
4D7FB5DE71C54FE9985BD965 /* libRNBluetoothManager.a */ = {isa = PBXFileReference; name = "libRNBluetoothManager.a"; path = "libRNBluetoothManager.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
......@@ -347,6 +350,7 @@
A03FF7594955491F84C43833 /* libRNNodeJsMobile.a in Frameworks */,
7BA397C7699A4B32971919B3 /* NodeMobile.framework in Frameworks */,
46AB3840219C446C98FABDA6 /* libimageCropPicker.a in Frameworks */,
5E0C8EFBCBEA458F9FB8DB6D /* libRNBluetoothManager.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -521,6 +525,7 @@
6891BB4EFE5D4C9F96382670 /* TcpSockets.xcodeproj */,
91C746E80A6847A3AAEF2CCF /* RNNodeJsMobile.xcodeproj */,
C70A1A3736F24275994AE454 /* imageCropPicker.xcodeproj */,
A95598164FE44CC2A24C5BD2 /* RNBluetoothManager.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
......@@ -1171,6 +1176,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
......@@ -1180,6 +1186,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/react-native-image-crop-picker/ios/**",
"$(SRCROOT)/../node_modules/react-native-bluetooth-status/ios",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......@@ -1212,6 +1219,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
......@@ -1221,6 +1229,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/react-native-image-crop-picker/ios/**",
"$(SRCROOT)/../node_modules/react-native-bluetooth-status/ios",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......@@ -1254,6 +1263,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/react-native-image-crop-picker/ios/**",
"$(SRCROOT)/../node_modules/react-native-bluetooth-status/ios",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......@@ -1285,6 +1295,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/react-native-image-crop-picker/ios/**",
"$(SRCROOT)/../node_modules/react-native-bluetooth-status/ios",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......@@ -1325,6 +1336,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
......@@ -1334,6 +1346,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/react-native-image-crop-picker/ios/**",
"$(SRCROOT)/../node_modules/react-native-bluetooth-status/ios",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......@@ -1375,6 +1388,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
......@@ -1384,6 +1398,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/react-native-image-crop-picker/ios/**",
"$(SRCROOT)/../node_modules/react-native-bluetooth-status/ios",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......@@ -1420,6 +1435,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......@@ -1456,6 +1472,7 @@ if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
......
......@@ -691,6 +691,11 @@
}
}
},
"@cs125/wait-until": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@cs125/wait-until/-/wait-until-0.0.3.tgz",
"integrity": "sha512-Z3+GXZbAjiiI0cJi1si+WfBBPfmLwZ9Tk4wqyFJGeZHJilyveBCpSktHdJlmqjHBBvrMi8ymXefYi2S833LhRA=="
},
"@cycle/isolate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@cycle/isolate/-/isolate-4.1.0.tgz",
......@@ -19331,7 +19336,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
......@@ -19702,7 +19708,8 @@
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
......@@ -19750,6 +19757,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
......@@ -19788,11 +19796,13 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true
"bundled": true,
"optional": true
}
}
},
......@@ -24019,6 +24029,14 @@
"resolved": "https://registry.npmjs.org/react-native-bluetooth-socket-bridge/-/react-native-bluetooth-socket-bridge-1.0.11.tgz",
"integrity": "sha512-lUfDTr4IbHE4yV738TenQkZ4mhOWY99jQDeQkGLourzxoCHXVmtHAwrxkwFcV0wh88+0tG585eFtUbOEVtaTuQ=="
},
"react-native-bluetooth-status": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/react-native-bluetooth-status/-/react-native-bluetooth-status-1.3.0.tgz",
"integrity": "sha512-GZ/T61RGYabaSGbfwyyKCiTvaDGSQMQY8XcPpEnU8/5KMLDd+PFSQtbFy2V+XoGFcQ+2rrPA864SkooVKf83Ew==",
"requires": {
"@cs125/wait-until": "^0.0.3"
}
},
"react-native-crypto": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-native-crypto/-/react-native-crypto-2.1.2.tgz",
......@@ -1456,7 +1456,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
......@@ -1808,7 +1809,8 @@
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
......@@ -1855,6 +1857,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
......@@ -1893,11 +1896,13 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true
"bundled": true,
"optional": true
}
}
},
......
......@@ -5,12 +5,27 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import xs, {Stream, Listener} from 'xstream';
import {BluetoothStatus} from 'react-native-bluetooth-status';
const wifi = require('react-native-android-wifi');
const hasInternet = require('react-native-has-internet');
export class NetworkSource {
constructor() {}
public bluetoothIsEnabled(): Stream<boolean> {
return xs.create({
async start(listener: Listener<boolean>) {
try {
listener.next(await BluetoothStatus.state());
listener.complete();
} catch (e) {
listener.error(e);
}
},
stop() {},
});
}
public wifiIsEnabled(): Stream<boolean> {
return xs.create({
start(listener: Listener<boolean>) {
......
......@@ -287,9 +287,25 @@ export class SSBSource {
)
.startWith([]);
const bluetoothNearby$: Stream<Array<BTPeer>> = xsFromPullStream(
api.sbot.pull.nearbyBluetoothPeers[0](1000),
).map((result: any) => result.discovered);
const bluetoothEnabled$: Stream<
boolean
> = api.sbot.obs.bluetoothEnabled[0]();
const bluetoothNearby$: Stream<Array<BTPeer>> = bluetoothEnabled$
.map(
bluetoothEnabled =>
bluetoothEnabled
? xsFromPullStream(
api.sbot.pull.nearbyBluetoothPeers[0](1000),
).replaceError(err =>
xsFromPullStream(
api.sbot.pull.nearbyBluetoothPeers[0](1000),
),
)
: xs.never(),
)
.flatten()
.map((result: any) => result.discovered);
const bluetoothConnected$ = this.peers$.map(peers =>
peers.filter(p => (p.source as any) === 'bt'),
......@@ -440,13 +456,23 @@ export type RemoveDhtInviteReq = {
invite: string;
};
export type EnableBluetoothReq = {
type: 'bluetooth.enable';
interval: number;
};
export type DisableBluetoothReq = {
type: 'bluetooth.disable';
interval: number;
};
export type SearchBluetoothReq = {
type: 'searchBluetooth';
type: 'bluetooth.search';
interval: number;
};
export type ConnectBluetoothReq = {
type: 'connectBluetooth';
type: 'bluetooth.connect';
address: string;
};
......@@ -457,6 +483,8 @@ export type Req =
| StartDhtReq
| AcceptDhtInviteReq
| RemoveDhtInviteReq
| EnableBluetoothReq
| DisableBluetoothReq
| SearchBluetoothReq
| ConnectBluetoothReq;
......@@ -522,12 +550,18 @@ export function ssbDriver(sink: Stream<Req>): SSBSource {
if (err) console.error(err.message || err);
});
}
if (req.type === 'searchBluetooth') {
if (req.type === 'bluetooth.enable') {
api.sbot.sync.enableBluetooth[0]();
}
if (req.type === 'bluetooth.disable') {
api.sbot.sync.disableBluetooth[0]();
}
if (req.type === 'bluetooth.search') {
api.sbot.async.searchBluetoothPeers[0](req.interval, (err: any) => {
if (err) console.error(err.message || err);
});
}
if (req.type === 'connectBluetooth') {
if (req.type === 'bluetooth.connect') {
api.sbot.async.gossipConnect[0](req.address, (err: any) => {
if (err) console.error(err.message || err);
const friendId = '@' + req.address.split('shs:')[1];
......
......@@ -9,6 +9,7 @@ import {State} from './model';
import {Command as AlertCommand} from 'cycle-native-alert';
export type Actions = {
showBluetoothHelp$: Stream<any>;
showLANHelp$: Stream<any>;
showDHTHelp$: Stream<any>;
showPubHelp$: Stream<any>;
......@@ -23,6 +24,15 @@ export default function alert(
return state$
.map(state =>
xs.merge(
actions.showBluetoothHelp$.mapTo({
title: 'Bluetooth',
message:
(state.bluetoothEnabled
? '(ENABLED)'
: '(Turn on Bluetooth to use this)') +
'\n\nDiscover users nearby and connect with them using Bluetooth.',
buttons: [{text: 'OK', id: 'okay'}],
}),
actions.showLANHelp$.mapTo({
title: 'Wi-Fi',
message:
......
......@@ -14,8 +14,7 @@ import {Palette} from '../../../global-styles/palette';
export default function floatingAction(state$: Stream<State>): Stream<Props> {
return state$.map(state => {
const bluetoothEnabled = true;
const visible = bluetoothEnabled || state.internetEnabled;
const visible = state.bluetoothEnabled || state.internetEnabled;
const actions: Array<IActionProps> = [];
if (state.internetEnabled) {
......@@ -35,7 +34,7 @@ export default function floatingAction(state$: Stream<State>): Stream<Props> {
);
}
if (bluetoothEnabled) {
if (state.bluetoothEnabled) {
actions.push({
color: Palette.backgroundCTA,
name: 'bluetooth-search',
......
......@@ -71,7 +71,7 @@ export function connectionsTab(sources: Sources): Sinks {
sources.network,
);
const fabProps$ = floatingAction(sources.state.stream);
const ssb$ = ssb(actionsPlus);
const ssb$ = ssb(actionsPlus, sources.network);
const alert$ = alert(actionsPlus, sources.state.stream);
const share$ = actionsPlus.shareDhtInvite$.map(inviteCode => ({
message:
......
......@@ -12,6 +12,8 @@ import {NavSource} from 'cycle-native-navigation';
import {StagedPeerMetadata as StagedPeer} from '../../../drivers/ssb';
import {State} from './model';
import sample from 'xstream-sample';
import dropRepeats from 'xstream/extra/dropRepeats';
import concat from 'xstream/extra/concat';
export default function intent(
reactSource: ReactSource,
......@@ -21,6 +23,20 @@ export default function intent(
) {
const back$ = navSource.backPress();
return {
pingConnectivityModes$: state$
.map(state => state.isVisible)
.compose(dropRepeats())
.map(
isTabVisible =>
isTabVisible
? concat(xs.of(0), xs.periodic(2000).take(2), xs.periodic(6000))
: xs.never(),
)
.flatten()
.startWith(null),
showBluetoothHelp$: reactSource.select('bluetooth-mode').events('press'),
showLANHelp$: reactSource.select('lan-mode').events('press'),
showDHTHelp$: reactSource.select('dht-mode').events('press'),
......
......@@ -5,7 +5,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import xs, {Stream} from 'xstream';
import concat from 'xstream/extra/concat';
import {PeerMetadata, FeedId} from 'ssb-typescript';
import {Reducer} from '@cycle/state';
import {
......@@ -14,11 +13,11 @@ import {
} from '../../../drivers/ssb';
import {NetworkSource} from '../../../drivers/network';
import {noteStorageKeyFor} from './asyncstorage';
import dropRepeats from 'xstream/extra/dropRepeats';
import {AsyncStorageSource} from 'cycle-native-asyncstorage';
export type State = {
selfFeedId: FeedId;
bluetoothEnabled: boolean;
lanEnabled: boolean;
internetEnabled: boolean;
peers: Array<PeerMetadata>;
......@@ -30,6 +29,7 @@ export type State = {
};
export type Actions = {
pingConnectivityModes$: Stream<any>;
openStagedPeer$: Stream<StagedPeer>;
closeInviteMenu$: Stream<any>;
infoClientDhtInvite$: Stream<any>;
......@@ -51,6 +51,7 @@ export default function model(
if (prev) return prev;
return {
selfFeedId: '',
bluetoothEnabled: false,
lanEnabled: false,
internetEnabled: false,
isSyncing: false,
......@@ -69,8 +70,17 @@ export default function model(
},
);
const updateLanEnabled$ = xs
.periodic(4000)
const updateBluetoothEnabled$ = actions.pingConnectivityModes$
.map(() => networkSource.bluetoothIsEnabled())
.flatten()
.map(
bluetoothEnabled =>
function updateBluetoothEnabled(prev: State): State {
return {...prev, bluetoothEnabled};
},
);
const updateLanEnabled$ = actions.pingConnectivityModes$
.map(() => networkSource.wifiIsEnabled())
.flatten()
.map(
......@@ -80,19 +90,7 @@ export default function model(
},
);
const shouldUpdateInternetEnabled$ = state$
.map(state => state.isVisible)
.compose(dropRepeats())
.map(
isTabVisible =>
isTabVisible
? concat(xs.of(0), xs.periodic(1000).take(2), xs.periodic(4000))
: xs.never(),
)
.flatten()
.startWith(null);
const updateInternetEnabled$ = shouldUpdateInternetEnabled$
const updateInternetEnabled$ = actions.pingConnectivityModes$
.map(() => networkSource.hasInternetConnection())
.flatten()
.map(
......@@ -179,6 +177,7 @@ export default function model(
return xs.merge(
initReducer$,
updateIsSyncing$,
updateBluetoothEnabled$,
updateLanEnabled$,
updateInternetEnabled$,
setPeersReducer$,
......
......@@ -6,23 +6,34 @@
import xs, {Stream} from 'xstream';
import {Req, StagedPeerMetadata} from '../../../drivers/ssb';
import {NetworkSource} from '../../../drivers/network';
export type Actions = {
removeDhtInvite$: Stream<string>;
bluetoothSearch$: Stream<any>;
openStagedPeer$: Stream<StagedPeerMetadata>;
pingConnectivityModes$: Stream<any>;
};
export default function ssb(actions: Actions) {
export default function ssb(actions: Actions, networkSource: NetworkSource) {
return xs.merge(
actions.removeDhtInvite$.map(
invite => ({type: 'dhtInvite.remove', invite} as Req),
),
actions.pingConnectivityModes$
.map(() => networkSource.bluetoothIsEnabled())
.flatten()
.map(
bluetoothEnabled =>
(bluetoothEnabled
? {type: 'bluetooth.enable'}
: {type: 'bluetooth.disable'}) as Req,
),
actions.bluetoothSearch$.mapTo(
{type: 'searchBluetooth', interval: 20e3} as Req,
{type: 'bluetooth.search', interval: 20e3} as Req,
),
actions.openStagedPeer$
.filter(peer => peer.source === 'bt')
.map(peer => ({type: 'connectBluetooth', address: peer.key} as Req)),
.map(peer => ({type: 'bluetooth.connect', address: peer.key} as Req)),
);
}
......@@ -58,7 +58,7 @@ function ConnectivityModes(state: State) {
return h(View, {style: styles.modesContainer}, [
h(ConnectivityMode, {
sel: 'bluetooth-mode',
active: false,
active: state.bluetoothEnabled,
icon: 'bluetooth',
label: 'Bluetooth Mode',
}),
......@@ -87,8 +87,13 @@ function ConnectivityModes(state: State) {
}
function Body(state: State) {
const {lanEnabled, internetEnabled, peers, stagedPeers} = state;
const bluetoothEnabled = true;
const {
bluetoothEnabled,
lanEnabled,
internetEnabled,
peers,
stagedPeers,
} = state;
if (!bluetoothEnabled && !lanEnabled && !internetEnabled) {
return h(EmptySection, {
style: styles.emptySection,
......
......@@ -76,6 +76,7 @@ export const connectionsTabLens: Lens<State, ConnectionsTabState> = {
} else {
return {
selfFeedId: parent.selfFeedId,
bluetoothEnabled: false,
lanEnabled: false,
internetEnabled: false,
isSyncing: parent.isSyncing,
......
......@@ -33,6 +33,8 @@ const gives = {
sync: {
cache: true,
invalidateAboutSocialValue: true,
enableBluetooth: true,
disableBluetooth: true,
},
async: {
get: true,
......@@ -67,6 +69,9 @@ const gives = {
claimingDhtInvites: true,
aboutSocialValueStream: true,
},
obs: {
bluetoothEnabled: true,
},
},
};
......@@ -82,6 +87,8 @@ const create = (api: any) => {
const keys = api.keys.sync.load();
let sbot: any = null;
const bluetoothEnabled$ = xs.createWithMemory<boolean>();
bluetoothEnabled$.shamefullySendNext(false);
const sbot$ = xs.createWithMemory<any>();
const DUNBAR = 150;
const socialValueCache = {
......@@ -150,6 +157,12 @@ const create = (api: any) => {
socialValueCache.name.delete(feedId);
socialValueCache.image.delete(feedId);
},
enableBluetooth: () => {
bluetoothEnabled$.shamefullySendNext(true);
},
disableBluetooth: () => {
bluetoothEnabled$.shamefullySendNext(false);
},
},
async: {
get: rec.async((key: any, cb: any) => {
......@@ -310,6 +323,9 @@ const create = (api: any) => {
return sbot.bluetooth.nearbyScuttlebuttDevices(refreshInterval);
}),
},
obs: {
bluetoothEnabled: () => bluetoothEnabled$,
},
},
};
......
declare module 'react-native-bluetooth-status' {
export const BluetoothStatus: any;
}
......@@ -21,6 +21,7 @@
"src/backend/loader.ts",
"src/backend/manifest.ts",
"src/frontend/index.ts",
"src/typings/react-native-bluetooth-status.d.ts",
"src/typings/react-native-dialogs.d.ts",