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

Select team via qr code #142

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions khelo/assets/locales/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
"add_team_player_role_title": "Playing role",
"add_team_player_add_via_contact_title": "Add via Contact",
"add_team_players_text": "Players",
"add_team_already_added": "Team already added",
"add_team_add_hint_text": "Added user will be shown here, tap on '+' button to add team member.",

"scan_qr_code": "Scan QR code",
Expand Down
27 changes: 20 additions & 7 deletions khelo/lib/ui/app_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:khelo/ui/flow/team/add_team_member/contact_selection/contact_sel
import 'package:khelo/ui/flow/team/detail/make_admin/make_team_admin_screen.dart';
import 'package:khelo/ui/flow/team/detail/team_detail_screen.dart';
import 'package:khelo/ui/flow/team/scanner/scanner_screen.dart';
import 'package:khelo/ui/flow/team/scanner/scanner_view_model.dart';
import 'package:khelo/ui/flow/team/search_team/search_team_screen.dart';
import 'package:khelo/ui/flow/tournament/add/add_tournament_screen.dart';
import 'package:khelo/ui/flow/tournament/detail/members/tournament_detail_members_screen.dart';
Expand Down Expand Up @@ -210,9 +211,9 @@ class AppRoute {
);

static AppRoute matchSelection({required String tournamentId}) => AppRoute(
pathMatchSelection,
builder: (_) => MatchSelectionScreen(tournamentId: tournamentId),
);
pathMatchSelection,
builder: (_) => MatchSelectionScreen(tournamentId: tournamentId),
);

static AppRoute tournamentDetail({required String tournamentId}) => AppRoute(
pathTournamentDetail,
Expand Down Expand Up @@ -301,9 +302,15 @@ class AppRoute {
pathEditProfile,
builder: (_) => EditProfileScreen(isToCreateAccount: isToCreateAccount));

static AppRoute teamDetail({required String teamId}) =>
static AppRoute teamDetail({
required String teamId,
bool showAddButton = false,
}) =>
AppRoute(pathTeamDetail,
builder: (_) => TeamDetailScreen(teamId: teamId));
builder: (_) => TeamDetailScreen(
teamId: teamId,
showAddButton: showAddButton,
));

