Skip to content

Commit

Permalink
feat: add winning to bingo and add invite deep links [2/2]
Browse files Browse the repository at this point in the history
  • Loading branch information
Turtlepaw committed Dec 10, 2024
1 parent eb6e3e2 commit 4bd69b3
Show file tree
Hide file tree
Showing 23 changed files with 639 additions and 561 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<div align="center">

<img src="./images/icon_rounded.png" width="100"/>

# Fitness Challenges

#### Compete in challenges with friends and family.

</div>

### 📸 Preview Images

| | | | |
Expand All @@ -14,18 +20,25 @@
This app is built with [Flutter](https://flutter.dev/), if you don't have flutter setup, flutter has a guide on [how to install and setup flutter on your desktop](https://docs.flutter.dev/get-started/install).

#### 🔍 Tools for developing

- [ ] JDK 21
- [ ] Android Studio
- [ ] Flutter SDK

#### ⌚ Wear OS
## ⌚ Wear OS

<img src="./images/Wear_Screenshot (with shell).png" width="200" alt="Wear OS Preview"/>

The app includes a native Wear OS companion app for users to view challenges located in [`/wear-os`](./wear-os).

> ![NOTE]
> The Wear OS app is moving away from syncing health data and towards displaying challenges
> ![WARNING]
> The Wear OS app is currently on pause
### 💻 Creating a Pocketbase instance

See [Pocketbase's documentation](https://pocketbase.io/docs/) to learn how to setup pocketbase locally. All hooks are located in `/pb_hooks`, you will need to add them to your pocketbase instance for developing. [(learn more)](https://pocketbase.io/docs/js-overview/)

If you need to deploy a online instance, you can create a free virtual machine using Oracle and follow [my guide](https://gist.github.com/Turtlepaw/107bf7470c94bed187db5aee6a432f3d) on how to setup pocketbase and deploy it on NGINX with Let's Encrypt.
If you need to deploy a online instance, you can create a free virtual machine using Oracle and follow [my guide](https://gist.github.com/Turtlepaw/107bf7470c94bed187db5aee6a432f3d) on how to setup pocketbase and deploy it on NGINX with Let's Encrypt.
2 changes: 1 addition & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="fitnesschallenges.vercel.app" />
<data android:scheme="https" />
<data android:scheme="https" android:host="fitnesschallenges.vercel.app" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
Expand Down
3 changes: 2 additions & 1 deletion iOS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Adding iOS support

## Libraries
- https://pub.dev/packages/permission_handler#setup
- https://pub.dev/packages/permission_handler#setup
- Health
33 changes: 24 additions & 9 deletions lib/components/challenge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,7 @@ class _ChallengeState extends State<Challenge> {
children: _challenge.expand["users"]!.map((user) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: AdvancedAvatar(
name: getUsernameFromUser(user),
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.onPrimary),
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(50),
),
),
child: Avatar(user: user),
);
}).toList(),
),
Expand All @@ -179,3 +171,26 @@ class _ChallengeState extends State<Challenge> {
}

}

class Avatar extends StatelessWidget {
final RecordModel user;
final double size;
const Avatar({super.key, required this.user, this.size = 40});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

return AdvancedAvatar(
size: size,
name: getUsernameFromUser(user),
autoTextSize: true,
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.onPrimary),
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(50),
),
);
}
}
3 changes: 2 additions & 1 deletion lib/components/communityChallenge.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:collection/collection.dart';
import 'package:fitness_challenges/components/communityJoin.dart';
import 'package:fitness_challenges/components/userPreview.dart';
import 'package:fitness_challenges/types/challenges.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_avatar/flutter_advanced_avatar.dart';
Expand Down Expand Up @@ -186,7 +187,7 @@ class _CommunityChallengeState extends State<CommunityChallenge> {
List<String> userListFromChallenge(RecordModel challenge,
{bool debugMode = false, int limit = 5}) {
var items = challenge.expand["users"]!
.map((user) => user.getStringValue("username"))
.map((user) => getUsernameFromUser(user))
.toList(growable: true);
if (debugMode) {
items = List.filled(10, "User", growable: true);
Expand Down
157 changes: 53 additions & 104 deletions lib/components/dialog/userDialog.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import 'package:fitness_challenges/components/challenge.dart';
import 'package:fitness_challenges/components/userPreview.dart';
import 'package:fitness_challenges/types/collections.dart';
import 'package:fitness_challenges/utils/common.dart';
import 'package:fitness_challenges/utils/data_source_manager.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:pocketbase/pocketbase.dart';

import '../../utils/manager.dart';

class UserDialog extends StatefulWidget {
final PocketBase pb;
final RecordModel challenge;
final RecordModel user;

const UserDialog({super.key, required this.pb, required this.challenge});
const UserDialog({super.key, required this.pb, required this.user});

@override
_UserDialogState createState() => _UserDialogState();
Expand All @@ -22,109 +19,61 @@ class _UserDialogState extends State<UserDialog> {

@override
Widget build(BuildContext context) {
final challenge = widget.challenge;
final user = widget.user;
var theme = Theme.of(context);
var dataSourceManager = DataSourceManager.fromChallenge(
challenge
);
print(dataSourceManager.users);
final badges = user.getListValue("badges").length;

return Dialog(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 5, left: 10, bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Users",
style: theme.textTheme.headlineSmall,
),
],
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(onPressed: (){
Navigator.of(context).pop();
}, icon: const Icon(Icons.close))
],
),
),
),
const SizedBox(height: 5),
Flexible(
child: ListView(
shrinkWrap: true, // Ensures the ListView takes only the necessary space
children: [
...challenge.expand["users"]!.map((user) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 15),
child: Row(
children: [
Text(
trimString(getUsernameFromUser(user), 15),
style: theme.textTheme.titleLarge,
),
SizedBox(width: 15,),
if(dataSourceManager.getUser(user.id) != null) Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1000),
color: Colors.white
),
padding: EdgeInsets.all(5),
child: Image.asset(
getSourceAsset(dataSourceManager.getUser(user.id)!),
//scale: 8.5,
width: 25,
height: 25,
),
)
],
const SizedBox(height: 5),
Flexible(
child: ListView(
shrinkWrap: true,
// Ensures the ListView takes only the necessary space
children: [
Avatar(user: user, size: 55,),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Text(
getUsernameFromUser(user),
style: theme.textTheme.headlineMedium,
textAlign: TextAlign.center, // Aligns text in the center horizontally
softWrap: true, // Ensures text wraps
overflow: TextOverflow.clip, // Prevents overflowing
),
),
),
TextButton(onPressed: user.id == challenge.getDataValue("host") ? null : () async {
final id = user.id;
final data = Manager.fromChallenge(challenge)
.removeUser(id)
.toJson();
await widget.pb
.collection(Collection.challenges)
.update(challenge.id,
body: {"users-": id, "data": data});

if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Kicked ${user.getStringValue("username")}'),
),
);
}
}, child: Text(user.id == challenge.getDataValue("host") ? "Host" :"Kick"))
],
))
],
),
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FilledButton(
onPressed: _handleClose,
child: const Text("Close"),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Symbols.award_star_rounded, color: theme.colorScheme.onSurfaceVariant, size: 20),
const SizedBox(width: 5),
Text("${badges > 0 ? "${badges} badge${badges > 1 ? "s" : ""}" : "No badges yet"}", textAlign: TextAlign.center, style: theme.textTheme.bodyLarge)
],
)
],
),
],
),
],
),
),
);
}

String getSourceAsset(DataSourceEntry entry){
if(entry.source == DataSource.healthConnect){
return "images/health_connect.png";
} else {
return "images/wear_os.png";
}
),
const SizedBox(height: 20),
])));
}

void _handleClose() {
Expand Down
Loading

0 comments on commit 4bd69b3

Please sign in to comment.