From 0e200e5114bd52b9e020a1b23b060d6a6bdbace2 Mon Sep 17 00:00:00 2001 From: Roshan Rijal Date: Thu, 23 Sep 2021 09:34:30 +0545 Subject: [PATCH 1/3] bloc pattern implemented for search (#34) --- lib/app/app.dart | 12 +- lib/common/bloc/bluetooth_cubit.dart | 103 +++++++++++++++ lib/common/bloc/bluetooth_state.dart | 78 ++++++++++++ lib/common/constants/app_constants.dart | 22 ++++ lib/common/constants/constant.dart | 4 +- lib/common/constants/env.dart | 13 ++ lib/common/constants/strings.dart | 1 + lib/common/route/route_generator.dart | 6 + lib/common/route/routes.dart | 1 + lib/common/utils/multi_bloc_listing.dart | 4 + lib/common/utils/multi_repo_listing.dart | 10 +- lib/common/widget/page_wrapper.dart | 2 +- .../dashboard/screen/dashboard_screen.dart | 87 +++++++------ .../dashboard/screen/search_rpi_screen.dart | 119 ++++++++++++++++++ .../dashboard/screen/search_screen.dart | 1 + .../onboard/ui/screen/landing_page.dart | 25 ++++ .../onboard/ui/screen/splash_page.dart | 3 +- .../onboard/ui/widget/onboard_widget.dart | 21 +++- lib/main.dart | 7 +- 19 files changed, 470 insertions(+), 49 deletions(-) create mode 100644 lib/common/bloc/bluetooth_cubit.dart create mode 100644 lib/common/bloc/bluetooth_state.dart create mode 100644 lib/common/constants/app_constants.dart create mode 100644 lib/common/constants/env.dart create mode 100644 lib/feature/dashboard/screen/search_rpi_screen.dart create mode 100644 lib/feature/onboard/ui/screen/landing_page.dart diff --git a/lib/app/app.dart b/lib/app/app.dart index 8cb13cef..e014c42d 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:treehousesble/app/theme.dart'; +import 'package:treehousesble/common/constants/env.dart'; import 'package:treehousesble/common/constants/strings.dart'; import 'package:treehousesble/common/navigation/nav.dart'; import 'package:treehousesble/common/route/route_generator.dart'; @@ -7,9 +8,10 @@ import 'package:treehousesble/common/route/routes.dart'; import 'package:treehousesble/common/utils/multi_repo_listing.dart'; import 'package:treehousesble/common/widget/global_error_widget.dart'; - class App extends StatefulWidget { - App({Key? key}) : super(key: key); + final Env env; + + App({Key? key, required this.env}) : super(key: key); @override _AppState createState() => _AppState(); @@ -18,7 +20,9 @@ class App extends StatefulWidget { class _AppState extends State { @override Widget build(BuildContext context) { - return MaterialApp( + return MultiRepoListing( + env: widget.env, + child: MaterialApp( navigatorKey: Nav.navKey, builder: (context, Widget? widget) { setErrorBuilder(context); @@ -30,6 +34,6 @@ class _AppState extends State { title: Strings.APP_TITLE, initialRoute: Routes.root, onGenerateRoute: RouteGenerator.generateRoute, - ); + )); } } diff --git a/lib/common/bloc/bluetooth_cubit.dart b/lib/common/bloc/bluetooth_cubit.dart new file mode 100644 index 00000000..71ff1463 --- /dev/null +++ b/lib/common/bloc/bluetooth_cubit.dart @@ -0,0 +1,103 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter_blue/flutter_blue.dart'; +import 'package:treehousesble/app/app.dart'; +import 'package:treehousesble/common/bloc/bluetooth_state.dart'; +import 'package:treehousesble/common/constants/app_constants.dart'; +import 'package:treehousesble/common/constants/constant.dart'; +import 'package:treehousesble/common/constants/strings.dart'; +import 'package:treehousesble/common/shared_pref/shared_pref.dart'; + +class BluetoothCubit extends Cubit { + BluetoothCubit() : super(StateDeviceNotConnected()); + final FlutterBlue flutterBlue = FlutterBlue.instance; + final List devicesList = []; + final Map> readValues = new Map>(); + BluetoothCharacteristic? characteristic; + + appStart() async { + final bool firstTimeAppOpen = await SharedPref.getFirstTimeAppOpen(); + if (firstTimeAppOpen) { + emit(FirstTimeAppOpen()); + } else { + emit(NotFirstTimeAppOpen()); + } + } + + fetchDeviceList(bool filterPi) { + emit(StateLoading()); + print("FETCH DEVICE LIST"); + flutterBlue.connectedDevices + .asStream() + .listen((List devices) { + for (BluetoothDevice device in devices) { + _addDeviceTolist(device, filterPi); + } + }); + flutterBlue.scanResults.listen((List results) { + print("Scan result " + results.length.toString()); + for (ScanResult result in results) { + _addDeviceTolist(result.device, filterPi); + } + }); + flutterBlue.startScan(); + } + + void _addDeviceTolist(BluetoothDevice device, bool filterPi) { + print("Filter pi " + device.name); + if (!devicesList.contains(device) && checkIfPi(device, filterPi)) { + devicesList.add(device); + } + emit(StateIniital()); + emit(StateFoundDevices(list: devicesList)); + // if (devicesList.length > 0) ); + } + + fetchServicesAndConnect(BluetoothDevice device) async { + emit(StateLoading()); + try { + await device.connect(timeout: Duration(seconds: 20)); + List services = await device.discoverServices(); + for (BluetoothService service in services) { + if (service.uuid.toString() == Strings.BLUETOOTH_UUID) { + List characteristics = + service.characteristics; + if (characteristics.length > 0) { + characteristic = characteristics[0]; + emit(StateDeviceConnected(characteristic: characteristic!)); + print("CONNECTED TO DEVICE"); + break; + } + } + } + } catch (e) { + emit(StateError(message: "Unable to connect please try again")); + } + } + + checkDeviceConnected() async { + if (characteristic == null) { + emit(StateDeviceNotConnected()); + var connected = await flutterBlue.connectedDevices; + if (connected.length > 0) fetchServicesAndConnect(connected[0]); + } else { + emit(StateDeviceConnected(characteristic: characteristic!)); + } + } + + bool checkIfPi(BluetoothDevice device, bool filterPi) { + if (filterPi) { + bool isPi = false; + for (int i = 0; i < AppConstants.PI_ADDRESS.length; i++) { + if (device.id + .toString() + .toLowerCase() + .startsWith(AppConstants.PI_ADDRESS[i].toLowerCase())) { + isPi = true; + break; + } + } + return isPi; + } + return true; + } +} diff --git a/lib/common/bloc/bluetooth_state.dart b/lib/common/bloc/bluetooth_state.dart new file mode 100644 index 00000000..9f70feb9 --- /dev/null +++ b/lib/common/bloc/bluetooth_state.dart @@ -0,0 +1,78 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_blue/flutter_blue.dart'; + +class DataState extends Equatable { + final dynamic data; + + const DataState({this.data}); + + @override + List get props => [...data]; +} + +class FirstTimeAppOpen extends DataState { + @override + List get props => []; +} + + +class NotFirstTimeAppOpen extends DataState { + @override + List get props => []; +} + + +class StateIniital extends DataState {} + +class StateLoading extends DataState {} + +class StateError extends DataState { + final String message; + + const StateError({required this.message}); + + @override + List get props => [message]; +} + +class StateNoData extends DataState { + final String message; + + const StateNoData({required this.message}); + + List get props => [message]; +} + +class StateReadSuccess extends DataState { + final dynamic data; + + const StateReadSuccess({required this.data}) : super(data: data); + + @override + List get props => [...data]; +} + +class StateFoundDevices extends DataState { + final List list; + + const StateFoundDevices({required this.list}) : super(data: list); + + @override + List get props => [...data]; +} + + +class StateDeviceConnected extends DataState { + final BluetoothCharacteristic characteristic; + + const StateDeviceConnected({required this.characteristic}); + + List get props => []; +} + +class StateDeviceNotConnected extends DataState { + + const StateDeviceNotConnected(); + + List get props => []; +} \ No newline at end of file diff --git a/lib/common/constants/app_constants.dart b/lib/common/constants/app_constants.dart new file mode 100644 index 00000000..dc5effb9 --- /dev/null +++ b/lib/common/constants/app_constants.dart @@ -0,0 +1,22 @@ +class AppConstants{ + static var PI_ADDRESS = [ + "B8:27:EB", + "DC:A6:32", + "E4:5F:01", + "B8-27-EB", + "DC-A6-32", + "E4-5F-01", + "B827.EB", + "DCA6.32", + "E45F.01", + "b8:27:eb", + "dc:a6:32", + "e4:5f:01", + "b8-27-eb", + "dc-a6-32", + "e4-5f-01", + "b827.eb", + "dca6.32", + "e45f.01" + ]; +} \ No newline at end of file diff --git a/lib/common/constants/constant.dart b/lib/common/constants/constant.dart index c48bd5c5..d628c996 100644 --- a/lib/common/constants/constant.dart +++ b/lib/common/constants/constant.dart @@ -1,2 +1,4 @@ export 'assets.dart'; - +export 'strings.dart'; +export 'fonts.dart'; +export 'app_constants.dart'; diff --git a/lib/common/constants/env.dart b/lib/common/constants/env.dart new file mode 100644 index 00000000..97d4de03 --- /dev/null +++ b/lib/common/constants/env.dart @@ -0,0 +1,13 @@ +class Env { + Env(this.uuid); + final String uuid; +} + +mixin EnvValue { + static final Env development = + Env('6e400001-b5a3-f393-e0a9-e50e24dcca9e'); + static final Env staging = + Env('6e400001-b5a3-f393-e0a9-e50e24dcca9e'); + static final Env production = + Env('6e400001-b5a3-f393-e0a9-e50e24dcca9e'); +} diff --git a/lib/common/constants/strings.dart b/lib/common/constants/strings.dart index 0e170c71..f266e1be 100644 --- a/lib/common/constants/strings.dart +++ b/lib/common/constants/strings.dart @@ -1,3 +1,4 @@ class Strings { + static const BLUETOOTH_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; static const APP_TITLE = "Treehouses Remote II"; } diff --git a/lib/common/route/route_generator.dart b/lib/common/route/route_generator.dart index 1d216091..9c30ad24 100644 --- a/lib/common/route/route_generator.dart +++ b/lib/common/route/route_generator.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:treehousesble/common/route/routes.dart'; import 'package:treehousesble/feature/dashboard/screen/dashboard_page.dart'; import 'package:treehousesble/feature/dashboard/screen/dashboard_screen.dart'; +import 'package:treehousesble/feature/dashboard/screen/search_rpi_screen.dart'; +import 'package:treehousesble/feature/onboard/ui/screen/landing_page.dart'; import 'package:treehousesble/feature/onboard/ui/screen/onboard_page.dart'; import 'package:treehousesble/feature/onboard/ui/screen/splash_page.dart'; @@ -12,8 +14,12 @@ class RouteGenerator { return MaterialPageRoute(builder: (_) => SplashPage()); case Routes.onboarding: return MaterialPageRoute(builder: (_) => OnboardPage()); + case Routes.landing: + return MaterialPageRoute(builder: (_) => LandingPage()); case Routes.dashboard: return MaterialPageRoute(builder: (_) => DashboardPage()); + case Routes.search: + return MaterialPageRoute(builder: (_) => SearchRpiScreen()); default: return MaterialPageRoute(builder: (_) => SplashPage()); } diff --git a/lib/common/route/routes.dart b/lib/common/route/routes.dart index 7c76894d..afdad440 100644 --- a/lib/common/route/routes.dart +++ b/lib/common/route/routes.dart @@ -3,6 +3,7 @@ class Routes { static const landing = "/landing"; static const onboarding = "/onboarding"; static const dashboard = "/dashboard"; + static const search = "/search"; static const settings = "/settings"; static const notification = "/notification"; } diff --git a/lib/common/utils/multi_bloc_listing.dart b/lib/common/utils/multi_bloc_listing.dart index b2408cf3..98a3e5dc 100644 --- a/lib/common/utils/multi_bloc_listing.dart +++ b/lib/common/utils/multi_bloc_listing.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:treehousesble/common/bloc/bluetooth_cubit.dart'; class MultiBlocListing extends StatelessWidget { final Widget child; @@ -8,6 +9,9 @@ class MultiBlocListing extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider( + create: (context) => BluetoothCubit()..checkDeviceConnected(), + ), ], child: child, ); diff --git a/lib/common/utils/multi_repo_listing.dart b/lib/common/utils/multi_repo_listing.dart index 5ce7124c..8340e38e 100644 --- a/lib/common/utils/multi_repo_listing.dart +++ b/lib/common/utils/multi_repo_listing.dart @@ -1,13 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:treehousesble/common/constants/env.dart'; import 'multi_bloc_listing.dart'; class MultiRepoListing extends StatelessWidget { final Widget child; - const MultiRepoListing({required this.child}); + final Env env; + + const MultiRepoListing({required this.child, required this.env}); + @override Widget build(BuildContext context) { - return MultiRepositoryProvider(providers: [], child: MultiBlocListing(child: child)); + return MultiRepositoryProvider(providers: [ + RepositoryProvider(create: (context) => env, lazy: true), + ], child: MultiBlocListing(child: child)); } } diff --git a/lib/common/widget/page_wrapper.dart b/lib/common/widget/page_wrapper.dart index fac6213f..c93f5c51 100644 --- a/lib/common/widget/page_wrapper.dart +++ b/lib/common/widget/page_wrapper.dart @@ -14,7 +14,7 @@ class _PageWrapperState extends State { @override Widget build(BuildContext context) { if (widget.hasScaffold!) { - return Scaffold(body: widget.body); + return Scaffold( body: widget.body); } else { return widget.body; } diff --git a/lib/feature/dashboard/screen/dashboard_screen.dart b/lib/feature/dashboard/screen/dashboard_screen.dart index 24b7bbc9..4b86ec65 100644 --- a/lib/feature/dashboard/screen/dashboard_screen.dart +++ b/lib/feature/dashboard/screen/dashboard_screen.dart @@ -1,6 +1,10 @@ - import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_blue/flutter_blue.dart'; +import 'package:treehousesble/common/bloc/bluetooth_cubit.dart'; +import 'package:treehousesble/common/bloc/bluetooth_state.dart'; +import 'package:treehousesble/common/navigation/nav.dart'; +import 'package:treehousesble/common/route/routes.dart'; import 'package:treehousesble/feature/dashboard/screen/search_screen.dart'; import 'package:treehousesble/feature/dashboard/widget/fab_bottom_app_bar.dart'; import 'package:treehousesble/feature/network/screen/network_screen.dart'; @@ -14,10 +18,33 @@ class FindDevicesScreen extends StatefulWidget { class _FindDevicesScreenState extends State { int pageIndex = 0; + @override + void initState() { + super.initState(); + context.read()..checkDeviceConnected(); + } + + Widget getHome() { + return BlocConsumer( + bloc: context.read(), + listener: (con, state) { + + }, + builder: (context, state) { + if (state is StateDeviceConnected) { + return Text("Connected to device " + + state.characteristic.deviceId.toString()); + } else if (state is StateDeviceNotConnected) { + return Text("Not connected to device"); + } + return Container(); + }); + } + Widget pages(int index) { - switch(index) { + switch (index) { case 0: - return SearchPage(); + return getHome(); case 1: return Container(); case 2: @@ -30,7 +57,7 @@ class _FindDevicesScreenState extends State { } String pageName(int index) { - switch(index) { + switch (index) { case 0: return "Home"; case 1: @@ -44,7 +71,6 @@ class _FindDevicesScreenState extends State { } } - @override Widget build(BuildContext context) { return Scaffold( @@ -57,40 +83,29 @@ class _FindDevicesScreenState extends State { stream: FlutterBlue.instance.isScanning, initialData: false, builder: (c, snapshot) { - if (snapshot.data!) { - return FloatingActionButton( - child: Icon(Icons.stop), - onPressed: () => FlutterBlue.instance.stopScan(), - backgroundColor: Colors.red, - ); - } else { - return FloatingActionButton( - child: Icon(Icons.search), - onPressed: () => FlutterBlue.instance - .startScan(timeout: Duration(seconds: 10))); - } + return FloatingActionButton( + child: Icon(Icons.search), + onPressed: () => Navigator.pushNamed(context, Routes.search)); }, ), bottomNavigationBar: FABBottomAppBar( - items: [ - FABBottomAppBarItem(iconData: Icons.dashboard, text: 'Home'), - FABBottomAppBarItem(iconData: Icons.branding_watermark_outlined, text: 'Terminal'), - FABBottomAppBarItem(iconData: Icons.network_wifi, text: 'Network'), - FABBottomAppBarItem(iconData: Icons.settings, text: 'Settings'), - ], - notchedShape: CircularNotchedRectangle(), - color: Colors.black54, - selectedColor: Colors.white, - centerItemText: '', - backgroundColor: Theme.of(context).primaryColor, - onTabSelected: (int value) { - setState(() { - pageIndex = value; - }); - } - ), + items: [ + FABBottomAppBarItem(iconData: Icons.dashboard, text: 'Home'), + FABBottomAppBarItem( + iconData: Icons.branding_watermark_outlined, text: 'Terminal'), + FABBottomAppBarItem(iconData: Icons.network_wifi, text: 'Network'), + FABBottomAppBarItem(iconData: Icons.settings, text: 'Settings'), + ], + notchedShape: CircularNotchedRectangle(), + color: Colors.black54, + selectedColor: Colors.white, + centerItemText: '', + backgroundColor: Theme.of(context).primaryColor, + onTabSelected: (int value) { + setState(() { + pageIndex = value; + }); + }), ); } - - } diff --git a/lib/feature/dashboard/screen/search_rpi_screen.dart b/lib/feature/dashboard/screen/search_rpi_screen.dart new file mode 100644 index 00000000..7c6544f0 --- /dev/null +++ b/lib/feature/dashboard/screen/search_rpi_screen.dart @@ -0,0 +1,119 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_blue/flutter_blue.dart'; +import 'package:treehousesble/common/bloc/bluetooth_cubit.dart'; +import 'package:treehousesble/common/bloc/bluetooth_state.dart'; +import 'package:treehousesble/common/navigation/nav.dart'; +import 'package:treehousesble/common/route/routes.dart'; +import 'package:treehousesble/common/widget/page_wrapper.dart'; +import 'package:treehousesble/feature/dashboard/widget/scan_result_tile.dart'; + +import 'bluetooth_off_screen.dart'; +import 'device_screen.dart'; + +class SearchRpiScreen extends StatefulWidget { + @override + _SearchRpiScreenState createState() => _SearchRpiScreenState(); +} + +class _SearchRpiScreenState extends State { + @override + void initState() { + super.initState(); + context.read()..fetchDeviceList(true); + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: FlutterBlue.instance.state, + initialData: BluetoothState.unknown, + builder: (c, snapshot) { + final state = snapshot.data; + if (state == BluetoothState.on) { + return Scaffold( + appBar: AppBar( + title: Text("Search RPI"), + actions: [ + IconButton( + icon: Icon(Icons.refresh), + onPressed: () => + context.read()..fetchDeviceList(true), + ) + ], + ), + body: BlocConsumer( + listener: (con, state) { + if (state is StateDeviceConnected) { + Navigator.of(con).pushNamed(Routes.dashboard); + } + }, + builder: (context, state) { + if (state is StateFoundDevices) { + print(state.list.length); + print("FOUND DEVICES"); + return RefreshIndicator( + onRefresh: () { + context.read()..fetchDeviceList(true); + return new Future.value(); + }, + child: SingleChildScrollView( + child: Column( + children: state.list + .map((d) => ListTile( + title: Text(d.name), + subtitle: Text(d.id.toString()), + trailing: StreamBuilder( + stream: d.state, + initialData: BluetoothDeviceState.disconnected, + builder: (c, snapshot) { + if (snapshot.data == + BluetoothDeviceState.connected) { + return RaisedButton( + child: Text('OPEN'), + onPressed: () => context + .read() + .checkDeviceConnected(), + ); + } else { + return RaisedButton( + child: Text('CONNECT'), + onPressed: () { + context.read() + ..fetchServicesAndConnect(d); + }, + ); + } + return Text(snapshot.data.toString()); + }, + ), + )) + .toList(), + )), + ); + } else if (state is StateLoading) { + return Center( + child: CircularProgressIndicator(), + ); + } else if (state is StateError) { + return Container( + child: RaisedButton( + onPressed: () { + BlocProvider.of(context) + ..fetchDeviceList(true); + }, + child: Text("Retry"), + ), + ); + } + return Container(); + }, + ), + ); + } + return BluetoothOffScreen(state: state); + }); + + } +} diff --git a/lib/feature/dashboard/screen/search_screen.dart b/lib/feature/dashboard/screen/search_screen.dart index 3aa01802..297b7d5a 100644 --- a/lib/feature/dashboard/screen/search_screen.dart +++ b/lib/feature/dashboard/screen/search_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:treehousesble/feature/dashboard/widget/scan_result_tile.dart'; diff --git a/lib/feature/onboard/ui/screen/landing_page.dart b/lib/feature/onboard/ui/screen/landing_page.dart new file mode 100644 index 00000000..7ebc7cf6 --- /dev/null +++ b/lib/feature/onboard/ui/screen/landing_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:treehousesble/common/bloc/bluetooth_cubit.dart'; +import 'package:treehousesble/common/bloc/bluetooth_state.dart'; +import 'package:treehousesble/feature/dashboard/screen/dashboard_page.dart'; +import 'package:treehousesble/feature/dashboard/screen/search_rpi_screen.dart'; + +import 'onboard_page.dart'; + +class LandingPage extends StatelessWidget { + const LandingPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is FirstTimeAppOpen) { + return OnboardPage(); + } else { + return SearchRpiScreen(); + } + }, + ); + } +} diff --git a/lib/feature/onboard/ui/screen/splash_page.dart b/lib/feature/onboard/ui/screen/splash_page.dart index 0ba5e1df..2b7d1f3c 100644 --- a/lib/feature/onboard/ui/screen/splash_page.dart +++ b/lib/feature/onboard/ui/screen/splash_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:treehousesble/common/navigation/nav.dart'; import 'package:treehousesble/common/route/routes.dart'; import 'package:treehousesble/feature/onboard/ui/widget/splash_widget.dart'; @@ -14,7 +15,7 @@ class _SplashPageState extends State { void initState() { super.initState(); Timer(Duration(seconds: 2), () { - Navigator.pushReplacementNamed(context, Routes.onboarding); + Nav.pushReplacementNamed(Routes.landing); }); } diff --git a/lib/feature/onboard/ui/widget/onboard_widget.dart b/lib/feature/onboard/ui/widget/onboard_widget.dart index 013c6153..87749788 100644 --- a/lib/feature/onboard/ui/widget/onboard_widget.dart +++ b/lib/feature/onboard/ui/widget/onboard_widget.dart @@ -1,9 +1,26 @@ import 'package:flutter/material.dart'; import 'package:treehousesble/app/theme.dart'; import 'package:treehousesble/common/route/routes.dart'; +import 'package:treehousesble/common/shared_pref/shared_pref.dart'; import 'package:treehousesble/common/widget/page_wrapper.dart'; -class OnboardWidget extends StatelessWidget { +class OnboardWidget extends StatefulWidget { + + @override + _OnboardWidgetState createState() => _OnboardWidgetState(); +} + +class _OnboardWidgetState extends State { + @override + void initState() { + super.initState(); + setOnOpenFirstTime(); + } + + setOnOpenFirstTime() async { + await SharedPref.setFirstTimeAppOpen(false); + } + @override Widget build(BuildContext context) { return PageWrapper( @@ -19,7 +36,7 @@ class OnboardWidget extends StatelessWidget { customBorder: RoundedRectangleBorder( borderRadius: BorderRadius.circular(45)), onTap: () { - Navigator.pushReplacementNamed(context, Routes.dashboard); + Navigator.pushReplacementNamed(context, Routes.search); }, child: Container( height: 65, diff --git a/lib/main.dart b/lib/main.dart index 957dd8e8..dbc9e3cc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ - import 'dart:async'; import 'dart:math'; @@ -7,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:treehousesble/app/app.dart'; import 'package:treehousesble/app/theme.dart'; +import 'package:treehousesble/common/constants/env.dart'; import 'common/utils/log.dart'; @@ -15,7 +15,10 @@ void main() { // runApp(FlutterBlueApp()); runZonedGuarded(() { runApp( - App(), ); + App( + env: EnvValue.production, + ), + ); }, (e, s) { Log.e(e); Log.d(s); From 933e80aa9ec74a7868ee46f08b55b0cc76eb879f Mon Sep 17 00:00:00 2001 From: JLKwong <61807745+JLKwong@users.noreply.github.com> Date: Fri, 24 Sep 2021 20:05:34 -0700 Subject: [PATCH 2/3] system screen tab added (fixes #29) (#31) Co-authored-by: rrijal53 --- .../dashboard/screen/dashboard_screen.dart | 34 +++++++++-------- lib/feature/system/screen/system_screen.dart | 37 +++++++++++++++++++ .../system/widget/system_home_widget.dart | 25 +++++++++++++ .../system/widget/system_internet_widget.dart | 21 +++++++++++ .../system/widget/system_ssh_widget.dart | 21 +++++++++++ 5 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 lib/feature/system/screen/system_screen.dart create mode 100644 lib/feature/system/widget/system_home_widget.dart create mode 100644 lib/feature/system/widget/system_internet_widget.dart create mode 100644 lib/feature/system/widget/system_ssh_widget.dart diff --git a/lib/feature/dashboard/screen/dashboard_screen.dart b/lib/feature/dashboard/screen/dashboard_screen.dart index 4b86ec65..69984e44 100644 --- a/lib/feature/dashboard/screen/dashboard_screen.dart +++ b/lib/feature/dashboard/screen/dashboard_screen.dart @@ -9,6 +9,7 @@ import 'package:treehousesble/feature/dashboard/screen/search_screen.dart'; import 'package:treehousesble/feature/dashboard/widget/fab_bottom_app_bar.dart'; import 'package:treehousesble/feature/network/screen/network_screen.dart'; import 'package:treehousesble/feature/settings/screen/settings_screen.dart'; +import 'package:treehousesble/feature/system/screen/system_screen.dart'; class FindDevicesScreen extends StatefulWidget { @override @@ -27,9 +28,7 @@ class _FindDevicesScreenState extends State { Widget getHome() { return BlocConsumer( bloc: context.read(), - listener: (con, state) { - - }, + listener: (con, state) {}, builder: (context, state) { if (state is StateDeviceConnected) { return Text("Connected to device " + @@ -46,11 +45,13 @@ class _FindDevicesScreenState extends State { case 0: return getHome(); case 1: - return Container(); + return SystemScreen(); case 2: return NetWorkScreen(); case 3: return SettingsScreen(); + case 6: + return Container(); default: return Container(); } @@ -60,14 +61,16 @@ class _FindDevicesScreenState extends State { switch (index) { case 0: return "Home"; - case 1: + case 6: return "Terminal"; + case 1: + return "System"; case 2: return "Network"; case 3: return "Settings"; default: - return "Default"; + return "Home"; } } @@ -79,20 +82,19 @@ class _FindDevicesScreenState extends State { ), body: pages(pageIndex), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - floatingActionButton: StreamBuilder( - stream: FlutterBlue.instance.isScanning, - initialData: false, - builder: (c, snapshot) { - return FloatingActionButton( - child: Icon(Icons.search), - onPressed: () => Navigator.pushNamed(context, Routes.search)); - }, - ), + floatingActionButton: FloatingActionButton( + child: Icon(Icons.branding_watermark_outlined), + // on Pressed: () => Navigator.pushNamed(context, Routes.search)); + onPressed: () { + setState(() { + pageIndex = 6; + }); + }), bottomNavigationBar: FABBottomAppBar( items: [ FABBottomAppBarItem(iconData: Icons.dashboard, text: 'Home'), FABBottomAppBarItem( - iconData: Icons.branding_watermark_outlined, text: 'Terminal'), + iconData: Icons.branding_watermark_outlined, text: 'System'), FABBottomAppBarItem(iconData: Icons.network_wifi, text: 'Network'), FABBottomAppBarItem(iconData: Icons.settings, text: 'Settings'), ], diff --git a/lib/feature/system/screen/system_screen.dart b/lib/feature/system/screen/system_screen.dart new file mode 100644 index 00000000..9f3d4d0e --- /dev/null +++ b/lib/feature/system/screen/system_screen.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:treehousesble/feature/system/widget/system_home_widget.dart'; +import 'package:treehousesble/feature/system/widget/system_internet_widget.dart'; +import 'package:treehousesble/feature/system/widget/system_ssh_widget.dart'; + + +class SystemScreen extends StatefulWidget{ + @override + _SystemScreenState createState() => _SystemScreenState(); +} + +class _SystemScreenState extends State { + @override + Widget build(BuildContext context) { + + return DefaultTabController( + length: 3, + child: Scaffold( + appBar: TabBar( + labelColor: Theme.of(context).primaryColor, + tabs: [ + Tab(text: "SSH",), + Tab(text: "Internet",), + Tab(text: "Home",), + ], + ), + body: TabBarView( + children: [ + SystemSSHWidget(), + SystemInternetWidget(), + SystemHomeWidget(), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/feature/system/widget/system_home_widget.dart b/lib/feature/system/widget/system_home_widget.dart new file mode 100644 index 00000000..18733153 --- /dev/null +++ b/lib/feature/system/widget/system_home_widget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class SystemHomeWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + InkWell( + child: Text("Shutdown & Reboot",), + onTap: () { + + }, + ), + InkWell( + child: Text("Open VNC"), + onTap: () {}, + ), + InkWell( + child: Text("Toggle Camera"), + onTap: () {}, + ), + ], + ); + } +} diff --git a/lib/feature/system/widget/system_internet_widget.dart b/lib/feature/system/widget/system_internet_widget.dart new file mode 100644 index 00000000..44c32cd6 --- /dev/null +++ b/lib/feature/system/widget/system_internet_widget.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class SystemInternetWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + InkWell( + child: Text("Share Internet With Pi (beta)",), + onTap: () { + + }, + ), + InkWell( + child: Text("Internet Blocking"), + onTap: () {}, + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/feature/system/widget/system_ssh_widget.dart b/lib/feature/system/widget/system_ssh_widget.dart new file mode 100644 index 00000000..8acd6655 --- /dev/null +++ b/lib/feature/system/widget/system_ssh_widget.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class SystemSSHWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + InkWell( + child: Text("Add SSH Key",), + onTap: () { + + }, + ), + InkWell( + child: Text("SSH 2 Factor Authentication"), + onTap: () {}, + ), + ], + ); + } +} From 7858e4d0241670424ff187a2361b3888802122c4 Mon Sep 17 00:00:00 2001 From: Roshan Rijal Date: Sat, 9 Oct 2021 21:04:20 +0545 Subject: [PATCH 3/3] terminal screen using bloc (fixes #20) (#38) --- lib/common/bloc/bluetooth_cubit.dart | 59 +++++++++--- lib/common/bloc/bluetooth_state.dart | 1 + .../dashboard/screen/dashboard_screen.dart | 3 +- .../terminal/screen/terminal_screen.dart | 96 +++++++++++++++++++ 4 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 lib/feature/terminal/screen/terminal_screen.dart diff --git a/lib/common/bloc/bluetooth_cubit.dart b/lib/common/bloc/bluetooth_cubit.dart index 71ff1463..561b1c2b 100644 --- a/lib/common/bloc/bluetooth_cubit.dart +++ b/lib/common/bloc/bluetooth_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:bloc/bloc.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:treehousesble/app/app.dart'; @@ -22,6 +24,27 @@ class BluetoothCubit extends Cubit { emit(NotFirstTimeAppOpen()); } } + List _sendCommand(String command) { + return utf8.encode(command); + } + + writeMessage(String command) async{ + if(characteristic != null){ + await characteristic?.write(_sendCommand(command), withoutResponse: true); + print("SEND MESSAGE " + command); + readMessage(); + } + } + + readMessage() async{ + if(characteristic != null){ + emit(StateReading()); + List response = await characteristic!.read(); + var responseString = utf8.decode(response); + emit(StateReadSuccess(data: responseString)); + } + + } fetchDeviceList(bool filterPi) { emit(StateLoading()); @@ -56,21 +79,13 @@ class BluetoothCubit extends Cubit { emit(StateLoading()); try { await device.connect(timeout: Duration(seconds: 20)); - List services = await device.discoverServices(); - for (BluetoothService service in services) { - if (service.uuid.toString() == Strings.BLUETOOTH_UUID) { - List characteristics = - service.characteristics; - if (characteristics.length > 0) { - characteristic = characteristics[0]; - emit(StateDeviceConnected(characteristic: characteristic!)); - print("CONNECTED TO DEVICE"); - break; - } - } - } + discoverServices(device); } catch (e) { - emit(StateError(message: "Unable to connect please try again")); + if(e.toString().contains("already")){ + discoverServices(device); + }else{ + emit(StateError(message: "Unable to connect please try again")); + } } } @@ -100,4 +115,20 @@ class BluetoothCubit extends Cubit { } return true; } + + void discoverServices(BluetoothDevice device)async{ + List services = await device.discoverServices(); + for (BluetoothService service in services) { + if (service.uuid.toString() == Strings.BLUETOOTH_UUID) { + List characteristics = + service.characteristics; + if (characteristics.length > 0) { + characteristic = characteristics[0]; + emit(StateDeviceConnected(characteristic: characteristic!)); + print("CONNECTED TO DEVICE"); + break; + } + } + } + } } diff --git a/lib/common/bloc/bluetooth_state.dart b/lib/common/bloc/bluetooth_state.dart index 9f70feb9..ae4b121a 100644 --- a/lib/common/bloc/bluetooth_state.dart +++ b/lib/common/bloc/bluetooth_state.dart @@ -25,6 +25,7 @@ class NotFirstTimeAppOpen extends DataState { class StateIniital extends DataState {} class StateLoading extends DataState {} +class StateReading extends DataState {} class StateError extends DataState { final String message; diff --git a/lib/feature/dashboard/screen/dashboard_screen.dart b/lib/feature/dashboard/screen/dashboard_screen.dart index 69984e44..debb69b5 100644 --- a/lib/feature/dashboard/screen/dashboard_screen.dart +++ b/lib/feature/dashboard/screen/dashboard_screen.dart @@ -10,6 +10,7 @@ import 'package:treehousesble/feature/dashboard/widget/fab_bottom_app_bar.dart'; import 'package:treehousesble/feature/network/screen/network_screen.dart'; import 'package:treehousesble/feature/settings/screen/settings_screen.dart'; import 'package:treehousesble/feature/system/screen/system_screen.dart'; +import 'package:treehousesble/feature/terminal/screen/terminal_screen.dart'; class FindDevicesScreen extends StatefulWidget { @override @@ -51,7 +52,7 @@ class _FindDevicesScreenState extends State { case 3: return SettingsScreen(); case 6: - return Container(); + return TerminalScreen(); default: return Container(); } diff --git a/lib/feature/terminal/screen/terminal_screen.dart b/lib/feature/terminal/screen/terminal_screen.dart new file mode 100644 index 00000000..b84845cc --- /dev/null +++ b/lib/feature/terminal/screen/terminal_screen.dart @@ -0,0 +1,96 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_blue/flutter_blue.dart'; +import 'package:treehousesble/common/bloc/bluetooth_cubit.dart'; +import 'package:treehousesble/common/bloc/bluetooth_state.dart'; + +class TerminalScreen extends StatefulWidget { + TerminalScreen(); + + @override + State createState() => _TerminalScreenState(); +} + +class _TerminalScreenState extends State { + List responseList = []; + TextEditingController inputController = new TextEditingController(); + + @override + void initState() { + super.initState(); + context.read()..checkDeviceConnected(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + bloc: context.read(), + listener: (con, state) { + if (state is StateReadSuccess) { + responseList.add(state.data); + } + }, + builder: (context, state) { + return Scaffold( + body: SafeArea( + child: Padding(child: Column( + children: [ + Expanded( + child: ListView.builder( + padding: EdgeInsets.zero, + scrollDirection: Axis.vertical, + itemCount: responseList.length, + itemBuilder: (BuildContext context, int index) { + return Text(responseList[index]); + }, + )), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: TextFormField( + controller: inputController, + obscureText: false, + decoration: InputDecoration( + hintText: 'Enter Command', + ), + ), + ), + state is StateDeviceNotConnected + ? Text("Not connected to device") + : IconButton( + icon: Icon(Icons.send, + color: Theme.of(context).iconTheme.color), + onPressed: () { + String input = inputController.text; + responseList.add(input); + context.read() + ..writeMessage(inputController.text); + inputController.clear(); + }, + ), + IconButton( + icon: Icon( + Icons.settings_outlined, + color: Colors.black, + size: 30, + ), + onPressed: () {}, + ), + IconButton( + icon: Icon( + Icons.settings_outlined, + color: Colors.black, + size: 30, + ), + onPressed: () {}, + ) + ], + ) + ], + ), padding: EdgeInsets.all(16),))); + }); + } +}