diff --git a/Wordle+/django/djangoproject/settings.py b/Wordle+/django/djangoproject/settings.py index e1e7784..896371c 100644 --- a/Wordle+/django/djangoproject/settings.py +++ b/Wordle+/django/djangoproject/settings.py @@ -55,16 +55,15 @@ } MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.security.SecurityMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.common.CommonMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'djapi.token_expire.TokenExpirationMiddleware' ] diff --git a/Wordle+/django/djangoproject/urls.py b/Wordle+/django/djangoproject/urls.py index 75660a3..cbd7879 100644 --- a/Wordle+/django/djangoproject/urls.py +++ b/Wordle+/django/djangoproject/urls.py @@ -27,6 +27,7 @@ router.register(r'api/groups', views.GroupViewSet) router.register(r'api/classicwordles', views.ClassicWordleViewSet) router.register(r'api/notifications', views.NotificationsViewSet) +router.register(r'api/games', views.GameViewSet) router.register('api/friendlist', FriendListViewSet, basename='friendlist') router.register('api/friendrequest', FriendRequestViewSet, basename='friendrequest') @@ -46,4 +47,4 @@ path('api/list-players/', PlayerListAPIView.as_view(), name='player-list'), -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/Wordle+/django/djapi/admin.py b/Wordle+/django/djapi/admin.py index b49af2f..b5d6805 100644 --- a/Wordle+/django/djapi/admin.py +++ b/Wordle+/django/djapi/admin.py @@ -6,7 +6,7 @@ from django import forms from django.core.validators import MaxValueValidator -from .models import CustomUser, Player, StaffCode, ClassicWordle, Notification, Tournament, Participation, FriendList, FriendRequest +from .models import CustomUser, Player, StaffCode, ClassicWordle, Notification, Tournament, Participation, FriendList, FriendRequest, Game class CustomUserAdmin(UserAdmin): model = CustomUser @@ -174,6 +174,10 @@ class FriendListAdmin(admin.ModelAdmin): class FriendRequestAdmin(admin.ModelAdmin): list_display = ('id', 'sender', 'receiver',) +class GameAdmin(admin.ModelAdmin): + list_display = ('id', 'player1', 'player2', 'winner', 'word',) + +admin.site.register(Game, GameAdmin) admin.site.register(FriendRequest, FriendRequestAdmin) admin.site.register(FriendList, FriendListAdmin) admin.site.register(Participation, ParticipationAdmin) diff --git a/Wordle+/django/djapi/models.py b/Wordle+/django/djapi/models.py index 5265ef7..a5e4930 100644 --- a/Wordle+/django/djapi/models.py +++ b/Wordle+/django/djapi/models.py @@ -120,7 +120,23 @@ def clean(self): def __str__(self): return f"{self.sender.user.username} - {self.receiver.user.username}" - + +class Game(models.Model): + player1 = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='player1_wordle') + player2 = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='player2_wordle') + word = models.CharField(max_length=255) + player1_time = models.PositiveIntegerField(default=0) + player1_attempts = models.PositiveIntegerField(default=0) + player1_xp = models.PositiveIntegerField(default=0) + player2_time = models.PositiveIntegerField(default=0) + player2_attempts = models.PositiveIntegerField(default=0) + player2_xp = models.PositiveIntegerField(default=0) + winner = models.ForeignKey(Player, on_delete=models.SET_NULL, null=True, blank=True, related_name='winner') + timestamp = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.player1.user.username} - {self.player2.user.username}" + # Method to add the 'Staff' group automatically when creating an administrator @receiver(post_save, sender=CustomUser) def assign_permissions(sender, instance, created, **kwargs): diff --git a/Wordle+/django/djapi/serializers.py b/Wordle+/django/djapi/serializers.py index a4b44d0..558732f 100644 --- a/Wordle+/django/djapi/serializers.py +++ b/Wordle+/django/djapi/serializers.py @@ -1,5 +1,5 @@ from django.contrib.auth.models import Group -from .models import CustomUser, Player, StaffCode, ClassicWordle, Notification, Tournament, Participation, FriendList, FriendRequest +from .models import CustomUser, Player, StaffCode, ClassicWordle, Notification, Tournament, Participation, FriendList, FriendRequest, Game from rest_framework import serializers from django.contrib.auth.hashers import make_password @@ -167,6 +167,34 @@ class Meta: def get_sender(self, obj): return {'username': obj.sender.user.username, 'id_player': obj.sender.user.player.id} +class GameDetailSerializer(serializers.ModelSerializer): + player1 = serializers.SerializerMethodField() + player2 = serializers.SerializerMethodField() + + class Meta: + model = Game + fields = ['id', 'player1', 'player2', 'player1_time', 'player2_time', 'player1_xp', + 'player2_xp', 'timestamp', 'word', 'player1_attempts', 'player2_attempts', + 'winner'] + + def get_player1(self, obj): + return obj.player1.user.username + + def get_player2(self, obj): + return obj.player2.user.username + + +class GameCreateSerializer(serializers.ModelSerializer): + player2 = serializers.SerializerMethodField() + class Meta: + model = Game + fields = ['id', 'player2', 'player1_time', 'player2_time', 'player1_xp', + 'player2_xp', 'timestamp', 'word', 'player1_attempts', 'player2_attempts', + 'winner'] + + def get_player2(self, obj): + return obj.player2.user.username + class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group diff --git a/Wordle+/django/djapi/views.py b/Wordle+/django/djapi/views.py index 43fc584..a4f7e59 100644 --- a/Wordle+/django/djapi/views.py +++ b/Wordle+/django/djapi/views.py @@ -418,7 +418,7 @@ def create(self, request, *args, **kwargs): serializer = FriendRequestSerializer(friend_request) return Response(serializer.data, status=status.HTTP_201_CREATED) - + @action(detail=True, methods=['post']) def accept(self, request, *args, **kwargs): instance = self.get_object() @@ -456,4 +456,139 @@ def reject(self, request, *args, **kwargs): return Response({'error': 'Permission denied'}, status=403) instance.delete() - return Response({'message': 'Friend request rejected'}, status=200) \ No newline at end of file + return Response({'message': 'Friend request rejected'}, status=200) + +class GameViewSet(viewsets.ModelViewSet): + queryset = Game.objects.all() + serializer_class = GameCreateSerializer + permission_classes = [permissions.IsAuthenticated] + http_method_names = ['get', 'post', 'patch'] + + def get_serializer_class(self): + if self.action in ['list', 'retrieve', 'completed_games', 'pending_games']: + return GameDetailSerializer + elif self.action in ['create', 'partial_update']: + return GameCreateSerializer + return super().get_serializer_class() + + # Completed games are those which the winner is not null + @action(detail=False, methods=['get']) + def completed_games(self, request): + player = getattr(request.user, 'player', None) + if not player: + return Response({'error': 'Player not found'}, status=404) + + queryset = Game.objects.filter(Q(player1=player) | Q(player2=player), ~Q(winner=None)).order_by('timestamp') + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + + # Pending games are those which the winner is null and the player is the receiver (player2) + @action(detail=False, methods=['get']) + def pending_games(self, request): + player = getattr(request.user, 'player', None) + if not player: + return Response({'error': 'Player not found'}, status=404) + queryset = Game.objects.filter(player2=player, winner=None).order_by('timestamp') + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + player = getattr(request.user, 'player', None) + if not player: + return Response({'error': 'Player not found'}, status=404) + + if player not in [instance.player1, instance.player2]: + return Response({'error': 'You can not access to this game.'}, status=403) + if instance.winner is not None: + return Response({'error': 'This game is already completed and cannot be modified.'}, status=403) + + serializer = self.get_serializer(instance) + data = serializer.data + + return Response(data) + + def create(self, request, *args, **kwargs): + player1 = getattr(request.user, 'player', None) + if not player1: + return Response({'error': 'Player1 not found'}, status=404) + player2_id = request.data.get('player2') + + if not player2_id: + return Response({'error': 'player2 parameter is required'}, status=400) + + try: + player2 = Player.objects.get(id=player2_id) + except Player.DoesNotExist: + return Response({'error': 'Player2 not found'}, status=status.HTTP_404_NOT_FOUND) + + allowed_fields = ['player2', 'player1_xp', 'player1_time', 'player1_attempts', 'word'] + data = {key: request.data.get(key) for key in allowed_fields} + + if 'winner' in data: + return Response({'error': 'The "winner" field cannot be modified.'}, status=400) + + + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + + # Increment player XP + player1.xp += serializer.validated_data['player1_xp'] + player1.save() + + serializer.save(player1=player1, player2=player2) + + # Create notification to the guest player + Notification.objects.create( + player=player2, + text=f"You have been challenged by {player1.user.username}. Let's play!", + link='' + ) + + return Response(serializer.data, status=201) + + def partial_update(self, request, *args, **kwargs): + instance = self.get_object() + player = request.user.player + + if instance.player2 != player: + return Response({'error': 'You do not have permission to update this game.'}, status=403) + if instance.winner is not None: + return Response({'error': 'This game is already completed and cannot be modified.'}, status=400) + + allowed_fields = ['player2_xp', 'player2_time', 'player2_attempts'] + data = {key: request.data.get(key) for key in allowed_fields} + + if 'winner' in data: + return Response({'error': 'The "winner" field cannot be modified.'}, status=400) + + player2_xp = request.data.get('player2_xp') + player1_xp = instance.player1_xp + player2_time = request.data.get('player2_time') + player1_time = instance.player1_time + + if player2_xp is not None: + player1_xp = instance.player1_xp + if player2_xp > player1_xp: + instance.winner = instance.player2 + instance.winner.wins_pvp += 1 + instance.winner.save() + elif player2_xp < player1_xp: + instance.winner = instance.player1 + instance.winner.wins_pvp += 1 + instance.winner.save() + else: + if player2_time <= player1_time: + instance.winner = instance.player2 + instance.winner.wins_pvp += 1 + instance.winner.save() + else: + instance.winner = instance.player1 + instance.winner.wins_pvp += 1 + instance.winner.save() + + serializer = self.get_serializer(instance, data=data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + + return Response({'winner': instance.winner.user.username}) \ No newline at end of file diff --git a/Wordle+/ionic/ionic-app/src/app/app-routing.module.ts b/Wordle+/ionic/ionic-app/src/app/app-routing.module.ts index 42f65e0..000fa56 100644 --- a/Wordle+/ionic/ionic-app/src/app/app-routing.module.ts +++ b/Wordle+/ionic/ionic-app/src/app/app-routing.module.ts @@ -41,10 +41,21 @@ const routes: Routes = [ loadChildren: () => import('./pages/friendlist/friendlist.module').then( m => m.FriendlistPageModule), canActivate: [AuthGuard] }, - - - - + { + path: 'create-game', + loadChildren: () => import('./pages/create-game/create-game.module').then( m => m.CreateGamePageModule), + canActivate: [AuthGuard] + }, + { + path: 'respond-game', + loadChildren: () => import('./pages/respond-game/respond-game.module').then( m => m.RespondGamePageModule), + canActivate: [AuthGuard] + }, + { + path: 'history', + loadChildren: () => import('./pages/history/history.module').then( m => m.HistoryPageModule), + canActivate: [AuthGuard] + }, ]; @NgModule({ diff --git a/Wordle+/ionic/ionic-app/src/app/components/wordle-dashboard/wordle-dashboard.component.ts b/Wordle+/ionic/ionic-app/src/app/components/wordle-dashboard/wordle-dashboard.component.ts index 0f3c187..b8e14a6 100644 --- a/Wordle+/ionic/ionic-app/src/app/components/wordle-dashboard/wordle-dashboard.component.ts +++ b/Wordle+/ionic/ionic-app/src/app/components/wordle-dashboard/wordle-dashboard.component.ts @@ -1,5 +1,4 @@ -import { Component, HostListener, Input, OnInit } from '@angular/core'; -import { ToastController } from '@ionic/angular'; +import { Component, HostListener, Input, OnInit, Output, EventEmitter } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { StorageService } from 'src/app/services/storage.service'; import { ApiService } from 'src/app/services/api.service'; @@ -19,6 +18,10 @@ interface LetterBox { styleUrls: ['./wordle-dashboard.component.scss'], }) export class WordleDashboardComponent implements OnInit { + // For 1vs1 games + @Output() gameFinished: EventEmitter = new EventEmitter(); + @Input() isMultiplayer: boolean; + public letterRows: LetterBox[][]; public keyboardLetters: string[]; public firstRow: string[] = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p']; @@ -28,7 +31,7 @@ export class WordleDashboardComponent implements OnInit { private readonly MAX_GUESSES = 6; @Input() WORDS_LENGTH: number; private wordsOfDesiredLength: string[]; - private rightGuessString: string; + @Input() rightGuessString?: string; private guessesRemaining: number; private currentGuess: string[]; private nextLetter: number; @@ -79,7 +82,10 @@ export class WordleDashboardComponent implements OnInit { throw new Error(`No words found for length ${this.WORDS_LENGTH}`); } - this.rightGuessString = this.selectRandomWordByLength(this.WORDS_LENGTH); + // If it has not a value, then it has not been passed by parameter + if (!this.rightGuessString) { + this.rightGuessString = this.selectRandomWordByLength(this.WORDS_LENGTH); + } }, (error) => { throw new Error(`Failed to load words data: ${error.message}`); @@ -200,33 +206,40 @@ export class WordleDashboardComponent implements OnInit { win: won, xp_gained: xP, }; - - (await - // API call - this.apiService.addClassicGame(body)).subscribe( - (response) => { - console.log('Game added successfully', response); - }, - (error) => { - console.log('Game could not be added', error); - }); - this.storageService.incrementXP(xP); - this.notificationService.addNotification({'text': 'Well done!'}); - - if (won) { - setTimeout(() => { - this.toastService.showToast(`You won! You gained ${xP}`, 3000, 'top'); - this.storageService.incrementWins(); - - }, 250 * this.WORDS_LENGTH + 3000); + // Case of multiplayer game + if (this.isMultiplayer) { + const gameFinishedEvent = { + time: timeConsumed, + xp: xP, + attempts: attempsConsumed, + selectedWord: this.rightGuessString, + }; + this.gameFinished.emit(gameFinishedEvent); } else { + // Case of classic game + (await this.apiService.addClassicGame(body)).subscribe( + (response) => { + console.log('Game added successfully', response); + }, + (error) => { + console.log('Game could not be added', error); + }); + this.storageService.incrementXP(xP); + + if (won) { + setTimeout(() => { + this.toastService.showToast(`You won! You gained ${xP}`, 3000, 'top', 'success'); + this.storageService.incrementWins(); + }, 250 * this.WORDS_LENGTH + 3000); + } else { + setTimeout(() => { + this.toastService.showToast(`You lost! You gained ${xP}`, 3000, 'top', 'warning'); + }, 250 * this.WORDS_LENGTH + 3000); + } setTimeout(() => { - this.toastService.showToast(`You lost! You gained ${xP}`, 3000, 'top'); - }, 250 * this.WORDS_LENGTH + 3000); + this.router.navigate(['/tabs/main'], { queryParams: { refresh: 'true' } }); + }, 3000) } - setTimeout(() => { - this.router.navigate(['/tabs/main'], { queryParams: { refresh: 'true' } }); - }, 3000) } } diff --git a/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.html b/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.html index 2586496..f06e162 100644 --- a/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.html +++ b/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.html @@ -1,7 +1,7 @@ - {{ length }} letters + {{ length }} letters diff --git a/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.ts b/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.ts index 48835b4..82d9e45 100644 --- a/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.ts +++ b/Wordle+/ionic/ionic-app/src/app/components/words-popover/words-popover.component.ts @@ -1,4 +1,5 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; @Component({ selector: 'app-words-popover', @@ -6,10 +7,19 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./words-popover.component.scss'], }) export class WordsPopoverComponent implements OnInit { + @Input() playerId?: number; + @Input() username?: string; wordLengths = [4,5,6,7,8]; - constructor() { } + constructor(private router: Router) { } ngOnInit() {} + navigateToGame(length: number) { + if (this.playerId) { + this.router.navigate(['/create-game'], { queryParams: {length: length, opponentId: this.playerId, opponentUsername: this.username} }); + } else { + this.router.navigate([`/classic-wordle/${length}`]); + } + } } diff --git a/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.module.ts index 41bee92..6b5c437 100644 --- a/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.module.ts +++ b/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.module.ts @@ -4,8 +4,7 @@ import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { IonicModule } from '@ionic/angular'; -import { WordleDashboardComponent } from 'src/app/components/wordle-dashboard/wordle-dashboard.component'; - +import { WordleDashboardModule } from 'src/app/wordle-dashboard/wordle-dashboard.module'; import { ClassicWordlePageRoutingModule } from './classic-wordle-routing.module'; import { ApiService } from 'src/app/services/api.service'; import { ClassicWordlePage } from './classic-wordle.page'; @@ -16,9 +15,10 @@ import { ClassicWordlePage } from './classic-wordle.page'; FormsModule, IonicModule, HttpClientModule, - ClassicWordlePageRoutingModule + ClassicWordlePageRoutingModule, + WordleDashboardModule ], - declarations: [ClassicWordlePage, WordleDashboardComponent], + declarations: [ClassicWordlePage], providers: [ApiService] }) diff --git a/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.page.html b/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.page.html index 29279ec..b0fab6b 100644 --- a/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.page.html +++ b/Wordle+/ionic/ionic-app/src/app/pages/classic-wordle/classic-wordle.page.html @@ -7,5 +7,5 @@ - + diff --git a/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game-routing.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game-routing.module.ts new file mode 100644 index 0000000..dcc6d2e --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { CreateGamePage } from './create-game.page'; + +const routes: Routes = [ + { + path: '', + component: CreateGamePage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class GamePageRoutingModule {} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.module.ts new file mode 100644 index 0000000..9ff9a77 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { GamePageRoutingModule } from './create-game-routing.module'; + +import { CreateGamePage } from './create-game.page'; +import { WordleDashboardModule } from 'src/app/wordle-dashboard/wordle-dashboard.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + GamePageRoutingModule, + WordleDashboardModule + ], + declarations: [CreateGamePage] +}) +export class CreateGamePageModule {} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.html b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.html new file mode 100644 index 0000000..cec52cc --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.html @@ -0,0 +1,18 @@ + + + Game 1vs1 + + + + +
+
{{ selfUsername }}
+ VS icon +
{{ opponentUsername }}
+
+ + + + +
+ \ No newline at end of file diff --git a/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.scss b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.scss new file mode 100644 index 0000000..9b4c37a --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.scss @@ -0,0 +1,29 @@ +ion-content { + display: flex; + flex-direction: column; + align-items: center; + } + + .game-info { + display: flex; + align-items: center; + margin-bottom: 20px; + justify-content: center; + margin-top: 1.5vh; + margin-bottom: 0px; + font-size: calc(2.5vh + 1.5vw); + text-shadow: 2px 2px 0px #957dad, + + + } + + .game-info img { + height: calc(4vh + 1vw); + margin-right: 0.8vw; + margin-left: 0.8vw; + } + + .username { + font-weight: bold; + } + \ No newline at end of file diff --git a/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.spec.ts b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.spec.ts new file mode 100644 index 0000000..fc497a5 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.spec.ts @@ -0,0 +1,17 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CreateGamePage } from './create-game.page'; + +describe('GamePage', () => { + let component: CreateGamePage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + fixture = TestBed.createComponent(CreateGamePage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.ts b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.ts new file mode 100644 index 0000000..d93b4b1 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/create-game/create-game.page.ts @@ -0,0 +1,51 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ApiService } from 'src/app/services/api.service'; +import { ToastService } from 'src/app/services/toast.service'; +import { Router } from '@angular/router'; +import { StorageService } from 'src/app/services/storage.service'; + +@Component({ + selector: 'app-create-game', + templateUrl: './create-game.page.html', + styleUrls: ['./create-game.page.scss'], +}) +export class CreateGamePage implements OnInit { + opponentId: number; + opponentUsername: string; + selfUsername: string; + wordLength: number; + + constructor(private route: ActivatedRoute, private apiService: ApiService, + private toastService: ToastService, private router: Router, private storageService: StorageService) {} + + async ngOnInit() { + this.selfUsername = await this.storageService.getUsername(); + this.route.queryParams.subscribe((params) => { + this.opponentId = params['opponentId']; + this.opponentUsername = params['opponentUsername']; + this.wordLength = parseInt(params['length'],10); + }); + } + + async finishGame(time: number, xp: number, attempts: number, selectedWord: string) { + const gameData = { + player2: this.opponentId, + player1_time: time, + player1_xp: xp, + player1_attempts: attempts, + word: selectedWord, + }; + + (await this.apiService.createGame(gameData)).subscribe( + (response) => { + console.log('Game registered successfully', response); + this.toastService.showToast('Game registered successfully! Who will win?', 2000, 'top', 'success'); + setTimeout(() =>this.router.navigate(['/tabs/main'], { queryParams: { refresh: 'true' } }), 2500); + }, + (error) => { + console.error('Error creating game:', error); + } + ); + } +} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/edit-user/edit-user.page.ts b/Wordle+/ionic/ionic-app/src/app/pages/edit-user/edit-user.page.ts index 1c2cf85..a7baccb 100644 --- a/Wordle+/ionic/ionic-app/src/app/pages/edit-user/edit-user.page.ts +++ b/Wordle+/ionic/ionic-app/src/app/pages/edit-user/edit-user.page.ts @@ -82,7 +82,6 @@ export class EditUserPage implements OnInit { const reader = new FileReader(); reader.onloadend = () => { const avatarData = reader.result as string; - console.log(avatarData); this.saveAvatar(avatarData); }; reader.readAsDataURL(file); diff --git a/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.module.ts index 881d8db..85a4209 100644 --- a/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.module.ts +++ b/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; - +import { WordsPopoverComponent } from 'src/app/components/words-popover/words-popover.component'; import { IonicModule } from '@ionic/angular'; import { FriendlistPageRoutingModule } from './friendlist-routing.module'; diff --git a/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.html b/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.html index 2c9832c..dc377c8 100644 --- a/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.html +++ b/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.html @@ -27,7 +27,7 @@ Play Wordle - + @@ -68,7 +68,7 @@ {{ player.username }} - + diff --git a/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.ts b/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.ts index 735e219..57037d4 100644 --- a/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.ts +++ b/Wordle+/ionic/ionic-app/src/app/pages/friendlist/friendlist.page.ts @@ -1,7 +1,9 @@ import { Component, OnInit } from '@angular/core'; -import { ToastController } from '@ionic/angular'; import { ApiService } from 'src/app/services/api.service'; import { ToastService } from 'src/app/services/toast.service'; +import { PopoverController } from '@ionic/angular'; +import { WordsPopoverComponent } from 'src/app/components/words-popover/words-popover.component'; + @Component({ selector: 'app-friendlist', @@ -17,7 +19,8 @@ export class FriendlistPage implements OnInit { selectedSegment: string; showResults: boolean = false; - constructor(private apiService: ApiService, private toastService: ToastService) {} + constructor(private apiService: ApiService, private toastService: ToastService, + private popoverController: PopoverController) {} ngOnInit() { this.getAllPlayers(); @@ -28,6 +31,21 @@ export class FriendlistPage implements OnInit { this.loadFriendList(); } + // Popover of word length selection + async handleSelectionPopover(event: any, playerId: number, username: string) { + const popover = await this.popoverController.create({ + component: WordsPopoverComponent, + event: event, + dismissOnSelect: true, + componentProps: { + playerId: playerId, + username: username + } + }); + + await popover.present(); + } + // Method that gets all the players of the backend. Only the usernames async getAllPlayers() { (await this.apiService.getAllPlayers()).subscribe( @@ -43,10 +61,8 @@ export class FriendlistPage implements OnInit { // Method that send a friend request when clicking the add button async sendFriendRequest(playerId: number) { this.showResults = false; - console.log(playerId); (await this.apiService.sendFriendRequest(playerId)).subscribe( async (response) => { - console.log('Friend request sent successfully', response); this.toastService.showToast("Request was sent successfully!", 2000, 'top', 'success'); }, async (error) => { diff --git a/Wordle+/ionic/ionic-app/src/app/pages/history/history-routing.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/history/history-routing.module.ts new file mode 100644 index 0000000..17416de --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/history/history-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { HistoryPage } from './history.page'; + +const routes: Routes = [ + { + path: '', + component: HistoryPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class HistoryPageRoutingModule {} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/history/history.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/history/history.module.ts new file mode 100644 index 0000000..238e31c --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/history/history.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { HistoryPageRoutingModule } from './history-routing.module'; + +import { HistoryPage } from './history.page'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + HistoryPageRoutingModule + ], + declarations: [HistoryPage] +}) +export class HistoryPageModule {} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.html b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.html new file mode 100644 index 0000000..384f9e8 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.html @@ -0,0 +1,66 @@ + + + + History + + + + + + Classics + + + Completed PvP + + + Pending PvP + + + + + + +
+ +
+ +
+ +
+ +
+
+ + + + {{ game.player1 }} + + + + + + + Time + {{ game.player1_time }}" + + + + Word length + {{ game.word.length }} letters + + + + Play + + + +
+
+ + + No pending PvP games here! + + +
+
+
\ No newline at end of file diff --git a/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.scss b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.scss new file mode 100644 index 0000000..7ab87e4 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.scss @@ -0,0 +1,31 @@ +ion-card-header { + font-weight: bolder; + font-size: calc(2vh + 2vw); + padding-bottom: 5px; + display: flex; + align-content: space-between; + justify-content: space-between; + align-items: center; + align-content: center; + flex-direction: row; + } + + ion-icon { + color: var(--ion-color-purple); + } + + ion-list ion-item { + font-size: calc(1.5vh + 1vw); + } + +.play-button{ + margin-left: 100px; + margin-right: 100px; + --background: var(--ion-color-purple); + font-size: calc(1.5vh + 1vw); + font-weight: bold; +} + +ion-content{ + --background: var(--ion-color-turquoise); + } \ No newline at end of file diff --git a/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.spec.ts b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.spec.ts new file mode 100644 index 0000000..26abd3d --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.spec.ts @@ -0,0 +1,17 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HistoryPage } from './history.page'; + +describe('HistoryPage', () => { + let component: HistoryPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + fixture = TestBed.createComponent(HistoryPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.ts b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.ts new file mode 100644 index 0000000..d2fc537 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/history/history.page.ts @@ -0,0 +1,44 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApiService } from 'src/app/services/api.service'; +import { ToastService } from 'src/app/services/toast.service'; + +@Component({ + selector: 'app-history', + templateUrl: './history.page.html', + styleUrls: ['./history.page.scss'], +}) +export class HistoryPage implements OnInit { + selectedSegment: string = 'classic-wordles'; + pendingPvpGames: any[] = []; + + constructor(private apiService: ApiService, private toastService: ToastService, + private router: Router) { } + + ngOnInit() { + } + + // Method that loads the pending games when the "Pending" window is clicked + segmentChanged(event: any) { + this.selectedSegment = event.detail.value; + + if (this.selectedSegment === 'pending-pvp-games') { + this.loadPendingPvpGames(); + } + } + + async loadPendingPvpGames() { + try { + this.pendingPvpGames = await this.apiService.getPendingPVPGames(); + } catch (error) { + this.toastService.showToast("Error loading pending games", 2000, 'top', 'danger'); + } + } + + respondGame(idGame: string){ + // Refresh pending games and redirect + this.loadPendingPvpGames(); + this.router.navigate(['/respond-game'], { queryParams: { idGame: idGame } }); + } + +} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/login/login.page.ts b/Wordle+/ionic/ionic-app/src/app/pages/login/login.page.ts index 3530141..464b628 100644 --- a/Wordle+/ionic/ionic-app/src/app/pages/login/login.page.ts +++ b/Wordle+/ionic/ionic-app/src/app/pages/login/login.page.ts @@ -74,7 +74,7 @@ export class LoginPage implements OnInit { // Rank is calculated in the frontend } this.isLoading = false; - this.router.navigate(['/tabs/main'], { queryParams: { avatar: 'true' } }); + this.router.navigate(['/tabs/main']); }, (error) => { console.error('Log in error', error); diff --git a/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game-routing.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game-routing.module.ts new file mode 100644 index 0000000..6604e18 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { RespondGamePage } from './respond-game.page'; + +const routes: Routes = [ + { + path: '', + component: RespondGamePage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class RespondGamePageRoutingModule {} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.module.ts b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.module.ts new file mode 100644 index 0000000..f2f4dad --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; +import { WordleDashboardModule } from 'src/app/wordle-dashboard/wordle-dashboard.module'; +import { RespondGamePageRoutingModule } from './respond-game-routing.module'; + +import { RespondGamePage } from './respond-game.page'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + WordleDashboardModule, + RespondGamePageRoutingModule + ], + declarations: [RespondGamePage] +}) +export class RespondGamePageModule {} diff --git a/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.html b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.html new file mode 100644 index 0000000..dc42c3b --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.html @@ -0,0 +1,19 @@ + + + Game 1vs1 + + + + + +
+
{{ selfUsername }}
+ VS icon +
{{ opponentUsername }}
+
+
+ + + + +
diff --git a/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.scss b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.scss new file mode 100644 index 0000000..e524ef5 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.scss @@ -0,0 +1,28 @@ +ion-content { + display: flex; + flex-direction: column; + align-items: center; + } + + .game-info { + display: flex; + align-items: center; + margin-bottom: 20px; + justify-content: center; + margin-top: 1.5vh; + margin-bottom: 0px; + font-size: calc(2.5vh + 1.5vw); + text-shadow: 2px 2px 0px #957dad, + } + + + .username { + font-weight: bold; + } + + .game-info img { + height: calc(3vh + 1vw); + margin-right: 0.8vw; + margin-left: 0.8vw; + } + \ No newline at end of file diff --git a/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.spec.ts b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.spec.ts new file mode 100644 index 0000000..49d6821 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.spec.ts @@ -0,0 +1,17 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RespondGamePage } from './respond-game.page'; + +describe('RespondGamePage', () => { + let component: RespondGamePage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + fixture = TestBed.createComponent(RespondGamePage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.ts b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.ts new file mode 100644 index 0000000..a612208 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/pages/respond-game/respond-game.page.ts @@ -0,0 +1,82 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ApiService } from 'src/app/services/api.service'; +import { StorageService } from 'src/app/services/storage.service'; +import { AlertController } from '@ionic/angular'; + +@Component({ + selector: 'app-respond-game', + templateUrl: './respond-game.page.html', + styleUrls: ['./respond-game.page.scss'], +}) +export class RespondGamePage implements OnInit { + idGame: number; + selfUsername: string; + opponentUsername: string; + selectedWord: string; + wordLength: number; + + constructor(private route: ActivatedRoute, private apiService: ApiService, + private storageService: StorageService, + private router: Router, private alertController: AlertController) {} + + async ngOnInit() { + this.selfUsername = await this.storageService.getUsername(); + this.route.queryParams.subscribe((params) => { + this.idGame = params['idGame']; + }); + + (await this.apiService.getGame(this.idGame)).subscribe( + (response) => { + this.selectedWord = response.word; + this.opponentUsername = response.player1; + this.wordLength = this.selectedWord.length; + }, + (error) => { + this.showAlert("Ups!", "You can't play this game!"); + } + ); + } + async showAlert(header: string, message: string) { + const alert = await this.alertController.create({ + header: header, + message: message, + buttons: [ + { + text: 'OK', + handler: () => { + this.router.navigate(['/tabs/main']); + }, + }, + ], + }); + + await alert.present(); + const result = await alert.onDidDismiss(); + if (result.role === 'backdrop') { + this.router.navigate(['/tabs/main'], { queryParams: { refresh: 'true' } }); + } + } + + async finishGame(time: number, xp: number, attempts: number, selectedWord: string) { + const gameData = { + player2_time: time, + player2_xp: xp, + player2_attempts: attempts, + }; + + (await this.apiService.resolveGame(this.idGame, gameData)).subscribe( + (response) => { + console.log('Game resolved successfully', response); + if (response.winner === this.selfUsername) { + setTimeout( () => this.showAlert('Congratulations!', 'You won! Amazing!'), 2500); + } else { + setTimeout( () => this.showAlert('Bad news!', 'You lost. Try next time!'), 2500); + } + }, + (error) => { + console.log('Game could not be resolved', error); + } + ); + } +} diff --git a/Wordle+/ionic/ionic-app/src/app/services/api.service.ts b/Wordle+/ionic/ionic-app/src/app/services/api.service.ts index 69b0612..b055959 100644 --- a/Wordle+/ionic/ionic-app/src/app/services/api.service.ts +++ b/Wordle+/ionic/ionic-app/src/app/services/api.service.ts @@ -38,6 +38,20 @@ import { EncryptionService } from './encryption.service'; return this.http.post(url, userData); } + async getPlayerData(playerId: string) { + const url = `${this.baseURL}/api/players/${playerId}`; + const accessToken = await this.storageService.getAccessToken(); + if (!accessToken) { + return throwError('Access token not found'); + } + const decryptedToken = this.encryptionService.decryptData(accessToken); + const headers = new HttpHeaders({ + Authorization: `Token ${decryptedToken}`, + 'Content-Type': 'application/json' + }); + return this.http.get(url, {headers}); + } + async addClassicGame(gameData: any): Promise> { let url = `${this.baseURL}/api/classicwordles/`; const accessToken = this.storageService.getAccessToken(); @@ -195,7 +209,6 @@ import { EncryptionService } from './encryption.service'; return throwError('Access token not found') as any; } const decryptedToken = this.encryptionService.decryptData(await accessToken); - console.log(decryptedToken); const headers = new HttpHeaders({ Authorization: `Token ${decryptedToken}`, 'Content-Type': 'application/json' @@ -233,4 +246,61 @@ import { EncryptionService } from './encryption.service'; return this.http.get(url, { headers }); } + + async createGame(gameData: any): Promise> { + let url = `${this.baseURL}/api/games/`; + const accessToken = this.storageService.getAccessToken(); + if (!accessToken) { + return throwError('Access token not found') as any; + } + const decryptedToken = this.encryptionService.decryptData(await accessToken); + const headers = new HttpHeaders({ + Authorization: `Token ${decryptedToken}`, + 'Content-Type': 'application/json' + }); + return this.http.post(url, gameData, { headers }); + } + + async getGame(idGame: number): Promise> { + let url = `${this.baseURL}/api/games/${idGame}`; + const accessToken = this.storageService.getAccessToken(); + if (!accessToken) { + return throwError('Access token not found') as any; + } + const decryptedToken = this.encryptionService.decryptData(await accessToken); + const headers = new HttpHeaders({ + Authorization: `Token ${decryptedToken}`, + 'Content-Type': 'application/json' + }); + return this.http.get(url, { headers }); + } + + async resolveGame(idGame: number, gameData: any): Promise> { + let url = `${this.baseURL}/api/games/${idGame}/`; + const accessToken = this.storageService.getAccessToken(); + if (!accessToken) { + return throwError('Access token not found') as any; + } + const decryptedToken = this.encryptionService.decryptData(await accessToken); + const headers = new HttpHeaders({ + Authorization: `Token ${decryptedToken}`, + 'Content-Type': 'application/json' + }); + return this.http.patch(url, gameData, { headers }); + } + + async getPendingPVPGames(): Promise { + let url = `${this.baseURL}/api/games/pending_games/`; + const accessToken = this.storageService.getAccessToken(); + if (!accessToken) { + return throwError('Access token not found') as any; + } + const decryptedToken = this.encryptionService.decryptData(await accessToken); + const headers = new HttpHeaders({ + Authorization: `Token ${decryptedToken}`, + 'Content-Type': 'application/json' + }); + + return this.http.get(url, { headers }).toPromise(); + } } diff --git a/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.html b/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.html index 7ae378c..4d72254 100644 --- a/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.html +++ b/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.html @@ -1,5 +1,5 @@ -
+
@@ -30,7 +30,7 @@
- + diff --git a/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.ts b/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.ts index 81cfb20..47ab809 100644 --- a/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.ts +++ b/Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.ts @@ -23,6 +23,7 @@ export class Tab1Page implements OnInit{ xP: number; backgroundImage: string; avatarImage: string; + isReady: boolean = false; constructor( private route: ActivatedRoute, @@ -37,6 +38,10 @@ export class Tab1Page implements OnInit{ this.router.navigate(['/friendlist']); } + goToHistory() { + this.router.navigate(['/history']); + } + // Change background img depending on the width async ngOnInit() { if (window.innerWidth <= 767) { @@ -44,7 +49,8 @@ export class Tab1Page implements OnInit{ } else { this.backgroundImage = '../../assets/background_wordle_horizontal.png'; } - this.getAvatarImage(); + this.loadAvatarImage(); + // Optional param to update the player info: useful when // finishing a game @@ -61,6 +67,8 @@ export class Tab1Page implements OnInit{ } async ionViewWillEnter() { + this.refreshPlayerData(); + this.username = await this.storageService.getUsername(); this.victoriesClassic = await this.storageService.getWins(); this.victoriesPvp = await this.storageService.getWinsPVP(); @@ -70,6 +78,25 @@ export class Tab1Page implements OnInit{ this.rankImage = await this.getRankImage(this.rank); this.notificationService.refreshNotifications(); + this.getAvatarImage(); + + this.isReady = true; + } + + async refreshPlayerData() { + const playerId = await this.storageService.getPlayerID(); + if (playerId) { + (await this.apiService.getPlayerData(playerId)).subscribe( + (response: any) => { + this.storageService.setWinsPVP(response.wins_pvp); + this.storageService.setXP(response.xp); + this.storageService.setWinsTournament(response.wins_tournaments); + }, + (error) => { + console.error('Error retrieving player data:', error); + } + ) + } } async getAvatarImage() { @@ -112,11 +139,13 @@ export class Tab1Page implements OnInit{ this.storageService.setAvatarUrl(this.avatarImage); } else { this.avatarImage = '../../assets/avatar.png'; // Default avatar image + this.storageService.setAvatarUrl(this.avatarImage); } }, error => { console.error('Error loading avatar image:', error); this.avatarImage = '../../assets/avatar.png'; + this.storageService.setAvatarUrl(this.avatarImage); } ); } diff --git a/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.html b/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.html index fb695d1..3af214b 100644 --- a/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.html +++ b/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.html @@ -38,7 +38,7 @@ Players: - {{ tournament.num_players }} / {{ tournament.max_players }} +

{{ tournament.num_players }}/{{ tournament.max_players }}

Word Length: diff --git a/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.scss b/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.scss index 9786946..d349581 100644 --- a/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.scss +++ b/Wordle+/ionic/ionic-app/src/app/tab2/tab2.page.scss @@ -21,8 +21,9 @@ align-content: center; flex-direction: row; } -.value{ +.value, p{ text-align: right; +font-size: 3vh; } .label{ diff --git a/Wordle+/ionic/ionic-app/src/app/wordle-dashboard/wordle-dashboard.module.ts b/Wordle+/ionic/ionic-app/src/app/wordle-dashboard/wordle-dashboard.module.ts new file mode 100644 index 0000000..9508a83 --- /dev/null +++ b/Wordle+/ionic/ionic-app/src/app/wordle-dashboard/wordle-dashboard.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { WordleDashboardComponent } from '../components/wordle-dashboard/wordle-dashboard.component'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + imports: [ + CommonModule, + IonicModule + ], + declarations: [WordleDashboardComponent], + exports: [WordleDashboardComponent] +}) +export class WordleDashboardModule { } diff --git a/Wordle+/ionic/ionic-app/src/assets/icon/vs.png b/Wordle+/ionic/ionic-app/src/assets/icon/vs.png new file mode 100644 index 0000000..75012e5 Binary files /dev/null and b/Wordle+/ionic/ionic-app/src/assets/icon/vs.png differ