static AppRoute userDetail({
required String userId,
Expand All @@ -315,10 +322,16 @@ class AppRoute {
showAddButton: showAddButton,
));

static AppRoute scannerScreen({required List<String> addedMembers}) =>
static AppRoute scannerScreen({
required List<String> addedIds,
ScanTarget target = ScanTarget.player,
}) =>
AppRoute(
pathScannerScreen,
builder: (_) => ScannerScreen(addedMembers: addedMembers),
builder: (_) => ScannerScreen(
addedIds: addedIds,
target: target,
),
);

static AppRoute qrCodeView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ class _AddTeamMemberScreenState extends ConsumerState<AddTeamMemberScreen> {
actionButton(
context,
onPressed: () async {
final user = await AppRoute.scannerScreen(
addedMembers: notifier.getMemberIds())
.push<UserModel>(context);
final user =
await AppRoute.scannerScreen(addedIds: notifier.getMemberIds())
.push<UserModel>(context);
if (context.mounted && user != null) notifier.selectUser(user);
},
icon: SvgPicture.asset(
Expand Down
30 changes: 21 additions & 9 deletions khelo/lib/ui/flow/team/detail/team_detail_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:khelo/ui/flow/team/detail/components/team_detail_member_content.
import 'package:khelo/ui/flow/team/detail/team_detail_view_model.dart';
import 'package:style/button/action_button.dart';
import 'package:style/button/more_option_button.dart';
import 'package:style/button/secondary_button.dart';
import 'package:style/button/tab_button.dart';
import 'package:style/extensions/context_extensions.dart';
import 'package:style/indicator/progress_indicator.dart';
Expand All @@ -25,8 +26,13 @@ import 'components/team_detail_stat_content.dart';

class TeamDetailScreen extends ConsumerStatefulWidget {
final String teamId;
final bool showAddButton;

const TeamDetailScreen({super.key, required this.teamId});
const TeamDetailScreen({
super.key,
required this.teamId,
required this.showAddButton,
});

@override
ConsumerState createState() => _TeamDetailScreenState();
Expand Down Expand Up @@ -66,14 +72,20 @@ class _TeamDetailScreenState extends ConsumerState<TeamDetailScreen> {
size: 24,
color: context.colorScheme.textPrimary,
)),
actions: (state.team?.isAdminOrOwner(state.currentUserId) ?? false)
? [
moreOptionButton(
context,
onPressed: () => _moreActionButton(context, notifier, state),
),
]
: null,
actions: [
if (widget.showAddButton)
SecondaryButton(
context.l10n.common_add_title,
enabled: !state.loading,
onPressed: () => context.pop(state.team),
),
if (!widget.showAddButton &&
(state.team?.isAdminOrOwner(state.currentUserId) ?? false))
moreOptionButton(
context,
onPressed: () => _moreActionButton(context, notifier, state),
),
],
body: Builder(builder: (context) {
return _body(context, state);
}),
Expand Down
50 changes: 36 additions & 14 deletions khelo/lib/ui/flow/team/scanner/scanner_screen.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:math';

import 'package:data/api/team/team_model.dart';
import 'package:data/api/user/user_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand All @@ -22,9 +23,14 @@ import '../../../../components/app_page.dart';
import '../../../../components/error_snackbar.dart';

class ScannerScreen extends ConsumerStatefulWidget {
final List<String> addedMembers;
final List<String> addedIds;
final ScanTarget target;

const ScannerScreen({super.key, required this.addedMembers});
const ScannerScreen({
super.key,
required this.addedIds,
this.target = ScanTarget.player,
});

@override
ConsumerState<ScannerScreen> createState() => _ScannerScreenState();
Expand All @@ -36,21 +42,37 @@ class _ScannerScreenState extends ConsumerState<ScannerScreen> {
late final ScannerStateNotifier _notifier;

void _observeBarcode() {
ref.listen(scannerStateNotifierProvider.select((value) => value.userId),
(prev, userId) async {
if (widget.addedMembers.contains(userId)) {
showSnackBar(context, context.l10n.add_team_member_already_added);
} else if (userId.isNotEmpty) {
final user =
await AppRoute.userDetail(userId: userId, showAddButton: true)
.push<UserModel?>(context);
if (mounted) {
context.pop(user);
}
ref.listen(scannerStateNotifierProvider.select((value) => value.scannedId),
(prev, scannedId) async {
if (widget.addedIds.contains(scannedId)) {
showSnackBar(
context,
widget.target == ScanTarget.team
? context.l10n.add_team_already_added
: context.l10n.add_team_member_already_added);
} else if (scannedId.isNotEmpty) {
_handleScanTarget(widget.target, scannedId);
}
});
}

void _handleScanTarget(ScanTarget target, String scannedId) async {
switch (widget.target) {
case ScanTarget.team:
final team =
await AppRoute.teamDetail(teamId: scannedId, showAddButton: true)
.push<TeamModel>(context);
if (mounted) context.pop(team);
break;
case ScanTarget.player:
final user =
await AppRoute.userDetail(userId: scannedId, showAddButton: true)
.push<UserModel?>(context);
if (mounted) context.pop(user);
break;
}
}

void _observeError() {
ref.listen(scannerStateNotifierProvider.select((value) => value.error),
(prev, error) {
Expand All @@ -63,7 +85,7 @@ class _ScannerScreenState extends ConsumerState<ScannerScreen> {
@override
void initState() {
_notifier = ref.read(scannerStateNotifierProvider.notifier);
runPostFrame(() => _notifier.setData(widget.addedMembers));
runPostFrame(() => _notifier.setData(widget.addedIds));
super.initState();
}

Expand Down
14 changes: 8 additions & 6 deletions khelo/lib/ui/flow/team/scanner/scanner_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ final scannerStateNotifierProvider =
});

class ScannerStateNotifier extends StateNotifier<ScannerState> {
List<String> _addedMembers = [];
List<String> _addedIds = [];
StreamSubscription? _subscription;

ScannerStateNotifier() : super(const ScannerState()) {
_checkCameraPermission();
}

void setData(List<String> addedMembers) {
_addedMembers = addedMembers;
void setData(List<String> addedIds) {
_addedIds = addedIds;
}

Future<void> _checkCameraPermission() async {
Expand Down Expand Up @@ -62,11 +62,11 @@ class ScannerStateNotifier extends StateNotifier<ScannerState> {
_subscription = state.controller?.scannedDataStream.listen(
(event) {
final code = event.code;
if (!_addedMembers.contains(code)) {
if (!_addedIds.contains(code)) {
_removeListeners();
}
state = state.copyWith(
userId: code ?? "",
scannedId: code ?? "",
);
},
onError: (e) {
Expand All @@ -93,8 +93,10 @@ class ScannerState with _$ScannerState {
const factory ScannerState({
Object? error,
QRViewController? controller,
@Default('') String userId,
@Default('') String scannedId,
@Default(false) bool flashOn,
@Default(false) bool hasPermission,
}) = _ScannerState;
}

enum ScanTarget { player, team }
37 changes: 19 additions & 18 deletions khelo/lib/ui/flow/team/scanner/scanner_view_model.freezed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$ScannerState {
Object? get error => throw _privateConstructorUsedError;
QRViewController? get controller => throw _privateConstructorUsedError;
String get userId => throw _privateConstructorUsedError;
String get scannedId => throw _privateConstructorUsedError;
bool get flashOn => throw _privateConstructorUsedError;
bool get hasPermission => throw _privateConstructorUsedError;

Expand All @@ -38,7 +38,7 @@ abstract class $ScannerStateCopyWith<$Res> {
$Res call(
{Object? error,
QRViewController? controller,
String userId,
String scannedId,
bool flashOn,
bool hasPermission});
}
Expand All @@ -60,7 +60,7 @@ class _$ScannerStateCopyWithImpl<$Res, $Val extends ScannerState>
$Res call({
Object? error = freezed,
Object? controller = freezed,
Object? userId = null,
Object? scannedId = null,
Object? flashOn = null,
Object? hasPermission = null,
}) {
Expand All @@ -70,9 +70,9 @@ class _$ScannerStateCopyWithImpl<$Res, $Val extends ScannerState>
? _value.controller
: controller // ignore: cast_nullable_to_non_nullable
as QRViewController?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
scannedId: null == scannedId
? _value.scannedId
: scannedId // ignore: cast_nullable_to_non_nullable
as String,
flashOn: null == flashOn
? _value.flashOn
Expand All @@ -97,7 +97,7 @@ abstract class _$$ScannerStateImplCopyWith<$Res>
$Res call(
{Object? error,
QRViewController? controller,
String userId,
String scannedId,
bool flashOn,
bool hasPermission});
}
Expand All @@ -117,7 +117,7 @@ class __$$ScannerStateImplCopyWithImpl<$Res>
$Res call({
Object? error = freezed,
Object? controller = freezed,
Object? userId = null,
Object? scannedId = null,
Object? flashOn = null,
Object? hasPermission = null,
}) {
Expand All @@ -127,9 +127,9 @@ class __$$ScannerStateImplCopyWithImpl<$Res>
? _value.controller
: controller // ignore: cast_nullable_to_non_nullable
as QRViewController?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
scannedId: null == scannedId
? _value.scannedId
: scannedId // ignore: cast_nullable_to_non_nullable
as String,
flashOn: null == flashOn
? _value.flashOn
Expand All @@ -149,7 +149,7 @@ class _$ScannerStateImpl implements _ScannerState {
const _$ScannerStateImpl(
{this.error,
this.controller,
this.userId = '',
this.scannedId = '',
this.flashOn = false,
this.hasPermission = false});

Expand All @@ -159,7 +159,7 @@ class _$ScannerStateImpl implements _ScannerState {
final QRViewController? controller;
@override
@JsonKey()
final String userId;
final String scannedId;
@override
@JsonKey()
final bool flashOn;
Expand All @@ -169,7 +169,7 @@ class _$ScannerStateImpl implements _ScannerState {

@override
String toString() {
return 'ScannerState(error: $error, controller: $controller, userId: $userId, flashOn: $flashOn, hasPermission: $hasPermission)';
return 'ScannerState(error: $error, controller: $controller, scannedId: $scannedId, flashOn: $flashOn, hasPermission: $hasPermission)';
}

@override
Expand All @@ -180,7 +180,8 @@ class _$ScannerStateImpl implements _ScannerState {
const DeepCollectionEquality().equals(other.error, error) &&
(identical(other.controller, controller) ||
other.controller == controller) &&
(identical(other.userId, userId) || other.userId == userId) &&
(identical(other.scannedId, scannedId) ||
other.scannedId == scannedId) &&
(identical(other.flashOn, flashOn) || other.flashOn == flashOn) &&
(identical(other.hasPermission, hasPermission) ||
other.hasPermission == hasPermission));
Expand All @@ -191,7 +192,7 @@ class _$ScannerStateImpl implements _ScannerState {
runtimeType,
const DeepCollectionEquality().hash(error),
controller,
userId,
scannedId,
flashOn,
hasPermission);

Expand All @@ -208,7 +209,7 @@ abstract class _ScannerState implements ScannerState {
const factory _ScannerState(
{final Object? error,
final QRViewController? controller,
final String userId,
final String scannedId,
final bool flashOn,
final bool hasPermission}) = _$ScannerStateImpl;

Expand All @@ -217,7 +218,7 @@ abstract class _ScannerState implements ScannerState {
@override
QRViewController? get controller;
@override
String get userId;
String get scannedId;
@override
bool get flashOn;
@override
Expand Down
Loading
Loading