Skip to content

Commit

Permalink
Add +200 colors for Sharezone Plus users (#1033)
Browse files Browse the repository at this point in the history
## Description

I added a new color picker for Sharezone Plus users. Sharezone Plus
users first select a base color and can then select an accurate color.
The base colors are just all primaries Google Material colors and the
accurate colors are all variants of the base color.

## Demo

When a user has Sharezone Plus:


https://github.com/SharezoneApp/sharezone-app/assets/24459435/364ea5d3-8b5e-4a93-ba59-dff2fd91251f

When a user has _not_ Sharezone Plus:


![image](https://github.com/SharezoneApp/sharezone-app/assets/24459435/8756e32b-ab98-41b5-aa63-2902c522503f)

## Related tickets

Closes #855
Closes #854
  • Loading branch information
nilsreichardt authored Dec 9, 2023
1 parent f904a11 commit 89bd64a
Show file tree
Hide file tree
Showing 37 changed files with 488 additions and 43 deletions.
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)
],
),
),
),
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

0 comments on commit 89bd64a

Please sign in to comment.