Skip to content

Commit

Permalink
feat(raffle): Option to enter user from the admin screen directly
Browse files Browse the repository at this point in the history
  • Loading branch information
vanlooverenkoen committed Jan 23, 2024
1 parent 178b7c0 commit ef72898
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 124 deletions.
6 changes: 6 additions & 0 deletions lib/navigation/main_navigator.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_belgium/navigation/page_route/no_transition_page_route.dart';
import 'package:flutter_belgium/widget/raffle/add_participant_dialog.dart';
import 'package:flutter_navigation_generator_annotations/flutter_navigation_generator_annotations.dart';
import 'package:injectable/injectable.dart';
import 'package:flutter_belgium/navigation/main_navigator.navigator.dart';
Expand Down Expand Up @@ -54,4 +55,9 @@ class MainNavigator with BaseNavigator {
leftBarIndicatorColor: Colors.blue,
).show(context);
}

Future<String?> goToAddParticipantDialog() => showDialog<String>(
context: context,
builder: (context) => const AddParticipantDialog(),
);
}
33 changes: 32 additions & 1 deletion lib/repo/raffle_repo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_belgium/model/data/raffle/winner.dart';
import 'package:flutter_belgium/repo/login_repo.dart';
import 'package:injectable/injectable.dart';
import 'package:rxdart/rxdart.dart';
import 'package:uuid/uuid.dart';

@lazySingleton
abstract class RaffleRepository {
Expand All @@ -32,6 +33,11 @@ abstract class RaffleRepository {
required String raffleId,
required RaffleParticipant winner,
});

Future<void> manuallyEnterRaffle({
required String raffleId,
required String name,
});
}

class _RaffleRepository implements RaffleRepository {
Expand Down Expand Up @@ -87,7 +93,10 @@ class _RaffleRepository implements RaffleRepository {
userUid: _loginRepository.userId!,
name: _loginRepository.userName!,
);
await _firebaseFirestore.collection('raffle').doc(raffleId).collection('participants').doc(_loginRepository.userId).set(participant.toJson());
await _addParticipantToRaffle(
raffleId: raffleId,
raffleParticipant: participant,
);
}

@override
Expand All @@ -101,4 +110,26 @@ class _RaffleRepository implements RaffleRepository {
@override
Stream<bool> hasWonRaffle(String raffleId) =>
_firebaseFirestore.collection('raffle').doc(raffleId).collection('winners').doc(_loginRepository.userId).snapshots().map((event) => event.exists);

@override
Future<void> manuallyEnterRaffle({
required String raffleId,
required String name,
}) async {
final participant = RaffleParticipant(
userUid: 'manual_${const Uuid().v4()}',
name: name,
);
await _addParticipantToRaffle(
raffleId: raffleId,
raffleParticipant: participant,
);
}

Future<void> _addParticipantToRaffle({
required String raffleId,
required RaffleParticipant raffleParticipant,
}) async {
await _firebaseFirestore.collection('raffle').doc(raffleId).collection('participants').doc(raffleParticipant.userUid).set(raffleParticipant.toJson());
}
}
159 changes: 69 additions & 90 deletions lib/screen/raffle_winner_picker_screen.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_belgium/style/theme.dart';
import 'package:flutter_belgium/style/theme_duration.dart';
import 'package:flutter_belgium/viewmodel/raffle_winner_picker_viewmodel.dart';
import 'package:flutter_belgium/widget/general/button.dart';
import 'package:flutter_belgium/widget/general/loading.dart';
import 'package:flutter_belgium/widget/raffle/custom_confetti.dart';
import 'package:flutter_belgium/widget/raffle/custom_fortune_wheel.dart';
import 'package:flutter_navigation_generator_annotations/flutter_navigation_generator_annotations.dart';
import 'package:flutter_fortune_wheel/flutter_fortune_wheel.dart';
import 'package:flutter_belgium/di/injectable.dart';
import 'package:flutter_belgium/widget/provider/provider_widget.dart';

