Skip to content

Commit

Permalink
Do not allow to delete group if there are more than 1 member (#158)
Browse files Browse the repository at this point in the history
* do not allow to delete group if there are more than 1 member

* flow for change admin as per current admin choice

* update map marker size

* fix lint

* Minor

* fix android build

* fix android build

---------

Co-authored-by: kaushik <[email protected]>
  • Loading branch information
cp-ishita-g and kaushik authored Dec 12, 2024
1 parent 643c914 commit 605e791
Show file tree
Hide file tree
Showing 20 changed files with 702 additions and 101 deletions.
4 changes: 4 additions & 0 deletions app/assets/locales/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@
"edit_space_leave_space_alert_confirm_button_text": "Leave",
"edit_space_remove_user_title": "Remove member",
"edit_space_remove_user_subtitle": "This action cannot be undone, are you sure you want to remove this member from group",
"edit_space_admin_text": "(Admin)",
"edit_space_change_admin_text": "Change",
"edit_space_change_admin_title": "Change Admin",
"edit_space_change_admin_subtitle": "To leave the group, you must assign another member as admin. This action is irreversible unless the new admin changes it.",

"contact_support_title": "Contact support",
"contact_support_title_field": "Title",
Expand Down
13 changes: 13 additions & 0 deletions app/lib/ui/app_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'package:yourspace_flutter/ui/flow/permission/enable_permission_view.dart
import 'package:yourspace_flutter/ui/flow/setting/contact_support/contact_support_screen.dart';
import 'package:yourspace_flutter/ui/flow/setting/profile/profile_screen.dart';
import 'package:yourspace_flutter/ui/flow/setting/setting_screen.dart';
import 'package:yourspace_flutter/ui/flow/setting/space/admin/change_admin_screen.dart';
import 'package:yourspace_flutter/ui/flow/setting/space/edit_space_screen.dart';
import 'package:yourspace_flutter/ui/flow/space/create/create_space_screen.dart';
import 'package:yourspace_flutter/ui/flow/space/invite/invite_code_screen.dart';
Expand All @@ -41,6 +42,7 @@ class AppRoute {
static const pathSetting = '/setting';
static const pathProfile = '/profile';
static const pathEditSpace = '/space';
static const pathChangeAdmin = '/admin';
static const pathContactSupport = '/contact-support';
static const pathMessage = '/message';
static const pathChat = '/chat';
Expand Down Expand Up @@ -185,6 +187,13 @@ class AppRoute {
);
}

static AppRoute changeAdmin(SpaceInfo space) {
return AppRoute(
pathChangeAdmin,
builder: (_) => ChangeAdminScreen(spaceInfo: space),
);
}

static AppRoute get contactSupport => AppRoute(pathContactSupport,
builder: (_) => const ContactSupportScreen());

Expand Down Expand Up @@ -325,6 +334,10 @@ class AppRoute {
path: pathEditSpace,
builder: (context, state) => state.widget(context),
),
GoRoute(
path: pathChangeAdmin,
builder: (context, state) => state.widget(context),
),
GoRoute(
path: pathContactSupport,
builder: (context, state) => state.widget(context),
Expand Down
10 changes: 5 additions & 5 deletions app/lib/ui/flow/auth/sign_in_method_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ class _SignInMethodScreenState extends ConsumerState<SignInMethodScreen> {
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
context.colorScheme.primary.withOpacity(0),
context.colorScheme.primary.withOpacity(0.02),
context.colorScheme.primary.withOpacity(0.12),
context.colorScheme.primary.withOpacity(0.02),
context.colorScheme.primary.withOpacity(0),
context.colorScheme.primary.withAlpha(0),
context.colorScheme.primary.withAlpha((0.02 * 255).toInt()),
context.colorScheme.primary.withAlpha((0.12 * 255).toInt()),
context.colorScheme.primary.withAlpha((0.02 * 255).toInt()),
context.colorScheme.primary.withAlpha(0),
],
),
);
Expand Down
2 changes: 1 addition & 1 deletion app/lib/ui/flow/geofence/add/components/place_marker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class PlaceMarker extends StatelessWidget {
height: radius,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
color: context.colorScheme.primary.withOpacity(0.5),
color: context.colorScheme.primary.withAlpha((0.5 * 255).toInt()),
),
),
),
Expand Down
4 changes: 3 additions & 1 deletion app/lib/ui/flow/geofence/edit/edit_place_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ class EditPlaceViewNotifier extends StateNotifier<EditPlaceState> {
void onSavePlace() async {
if (state.saving &&
state.updatedSetting == null &&
state.updatedPlace == null) return;
state.updatedPlace == null) {
return;
}

try {
state = state.copyWith(saving: true, error: null);
Expand Down
40 changes: 21 additions & 19 deletions app/lib/ui/flow/home/map/map_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:data/api/space/space_models.dart';
Expand All @@ -23,7 +24,8 @@ import 'map_view_model.dart';

const defaultCameraZoom = 15.0;
const defaultCameraZoomForSelectedUser = 17.0;
const markerSize = 124.0;
double markerSize = Platform.isAndroid ? 124.0 : 70.0;
double markerRadius = Platform.isAndroid ? 60.0 : 30.0;
const placeSize = 80;

class MapScreen extends ConsumerStatefulWidget {
Expand Down Expand Up @@ -125,7 +127,8 @@ class _MapScreenState extends ConsumerState<MapScreen> {
}
},
onDismiss: () => notifier.onDismissMemberDetail(),
currentUserLocation: state.currentUserLocation ?? const LatLng(0.0, 0.0),
currentUserLocation:
state.currentUserLocation ?? const LatLng(0.0, 0.0),
),
Visibility(visible: enabled, child: _permissionFooter(state))
],
Expand Down Expand Up @@ -375,11 +378,11 @@ class _MapScreenState extends ConsumerState<MapScreen> {
// Draw background rectangle
canvas.drawRRect(
RRect.fromRectAndCorners(
const Rect.fromLTWH(0.0, 0.0, markerSize, markerSize),
topLeft: const Radius.circular(60),
topRight: const Radius.circular(60),
Rect.fromLTWH(0.0, 0.0, markerSize, markerSize),
topLeft: Radius.circular(markerRadius),
topRight: Radius.circular(markerRadius),
bottomLeft: const Radius.circular(0),
bottomRight: const Radius.circular(60),
bottomRight: Radius.circular(markerRadius),
),
Paint()..color = markerBgColor,
);
Expand All @@ -399,12 +402,13 @@ class _MapScreenState extends ConsumerState<MapScreen> {
void _drawUserName(Canvas canvas, String userName, Color bgColor) {
final textPainter = TextPainter(textDirection: TextDirection.ltr);

canvas.drawCircle(const Offset(markerSize / 2, markerSize / 2), 50,
Paint()..color = bgColor);
canvas.drawCircle(Offset(markerSize / 2, markerSize / 2),
Platform.isAndroid ? 50 : 30, Paint()..color = bgColor);

textPainter.text = TextSpan(
text: userName.isNotEmpty ? userName[0] : '',
style: const TextStyle(fontSize: 70, color: Colors.white),
style: TextStyle(
fontSize: Platform.isAndroid ? 70 : 40, color: Colors.white),
);
textPainter.layout();
textPainter.paint(
Expand All @@ -426,19 +430,17 @@ class _MapScreenState extends ConsumerState<MapScreen> {
) async {
// Prepare the canvas to draw the rounded rectangle and the image
final recorder = ui.PictureRecorder();
final canvas = ui.Canvas(
recorder,
Rect.fromPoints(
const Offset(0, 0), const Offset(markerSize, markerSize)));
final canvas = ui.Canvas(recorder,
Rect.fromPoints(const Offset(0, 0), Offset(markerSize, markerSize)));

// Draw the rounded rectangle
canvas.drawRRect(
RRect.fromRectAndCorners(
const Rect.fromLTWH(0.0, 0.0, markerSize, markerSize),
topLeft: const Radius.circular(60),
topRight: const Radius.circular(60),
Rect.fromLTWH(0.0, 0.0, markerSize, markerSize),
topLeft: Radius.circular(markerRadius),
topRight: Radius.circular(markerRadius),
bottomLeft: const Radius.circular(0),
bottomRight: const Radius.circular(60),
bottomRight: Radius.circular(markerRadius),
),
Paint()..color = markerBgColor,
);
Expand Down Expand Up @@ -477,8 +479,8 @@ class _MapScreenState extends ConsumerState<MapScreen> {
setState(() {
_places.add(Circle(
circleId: CircleId(place.id),
fillColor: context.colorScheme.primary.withOpacity(0.4),
strokeColor: context.colorScheme.primary.withOpacity(0.6),
fillColor: context.colorScheme.primary.withAlpha((0.4 * 255).toInt()),
strokeColor: context.colorScheme.primary.withAlpha((0.6 * 255).toInt()),
strokeWidth: 1,
center: latLng,
radius: place.radius,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ class UserJourneyDetailViewModel extends StateNotifier<UserJourneyDetailState> {
final data = await rootBundle.load(assetPath);
final codec = await ui.instantiateImageCodec(
data.buffer.asUint8List(),
targetWidth: 200,
targetHeight: 200,
targetWidth: 100,
targetHeight: 100,
);
final frameInfo = await codec.getNextFrame();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,7 @@ class JourneyTimelineViewModel extends StateNotifier<JourneyTimelineState> {
final data = await rootBundle.load(assetPath);
final codec = await ui.instantiateImageCodec(
data.buffer.asUint8List(),
targetWidth: 38,
targetHeight: 54,
targetHeight: 24,
);
final frameInfo = await codec.getNextFrame();

Expand Down
158 changes: 158 additions & 0 deletions app/lib/ui/flow/setting/space/admin/change_admin_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import 'package:data/api/space/space_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:style/button/action_button.dart';
import 'package:style/extenstions/context_extenstions.dart';
import 'package:style/indicator/progress_indicator.dart';
import 'package:style/text/app_text_dart.dart';
import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart';
import 'package:yourspace_flutter/ui/components/app_page.dart';
import 'package:yourspace_flutter/ui/components/error_snakebar.dart';

import '../../../../components/no_internet_screen.dart';
import '../../../../components/profile_picture.dart';
import 'change_admin_view_model.dart';

class ChangeAdminScreen extends ConsumerStatefulWidget {
final SpaceInfo spaceInfo;

const ChangeAdminScreen({super.key, required this.spaceInfo});

@override
ConsumerState createState() => _ChangeAdminScreenState();
}

class _ChangeAdminScreenState extends ConsumerState<ChangeAdminScreen> {
late ChangAdminViewNotifier notifier;

@override
Widget build(BuildContext context) {
notifier = ref.watch(changeAdminViewStateProvider.notifier);
final state = ref.watch(changeAdminViewStateProvider);
_observePop();
_observeError();

return AppPage(
title: context.l10n.edit_space_change_admin_title,
actions: [
actionButton(
context: context,
icon: state.saving
? const AppProgressIndicator(size: AppProgressIndicatorSize.small)
: Icon(
Icons.check,
size: 24,
color: state.allowSave
? context.colorScheme.primary
: context.colorScheme.textDisabled,
),
onPressed: () {
_checkUserInternet(() {
if (state.allowSave) {
_checkUserInternet(() {
notifier.updateSpaceAdmin(widget.spaceInfo.space);
});
}
});
},
),
],
body: SafeArea(child: _body(context, state)),
);
}

Widget _body(BuildContext context, ChangeAdminViewState state) {
return _memberListItem(state);
}

Widget _memberListItem(ChangeAdminViewState state) {
final members = widget.spaceInfo.members
.where((member) => member.user.id != state.currentUserId)
.toList();

return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: members.length,
itemBuilder: (context, index) {
final user = members[index].user;
final isSelected = state.newAdminId == user.id;

return GestureDetector(
onTap: () => notifier.updateNewAdminId(user.id),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: ProfileImage(
profileImageUrl: user.profile_image!,
firstLetter: user.firstChar,
size: 40,
backgroundColor: context.colorScheme.primary,
),
title: Text(
user.fullName,
style: AppTextStyle.subtitle2.copyWith(
color: context.colorScheme.textPrimary,
),
),
trailing: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: isSelected ? context.colorScheme.positive : Colors.transparent,
shape: BoxShape.circle,
border: Border.all(color: isSelected ? context.colorScheme.positive : context.colorScheme.textPrimary)
),
child: isSelected
? const Icon(
Icons.check_rounded,
color: Colors.white,
size: 16,
)
: null,
),
),
if (index != members.length - 1) ...[
Padding(
padding: const EdgeInsets.only(left: 50),
child: Divider(
thickness: 1,
color: context.colorScheme.outline,
),
)
]
],
),
);
},
);
}

void _observePop() {
ref.listen(changeAdminViewStateProvider.select((state) => state.adminIdChanged),
(previous, next) {
if (next) {
context.pop();
}
});
}

void _observeError() {
ref.listen(changeAdminViewStateProvider.select((state) => state.error),
(previous, next) {
if (next != null) {
showErrorSnackBar(context, next.toString());
}
});
}

void _checkUserInternet(VoidCallback onCallback) async {
final isNetworkOff = await checkInternetConnectivity();
isNetworkOff ? _showSnackBar() : onCallback();
}

void _showSnackBar() {
showErrorSnackBar(context, context.l10n.on_internet_error_sub_title);
}
}
Loading

0 comments on commit 605e791

Please sign in to comment.