Resolves #224 Main UI tablet support

This commit is contained in:
estevez-dev
2019-09-14 18:32:44 +03:00
parent 20ffe03139
commit 6da7a5ab90
15 changed files with 194 additions and 124 deletions

View File

@ -1,6 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keyboardcrumbs.hassclient"> package="com.keyboardcrumbs.hassclient">
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.

View File

@ -192,7 +192,7 @@ class CardWidget extends StatelessWidget {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
icon: Icon(MaterialDesignIcons.getIconDataFromIconName( icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical")), "mdi:dots-vertical")),
onPressed: () => eventBus.fire(new ShowEntityPageEvent(card.linkedEntityWrapper.entity)) onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: card.linkedEntityWrapper.entity))
) )
) )
] ]

View File

@ -114,4 +114,7 @@ class Sizes {
static const rowPadding = 10.0; static const rowPadding = 10.0;
static const doubleRowPadding = rowPadding*2; static const doubleRowPadding = rowPadding*2;
static const minViewColumnWidth = 350; static const minViewColumnWidth = 350;
static const entityPageMaxWidth = 400.0;
static const mainPageScreenSeparatorWidth = 5.0;
static const tabletMinWidth = minViewColumnWidth + entityPageMaxWidth + 5;
} }

View File

@ -140,6 +140,6 @@ class BadgeWidget extends StatelessWidget {
], ],
), ),
onTap: () => onTap: () =>
eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))); eventBus.fire(new ShowEntityPageEvent(entity: entityModel.entityWrapper.entity)));
} }
} }

View File

@ -211,21 +211,56 @@ class Entity {
); );
} }
Widget buildEntityPageWidget(BuildContext context) { Widget buildEntityPageWidget(BuildContext context, {bool showClose: false}) {
return EntityModel( return EntityModel(
entityWrapper: EntityWrapper(entity: this), entityWrapper: EntityWrapper(entity: this),
child: EntityPageContainer(children: <Widget>[ child: ListView(
Padding( padding: EdgeInsets.all(0),
padding: EdgeInsets.only(top: Sizes.rowPadding, left: Sizes.leftWidgetPadding), children: <Widget>[
child: DefaultEntityContainer(state: _buildStatePartForPage(context)), showClose ?
), Container(
LastUpdatedWidget(), color: Colors.blue[300],
Divider(), height: 36,
_buildAdditionalControlsForPage(context), child: Row(
Divider(), children: <Widget>[
buildHistoryWidget(), Expanded(
EntityAttributesList() child: Padding(
]), padding: EdgeInsets.only(left: 8),
child: Text(
this.displayName,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 22
),
),
),
),
IconButton(
padding: EdgeInsets.all(0),
icon: Icon(Icons.close),
color: Colors.white,
iconSize: 30.0,
onPressed: () {
eventBus.fire(ShowEntityPageEvent());
},
)
],
),
) :
Container(height: 0, width: 0,),
Padding(
padding: EdgeInsets.only(top: Sizes.rowPadding, left: Sizes.leftWidgetPadding),
child: DefaultEntityContainer(state: _buildStatePartForPage(context)),
),
LastUpdatedWidget(),
Divider(),
_buildAdditionalControlsForPage(context),
Divider(),
buildHistoryWidget(),
EntityAttributesList()
]
),
handleTap: false, handleTap: false,
); );
} }

View File

