Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add +200 colors for Sharezone Plus users #1033

Merged
merged 13 commits into from
Dec 9, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
//
// SPDX-License-Identifier: EUPL-1.2

import 'package:animations/animations.dart';
import 'package:bloc_provider/bloc_provider.dart';
import 'package:design/design.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sharezone/main/application_bloc.dart';
import 'package:sharezone/groups/group_permission.dart';
import 'package:sharezone/groups/src/pages/course/course_edit/design/src/bloc/course_edit_design_bloc.dart';
import 'package:sharezone/sharezone_plus/page/sharezone_plus_page.dart';
import 'package:sharezone/sharezone_plus/subscription_service/subscription_service.dart';
import 'package:sharezone_common/api_errors.dart';
import 'package:sharezone_widgets/sharezone_widgets.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,48 @@ class _SelectDesignAlert extends StatelessWidget {
Widget build(BuildContext context) {
final hasUserPersonalColor =
type == EditDesignType.personal && currentDesign != null;
final isUnlocked = context
.read<SubscriptionService>()
.hasFeatureUnlocked(SharezonePlusFeature.moreGroupColors);
return AlertDialog(
contentPadding:
EdgeInsets.fromLTRB(24, 24, 24, hasUserPersonalColor ? 12 : 24),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_Colors(
selectedDesign: currentDesign,
type: type,
),
if (hasUserPersonalColor) _RemovePersonalColor(),
if (hasUserPersonalColor) const SizedBox(height: 4),
if (!hasUserPersonalColor) const SizedBox(height: 16),
],
child: MaxWidthConstraintBox(
maxWidth: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (isUnlocked)
_PlusColors(selectedDesign: currentDesign)
else
_FreeColors(selectedDesign: currentDesign),
if (hasUserPersonalColor) _RemovePersonalColor(),
if (hasUserPersonalColor) const SizedBox(height: 4),
if (!hasUserPersonalColor) const SizedBox(height: 16),
if (!isUnlocked) const _SharezonePlusAd(),
],
),
),
),
);
}
}

class _SharezonePlusAd extends StatelessWidget {
const _SharezonePlusAd();

@override
Widget build(BuildContext context) {
return SharezonePlusFeatureInfoCard(
withLearnMoreButton: true,
onLearnMorePressed: () => navigateToSharezonePlusPage(context),
child: const Text(
'Nicht genug Farben? Schalte mit Sharezone Plus +200 zusätzliche Farben frei.'),
);
}
}

class _RemovePersonalColor extends StatelessWidget {
@override
Widget build(BuildContext context) {
Expand All @@ -82,15 +103,14 @@ class _RemovePersonalColor extends StatelessWidget {
}
}

class _Colors extends StatelessWidget {
const _Colors({
Key? key,
class _FreeColors extends StatelessWidget {
const _FreeColors({
this.selectedDesign,
required this.type,
}) : super(key: key);
});

final Design? selectedDesign;
final EditDesignType type;

bool _isDesignSelected(Design design) => selectedDesign == design;

@override
Widget build(BuildContext context) {
Expand All @@ -103,11 +123,15 @@ class _Colors extends StatelessWidget {
spacing: 10,
runSpacing: 10,
children: [
...Design.designList
...Design.freeDesigns
.map(
(design) => _ColorCircleSelectDesign(
design: design,
isSelected: _isDesignSelected(design),
onTap: () => Navigator.pop(
context,
SelectDesignPopResult(design: design),
),
),
)
.toList(),
Expand All @@ -116,19 +140,225 @@ class _Colors extends StatelessWidget {
],
);
}
}

/// This widget is used to select a base color and then select from the base
/// color the accurate color.
class _PlusColors extends StatefulWidget {
const _PlusColors({
this.selectedDesign,
});

final Design? selectedDesign;

@override
State<_PlusColors> createState() => _PlusColorsState();
}

class _PlusColorsState extends State<_PlusColors> {
/// The base color that was selected in the first page.
///
/// If this is null, the first page is shown. If this is not null, the second
/// page is shown.
MaterialColor? baseColor;

@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
// This is a hack to make sure that the dialog has the same size when
// switching between the two pages. Otherwise the dialog changes its
// size when switching between the two pages which looks weird.
const Opacity(
opacity: 0,
child: IgnorePointer(
child: Stack(
children: [
_PlusBaseColors(),
_PlusAccurateColors(baseColor: Colors.amber)
],
),
),
),
Comment on lines +170 to +183
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open for better solutions.

PageTransitionSwitcher(
reverse: baseColor != null,
transitionBuilder: (
Widget child,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.vertical,
fillColor: Colors.transparent,
child: child,
);
},
duration: const Duration(milliseconds: 250),
// The user can first select a base color and then select from the
// base color the accurate color.
child: baseColor == null
? _PlusBaseColors(
key: const Key('plus-accurate-colors'),
onBaseColorChanged: (color) =>
setState(() => baseColor = color))
: _PlusAccurateColors(
key: const Key('plus-accurate-colors'),
baseColor: baseColor!,
onBackButtonPressed: () => setState(() => baseColor = null),
selectedDesign: widget.selectedDesign,
),
),
],
);
}
}

