Masterarbeit Richard Stern. Flutter App, sich mit einem Bluetooth-Gerät verbindet und Berührungen auf einem Sensor visualisiert.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Sensor.dart 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. import 'dart:async';
  2. import 'dart:ui';
  3. import 'package:flutter/material.dart';
  4. import 'package:touch_demonstrator/model/buttonData.dart';
  5. import 'package:touch_demonstrator/model/touchData.dart';
  6. import 'package:touch_demonstrator/model/vibrate.dart';
  7. import 'package:touch_demonstrator/src/blocs/BlocProvider.dart';
  8. import 'package:flare_flutter/flare_actor.dart';
  9. import 'dart:core';
  10. import 'package:vibrate/vibrate.dart';
  11. Duration durationSensorAnimation = Duration(milliseconds: 200);
  12. class Battery extends StatelessWidget {
  13. @override
  14. Widget build(BuildContext context) {
  15. final bBloc = BlocProvider.of(context).bluetoothBlocGetter;
  16. return Positioned(
  17. bottom: 0,
  18. left: 0,
  19. child: StreamBuilder<int>(
  20. stream: bBloc.batteryValue$,
  21. initialData: null,
  22. builder: (BuildContext context, AsyncSnapshot<int> snapshotBattery) {
  23. return Container(
  24. padding: EdgeInsets.all(0.0),
  25. color: Colors.transparent,
  26. child: Padding(
  27. padding: const EdgeInsets.all(3.0),
  28. child: (snapshotBattery.data == null)
  29. ? Container()
  30. : Container(
  31. padding: EdgeInsets.all(4.0),
  32. decoration: BoxDecoration(
  33. color: Theme.of(context).accentColor,
  34. borderRadius: new BorderRadius.all(
  35. const Radius.circular(3.0))),
  36. child: Row(
  37. mainAxisAlignment: MainAxisAlignment.end,
  38. children: <Widget>[
  39. Icon(
  40. Icons.battery_full,
  41. color: Colors.white,
  42. ),
  43. Text(
  44. '${snapshotBattery.data.toString()}%',
  45. style: TextStyle(color: Colors.white),
  46. ),
  47. ],
  48. ),
  49. ),
  50. ),
  51. );
  52. }),
  53. );
  54. }
  55. }
  56. class Sensor extends StatefulWidget {
  57. @override
  58. SensorState createState() {
  59. return SensorState();
  60. }
  61. }
  62. class SensorState extends State<Sensor> {
  63. static var animationDisabled = false;
  64. static var firstTimeDidChangeDependencies = true;
  65. static var canVibrate = false;
  66. _drawButtons(
  67. BuildContext context, double _heightSensor, double _widthSensor) {
  68. final bBloc = BlocProvider.of(context).bluetoothBlocGetter;
  69. final _buttonsPosY = [0.74 * _heightSensor, 0.57 * _heightSensor, 0.39 * _heightSensor];
  70. final _buttonsPosX = 0.074 * _widthSensor;
  71. final _buttonWidth = 0.08 * _widthSensor;
  72. return StreamBuilder<List<bool>>(
  73. stream: bBloc.buttonsState$,
  74. initialData: [false, false, false],
  75. builder:
  76. (BuildContext context, AsyncSnapshot<List<bool>> snapshotButtons) {
  77. if (snapshotButtons.hasData) {
  78. return Stack(
  79. children: <Widget>[
  80. _singleButton(0, _buttonsPosX, _buttonsPosY[0], _buttonWidth,
  81. snapshotButtons.data[0]),
  82. _singleButton(1, _buttonsPosX, _buttonsPosY[1], _buttonWidth,
  83. snapshotButtons.data[1]),
  84. _singleButton(2, _buttonsPosX, _buttonsPosY[2], _buttonWidth,
  85. snapshotButtons.data[2])
  86. ],
  87. );
  88. } else
  89. return Container();
  90. });
  91. }
  92. _singleButton(int buttonNumber, double _buttonPosX,
  93. double _buttonPosY, double _widthButton, bool _isPressed) {
  94. ///draws a button of Type home(0), multitasking(1) or back(2).
  95. assert(buttonNumber >= 0 && buttonNumber <= 2);
  96. Color buttonColor;
  97. _isPressed
  98. ? buttonColor = Theme.of(context).primaryColor
  99. : buttonColor = Colors.white;
  100. if (buttonNumber >= 3) return Container();
  101. return AnimatedPositioned(
  102. duration: durationSensorAnimation,
  103. left: _buttonPosX,
  104. bottom: _buttonPosY,
  105. child: Image(
  106. image: AssetImage('assets/buttons/button$buttonNumber.png'),
  107. color: buttonColor,
  108. width: _widthButton,
  109. ),
  110. );
  111. }
  112. _notConnectedText() {
  113. return Positioned.fill(
  114. child: Stack(
  115. children: <Widget>[
  116. BackdropFilter(
  117. filter: ImageFilter.blur(
  118. sigmaX: 0.3,
  119. sigmaY: 0.3,
  120. ),
  121. child: Container(color: Colors.black.withOpacity(0)),
  122. ),
  123. Center(
  124. child: Container(
  125. padding: EdgeInsets.all(15.0),
  126. decoration: BoxDecoration(
  127. color: Colors.black45,
  128. shape: BoxShape.rectangle,
  129. borderRadius: BorderRadius.circular(8.0),
  130. ),
  131. child: Text(
  132. 'Not connected to a device',
  133. style: TextStyle(color: Colors.white),
  134. ))),
  135. ],
  136. ));
  137. }
  138. _successAnimation() {
  139. if (animationDisabled) {
  140. print('no animation');
  141. return Container();
  142. }
  143. print('animation');
  144. return Container(
  145. padding: EdgeInsets.all(80.0),
  146. child: Center(
  147. child: FlareActor(
  148. 'assets/Animations/success_hide.flr',
  149. animation: 'success',
  150. fit: BoxFit.contain,
  151. alignment: Alignment.center,
  152. isPaused: false,
  153. ),
  154. ),
  155. );
  156. }
  157. _middleSensor(
  158. BuildContext context, double _heightSensor, double _widthSensor) {
  159. final bBloc = BlocProvider.of(context).bluetoothBlocGetter;
  160. final _marginRight = _widthSensor * 0.168;
  161. final _marginLeft = _widthSensor * 0.18;
  162. return StreamBuilder<List<TouchData>>(
  163. stream: bBloc.getTouchesToVisualise$,
  164. initialData: [TouchData(5, 0, 1200, 1200)],
  165. builder: (BuildContext context,
  166. AsyncSnapshot<List<TouchData>> snapshotTouches) {
  167. if (snapshotTouches.hasData) {
  168. return Container(
  169. height: _heightSensor,
  170. width: _widthSensor,
  171. margin: EdgeInsets.only(left: _marginLeft, right: _marginRight),
  172. child: MiddleOfSensor(
  173. snapshotTouches.data, context, _heightSensor, _widthSensor),
  174. // CustomPaint( foregroundPainter:TouchPainter(context, snapshotTouches.data), ),
  175. );
  176. } else {
  177. return Container();
  178. }
  179. });
  180. }
  181. _sensorImage(){
  182. return BoxDecoration(
  183. image: DecorationImage(
  184. image: ExactAssetImage('assets/sensor.png'),
  185. fit: BoxFit.fitHeight),
  186. );
  187. }
  188. @override
  189. Widget build(BuildContext context) {
  190. final bBloc = BlocProvider.of(context).bluetoothBlocGetter;
  191. final widthOfScreen = MediaQuery.of(context).size.width;
  192. final heightOfScreen = MediaQuery.of(context).size.height;
  193. var _heightSensorConnected;
  194. var _widthSensorConnected;
  195. var _heightSensorDisconnected;
  196. var _widthSensorDisconnected;
  197. (heightOfScreen > widthOfScreen)
  198. ? _widthSensorConnected = _heightSensorConnected = widthOfScreen * 0.8
  199. : _heightSensorConnected = _widthSensorConnected = heightOfScreen * 0.8;
  200. _heightSensorDisconnected = _heightSensorConnected * 0.8;
  201. _widthSensorDisconnected = _widthSensorConnected * 0.8;
  202. return StreamBuilder(
  203. stream: bBloc.isConnected$,
  204. initialData: false,
  205. builder:
  206. (BuildContext context, AsyncSnapshot<bool> snapshotIsConnected) {
  207. if (snapshotIsConnected.hasData) {
  208. return Hero(
  209. tag: 'touchpadHero',
  210. child: AnimatedOpacity(
  211. duration: durationSensorAnimation,
  212. opacity: snapshotIsConnected.data ? 1.0 : 0.8,
  213. child: Container(
  214. // margin: EdgeInsets.only(left: 10.0, right: 10.0),
  215. width: _widthSensorConnected,
  216. height: _heightSensorConnected,
  217. child: Center(
  218. child: AnimatedContainer(
  219. duration: durationSensorAnimation,
  220. decoration: _sensorImage(),
  221. height: snapshotIsConnected.data
  222. ? _heightSensorConnected
  223. : _heightSensorDisconnected,
  224. width: snapshotIsConnected.data
  225. ? _widthSensorConnected
  226. : _widthSensorDisconnected,
  227. child: Stack(
  228. children: <Widget>[
  229. _middleSensor(context, _heightSensorConnected, _widthSensorConnected),
  230. snapshotIsConnected.data
  231. ? _drawButtons(
  232. context, _heightSensorConnected, _widthSensorConnected)
  233. : _drawButtons(context, _heightSensorDisconnected,
  234. _widthSensorDisconnected),
  235. DrawSlider(context, _widthSensorConnected, _heightSensorConnected),
  236. snapshotIsConnected.data
  237. ? _successAnimation()
  238. : _notConnectedText(),
  239. Battery(),
  240. ],
  241. ),
  242. ),
  243. ),
  244. ),
  245. ),
  246. );
  247. } else
  248. return Container();
  249. });
  250. }
  251. void _disableSuccessAnimation(bool isConnected) {
  252. // If already are connected to a device -> prevent success animation from showing again.
  253. // This would happens if you change orientation of device.
  254. if (isConnected) {
  255. vibrateDelayed(FeedbackType.success,Duration(milliseconds: 50));
  256. Future.delayed(Duration(seconds: 1), () {
  257. animationDisabled = true;
  258. });
  259. } else {
  260. // Activate success animation for next connection.
  261. animationDisabled = false;
  262. }
  263. }
  264. void _vibrateDependingOnButtonEvent(buttonsState buttonState) {
  265. (buttonState == buttonsState.pressed)
  266. ? vibrate(FeedbackType.selection)
  267. : vibrate(FeedbackType.impact);
  268. }
  269. @override
  270. void didChangeDependencies() {
  271. final bBloc = BlocProvider.of(context).bluetoothBlocGetter;
  272. print('didchangedependencies');
  273. if (firstTimeDidChangeDependencies) {
  274. bBloc.isConnected$.listen((data) => _disableSuccessAnimation(data));
  275. bBloc.vibrationButton$
  276. .listen((data) => _vibrateDependingOnButtonEvent(data));
  277. firstTimeDidChangeDependencies = false;
  278. }
  279. super.didChangeDependencies();
  280. }
  281. }
  282. class DrawSlider extends StatefulWidget {
  283. final BuildContext context;
  284. final double _heightSensor, _widthSensor;
  285. DrawSlider(this.context, this._heightSensor, this._widthSensor);
  286. @override
  287. _DrawSliderState createState() => _DrawSliderState();
  288. }
  289. class _DrawSliderState extends State<DrawSlider>
  290. with SingleTickerProviderStateMixin {
  291. AnimationController _controller;
  292. Animation _animation;
  293. static const Duration sliderAnimationMoveDuration = Duration(milliseconds: 1);
  294. static int lastPercent = 101;
  295. @override
  296. void initState() {
  297. super.initState();
  298. _controller = AnimationController(
  299. vsync: this,
  300. duration: Duration(milliseconds: 300),
  301. );
  302. _animation = Tween(
  303. begin: 1.0,
  304. end: 0.0,
  305. ).animate(_controller);
  306. _controller.reverse();
  307. }
  308. @override
  309. void dispose() {
  310. _controller.dispose();
  311. _controller = null;
  312. super.dispose();
  313. }
  314. _sliderFadeAwayAnimation() {
  315. if (_controller != null) {
  316. _controller.reset();
  317. }
  318. Future.delayed(Duration(seconds: 1), () {
  319. if (_controller != null) {
  320. _controller.forward();
  321. }
  322. });
  323. }
  324. _drawSlider(double percent) {
  325. var _sliderPosY = 0.12 * widget._heightSensor +
  326. percent * widget._heightSensor * 0.01 * 0.78;
  327. var _sliderPosX = widget._widthSensor * 0.83;
  328. return Positioned(
  329. left: _sliderPosX,
  330. bottom: _sliderPosY,
  331. child: Image(
  332. color: Theme.of(context).primaryColor,
  333. image: AssetImage('assets/slider_4.png'),
  334. width: widget._widthSensor * 0.15,
  335. ),
  336. );
  337. }
  338. _drawSliderText(int percent) {
  339. var _sliderTextPosX = widget._widthSensor * 0.73;
  340. var _sliderTextPosY = 0.13 * widget._heightSensor +
  341. percent * widget._heightSensor * 0.01 * 0.78;
  342. // return AnimatedPositioned(
  343. return Positioned(
  344. // duration: sliderAnimationMoveDuration,
  345. left: _sliderTextPosX,
  346. bottom: _sliderTextPosY,
  347. child: Container(
  348. width: 25.0,
  349. child: Text(
  350. '$percent',
  351. textAlign: TextAlign.end,
  352. style: TextStyle(color: Colors.white),
  353. ),
  354. ),
  355. );
  356. }
  357. @override
  358. Widget build(BuildContext context) {
  359. final bBloc = BlocProvider.of(context).bluetoothBlocGetter;
  360. // print('slider build');
  361. return StreamBuilder<int>(
  362. stream: bBloc.slider$,
  363. builder: (BuildContext context, AsyncSnapshot<int> sliderSnapshot) {
  364. if (sliderSnapshot.hasData) {
  365. if (sliderSnapshot.data <= 100 && sliderSnapshot.data >= 0) {
  366. if (lastPercent != sliderSnapshot.data)
  367. _sliderFadeAwayAnimation();
  368. lastPercent = sliderSnapshot.data;
  369. print('Slider: ${sliderSnapshot.data}');
  370. print('');
  371. return FadeTransition(
  372. opacity: _animation,
  373. child: Stack(
  374. children: <Widget>[
  375. _drawSlider(sliderSnapshot.data.toDouble()),
  376. _drawSliderText(lastPercent),
  377. ],
  378. ),
  379. );
  380. } else
  381. return Container();
  382. } else
  383. return Container();
  384. });
  385. }
  386. }
  387. class MiddleOfSensor extends StatefulWidget {
  388. final List<TouchData> touchCollection;
  389. final BuildContext context;
  390. final double _heightSensor;
  391. final double _widthSensor;
  392. MiddleOfSensor(this.touchCollection, this.context, this._heightSensor,
  393. this._widthSensor);
  394. @override
  395. _MiddleOfSensorState createState() => _MiddleOfSensorState();
  396. }
  397. class _MiddleOfSensorState extends State<MiddleOfSensor> {
  398. Duration durationTouchPointChange = Duration(microseconds: 200);
  399. static const leftLine = 100.0;
  400. static const rightLine = 900.0;
  401. static const maxSize = rightLine - leftLine;
  402. static const yMaxSize = 1023;
  403. static const fingerSize = 25.0;
  404. static const List<Color> _fingerColors = [
  405. Colors.lightBlue,
  406. Colors.green,
  407. Colors.deepPurple,
  408. Colors.deepOrangeAccent,
  409. Colors.pink,
  410. ];
  411. Widget _buildMiddleOfSensor(List<TouchData> _lastFingerTouchDataPoints) {
  412. Widget _touchPoint(TouchData item, BoxConstraints constraints) {
  413. var _height = constraints.constrainHeight();
  414. var _width = constraints.constrainWidth();
  415. var _xCircle = _width * (item.x.toDouble() - leftLine) / maxSize;
  416. var _yCircle = 0.04 * _height + 0.84 * _height * item.y.toDouble() / yMaxSize;
  417. Text _buildTouchPointText = Text(
  418. '${item.fingerNumber.toString()}',
  419. style: TextStyle(fontSize: 20, color: Colors.white),
  420. );
  421. return AnimatedPositioned(
  422. // return Positioned(
  423. duration: durationTouchPointChange,
  424. left: _xCircle - fingerSize / 2,
  425. top: _yCircle - fingerSize / 2,
  426. child: Container(
  427. width: fingerSize,
  428. height: fingerSize,
  429. decoration: BoxDecoration(
  430. color: _fingerColors[item.fingerNumber],
  431. shape: BoxShape.circle,
  432. ),
  433. child: Center(
  434. child: _buildTouchPointText))
  435. );
  436. }
  437. return LayoutBuilder(
  438. builder: (BuildContext context, BoxConstraints constraints) {
  439. return Stack(
  440. children: _lastFingerTouchDataPoints
  441. .where((TouchData item) => item.event == 4 || item.event == 1)
  442. .where((TouchData item) => item.x < 1200 && item.y < 1200)
  443. .where((TouchData item) => item.fingerNumber < 5)
  444. .map((TouchData item) => _touchPoint(item, constraints))
  445. .toList());
  446. });
  447. }
  448. @override
  449. Widget build(BuildContext context) {
  450. return _buildMiddleOfSensor(widget.touchCollection);
  451. }
  452. }
  453. /*class TouchPainter extends CustomPainter {
  454. /// Draws Slider or touches in the middle of the touch pad.
  455. List<TouchData> touchCollection;
  456. BuildContext context;
  457. double width;
  458. TouchPainter(this.context, this.touchCollection);
  459. static List<Color> _colorOfTouches = [
  460. Colors.lightBlue,
  461. Colors.green,
  462. Colors.deepPurple,
  463. Colors.deepOrangeAccent,
  464. Colors.pink,
  465. ];
  466. @override
  467. void paint(Canvas canvas, Size size) {
  468. double leftLine = 80.0;
  469. double rightLine = 900.0;
  470. double maxSize = rightLine - leftLine;
  471. double xCircle, xText;
  472. double yCircle, yText;
  473. Offset offsetCircle, offsetText;
  474. void _drawCircle(int fingerNumber, Offset offset, Color v) {
  475. // double xCircle;
  476. // double yCircle;
  477. // xCircle = x.toDouble() / maxSize * size.width;
  478. // yCircle = y.toDouble() / 1100 * size.height;
  479. Paint line = Paint()
  480. ..color = v
  481. ..strokeCap = StrokeCap.round
  482. ..style = PaintingStyle.fill
  483. ..strokeWidth = 15.0;
  484. // print("C: $xCircle / ${size.width}");
  485. canvas.drawCircle(offset, 16.0, line);
  486. }
  487. void _drawPercent(int fingerNumber, Offset offset){
  488. TextSpan span = TextSpan(style: TextStyle(color: Colors.white, fontSize: 20), text: '$fingerNumber');
  489. TextPainter tp = TextPainter(text: span, textAlign: TextAlign.right, textDirection: TextDirection.ltr);
  490. tp.layout();
  491. tp.paint(canvas, offset);
  492. }
  493. touchCollection.forEach((k) {
  494. // print("${k.f} ${k.e} ${k.x} ${k.y}");
  495. if (k.x >= leftLine && k.x <= rightLine && k.fingerNumber <=4) {
  496. switch (k.event) {
  497. case 5:
  498. break;
  499. case 4:
  500. continue move;
  501. move:
  502. case 1:
  503. {
  504. xCircle = (k.x.toDouble() - leftLine) / maxSize * size.width;
  505. yCircle = k.y.toDouble() / 1100 * size.height;
  506. print("Incoming (${k.x.round().toString()}|${k.y.round().toString()})");
  507. xText = (k.x.toDouble() - leftLine - 27.0) / maxSize * size.width;
  508. yText = (k.y.toDouble() - 44.0) / 1100 * size.height;
  509. offsetCircle = Offset(xCircle, yCircle );
  510. offsetText = Offset(xText, yText);
  511. _drawCircle(k.fingerNumber, offsetCircle, _colorOfTouches[k.fingerNumber.toInt()]);
  512. _drawPercent(k.fingerNumber, offsetText);
  513. break;
  514. }
  515. default:
  516. {
  517. print("Data is Corrupt");
  518. break;
  519. }
  520. }
  521. }
  522. });
  523. }
  524. @override
  525. bool shouldRepaint(CustomPainter oldDelegate) {
  526. return true;
  527. }
  528. }*/