@ -53,7 +53,7 @@ class EntityWrapper {
case EntityUIAction.moreInfo: { case EntityUIAction.moreInfo: {
eventBus.fire( eventBus.fire(
new ShowEntityPageEvent(entity)); new ShowEntityPageEvent(entity: entity));
break; break;
} }
@ -93,7 +93,7 @@ class EntityWrapper {
case EntityUIAction.moreInfo: { case EntityUIAction.moreInfo: {
eventBus.fire( eventBus.fire(
new ShowEntityPageEvent(entity)); new ShowEntityPageEvent(entity: entity));
break; break;
} }

View File

@ -27,7 +27,6 @@ class LightColorPickerState extends State<LightColorPicker> {
List<Widget> colorRows = []; List<Widget> colorRows = [];
Border border; Border border;
bool isSomethingSelected = false; bool isSomethingSelected = false;
Logger.d("Current colotfor picker: [${widget.color.hue}, ${widget.color.saturation}]");
for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) { for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) {
List<Widget> rowChildren = []; List<Widget> rowChildren = [];
//Logger.d("$saturation"); //Logger.d("$saturation");

View File

@ -229,7 +229,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
IconButton( IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName( icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical")), "mdi:dots-vertical")),
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity)) onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: entity))
) )
); );
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) { } else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {

View File

@ -69,7 +69,6 @@ part 'entities/flat_service_button.widget.dart';
part 'entities/light/widgets/light_color_picker.dart'; part 'entities/light/widgets/light_color_picker.dart';
part 'entities/camera/widgets/camera_stream_view.dart'; part 'entities/camera/widgets/camera_stream_view.dart';
part 'entities/entity_colors.class.dart'; part 'entities/entity_colors.class.dart';
part 'pages/widgets/entity_page_container.widget.dart';
part 'plugins/history_chart/entity_history.dart'; part 'plugins/history_chart/entity_history.dart';
part 'plugins/history_chart/simple_state_history_chart.dart'; part 'plugins/history_chart/simple_state_history_chart.dart';
part 'plugins/history_chart/numeric_state_history_chart.dart'; part 'plugins/history_chart/numeric_state_history_chart.dart';

View File

@ -10,9 +10,10 @@ class EntityViewPage extends StatefulWidget {
} }
class _EntityViewPageState extends State<EntityViewPage> { class _EntityViewPageState extends State<EntityViewPage> {
String _title;
StreamSubscription _refreshDataSubscription; StreamSubscription _refreshDataSubscription;
StreamSubscription _stateSubscription; StreamSubscription _stateSubscription;
Entity entity;
Entity forwardToMainPage;
@override @override
void initState() { void initState() {
@ -26,16 +27,18 @@ class _EntityViewPageState extends State<EntityViewPage> {
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) { _refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
setState(() {}); setState(() {});
}); });
_prepareData(); entity = HomeAssistant().entities.get(widget.entityId);
} }
void _prepareData() async {
_title = HomeAssistant().entities.get(widget.entityId).displayName;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget body;
if (MediaQuery.of(context).size.width >= Sizes.tabletMinWidth) {
_popAfterBuild();
body = PageLoadingIndicator();
} else {
body = entity.buildEntityPageWidget(context);
}
return new Scaffold( return new Scaffold(
appBar: new AppBar( appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){ leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
@ -43,16 +46,23 @@ class _EntityViewPageState extends State<EntityViewPage> {
}), }),
// Here we take the value from the MyHomePage object that was created by // 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. // the App.build method, and use it to set our appbar title.
title: new Text(_title), title: new Text("${entity.displayName}"),
), ),
body: HomeAssistant().entities.get(widget.entityId).buildEntityPageWidget(context), body: body,
); );
} }
_popAfterBuild() async {
forwardToMainPage = entity;
await Future.delayed(Duration(milliseconds: 300));
Navigator.of(context).pop();
}
@override @override
void dispose(){ void dispose(){
if (_stateSubscription != null) _stateSubscription.cancel(); if (_stateSubscription != null) _stateSubscription.cancel();
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel(); if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
eventBus.fire(ShowEntityPageEvent(entity: forwardToMainPage));
super.dispose(); super.dispose();
} }
} }

View File

