Commit c154f30b authored by Nicolas Richard Walter Boeckh's avatar Nicolas Richard Walter Boeckh 💬
Browse files

Refactoring, adding upper pane, need bg service (BLE + Location)

parent 1172959b
......@@ -11,8 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Upcoming Features
- Add | (BS) Bluetooth LE (expansion needed)
- Add | (BS) Location (expansion needed)
- Add | (nil) Internationalization
- Add | (UX) Accessibility options
- Add | (Func) Param for download maps on Wifi
- Add | (Func) Param for data push on Wifi
- add
- Add | (UI) Options menu
- Add | (UI) Leaflet (flutter_map) | just the map (6h) + (overlay data || )
- Add | (Func) Cache file for data | 2+3-4h
- Add | (BS) Location (expansion needed) | ~
- Add | (BS) Bluetooth LE (expansion needed) | 4-8h (flutter_blue )
- Add | (BS) Background service framework () | 2-6h (sticky notification system + )
### Added
......
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';
import './ui/home_view.dart';
import 'package:logair_application/ui/home_view.dart';
final bool displayMode = false;
......
library carousel_slider;
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class CarouselSlider extends StatefulWidget {
CarouselSlider(
{@required this.items,
this.height,
this.aspectRatio: 16 / 9,
this.viewportFraction: 0.8,
this.initialPage: 0,
int realPage: 10000,
this.enableInfiniteScroll: true,
this.reverse: false,
this.autoPlay: false,
this.autoPlayInterval: const Duration(seconds: 4),
this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
this.autoPlayCurve: Curves.fastOutSlowIn,
this.pauseAutoPlayOnTouch,
this.enlargeCenterPage = false,
this.onPageChanged,
this.scrollPhysics,
this.scrollDirection: Axis.horizontal})
: this.realPage =
enableInfiniteScroll ? realPage + initialPage : initialPage,
this.itemCount = items.length,
this.itemBuilder = null,
this.pageController = PageController(
viewportFraction: viewportFraction,
initialPage:
enableInfiniteScroll ? realPage + initialPage : initialPage,
);
/// The on demand item builder constructor
CarouselSlider.builder(
{@required this.itemCount,
@required this.itemBuilder,
this.height,
this.aspectRatio: 16 / 9,
this.viewportFraction: 0.8,
this.initialPage: 0,
int realPage: 10000,
this.enableInfiniteScroll: true,
this.reverse: false,
this.autoPlay: false,
this.autoPlayInterval: const Duration(seconds: 4),
this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
this.autoPlayCurve: Curves.fastOutSlowIn,
this.pauseAutoPlayOnTouch,
this.enlargeCenterPage = false,
this.onPageChanged,
this.scrollPhysics,
this.scrollDirection: Axis.horizontal})
: this.realPage =
enableInfiniteScroll ? realPage + initialPage : initialPage,
this.items = null,
this.pageController = PageController(
viewportFraction: viewportFraction,
initialPage:
enableInfiniteScroll ? realPage + initialPage : initialPage,
);
/// The widgets to be shown in the carousel of default constructor
final List<Widget> items;
/// The widget item builder that will be used to build item on demand
final IndexedWidgetBuilder itemBuilder;
/// The widgets count that should be shown at carousel
final int itemCount;
/// Set carousel height and overrides any existing [aspectRatio].
final double height;
/// Aspect ratio is used if no height have been declared.
///
/// Defaults to 16:9 aspect ratio.
final double aspectRatio;
/// The fraction of the viewport that each page should occupy.
///
/// Defaults to 0.8, which means each page fills 80% of the carousel.
final num viewportFraction;
/// The initial page to show when first creating the [CarouselSlider].
///
/// Defaults to 0.
final num initialPage;
/// The actual index of the [PageView].
///
/// This value can be ignored unless you know the carousel will be scrolled
/// backwards more then 10000 pages.
/// Defaults to 10000 to simulate infinite backwards scrolling.
final num realPage;
///Determines if carousel should loop infinitely or be limited to item length.
///
///Defaults to true, i.e. infinite loop.
final bool enableInfiniteScroll;
/// Reverse the order of items if set to true.
///
/// Defaults to false.
final bool reverse;
/// Enables auto play, sliding one page at a time.
///
/// Use [autoPlayInterval] to determent the frequency of slides.
/// Defaults to false.
final bool autoPlay;
/// Sets Duration to determent the frequency of slides when
///
/// [autoPlay] is set to true.
/// Defaults to 4 seconds.
final Duration autoPlayInterval;
/// The animation duration between two transitioning pages while in auto playback.
///
/// Defaults to 800 ms.
final Duration autoPlayAnimationDuration;
/// Determines the animation curve physics.
///
/// Defaults to [Curves.fastOutSlowIn].
final Curve autoPlayCurve;
/// Sets a timer on touch detected that pause the auto play with
/// the given [Duration].
///
/// Touch Detection is only active if [autoPlay] is true.
final Duration pauseAutoPlayOnTouch;
/// Determines if current page should be larger then the side images,
/// creating a feeling of depth in the carousel.
///
/// Defaults to false.
final bool enlargeCenterPage;
/// The axis along which the page view scrolls.
///
/// Defaults to [Axis.horizontal].
final Axis scrollDirection;
/// Called whenever the page in the center of the viewport changes.
final Function(int index) onPageChanged;
/// How the carousel should respond to user input.
///
/// For example, determines how the items continues to animate after the
/// user stops dragging the page view.
///
/// The physics are modified to snap to page boundaries using
/// [PageScrollPhysics] prior to being used.
///
/// Defaults to matching platform conventions.
final ScrollPhysics scrollPhysics;
/// [pageController] is created using the properties passed to the constructor
/// and can be used to control the [PageView] it is passed to.
final PageController pageController;
/// Animates the controlled [CarouselSlider] to the next page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> nextPage({Duration duration, Curve curve}) {
return pageController.nextPage(duration: duration, curve: curve);
}
/// Animates the controlled [CarouselSlider] to the previous page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> previousPage({Duration duration, Curve curve}) {
return pageController.previousPage(duration: duration, curve: curve);
}
/// Changes which page is displayed in the controlled [CarouselSlider].
///
/// Jumps the page position from its current value to the given value,
/// without animation, and without checking if the new value is in range.
void jumpToPage(int page) {
final index =
_getRealIndex(
pageController.page.toInt(), realPage - initialPage, itemCount);
return pageController
.jumpToPage(pageController.page.toInt() + page - index);
}
/// Animates the controlled [CarouselSlider] from the current page to the given page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> animateToPage(int page, {Duration duration, Curve curve}) {
final index =
_getRealIndex(
pageController.page.toInt(), realPage - initialPage, itemCount);
return pageController.animateToPage(
pageController.page.toInt() + page - index,
duration: duration,
curve: curve);
}
@override
_CarouselSliderState createState() => _CarouselSliderState();
}
class _CarouselSliderState extends State<CarouselSlider>
with TickerProviderStateMixin {
Timer timer;
@override
void initState() {
super.initState();
timer = getTimer();
}
Timer getTimer() {
return widget.autoPlay ? Timer.periodic(widget.autoPlayInterval, (_) {
widget.pageController.nextPage(
duration: widget.autoPlayAnimationDuration,
curve: widget.autoPlayCurve);
}) : null;
}
void pauseOnTouch() {
timer.cancel();
timer = Timer(widget.pauseAutoPlayOnTouch, () {
timer = getTimer();
});
}
Widget getWrapper(Widget child) {
if (widget.height != null) {
final Widget wrapper = Container(height: widget.height, child: child);
return widget.autoPlay && widget.pauseAutoPlayOnTouch != null
? addGestureDetection(wrapper)
: wrapper;
} else {
final Widget wrapper =
AspectRatio(aspectRatio: widget.aspectRatio, child: child);
return widget.autoPlay && widget.pauseAutoPlayOnTouch != null
? addGestureDetection(wrapper)
: wrapper;
}
}
Widget addGestureDetection(Widget child) =>
GestureDetector(onPanDown: (_) => pauseOnTouch(), child: child);
@override
void dispose() {
super.dispose();
timer?.cancel();
}
@override
Widget build(BuildContext context) {
return getWrapper(PageView.builder(
physics: widget.scrollPhysics,
scrollDirection: widget.scrollDirection,
controller: widget.pageController,
reverse: widget.reverse,
itemCount: widget.enableInfiniteScroll ? null : widget.itemCount,
onPageChanged: (int index) {
int currentPage = _getRealIndex(
index + widget.initialPage, widget.realPage, widget.itemCount);
if (widget.onPageChanged != null) {
widget.onPageChanged(currentPage);
}
},
itemBuilder: (BuildContext context, int i) {
final int index = _getRealIndex(
i + widget.initialPage, widget.realPage, widget.itemCount);
return AnimatedBuilder(
animation: widget.pageController,
child: (widget.items != null)
? widget.items[index]
: widget.itemBuilder(context, index),
builder: (BuildContext context, child) {
// on the first render, the pageController.page is null,
// this is a dirty hack
if (widget.pageController.position.minScrollExtent == null ||
widget.pageController.position.maxScrollExtent == null) {
Future.delayed(Duration(microseconds: 1), () {
if (this.mounted) {
setState(() {});
}
});
return Container();
}
double value = widget.pageController.page - i;
value = (1 - (value.abs() * 0.3)).clamp(0.0, 1.0);
final double height = widget.height ??
MediaQuery.of(context).size.width * (1 / widget.aspectRatio);
final double distortionValue = widget.enlargeCenterPage
? Curves.easeOut.transform(value)
: 1.0;
if (widget.scrollDirection == Axis.horizontal) {
return Center(
child:
SizedBox(height: distortionValue * height, child: child));
} else {
return Center(
child: SizedBox(
width:
distortionValue * MediaQuery.of(context).size.width,
child: child));
}
},
);
},
));
}
}
/// Converts an index of a set size to the corresponding index of a collection of another size
/// as if they were circular.
///
/// Takes a [position] from collection Foo, a [base] from where Foo's index originated
/// and the [length] of a second collection Baa, for which the correlating index is sought.
///
/// For example; We have a Carousel of 10000(simulating infinity) but only 6 images.
/// We need to repeat the images to give the illusion of a never ending stream.
/// By calling _getRealIndex with position and base we get an offset.
/// This offset modulo our length, 6, will return a number between 0 and 5, which represent the image
/// to be placed in the given position.
int _getRealIndex(int position, int base, int length) {
final int offset = position - base;
return _remainder(offset, length);
}
/// Returns the remainder of the modulo operation [input] % [source], and adjust it for
/// negative values.
int _remainder(int input, int source) {
if (source == 0) return 0;
final int result = input % source;
return result < 0 ? source + result : result;
}
\ No newline at end of file
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/ui/base_widget.dart';
import 'package:logair_application/ui/data_widget.dart';
import 'package:logair_application/ui/map_widget.dart';
import 'package:logair_application/ui/social_widget.dart';
class AnimatedDrawer extends StatefulWidget {
AnimatedDrawer({Key key}) : super(key: key);
@override
_AnimatedDrawerState createState() => _AnimatedDrawerState();
}
class _AnimatedDrawerState extends State<AnimatedDrawer> {
bool onCarousel = true;
bool canChange = false;
double deltaSum = 0.0;
double gestureStartY = 0.0;
_AnimatedDrawerState({Key key});
@override
Widget build(BuildContext context) {
return BaseWidget(
builder: (context, sizingInfo) => GestureDetector(
onVerticalDragStart: (details) {
gestureStartY = details.globalPosition.dy;
canChange = ((onCarousel && sizingInfo.screenSize.height * 0.1 >= gestureStartY) || (!onCarousel && sizingInfo.screenSize.height * 0.9 <= gestureStartY));
},
onVerticalDragUpdate: (details) {
if (deltaSum <= -50 && onCarousel && canChange)
setState(() => onCarousel = false);
else if (deltaSum >= 50 && !onCarousel && canChange)
setState(() => onCarousel = true);
else
deltaSum -= details.delta.dy;
},
onVerticalDragEnd: (_) {
deltaSum = 0.0;
canChange = false;
},
child: AnimatedContainer(
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
// Header
Container(
color: Colors.amber,
child: BaseWidget(
builder: (context, sizingInfo) => Container(
height: onCarousel ? sizingInfo.screenSize.height * 0.1 : sizingInfo.screenSize.height,
child: Container(
width: sizingInfo.localWidgetSize.width,
height: sizingInfo.localWidgetSize.height,
child: Center(child: Text("HEADER")),
)
),
),
),
// Carousel
Container(
color: Colors.blue,
child: BaseWidget(
builder: (context, sizingInfo) => CarouselSlider(
height: onCarousel ? sizingInfo.screenSize.height * 0.9 : 0,
autoPlay: false,
enlargeCenterPage: false,
viewportFraction: 1.0,
initialPage: 1,
items: [MapWidget(), DataWidget(), SocialWidget()],
),
),
),
],
),
),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:logair_application/ui/sizing_information.dart';
import 'package:logair_application/utils/ui_utils.dart';
import './sizing_information.dart';
class BaseWidget extends StatelessWidget {
final Widget Function (BuildContext ctx, SizingInformation sizingInformation) builder;
const BaseWidget({Key key, this.builder}) : super(key: key);
@override
Widget build(BuildContext context) {
var mediaQuery = MediaQuery.of(context);
return LayoutBuilder(builder: (context, boxSizing) {
var sizingInformation = SizingInformation(
return LayoutBuilder(
builder: (context, boxSizing) {
var sizingInformation = SizingInformation(
orientation: mediaQuery.orientation,
deviceType: getDeviceType(mediaQuery),
screenSize: mediaQuery.size,
localWidgetSize: Size(boxSizing.maxWidth, boxSizing.maxHeight),
padding: mediaQuery.padding,
);
return builder(context, sizingInformation);
......
......@@ -17,7 +17,14 @@ class CarouselCard extends StatelessWidget {
color: color,
width: sizingInfo.localWidgetSize.width,
padding: EdgeInsets.all(5),
child: child,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade500),
borderRadius: BorderRadius.all(Radius.circular(10)),
),
child: child
),
),
);
}
......
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/ui/animated_view_container.dart';
import 'package:logair_application/ui/base_widget.dart';
import 'package:logair_application/ui/data_widget.dart';
import 'package:logair_application/ui/map_widget.dart';
import 'package:logair_application/ui/social_widget.dart';
class HomeView extends StatefulWidget {
HomeView({Key key}) : super(key: key);
@override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
bool onCarousel = true;
bool canChange = false;
double deltaSum = 0.0;
double gestureStartY = 0.0;
_HomeViewState({Key key});
class HomeView extends StatelessWidget {
const HomeView({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedDrawer();
return BaseWidget(
builder: (context, sizingInfo) => GestureDetector(
onVerticalDragStart: (details) {
gestureStartY = details.globalPosition.dy;
canChange = ((onCarousel && sizingInfo.screenSize.height * 0.1 >= gestureStartY) || (!onCarousel && sizingInfo.screenSize.height * 0.9 <= gestureStartY));
print('SIZING INFO : ${sizingInfo.toString()}');
},
onVerticalDragUpdate: (details) {
if (deltaSum <= -50 && onCarousel && canChange)
setState(() => onCarousel = false);
else if (deltaSum >= 50 && !onCarousel && canChange)
setState(() => onCarousel = true);
else
deltaSum -= details.delta.dy;
},
onVerticalDragEnd: (_) {
deltaSum = 0.0;
canChange = false;
},
child: AnimatedContainer(
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
// Notch masking
Container(
color: Colors.black,
height: sizingInfo.padding.top,
),
// Header
BaseWidget(
builder: (context, sizingInfo) => Container(
height: onCarousel ? sizingInfo.screenSize.height * 0.1 : sizingInfo.screenSize.height - sizingInfo.padding.top,
child: Column(
children: [
Container(
height: onCarousel ? 0 : sizingInfo.screenSize.height * 0.9 - sizingInfo.padding.top,
width: sizingInfo.screenSize.width,
child: Center(child: Text("PLACEHOLDER: STATS"),),
),
Container(
color: Colors.amber,
height: sizingInfo.screenSize.height * 0.1,
width: sizingInfo.screenSize.width,
child: Center(child: Text("BATTERY INDIC"))
)
],
)
),
),
// Carousel
Container(
color: Colors.blue,
child: BaseWidget(
builder: (context, sizingInfo) => CarouselSlider(
height: onCarousel ? sizingInfo.screenSize.height * 0.9 - sizingInfo.padding.top : 0,
autoPlay: false,
enlargeCenterPage: false,
viewportFraction: 1.0,
initialPage: 1,
items: [MapWidget(), DataWidget(), SocialWidget()],
),
),
),
],
),
),
),
),
),
);
}