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"
package="com.keyboardcrumbs.hassclient">
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.

View File

@ -192,7 +192,7 @@ class CardWidget extends StatelessWidget {
alignment: Alignment.centerRight,
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"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 doubleRowPadding = rowPadding*2;
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: () =>
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(
entityWrapper: EntityWrapper(entity: this),
child: EntityPageContainer(children: <Widget>[
Padding(
padding: EdgeInsets.only(top: Sizes.rowPadding, left: Sizes.leftWidgetPadding),
child: DefaultEntityContainer(state: _buildStatePartForPage(context)),
),
LastUpdatedWidget(),
Divider(),
_buildAdditionalControlsForPage(context),
Divider(),
buildHistoryWidget(),
EntityAttributesList()
]),
child: ListView(
padding: EdgeInsets.all(0),
children: <Widget>[
showClose ?
Container(
color: Colors.blue[300],
height: 36,
child: Row(
children: <Widget>[
Expanded(
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,
);
}

View File

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

View File

@ -27,7 +27,6 @@ class LightColorPickerState extends State<LightColorPicker> {
List<Widget> colorRows = [];
Border border;
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))) {
List<Widget> rowChildren = [];
//Logger.d("$saturation");

View File

@ -229,7 +229,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"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) {

View File

@ -69,7 +69,6 @@ part 'entities/flat_service_button.widget.dart';
part 'entities/light/widgets/light_color_picker.dart';
part 'entities/camera/widgets/camera_stream_view.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/simple_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> {
String _title;
StreamSubscription _refreshDataSubscription;
StreamSubscription _stateSubscription;
Entity entity;
Entity forwardToMainPage;
@override
void initState() {
@ -26,16 +27,18 @@ class _EntityViewPageState extends State<EntityViewPage> {
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
setState(() {});
});
_prepareData();
entity = HomeAssistant().entities.get(widget.entityId);
}
void _prepareData() async {
_title = HomeAssistant().entities.get(widget.entityId).displayName;
}
@override
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(
appBar: new AppBar(
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
// 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
void dispose(){
if (_stateSubscription != null) _stateSubscription.cancel();
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
eventBus.fire(ShowEntityPageEvent(entity: forwardToMainPage));
super.dispose();
}
}

View File

@ -26,6 +26,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
bool _showLoginButton = false;
bool _preventAppRefresh = false;
String _savedSharedText;
String _entityToShow;
@override
void initState() {
@ -227,7 +228,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
if (_showEntityPageSubscription == null) {
_showEntityPageSubscription =
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) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EntityViewPage(entityId: entityId),
)
);
setState(() {
_entityToShow = 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) {
@ -643,9 +649,6 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
child: new Text("Reload"),
value: "reload",
));
List<Widget> emptyBody = [
Text("."),
];
if (ConnectionManager().isAuthenticated) {
_showLoginButton = false;
popupMenuItems.add(
@ -654,73 +657,106 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse
value: "logout",
));
}
if (_showLoginButton) {
emptyBody = [
FlatButton(
child: Text("Login with Home Assistant", style: TextStyle(fontSize: 16.0, color: Colors.white)),
color: Colors.blue,
onPressed: () => _fullLoad(),
)
];
}
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
pinned: true,
primary: true,
title: Text(HomeAssistant().locationName ?? ""),
actions: <Widget>[
IconButton(
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,
),
Widget mainScrollBody;
if (empty) {
if (_showLoginButton) {
mainScrollBody = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Text("Login with Home Assistant", style: TextStyle(fontSize: 16.0, color: Colors.white)),
color: Colors.blue,
onPressed: () => _fullLoad(),
)
]
)
);
} else {
mainScrollBody = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("...")
]
),
);
}
} 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);
}
}
];
},
body: empty ?
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: emptyBody
),
)
:
HomeAssistant().buildViews(context, _viewsTabController),
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
pinned: true,
primary: true,
title: Text(HomeAssistant().locationName ?? ""),
actions: <Widget>[
IconButton(
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
}
);
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;
List<double> columnXPositions = [];
List<double> columnYPositions = [];
if (childCount == 0) {
columnsCount = (constraints.maxWidth ~/ this.minColumnWidth);
if (childCount == 0 || columnsCount == 0) {
size = constraints.biggest;
assert(size.isFinite);
return;
}
columnsCount = (constraints.maxWidth ~/ this.minColumnWidth);
double columnWidth = constraints.maxWidth / columnsCount;
double startY = 0;
for (int i =0; i < columnsCount; i++) {

View File

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