Commit 14eddb39 authored by Philipp Hempel's avatar Philipp Hempel
Browse files

Merge branch 'f42-revise-mainscreen' into 'dev'

Revised the mainscreen like it is shown in the mockup

See merge request !69
parents 7561f3ec b70aca10
Pipeline #620552 passed with stage
in 1 minute and 14 seconds
......@@ -25,6 +25,7 @@ bool? showIntroScreen;
/// Runs the app by building the widget [KuTUD]
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
final prefs = await SharedPreferences.getInstance();
showIntroScreen = prefs.getBool("showIntroScreen") ?? true;
// uncomment the next two lines and the imports for release version before merging in master
......
import 'package:flutter/material.dart';
import 'package:kunstforum_tu_darmstadt/ui/widgets/image_overlay.dart';
import 'package:provider/provider.dart';
//***********************************************
//***** Custom Imports *******
//***********************************************
import 'package:kunstforum_tu_darmstadt/config/app_setting.dart';
import 'package:kunstforum_tu_darmstadt/ui/widgets/carousel_slider_campus.dart';
import 'package:kunstforum_tu_darmstadt/ui/widgets/custom_app_bar.dart';
import 'package:kunstforum_tu_darmstadt/api/artwork_service.dart';
///
/// A [StatelessWidget] that displays the exhibitions page of the app with a central [BottomNavigationBar]
/// and a [CustomAppBar]. The Screen displays a list of exhibitions fetched from backend in its body as [CarouselSliderBox]
/// and a [CustomAppBar]. The Screen displays the list of all exhibitions fetched from backend
///
class ExhibitionsScreen extends StatelessWidget {
//***************************************************
......@@ -25,20 +24,24 @@ class ExhibitionsScreen extends StatelessWidget {
builder: (context, orientation) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
Size boxSize = Size(
constraints.maxWidth,
orientation == Orientation.portrait
? constraints.maxHeight / 2
: constraints.maxHeight);
return ListView.builder(
scrollDirection: Axis.vertical,
itemCount: artworkService.exhibitionList.length,
itemBuilder: (BuildContext context, int i) {
return CarouselSliderCampusBox(
campusExhibition: artworkService.exhibitionList[i],
boxSize: boxSize,
);
});
scrollDirection: Axis.vertical,
itemCount: artworkService.exhibitionList.length,
itemBuilder: (BuildContext context, int i) {
return Container(
height: constraints.maxHeight / 2,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(artworkService
.exhibitionList[i].artworkList[0].images!.first),
fit: BoxFit.cover,
),
),
child: ImageOverlay(
title: artworkService.exhibitionList[i].name),
);
},
);
},
);
},
......
......@@ -31,20 +31,18 @@ class MainScreen extends StatelessWidget {
orientation == Orientation.portrait
? constraints.maxHeight / 2
: constraints.maxHeight);
return ListView.builder(
scrollDirection: Axis.vertical,
itemCount: 2,
itemBuilder: (BuildContext context, int i) {
return i == 0
? CarouselSliderCampusBox(
campusExhibition: artworkService.campusExhibition,
boxSize: boxSize,
)
: CarouselSliderBoxExhibition(
exhibitionList: artworkService.exhibitionList,
boxSize: boxSize,
);
});
return Column(
children: [
CarouselSliderCampusBox(
campusExhibition: artworkService.campusExhibition,
boxSize: boxSize,
),
CarouselSliderBoxExhibition(
exhibitionList: artworkService.exhibitionList,
boxSize: boxSize,
)
],
);
},
);
},
......
import 'package:auto_size_text/auto_size_text.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:kunstforum_tu_darmstadt/config/app_setting.dart';
import 'package:kunstforum_tu_darmstadt/ui/widgets/image_overlay.dart';
import 'package:provider/provider.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
/// A enum for choosing the type of the [CarouselSliderBox].
enum CarouselSliderIndicator {
/// The page indicator and the given text are overlayed on top of the image.
insideText,
/// The image is displayed at maximum size and the page indicator in the middle underneath it.
/// For this type it's not possible to overlay any text.
below,
/// A enum for choosing if and how image names should be displayed.
enum ImageNameStyle {
// the name of the current image is not displayed
none,
// the name of the current image is displayed as a title at the top of the image
title,
// the name of the current image is displayed as a subtitle under a fixed title
subtitle
}
class CarouselSliderBox extends StatefulWidget {
/// The [type] determines the location of the page indication and whether text is shown or not.
/// See [CarouselSliderIndicator] for more information.
final CarouselSliderIndicator type;
/// The [BoxFit] of the displayed images in the carousel.
/// If not set, it defaults to [BoxFit.contain].
final BoxFit imageFit;
......@@ -36,7 +32,6 @@ class CarouselSliderBox extends StatefulWidget {
/// A function to provide a text for a given index.
/// The parameter [index] is guaranteed to be 0 <= index < elementCount.
/// Only required if the [type] is set to [CarouselSliderIndicator.insideText].
final String Function(int index)? provideText;
/// A function which will be called if a image has been tapped by a user.
......@@ -44,16 +39,45 @@ class CarouselSliderBox extends StatefulWidget {
/// is guaranteed to be 0 <= index < elementCount.
final void Function(int index)? onTap;
/// The title of the slider.
/// if a title is provided the imageNameStyle must not be set to ImageNameStyle.title
final String? title;
/// The style in which the image names provided by [provideText] should be displayed
/// if it is set to [ImageNameStyle.none], the name of the current image is not displayed
/// if it is set to [ImageNameStyle.title], the name of the current image is displayed as a title at the top of the image
/// if it is set to [ImageNameStyle.subtitle], the name of the current image is displayed as a subtitle under the given fixed [title]. The title must be set in this case
/// If not set, it defaults to [ImageNameStyle.title].
final ImageNameStyle imageNameStyle;
/// Determines if current page should be larger then the side images,
/// creating a feeling of depth in the carousel.
/// If not set, it defaults to false.
final bool enlargeCenterPage;
CarouselSliderBox({
Key? key,
required this.type,
required this.boxSize,
required this.elementCount,
required this.provideImage,
this.imageFit = BoxFit.contain,
this.provideText,
this.onTap,
}) : super(key: key);
this.title,
this.imageNameStyle = ImageNameStyle.title,
this.enlargeCenterPage = true,
}) : assert(title == null ||
imageNameStyle !=
ImageNameStyle
.title), // if the image name is displayed as the title, no title must be specified
assert(provideText == null ||
imageNameStyle !=
ImageNameStyle
.none), // if names for the images are given, these names should also be displayed
assert(imageNameStyle != ImageNameStyle.subtitle ||
title !=
null), // if the image name is displayed as the subtitle, there has to be a fixed title for the slider
super(key: key);
@override
_CarouselSliderBoxState createState() => _CarouselSliderBoxState();
......@@ -63,6 +87,9 @@ class _CarouselSliderBoxState extends State<CarouselSliderBox> {
/// The index of the image which is currently shown.
int position = 0;
/// Used to control the the CarouselSlider for swiping to the next page with the [AnimatedSmoothIndicator]
CarouselController carouselController = CarouselController();
@override
void didChangeDependencies() {
// Run the precache method after the widget is built
......@@ -95,50 +122,6 @@ class _CarouselSliderBoxState extends State<CarouselSliderBox> {
@override
Widget build(BuildContext context) {
switch (widget.type) {
case CarouselSliderIndicator.below:
return _buildIndicatorsBelow();
case CarouselSliderIndicator.insideText:
return _buildIndicatorsInsideText();
}
}
/// Builds the full widget for the option [CarouselSliderIndicator.below].
/// The indicator is shown below the image.
Widget _buildIndicatorsBelow() {
return Column(
children: [
Expanded(
flex: 8,
child: _buildCarouselSlider(),
),
// Only show indicator when more than one artwork is to be shown
if (widget.elementCount > 1)
Flexible(
flex: 1,
child: _buildCenteredPageIndicator(),
),
],
);
}
/// Centers the [_PageIndicator] widget.
Widget _buildCenteredPageIndicator() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.center,
child: _PageIndicator(
position: position,
elementCount: widget.elementCount,
),
),
);
}
/// Builds the full widget for the option [CarouselSliderIndicator.insideText].
/// The indicator and a text is put on top of the image.
Widget _buildIndicatorsInsideText() {
return Container(
width: widget.boxSize.width,
height: widget.boxSize.height,
......@@ -149,46 +132,58 @@ class _CarouselSliderBoxState extends State<CarouselSliderBox> {
/// Builds a [CarouselSlider] widget containing the images.
/// The images shown are built using the [_buildImage] method.
Widget _buildCarouselSlider() {
return CarouselSlider.builder(
options: CarouselOptions(
enlargeCenterPage: widget.type == CarouselSliderIndicator.insideText,
aspectRatio: 1,
// Only enable infinite scrolling when at least 2 artworks are to be shown
enableInfiniteScroll: widget.elementCount > 1,
viewportFraction: 1,
onPageChanged: (index, reason) {
setState(() {
position = index;
});
_precacheNeighbours(index);
},
),
itemCount: widget.elementCount,
itemBuilder: (context, index, realIndex) => _buildImage(index),
final appSetting = Provider.of<AppSetting>(context);
return Stack(
fit: StackFit.expand,
children: [
CarouselSlider.builder(
carouselController: carouselController,
options: CarouselOptions(
enlargeCenterPage: widget.enlargeCenterPage,
aspectRatio: 1,
// Only enable infinite scrolling when at least 2 artworks are to be shown
enableInfiniteScroll: widget.elementCount > 1,
viewportFraction: 1,
onPageChanged: (index, reason) {
setState(() {
position = index;
});
_precacheNeighbours(index);
},
),
itemCount: widget.elementCount,
itemBuilder: (context, index, realIndex) => _buildImage(index),
),
if (widget.title != null)
Positioned(
top: 20,
left: 20,
child: Text(
widget.title!,
style: TextStyle(
color: Colors.white,
fontFamily: 'Avenir',
fontSize: appSetting.bodyTitleFontSize + 4,
fontWeight: FontWeight.w900,
),
),
),
if (widget.elementCount >= 2)
Positioned(
right: 20,
bottom: 20,
child: _PageIndicator(
position: position,
elementCount: widget.elementCount,
onTap: (index) => carouselController.animateToPage(index),
),
),
],
);
}
/// Builds the correct image widget for the [widget.type].
Widget _buildImage(int index) {
final Widget? child;
// Determine the child of the Container with the images based on the indicator type.
switch (widget.type) {
case CarouselSliderIndicator.insideText:
child = _ImageOverlay(
size: widget.boxSize,
title: _Title(text: widget.provideText!(position)),
pageIndicator: _PageIndicator(
position: position,
elementCount: widget.elementCount,
),
);
break;
case CarouselSliderIndicator.below:
child = null;
break;
}
return GestureDetector(
onTap: () {
// An assignment is required for the Dart compiler to be sure that the
......@@ -197,103 +192,24 @@ class _CarouselSliderBoxState extends State<CarouselSliderBox> {
if (onTap != null) onTap(index);
},
child: Container(
width: widget.boxSize.width,
height: widget.boxSize.height,
decoration: BoxDecoration(
image: _buildDecorationImage(index),
),
child: child,
),
);
}
/// Builds a [DecorationImage] which can be used in [Container]s for
/// displaying the image provided by [widget.provideImage] at the [index].
DecorationImage _buildDecorationImage(int index) {
return DecorationImage(
image: widget.provideImage(index),
fit: widget.imageFit,
);
}
}
/// A [StatelessWidget] used to overlay information on an image.
/// The [size] are the dimensions of the image viewport.
/// The [title] is displayed on the upper left corner.
/// The [pageIndicator] is shown on the lower right corner.
class _ImageOverlay extends StatelessWidget {
const _ImageOverlay({
Key? key,
required this.size,
required this.title,
required this.pageIndicator,
}) : super(key: key);
final Size size;
final Widget title;
final Widget pageIndicator;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 20),
width: size.width,
height: size.height,
color: Colors.transparent,
child: Column(
children: [
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: title,
),
),
image: DecorationImage(
image: widget.provideImage(index),
fit: widget.imageFit,
),
Flexible(
flex: 1,
child: Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: pageIndicator,
),
),
),
],
),
);
}
}
/// A [StatelessWidget] displaying the given [text] formatted as a title.
class _Title extends StatelessWidget {
const _Title({
Key? key,
required this.text,
}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
final appSetting = Provider.of<AppSetting>(context);
return Container(
child: AutoSizeText(
text,
maxLines: 1,
style: TextStyle(
color: Colors.white,
fontFamily: 'Avenir',
fontSize: appSetting.bodyTitleFontSize,
fontWeight: FontWeight.w900,
),
),
padding: EdgeInsets.fromLTRB(8, 5, 8, 5),
decoration: BoxDecoration(
color: Colors.black.withOpacity(.7),
borderRadius: BorderRadius.circular(7),
child: (widget.provideText != null &&
widget.imageNameStyle != ImageNameStyle.none)
? ImageOverlay(
title: widget.imageNameStyle == ImageNameStyle.title
? widget.provideText!(index)
: null,
subtitle: widget.imageNameStyle == ImageNameStyle.subtitle
? widget.provideText!(index)
: null)
: null,
),
);
}
......@@ -307,27 +223,28 @@ class _PageIndicator extends StatelessWidget {
Key? key,
required this.position,
required this.elementCount,
required this.onTap,
}) : super(key: key);
final int position;
final int elementCount;
final Function(int) onTap;
@override
Widget build(BuildContext context) {
final appSetting = Provider.of<AppSetting>(context);
final displaySize = MediaQuery.of(context).size;
final double dotScale = MediaQuery.of(context).size.width * 0.025;
return AnimatedSmoothIndicator(
activeIndex: position,
count: elementCount,
onDotClicked: onTap,
effect: ScrollingDotsEffect(
activeDotColor: appSetting.primaryColor,
dotColor: appSetting.primaryColor.withOpacity(0.5),
activeDotScale: 1.6,
maxVisibleDots: 5,
dotWidth: displaySize.width * 0.015,
dotHeight: displaySize.width * 0.015,
spacing: 15,
dotColor: Colors.white,
dotWidth: dotScale,
dotHeight: dotScale,
spacing: 17,
),
);
}
......
......@@ -19,10 +19,10 @@ class CarouselSliderBoxImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CarouselSliderBox(
type: CarouselSliderIndicator.below,
boxSize: boxSize,
elementCount: imageLinks.length,
provideImage: (index) => NetworkImage(imageLinks[index]),
imageNameStyle: ImageNameStyle.none,
);
}
}
......@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:kunstforum_tu_darmstadt/config/route_generator.dart';
import 'package:kunstforum_tu_darmstadt/models/exhibition.dart';
import 'package:kunstforum_tu_darmstadt/ui/widgets/carousel_slider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
/// Builds a carousel slider for displaying images provided by [campusExhibition].
/// The page indicator and a text is shown on top of the image.
......@@ -23,14 +24,15 @@ class CarouselSliderCampusBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CarouselSliderBox(
type: CarouselSliderIndicator.insideText,
title: AppLocalizations.of(context)!.campusArt.toUpperCase(),
imageFit: BoxFit.cover,
boxSize: boxSize,
elementCount: campusExhibition.artworkList.length,
provideImage: (index) => NetworkImage(
campusExhibition.artworkList[index].images!.first,
),
provideText: (_) => campusExhibition.name,
imageNameStyle: ImageNameStyle.subtitle,
provideText: (index) => campusExhibition.artworkList[index].name,
onTap: (index) {
Navigator.of(context).pushNamed(
RouteGenerator.audioGuideScreenRouteName,
......
......@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:kunstforum_tu_darmstadt/config/route_generator.dart';
import 'package:kunstforum_tu_darmstadt/models/exhibition.dart';
import 'package:kunstforum_tu_darmstadt/ui/widgets/carousel_slider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
/// Builds a carousel slider for displaying the first images for each exhibition.
/// The exhibitions are provided by the [exhibitionList].
......@@ -25,7 +26,8 @@ class CarouselSliderBoxExhibition extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CarouselSliderBox(
type: CarouselSliderIndicator.insideText,
title: AppLocalizations.of(context)!.exhibition.toUpperCase(),
imageNameStyle: ImageNameStyle.subtitle,
imageFit: BoxFit.cover,
boxSize: boxSize,
elementCount: exhibitionList.length,
......
import 'package:flutter/material.dart';
import 'package:kunstforum_tu_darmstadt/config/app_setting.dart';
import 'package:provider/provider.dart';
/// A [StatelessWidget] displaying the given [title] and [subtitle] if given on a black gradient.
///
class ImageOverlay extends StatelessWidget {
const ImageOverlay({Key? key, this.title, this.subtitle})
: assert(title != null || subtitle != null),
super(key: key);
/// The title that should be displayed.
final String? title;
/// The subtitle that should be displayed. It will be displayed below the title in a smaller font size
final String? subtitle;
@override
Widget build(BuildContext context) {
final appSetting = Provider.of<AppSetting>(context);
return Container(
child: Stack(
children: [
if (title != null)
Padding(
padding: EdgeInsets.fromLTRB(20, 20, 12, 12),
child: Text(
title!,
maxLines: subtitle != null ? 1 : null,
style: TextStyle(
color: Colors.white,
fontFamily: 'Avenir',
fontSize: appSetting.bodyTitleFontSize + 4,
fontWeight: FontWeight.w900,
),
),
),
if (subtitle != null)
Padding(
padding: EdgeInsets.fromLTRB(26, 46, 12, 12),
child: Text(
subtitle!,
style: TextStyle(
color: Colors.white,
fontFamily: 'Avenir',
fontSize: appSetting.bodyTextFontSize,
fontWeight: FontWeight.w700,
),
),
),
],
),
decoration: BoxDecoration(
color: Colors.white,
gradient: LinearGradient(