class _PlusBaseColors extends StatelessWidget {
const _PlusBaseColors({
super.key,
this.onBaseColorChanged,
});

final ValueChanged<MaterialColor>? onBaseColorChanged;

/// As base colors we use the Material colors.
///
/// We copied the list from [Colors.primaries] to make sure we don't break
/// anything if the Flutter team changes the list.
static const _baseColors = <MaterialColor>[
Colors.red,
Colors.pink,
Colors.purple,
Colors.deepPurple,
Colors.indigo,
Colors.blue,
Colors.lightBlue,
Colors.cyan,
Colors.teal,
Colors.green,
Colors.lightGreen,
Colors.lime,
Colors.yellow,
Colors.amber,
Colors.orange,
Colors.deepOrange,
Colors.brown,
Colors.blueGrey,
];

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Center(
child: Text(
'Grundfarbe auswählen',
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.only(left: 30),
child: _BackToSelectTypeButton(),
),
const SizedBox(height: 16),
Center(
child: Wrap(
spacing: 10,
runSpacing: 10,
children: [
..._baseColors.map(
(color) {
final design = Design.fromColor(color);
return _ColorCircleSelectDesign(
design: design,
onTap: () => onBaseColorChanged!(color),
);
},
).toList(),
],
),
),
],
);
}
}

class _PlusAccurateColors extends StatelessWidget {
const _PlusAccurateColors({
super.key,
required this.baseColor,
this.onBackButtonPressed,
this.selectedDesign,
});

final MaterialColor baseColor;
final Design? selectedDesign;
final VoidCallback? onBackButtonPressed;

bool _isDesignSelected(Design design) => selectedDesign == design;

List<Design> getDesigns() {
List<Color> colors = [];
for (final level in [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]) {
try {
colors.add(baseColor[level]!);
} catch (e) {
// Ignore, color level does not exist.
}
}
return colors.map((c) => Design.fromColor(c)).toList();
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BackButton(
color: Colors.grey,
onPressed: onBackButtonPressed,
),
const SizedBox(height: 16),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
...getDesigns()
.map(
(design) => _ColorCircleSelectDesign(
design: design,
isSelected: _isDesignSelected(design),
onTap: () => Navigator.pop(
context,
SelectDesignPopResult(design: design),
),
),
)
.toList(),
],
),
],
);
}
}

class _ColorCircleSelectDesign extends StatelessWidget {
const _ColorCircleSelectDesign({
Key? key,
required this.design,
this.isSelected = false,
required this.onTap,
}) : super(key: key);

final Design? design;
final Design design;
final bool isSelected;
final VoidCallback? onTap;

@override
Widget build(BuildContext context) {
Expand All @@ -137,12 +367,12 @@ class _ColorCircleSelectDesign extends StatelessWidget {
isSelected ? const Icon(Icons.check, color: Colors.white) : null;

return Material(
color: design?.color,
color: design.color,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(size)),
child: InkWell(
key: Key('color-circle-${design.color.value}'),
borderRadius: BorderRadius.circular(size),
onTap: () =>
Navigator.pop(context, SelectDesignPopResult(design: design)),
onTap: onTap,
child: SizedBox(
width: size,
height: size,
Expand All @@ -156,8 +386,7 @@ class _ColorCircleSelectDesign extends StatelessWidget {
class _BackToSelectTypeButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.arrow_back),
return BackButton(
color: Colors.grey,
onPressed: () => Navigator.pop(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const _featuresMap = {
SharezonePlusFeature.filterTimetableByClass,
SharezonePlusFeature.changeHomeworkReminderTime,
SharezonePlusFeature.plusSupport,
SharezonePlusFeature.moreGroupColors,
SharezonePlusFeature.addEventToLocalCalendar,
SharezonePlusFeature.viewPastEvents,
},
Expand All @@ -84,6 +85,7 @@ enum SharezonePlusFeature {
filterTimetableByClass,
changeHomeworkReminderTime,
plusSupport,
moreGroupColors,
addEventToLocalCalendar,
viewPastEvents,
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading