Skip to content

Commit

Permalink
feat: add community onboarding (wip) and privacy controls (also wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
Turtlepaw committed Nov 13, 2024
1 parent 8674e22 commit a998fe5
Show file tree
Hide file tree
Showing 10 changed files with 620 additions and 283 deletions.
43 changes: 3 additions & 40 deletions lib/components/communityJoin.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:fitness_challenges/components/userPreview.dart';
import 'package:fitness_challenges/routes/join.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_avatar/flutter_advanced_avatar.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -89,45 +89,8 @@ class _CommunityJoinDialogState extends State<CommunityJoinDialog> {
style: theme.textTheme.bodyLarge,
),
const SizedBox(height: 5),
IntrinsicWidth(
child: Card.outlined(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AdvancedAvatar(
name: pb.authStore.model?.getStringValue("username"),
style: theme.textTheme.titleMedium
?.copyWith(color: theme.colorScheme.onPrimary),
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(50),
),
size: 35,
),
const SizedBox(
width: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
(pb.authStore.model as RecordModel)
.getStringValue("username", "unknown"),
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
),
],
),
],
),
],
),
),
),
const IntrinsicWidth(
child: UserPreview(),
),
const SizedBox(height: 10),
Row(
Expand Down
116 changes: 116 additions & 0 deletions lib/components/privacy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:provider/provider.dart';
import 'package:reactive_forms/reactive_forms.dart';

const hideUsername = "hide_username";

class PrivacyControls extends StatefulWidget {
final void Function(PrivacyControl, bool)? onChanged;

const PrivacyControls({this.onChanged, super.key});

@override
_PrivacyControlsState createState() => _PrivacyControlsState();
}

enum PrivacyControl { hideUsernameInCommunity, hideUsernameInPrivateChallenges }

class _PrivacyControlsState extends State<PrivacyControls> {
final form = FormGroup({
PrivacyControl.hideUsernameInCommunity.name:
FormControl<bool>(validators: [Validators.required], value: false),
PrivacyControl.hideUsernameInPrivateChallenges.name:
FormControl<bool>(validators: [Validators.required], value: false),
});
late PocketBase pb;

@override
void initState() {
pb = Provider.of<PocketBase>(context, listen: false);
if(pb.authStore.model != null) setFormStates(pb.authStore.model);
setupRealtime();
super.initState();
}

void setupRealtime(){
if(pb.authStore.model != null) {
pb.collection("users").subscribe(pb.authStore.model.id, (model){
if(model.record != null) setFormStates(model.record!);
});
}
}

void setFormStates(RecordModel model){
setState(() {
form.control(PrivacyControl.hideUsernameInCommunity.name).value = model.getBoolValue(
PrivacyControl.hideUsernameInCommunity.name,
false
);
form.control(PrivacyControl.hideUsernameInPrivateChallenges.name).value = model.getBoolValue(
PrivacyControl.hideUsernameInPrivateChallenges.name,
false
);
});
}

@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: form,
child: Column(
children: [
buildPrivacyControl(
"Hide username in community",
"Display your user ID instead of your username in the community",
Symbols.disabled_visible_rounded,
form.control(PrivacyControl.hideUsernameInCommunity.name).value,
(value) => _updatePrivacyControl(
PrivacyControl.hideUsernameInCommunity, value)),
buildPrivacyControl(
"Hide username in private challenges",
"Display your user ID instead of your username in the invite only challenges",
Symbols.shield_lock_rounded,
form.control(PrivacyControl.hideUsernameInPrivateChallenges.name).value,
(value) => _updatePrivacyControl(
PrivacyControl.hideUsernameInPrivateChallenges, value)),
],
),
);
}

void _updatePrivacyControl(PrivacyControl control, bool value) {
value = !value;
if (widget.onChanged != null) {
widget.onChanged!(control, value);
}

pb.collection("users").update((pb.authStore.model as RecordModel).id,
body: {control.name: value});

setState(() {
form.control(control.name).value = value;
});
}