Expand Down Expand Up @@ -48,49 +45,22 @@ class RaffleWinnerPickerScreen extends StatelessWidget {
if (viewModel.isLoading) {
return Container();
}
if (!viewModel.hasEnoughParticipants) {
return Center(
child: Text('Not enough participants, ${viewModel.participants.length} participant(s) entered this raffle. (min 2 required)'),
);
}
return Column(
children: [
Expanded(
child: FortuneWheel(
selected: viewModel.selectedIndexStream,
animateFirst: false,
duration: ThemeDuration.raffleWheelDuration,
rotationCount: Random().nextInt(10) + 20,
indicators: const [
FortuneIndicator(
alignment: Alignment.topCenter,
child: TriangleIndicator(
color: ThemeColors.primary,
),
),
],
physics: CircularPanPhysics(
duration: const Duration(seconds: 1),
curve: Curves.decelerate,
),
items: [
for (final participant in viewModel.participants)
FortuneItem(
child: Builder(
builder: (context) {
if (!viewModel.hasEnoughParticipants) {
return Center(
child: Text(
participant.name,
style: TextStyle(
color: ThemeColors.primary,
fontSize: viewModel.participants.length > 30 ? 16 : 24,
fontWeight: FontWeight.bold,
),
),
style: const FortuneItemStyle(
color: ThemeColors.primaryUltraLight, // <-- custom circle slice fill color
borderColor: ThemeColors.primary,
borderWidth: 2,
),
),
],
'Not enough participants, ${viewModel.participants.length} participant(s) entered this raffle. (min ${viewModel.minRequiredParticipants} required)'),
);
}
return CustomFortuneWheel(
selected: viewModel.selectedIndexStream,
participants: viewModel.participants,
);
},
),
),
Text(
Expand All @@ -115,57 +85,66 @@ class RaffleWinnerPickerScreen extends StatelessWidget {
],
),
),
if (viewModel.hasEnoughParticipants) ...[
Container(
width: 350,
padding: const EdgeInsets.all(16),
color: ThemeColors.primaryUltraLight,
alignment: Alignment.center,
child: Builder(builder: (context) {
if (viewModel.isLoading) {
return const Loading();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Participants list (${viewModel.participants.length})',
style: const TextStyle(
fontSize: 24,
Container(
width: 350,
padding: const EdgeInsets.all(16),
color: ThemeColors.primaryUltraLight,
alignment: Alignment.center,
child: Builder(builder: (context) {
if (viewModel.isLoading) {
return const Loading();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'Participants list (${viewModel.participants.length})',
style: const TextStyle(
fontSize: 24,
),
),
),
),
Expanded(
child: ListView.builder(
itemCount: viewModel.participants.length,
itemBuilder: (context, index) {
final item = viewModel.participants[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
item.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
},
Button(
onTap: viewModel.onAddParticipantTapped,
text: '+',
fullWidth: false,
),
],
),
Expanded(
child: ListView.builder(
itemCount: viewModel.participants.length,
itemBuilder: (context, index) {
final item = viewModel.participants[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
item.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
},
),
if (viewModel.winnerName == null) ...[
Center(
child: Button(
onTap: viewModel.onPickWinnerTapped,
text: 'Pick Winner',
fullWidth: false,
),
),
if (viewModel.hasEnoughParticipants && viewModel.winnerName == null) ...[
Center(
child: Button(
onTap: viewModel.onPickWinnerTapped,
text: 'Pick Winner',
fullWidth: false,
),
]
],
);
}),
),
],
),
]
],
);
}),
),
],
);
}),
Expand Down
18 changes: 17 additions & 1 deletion lib/viewmodel/raffle_winner_picker_viewmodel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class RaffleWinnerPickerViewModel with ChangeNotifier {

Stream<int> get selectedIndexStream => _selectedIndexStreamController.stream;

int get minRequiredParticipants => 2;

List<RaffleParticipant> get participants => _lockedParticipants ?? _allowedParticipants;

bool get isLoading => _raffle == null;
Expand All @@ -38,7 +40,7 @@ class RaffleWinnerPickerViewModel with ChangeNotifier {

bool get hasInactiveRaffle => _raffle?.active == false;

bool get hasEnoughParticipants => participants.length > 2;
bool get hasEnoughParticipants => participants.length >= minRequiredParticipants;

RaffleWinnerPickerViewModel(
this._raffleRepository,
Expand Down Expand Up @@ -102,4 +104,18 @@ class RaffleWinnerPickerViewModel with ChangeNotifier {
}
_raffleRepository.setRaffleActive(raffleId: docId, active: true);
}

Future<void> onAddParticipantTapped() async {
final docId = _raffle?.id;
if (docId == null) {
_mainNavigator.showError('Failed to make raffle active (no raffle available)');
return;
}
final name = await _mainNavigator.goToAddParticipantDialog();
if (name == null) return;
await _raffleRepository.manuallyEnterRaffle(
raffleId: docId,
name: name,
);
}
}
77 changes: 46 additions & 31 deletions lib/widget/general/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Button extends StatefulWidget {
super.key,
}) : child = null;

const Button.chidl({
const Button.child({
required this.onTap,
required this.child,
this.color,
Expand All @@ -34,37 +34,52 @@ class _ButtonState extends State<Button> {
var _isLoading = false;
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Loading();
}
return GestureDetector(
onTap: _onTap,
child: Container(
width: widget.fullWidth && widget.text != null ? double.infinity : null,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
boxShadow: const [
BoxShadow(
blurRadius: 10,
spreadRadius: 4,
color: ThemeColors.shadow,
return Stack(
alignment: Alignment.center,
children: [
if (_isLoading) ...[
const SizedBox(
width: 24,
height: 24,
child: Loading(),
),
],
Opacity(
opacity: _isLoading ? 0 : 1,
child: IgnorePointer(
ignoring: _isLoading,
child: GestureDetector(
onTap: _onTap,
child: Container(
width: widget.fullWidth && widget.text != null ? double.infinity : null,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
boxShadow: const [
BoxShadow(
blurRadius: 10,
spreadRadius: 4,
color: ThemeColors.shadow,
),
],
color: widget.color ?? ThemeColors.primary,
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
child: Builder(builder: (context) {
if (widget.child != null) return widget.child!;
return Text(
widget.text ?? '',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelMedium?.copyWith(color: ThemeColors.white),
);
}),
),
),
],
color: widget.color ?? ThemeColors.primary,
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
child: Builder(builder: (context) {
if (widget.child != null) return widget.child!;
return Text(
widget.text ?? '',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelMedium?.copyWith(color: ThemeColors.white),
);
}),
),
),
)
],
);
}

Expand Down
Loading

0 comments on commit ef72898

Please sign in to comment.