2018-09-10 00:34:52 +03:00
import ' dart:convert ' ;
2018-09-15 01:46:15 +03:00
import ' dart:async ' ;
2018-09-15 12:56:42 +03:00
import ' package:flutter/rendering.dart ' ;
2018-09-10 00:34:52 +03:00
import ' package:http/http.dart ' as http ;
import ' package:flutter/material.dart ' ;
2018-09-10 03:06:35 +03:00
import ' package:shared_preferences/shared_preferences.dart ' ;
2018-09-12 00:32:04 +03:00
import ' package:web_socket_channel/io.dart ' ;
import ' package:web_socket_channel/status.dart ' as socketStatus ;
2018-09-15 01:46:15 +03:00
import ' package:progress_indicators/progress_indicators.dart ' ;
2018-09-16 18:02:12 +03:00
import ' package:event_bus/event_bus.dart ' ;
2018-09-16 19:24:26 +03:00
import ' package:flutter/widgets.dart ' ;
2018-09-17 01:03:34 +03:00
import ' package:package_info/package_info.dart ' ;
2018-09-10 03:06:35 +03:00
part ' settings.dart ' ;
2018-09-15 01:46:15 +03:00
part ' data_model.dart ' ;
2018-09-10 00:34:52 +03:00
2018-09-16 18:02:12 +03:00
EventBus eventBus = new EventBus ( ) ;
2018-09-17 01:03:34 +03:00
const String appName = " Home Assistant Client " ;
const appVersion = " 0.0.5 " ;
2018-09-16 18:02:12 +03:00
2018-09-12 00:32:04 +03:00
void main ( ) = > runApp ( new HassClientApp ( ) ) ;
2018-09-10 00:34:52 +03:00
2018-09-12 00:32:04 +03:00
class HassClientApp extends StatelessWidget {
2018-09-10 00:34:52 +03:00
// This widget is the root of your application.
@ override
Widget build ( BuildContext context ) {
return new MaterialApp (
2018-09-17 01:03:34 +03:00
title: appName ,
2018-09-10 00:34:52 +03:00
theme: new ThemeData (
primarySwatch: Colors . blue ,
) ,
2018-09-10 03:06:35 +03:00
initialRoute: " / " ,
routes: {
" / " : ( context ) = > MainPage ( title: ' Hass Client ' ) ,
2018-09-17 00:28:19 +03:00
" /connection-settings " : ( context ) = > ConnectionSettingsPage ( title: " Connection Settings " )
2018-09-10 03:06:35 +03:00
} ,
2018-09-10 00:34:52 +03:00
) ;
}
}
2018-09-10 03:06:35 +03:00
class MainPage extends StatefulWidget {
MainPage ( { Key key , this . title } ) : super ( key: key ) ;
2018-09-10 00:34:52 +03:00
final String title ;
@ override
2018-09-10 03:06:35 +03:00
_MainPageState createState ( ) = > new _MainPageState ( ) ;
2018-09-10 00:34:52 +03:00
}
2018-09-16 19:24:26 +03:00
class _MainPageState extends State < MainPage > with WidgetsBindingObserver {
2018-09-15 01:46:15 +03:00
HassioDataModel _dataModel ;
Map _entitiesData ;
2018-09-15 12:56:42 +03:00
Map _uiStructure ;
2018-09-17 00:28:19 +03:00
Map _instanceConfig ;
2018-09-16 14:58:21 +03:00
int _uiViewsCount = 0 ;
2018-09-15 01:46:15 +03:00
String _dataModelErrorMessage = " " ;
2018-09-17 00:28:19 +03:00
String _instanceHost ;
2018-09-15 01:46:15 +03:00
bool loading = true ;
2018-09-16 00:06:07 +03:00
Map _stateIconColors = {
" on " : Colors . amber ,
" off " : Colors . blueGrey ,
" unavailable " : Colors . black12 ,
" unknown " : Colors . black12 ,
" playing " : Colors . amber
} ;
2018-09-10 03:06:35 +03:00
@ override
void initState ( ) {
super . initState ( ) ;
2018-09-16 19:24:26 +03:00
WidgetsBinding . instance . addObserver ( this ) ;
_init ( ) ;
2018-09-10 03:06:35 +03:00
}
2018-09-10 00:34:52 +03:00
2018-09-16 19:24:26 +03:00
@ override
void didChangeAppLifecycleState ( AppLifecycleState state ) {
debugPrint ( " $ state " ) ;
if ( state = = AppLifecycleState . resumed ) {
_refreshData ( ) ;
}
}
_init ( ) async {
2018-09-10 03:06:35 +03:00
SharedPreferences prefs = await SharedPreferences . getInstance ( ) ;
2018-09-17 00:28:19 +03:00
String domain = prefs . getString ( ' hassio-domain ' ) ;
String port = prefs . getString ( ' hassio-port ' ) ;
2018-09-17 22:20:36 +03:00
_instanceHost = " $ domain : $ port " ;
String _hassioAPIEndpoint = " ${ prefs . getString ( ' hassio-protocol ' ) } :// $ domain : $ port /api/websocket " ;
2018-09-15 01:46:15 +03:00
String _hassioPassword = prefs . getString ( ' hassio-password ' ) ;
_dataModel = HassioDataModel ( _hassioAPIEndpoint , _hassioPassword ) ;
2018-09-17 01:03:34 +03:00
_refreshData ( ) ;
2018-09-16 18:02:12 +03:00
eventBus . on < StateChangedEvent > ( ) . listen ( ( event ) {
debugPrint ( " State change event for ${ event . entityId } " ) ;
setState ( ( ) {
_entitiesData = _dataModel . entities ;
} ) ;
} ) ;
2018-09-12 00:32:04 +03:00
}
2018-09-15 01:46:15 +03:00
_refreshData ( ) async {
2018-09-12 00:32:04 +03:00
setState ( ( ) {
2018-09-15 01:46:15 +03:00
loading = true ;
2018-09-12 00:32:04 +03:00
} ) ;
2018-09-16 15:32:17 +03:00
_dataModelErrorMessage = null ;
2018-09-15 01:46:15 +03:00
if ( _dataModel ! = null ) {
await _dataModel . fetch ( ) . then ( ( result ) {
setState ( ( ) {
2018-09-17 00:28:19 +03:00
_instanceConfig = _dataModel . instanceConfig ;
2018-09-15 12:56:42 +03:00
_entitiesData = _dataModel . entities ;
_uiStructure = _dataModel . uiStructure ;
2018-09-16 14:58:21 +03:00
_uiViewsCount = _uiStructure . length ;
2018-09-15 01:46:15 +03:00
loading = false ;
} ) ;
} ) . catchError ( ( e ) {
setState ( ( ) {
_dataModelErrorMessage = e . toString ( ) ;
loading = false ;
} ) ;
2018-09-12 00:32:04 +03:00
} ) ;
2018-09-15 01:46:15 +03:00
}
2018-09-11 01:09:21 +03:00
}
2018-09-15 12:56:42 +03:00
Widget _buildEntityAction ( String entityId ) {
var entity = _entitiesData [ entityId ] ;
2018-09-16 00:06:07 +03:00
Widget result ;
2018-09-15 12:56:42 +03:00
if ( entity [ " actionType " ] = = " switch " ) {
2018-09-16 00:06:07 +03:00
result = Switch (
2018-09-15 12:56:42 +03:00
value: ( entity [ " state " ] = = " on " ) ,
onChanged: ( ( state ) {
2018-09-16 00:06:07 +03:00
_dataModel . callService (
entity [ " domain " ] , state ? " turn_on " : " turn_off " , entityId ) ;
2018-09-15 12:56:42 +03:00
setState ( ( ) {
_entitiesData [ entityId ] [ " state " ] = state ? " on " : " off " ;
} ) ;
} ) ,
) ;
2018-09-16 00:06:07 +03:00
} else if ( entity [ " actionType " ] = = " statelessIcon " ) {
result = SizedBox (
width: 60.0 ,
child: FlatButton (
onPressed: ( ( ) {
_dataModel . callService ( entity [ " domain " ] , " turn_on " , entityId ) ;
} ) ,
child: Text (
" Run " ,
textAlign: TextAlign . right ,
style: new TextStyle ( fontSize: 16.0 , color: Colors . blue ) ,
2018-09-15 12:56:42 +03:00
) ,
2018-09-16 00:06:07 +03:00
) ) ;
2018-09-15 12:56:42 +03:00
} else {
2018-09-16 00:06:07 +03:00
result = Padding (
padding: EdgeInsets . fromLTRB ( 0.0 , 0.0 , 16.0 , 0.0 ) ,
child: Text (
" ${ entity [ " state " ] } ${ ( entity [ " attributes " ] ! = null & & entity [ " attributes " ] [ " unit_of_measurement " ] ! = null ) ? entity [ " attributes " ] [ " unit_of_measurement " ] : ' ' } " ,
textAlign: TextAlign . right ,
style: new TextStyle (
fontSize: 16.0 ,
) ) ) ;
2018-09-15 12:56:42 +03:00
}
2018-09-16 00:06:07 +03:00
/ * return SizedBox (
width: 60.0 ,
// height: double.infinity,
child: result
) ; * /
return result ;
}
2018-09-15 12:56:42 +03:00
2018-09-16 14:58:21 +03:00
Card _buildCard ( List < String > ids , String name ) {
2018-09-15 12:56:42 +03:00
List < Widget > body = [ ] ;
2018-09-16 14:58:21 +03:00
body . add ( _buildCardHeader ( name ) ) ;
body . addAll ( _buildCardBody ( ids ) ) ;
2018-09-16 00:06:07 +03:00
Card result =
Card ( child: new Column ( mainAxisSize: MainAxisSize . min , children: body ) ) ;
2018-09-15 12:56:42 +03:00
return result ;
}
2018-09-16 14:58:21 +03:00
Widget _buildCardHeader ( String name ) {
2018-09-15 12:56:42 +03:00
var result ;
if ( name . length > 0 ) {
result = new ListTile (
//leading: const Icon(Icons.device_hub),
//subtitle: Text(".."),
//trailing: Text("${data["state"]}"),
2018-09-16 00:06:07 +03:00
title: Text ( " $ name " ,
2018-09-15 12:56:42 +03:00
textAlign: TextAlign . left ,
overflow: TextOverflow . ellipsis ,
2018-09-16 00:06:07 +03:00
style: new TextStyle ( fontWeight: FontWeight . bold , fontSize: 25.0 ) ) ,
2018-09-15 12:56:42 +03:00
) ;
} else {
result = new Container ( width: 0.0 , height: 0.0 ) ;
}
return result ;
2018-09-10 00:34:52 +03:00
}
2018-09-16 14:58:21 +03:00
List < Widget > _buildCardBody ( List < String > ids ) {
2018-09-15 12:56:42 +03:00
List < Widget > entities = [ ] ;
2018-09-16 00:06:07 +03:00
ids . forEach ( ( id ) {
2018-09-15 12:56:42 +03:00
var data = _entitiesData [ id ] ;
2018-09-17 22:20:36 +03:00
if ( data = = null ) {
debugPrint ( " Hiding unknown entity from card: $ id " ) ;
} else {
entities . add ( new ListTile (
leading: Icon (
_createMDIfromCode ( data [ " iconCode " ] ) ,
color: _stateIconColors [ data [ " state " ] ] ? ? Colors . blueGrey ,
) ,
//subtitle: Text("${data['entity_id']}"),
trailing: _buildEntityAction ( id ) ,
title: Text (
" ${ data [ " display_name " ] } " ,
overflow: TextOverflow . ellipsis ,
) ,
) ) ;
}
2018-09-15 12:56:42 +03:00
} ) ;
return entities ;
2018-09-12 22:31:58 +03:00
}
2018-09-10 00:34:52 +03:00
2018-09-16 14:58:21 +03:00
List < Widget > buildSingleView ( structure ) {
2018-09-15 01:46:15 +03:00
List < Widget > result = [ ] ;
2018-09-16 15:32:17 +03:00
structure [ " standalone " ] . forEach ( ( entityId ) {
result . add ( _buildCard ( [ entityId ] , " " ) ) ;
} ) ;
structure [ " groups " ] . forEach ( ( group ) {
result . add ( _buildCard (
group [ " children " ] , group [ " friendly_name " ] . toString ( ) ) ) ;
} ) ;
2018-09-15 01:46:15 +03:00
return result ;
2018-09-16 14:58:21 +03:00
}
List < ListView > buildUIViews ( ) {
List < ListView > result = [ ] ;
if ( ( _entitiesData ! = null ) & & ( _uiStructure ! = null ) ) {
_uiStructure . forEach ( ( viewId , structure ) {
result . add ( ListView (
children: buildSingleView ( structure ) ,
) ) ;
} ) ;
2018-09-15 01:46:15 +03:00
}
2018-09-16 14:58:21 +03:00
return result ;
}
2018-09-16 16:12:09 +03:00
IconData _createMDIfromCode ( int code ) {
return IconData ( code , fontFamily: ' Material Design Icons ' ) ;
}
2018-09-16 14:58:21 +03:00
List < Tab > buildUIViewTabs ( ) {
List < Tab > result = [ ] ;
if ( ( _entitiesData ! = null ) & & ( _uiStructure ! = null ) ) {
_uiStructure . forEach ( ( viewId , structure ) {
result . add (
2018-09-17 22:20:36 +03:00
Tab (
icon: Icon ( _createMDIfromCode ( structure [ " iconCode " ] ) )
)
2018-09-16 14:58:21 +03:00
) ;
} ) ;
}
return result ;
2018-09-15 01:46:15 +03:00
}
Widget _buildTitle ( ) {
Row titleRow = Row (
2018-09-17 01:03:34 +03:00
children: [ Text ( _instanceConfig ! = null ? _instanceConfig [ " location_name " ] : " " ) ] ,
2018-09-15 01:46:15 +03:00
) ;
if ( loading ) {
titleRow . children . add ( Padding (
child: JumpingDotsProgressIndicator (
2018-09-17 00:28:19 +03:00
fontSize: 26.0 ,
2018-09-15 01:46:15 +03:00
color: Colors . white ,
) ,
2018-09-17 00:28:19 +03:00
padding: const EdgeInsets . fromLTRB ( 5.0 , 0.0 , 0.0 , 30.0 ) ,
2018-09-16 00:06:07 +03:00
) ) ;
2018-09-15 01:46:15 +03:00
}
return titleRow ;
2018-09-10 00:34:52 +03:00
}
2018-09-16 14:58:21 +03:00
Drawer _buildAppDrawer ( ) {
return new Drawer (
child: ListView (
children: < Widget > [
new UserAccountsDrawerHeader (
2018-09-17 00:28:19 +03:00
accountName: Text ( _instanceConfig ! = null ? _instanceConfig [ " location_name " ] : " Unknown " ) ,
accountEmail: Text ( _instanceHost ? ? " Not configured " ) ,
currentAccountPicture: new Image . asset ( ' images/hassio-192x192.png ' ) ,
2018-09-16 14:58:21 +03:00
) ,
new ListTile (
leading: Icon ( Icons . settings ) ,
2018-09-17 00:28:19 +03:00
title: Text ( " Connection settings " ) ,
2018-09-16 14:58:21 +03:00
onTap: ( ) {
2018-09-17 00:28:19 +03:00
Navigator . pushNamed ( context , ' /connection-settings ' ) ;
2018-09-16 14:58:21 +03:00
} ,
) ,
new AboutListTile (
2018-09-17 01:03:34 +03:00
applicationName: appName ,
applicationVersion: appVersion ,
2018-09-16 14:58:21 +03:00
applicationLegalese: " Keyboard Crumbs " ,
)
] ,
) ,
) ;
}
2018-09-10 00:34:52 +03:00
@ override
Widget build ( BuildContext context ) {
2018-09-16 14:58:21 +03:00
// This method is rerun every time setState is called.
2018-09-10 00:34:52 +03:00
//
2018-09-16 14:58:21 +03:00
if ( _entitiesData = = null ) {
return new Scaffold (
appBar: new AppBar (
title: _buildTitle ( )
) ,
drawer: _buildAppDrawer ( ) ,
2018-09-16 16:12:09 +03:00
body: Center (
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Padding (
padding: EdgeInsets . fromLTRB ( 0.0 , 0.0 , 0.0 , 10.0 ) ,
child: Text (
2018-09-17 00:28:19 +03:00
_dataModelErrorMessage ! = null ? " Well... no. \n \n There was an error: $ _dataModelErrorMessage \n \n Check your internet connection or restart the app " : " Loading... I hope... " ,
2018-09-16 16:12:09 +03:00
textAlign: TextAlign . center ,
style: TextStyle ( fontSize: 16.0 ) ,
) ,
) ,
GlowingProgressIndicator (
child: Icon (
_createMDIfromCode ( MaterialDesignIcons . getCustomIconByName ( " mdi:home-assistant " ) ) ,
size: 40.0 ,
2018-09-16 21:27:16 +03:00
color: _dataModelErrorMessage = = null ? Colors . blue : Colors . redAccent ,
2018-09-16 16:12:09 +03:00
) ,
) ,
]
) ,
) ,
//Text(_dataModelErrorMessage != null ? "Well... no.\n\nThere was an error:\n$_dataModelErrorMessage" : "Loading... or not...\n\nJust wait 10 seconds"),
2018-09-16 14:58:21 +03:00
floatingActionButton: new FloatingActionButton (
onPressed: _refreshData ,
tooltip: ' Increment ' ,
child: new Icon ( Icons . refresh ) ,
) ,
) ;
} else {
return DefaultTabController (
length: _uiViewsCount ,
child: new Scaffold (
appBar: new AppBar (
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: _buildTitle ( ) ,
bottom: TabBar (
tabs: buildUIViewTabs ( )
2018-09-16 00:06:07 +03:00
) ,
2018-09-10 03:06:35 +03:00
) ,
2018-09-16 14:58:21 +03:00
drawer: _buildAppDrawer ( ) ,
body: TabBarView (
children: buildUIViews ( )
2018-09-10 01:25:25 +03:00
) ,
2018-09-16 14:58:21 +03:00
floatingActionButton: new FloatingActionButton (
onPressed: _refreshData ,
tooltip: ' Increment ' ,
child: new Icon ( Icons . refresh ) ,
) ,
)
) ;
}
2018-09-10 00:34:52 +03:00
}
2018-09-12 00:32:04 +03:00
@ override
void dispose ( ) {
2018-09-16 19:24:26 +03:00
WidgetsBinding . instance . removeObserver ( this ) ;
_dataModel . closeConnection ( ) ;
2018-09-12 00:32:04 +03:00
super . dispose ( ) ;
}
2018-09-10 00:34:52 +03:00
}