Widget buildPrivacyControl(String name, String description, IconData icon,
bool value, void Function(bool) onPressed) {
return SizedBox(
width: 410,
child: Card(
clipBehavior: Clip.hardEdge,
child: ListTile(
contentPadding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
leading: Icon(icon),
title: Text(name),
subtitle: Text(description),
onTap: () => onPressed(value), // Remove ! from !value
trailing: Switch(
value: value,
onChanged: (value) => onPressed(!value),
))),
);
}
}
63 changes: 63 additions & 0 deletions lib/components/userPreview.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'package:fitness_challenges/utils/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_avatar/flutter_advanced_avatar.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:provider/provider.dart';

String getUsernameFromUser(RecordModel user){
final hideUsername = user.getBoolValue("hideUsernameInCommunity", false);
if(hideUsername){
return "User ${user.id}";
} else {
return user.getStringValue("username", "User ${user.id}");
}
}

class UserPreview extends StatelessWidget {
final bool forceRandomUsername;

const UserPreview({super.key, this.forceRandomUsername = false});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final pb = Provider.of<PocketBase>(context, listen: false);
return Card.outlined(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AdvancedAvatar(
name: getUsernameFromUser(pb.authStore.model!),
style: theme.textTheme.titleMedium
?.copyWith(color: theme.colorScheme.onPrimary),
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(50),
),
size: 35,
),
const SizedBox(
width: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
trimString(getUsernameFromUser(pb.authStore.model!), 15),
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
),
],
),
],
),
],
),
),
);
}
}
3 changes: 2 additions & 1 deletion lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ const types = [

var permissions = types.map((type) => HealthDataAccess.READ).toList();

final pbDateFormat = DateFormat('yyyy-MM-dd HH:mm:ss');
final pbDateFormat = DateFormat('yyyy-MM-dd HH:mm:ss');
const MAX_USERNAME_LENGTH = 30;
3 changes: 2 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,10 @@ void main() async {
frequency: const Duration(hours: 1),
initialDelay: const Duration(minutes: 15));
logger.debug("Background sync registered");
if (pb.authStore.isValid)
if (pb.authStore.isValid) {
Workmanager().registerOneOffTask(
"background-sync-one-time", "BackgroundSyncOneTime");
}

if (kReleaseMode) {
debugPrint = (String? message, {int? wrapWidth}) {
Expand Down
14 changes: 4 additions & 10 deletions lib/routes/challenge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
import 'package:confetti/confetti.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fitness_challenges/components/loader.dart';
import 'package:fitness_challenges/constants.dart';
import 'package:fitness_challenges/routes/challenges/bingo.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_avatar/flutter_advanced_avatar.dart';
Expand Down Expand Up @@ -330,14 +331,6 @@ class _ChallengeDialogState extends State<ChallengeDialog> {
);
}

String trimString(String input, int maxCharacters) {
if (input.length <= maxCharacters) {
return input;
} else {
return '${input.substring(0, maxCharacters - 3)}...';
}
}

Path drawStar(Size size) {
// Method to convert degree to radians
double degToRad(double deg) => deg * (pi / 180.0);
Expand Down Expand Up @@ -396,7 +389,8 @@ class _ChallengeDialogState extends State<ChallengeDialog> {
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 1),
child: Text(
user.getStringValue("username", "Unknown"),
trimString(user.getStringValue("username", "Unknown"),
MAX_USERNAME_LENGTH),
// Substitute for user.getStringValue("username")
style: theme.textTheme.headlineSmall,
),
Expand Down Expand Up @@ -584,7 +578,7 @@ class _ChallengeDialogState extends State<ChallengeDialog> {

return LayoutBuilder(
builder: (context, constraints) {
final maxUsernameLength = (constraints.maxWidth / 30).floor();
final maxUsernameLength = (constraints.maxWidth / MAX_USERNAME_LENGTH).floor();

return Container(
padding: const EdgeInsets.all(10.0),
Expand Down
Loading

0 comments on commit a998fe5

Please sign in to comment.