@ -26,6 +26,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
bool _showLoginButton = false; bool _showLoginButton = false;
bool _preventAppRefresh = false; bool _preventAppRefresh = false;
String _savedSharedText; String _savedSharedText;
String _entityToShow;
@override @override
void initState() { void initState() {
@ -227,7 +228,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
if (_showEntityPageSubscription == null) { if (_showEntityPageSubscription == null) {
_showEntityPageSubscription = _showEntityPageSubscription =
eventBus.on<ShowEntityPageEvent>().listen((event) { eventBus.on<ShowEntityPageEvent>().listen((event) {
_showEntityPage(event.entity.entityId); _showEntityPage(event.entity?.entityId);
}); });
} }
@ -327,12 +328,17 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
} }
void _showEntityPage(String entityId) { void _showEntityPage(String entityId) {
Navigator.push( setState(() {
context, _entityToShow = entityId;
MaterialPageRoute( });
builder: (context) => EntityViewPage(entityId: entityId), if (_entityToShow!= null && MediaQuery.of(context).size.width < Sizes.tabletMinWidth) {
) Navigator.push(
); context,
MaterialPageRoute(
builder: (context) => EntityViewPage(entityId: entityId),
)
);
}
} }
void _showPage(String path, bool goBackFirst) { void _showPage(String path, bool goBackFirst) {
@ -643,9 +649,6 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
child: new Text("Reload"), child: new Text("Reload"),
value: "reload", value: "reload",
)); ));
List<Widget> emptyBody = [
Text("."),
];
if (ConnectionManager().isAuthenticated) { if (ConnectionManager().isAuthenticated) {
_showLoginButton = false; _showLoginButton = false;
popupMenuItems.add( popupMenuItems.add(
@ -654,73 +657,106 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
value: "logout", value: "logout",
)); ));
} }
if (_showLoginButton) { Widget mainScrollBody;
emptyBody = [ if (empty) {
FlatButton( if (_showLoginButton) {
child: Text("Login with Home Assistant", style: TextStyle(fontSize: 16.0, color: Colors.white)), mainScrollBody = Center(
color: Colors.blue, child: Column(
onPressed: () => _fullLoad(), mainAxisAlignment: MainAxisAlignment.center,
) children: <Widget>[
]; FlatButton(
} child: Text("Login with Home Assistant", style: TextStyle(fontSize: 16.0, color: Colors.white)),
return NestedScrollView( color: Colors.blue,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { onPressed: () => _fullLoad(),
return <Widget>[ )
SliverAppBar( ]
floating: true, )
pinned: true, );
primary: true, } else {
title: Text(HomeAssistant().locationName ?? ""), mainScrollBody = Center(
actions: <Widget>[ child: Column(
IconButton( mainAxisAlignment: MainAxisAlignment.center,
icon: Icon(MaterialDesignIcons.getIconDataFromIconName( children: <Widget>[
"mdi:television"), color: Colors.white,), Text("...")
onPressed: () => Navigator.pushNamed(context, "/play-media", arguments: {"url": ""}) ]
),
IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical"), color: Colors.white,),
onPressed: () {
showMenu(
position: RelativeRect.fromLTRB(MediaQuery.of(context).size.width, 70.0, 0.0, 0.0),
context: context,
items: popupMenuItems
).then((String val) {
if (val == "reload") {
_quickLoad();
} else if (val == "logout") {
HomeAssistant().logout().then((_) {
_quickLoad();
});
}
});
}
)
],
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
_scaffoldKey.currentState.openDrawer();
},
),
bottom: empty ? null : TabBar(
controller: _viewsTabController,
tabs: buildUIViewTabs(),
isScrollable: true,
),
), ),
);
}
} else {
if (_entityToShow != null && MediaQuery.of(context).size.width >= Sizes.tabletMinWidth) {
Entity entity = HomeAssistant().entities.get(_entityToShow);
mainScrollBody = Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
child: HomeAssistant().buildViews(context, _viewsTabController),
),
Container(
width: Sizes.mainPageScreenSeparatorWidth,
color: Colors.blue,
),
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: Sizes.entityPageMaxWidth),
child: entity.buildEntityPageWidget(context, showClose: true),
)
],
);
} else {
_entityToShow = null;
mainScrollBody = HomeAssistant().buildViews(context, _viewsTabController);
}
}
]; return NestedScrollView(
}, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
body: empty ? return <Widget>[
Center( SliverAppBar(
child: Column( floating: true,
mainAxisAlignment: MainAxisAlignment.center, pinned: true,
children: emptyBody primary: true,
), title: Text(HomeAssistant().locationName ?? ""),
) actions: <Widget>[
: IconButton(
HomeAssistant().buildViews(context, _viewsTabController), icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:television"), color: Colors.white,),
onPressed: () => Navigator.pushNamed(context, "/play-media", arguments: {"url": ""})
),
IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical"), color: Colors.white,),
onPressed: () {
showMenu(
position: RelativeRect.fromLTRB(MediaQuery.of(context).size.width, 70.0, 0.0, 0.0),
context: context,
items: popupMenuItems
).then((String val) {
if (val == "reload") {
_quickLoad();
} else if (val == "logout") {
HomeAssistant().logout().then((_) {
_quickLoad();
});
}
});
}
)
],
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
_scaffoldKey.currentState.openDrawer();
},
),
bottom: empty ? null : TabBar(
controller: _viewsTabController,
tabs: buildUIViewTabs(),
isScrollable: true,
),
),
];
},
body: mainScrollBody
); );
} }

View File

@ -83,7 +83,7 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
"media_content_type": _contentType "media_content_type": _contentType
} }
); );
eventBus.fire(ShowEntityPageEvent(entity)); eventBus.fire(ShowEntityPageEvent(entity: entity));
} }
} }

View File

@ -1,14 +0,0 @@
part of '../../main.dart';
class EntityPageContainer extends StatelessWidget {
EntityPageContainer({Key key, @required this.children}) : super(key: key);
final List<Widget> children;
@override
Widget build(BuildContext context) {
return ListView(
children: children,
);
}
}

View File

@ -86,13 +86,12 @@ class RenderCustomLayoutBox extends RenderBox
int columnsCount; int columnsCount;
List<double> columnXPositions = []; List<double> columnXPositions = [];
List<double> columnYPositions = []; List<double> columnYPositions = [];
if (childCount == 0) { columnsCount = (constraints.maxWidth ~/ this.minColumnWidth);
if (childCount == 0 || columnsCount == 0) {
size = constraints.biggest; size = constraints.biggest;
assert(size.isFinite); assert(size.isFinite);
return; return;
} }
columnsCount = (constraints.maxWidth ~/ this.minColumnWidth);
double columnWidth = constraints.maxWidth / columnsCount; double columnWidth = constraints.maxWidth / columnsCount;
double startY = 0; double startY = 0;
for (int i =0; i < columnsCount; i++) { for (int i =0; i < columnsCount; i++) {

View File

@ -63,9 +63,9 @@ class ShowPopupMessageEvent {
} }
class ShowEntityPageEvent { class ShowEntityPageEvent {
Entity entity; final Entity entity;
ShowEntityPageEvent(this.entity); ShowEntityPageEvent({this.entity});
} }
class ShowPageEvent { class ShowPageEvent {