Skip to content
This repository has been archived by the owner on Jul 29, 2023. It is now read-only.

Commit

Permalink
Merge pull request #36 from davidcr01/2.0
Browse files Browse the repository at this point in the history
2.0
  • Loading branch information
davidcr01 authored Jul 3, 2023
2 parents e3cd592 + 21add6d commit 3a35a5c
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ Wordle+/django/djapi/__pycache__
Wordle+/django/djapi/migrations/
Wordle+/ionic/ionic-app/src/env.json
Wordle+/ionic/ionic-app/src/assets/words.json
Wordle+/django/avatars/avatars/
Binary file removed Wordle+/django/avatars/avatars/guts-icon.jpg
Binary file not shown.
Binary file removed Wordle+/django/avatars/avatars/guts-icon_pPjMhWI.jpg
Binary file not shown.
34 changes: 19 additions & 15 deletions Wordle+/django/djapi/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64
import base64, os
import imghdr
from django.contrib.auth.models import Group
from .models import CustomUser, Player, ClassicWordle
from rest_framework import viewsets, permissions, status
Expand All @@ -13,6 +14,8 @@
from django.utils import timezone
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.core.files.base import ContentFile
from django.http import JsonResponse


class CustomUserViewSet(viewsets.ModelViewSet):
Expand Down Expand Up @@ -184,8 +187,6 @@ def create(self, request):
serializer.save(player=player)
return Response(serializer.data, status=201)



class AvatarView(APIView):
permission_classes = [permissions.IsAuthenticated]

Expand All @@ -194,33 +195,36 @@ def get(self, request, user_id):
user = get_object_or_404(CustomUser, id=user_id)
if request.user == user:
if user.avatar:
with open(user.avatar.path, 'rb') as f:
image_data = f.read()
base64_image = base64.b64encode(image_data).decode('utf-8')
return Response({'avatar': base64_image}, status=200)
avatar_data = user.avatar.read()
return JsonResponse({'avatar': avatar_data.decode('utf-8')}, status=200, safe=False)
else:
return Response({'detail': 'Avatar not available.'}, status=404)
else:
return Response({'detail': 'You do not have permission to get the avatar.'}, status=403)
except CustomUser.DoesNotExist:
return Response({'detail': 'The specified user does not exist.'}, status=404)

def post(self, request, user_id):
try:
user = get_object_or_404(CustomUser, id=user_id)
if request.user == user:
avatar = request.FILES.get('avatar')
if avatar:
user.avatar = avatar
user.save()
if request.user == user:
avatar_data = request.data.get('avatar')
if avatar_data:
# Delete the existing avatar if it exists
if user.avatar:
user.avatar.delete()

# Save the avatar image without encoding or decoding
filename = f'{user_id}_avatar.png'
user.avatar.save(filename, ContentFile(avatar_data.encode('utf-8')))
return Response({'detail': 'Avatar uploaded correctly.'}, status=200)
else:
return Response({'detail': 'No avatar image attached.'}, status=400)
else:
return Response({'detail': 'You do not have permission to upload an avatar.'}, status=403)
except CustomUser.DoesNotExist:
return Response({'detail': 'The specified user does not exist.'}, status=404)
return Response({'detail': 'The specified user does not exist.'}, status=404)

class NotificationsViewSet(viewsets.ModelViewSet):
queryset = Notifications.objects.all()
serializer_class = NotificationsSerializer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
</ion-header>

<ion-content>
<div class="flex-center">
<div class="edit-container">
<ion-card>
<ion-card-header>
<ion-card-title>Change Info</ion-card-title>
</ion-card-header>
<ion-card-content>
<form [formGroup]="userInfoForm" (ngSubmit)="saveUserInfo()">
<ion-list>
Expand Down Expand Up @@ -36,5 +39,22 @@
</form>
</ion-card-content>
</ion-card>

<ion-card>
<ion-card-header>
<ion-card-title>Change Avatar</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-item>
<ion-label class="field">Current Avatar</ion-label>
<ion-avatar slot="end">
<img [src]="avatarPreview" />
</ion-avatar>
</ion-item>

<input type="file" accept=".jpg, .jpeg" #avatarInput (change)="readAndPreviewAvatar(avatarInput.files[0])"/>
<ion-button expand="full" (click)="uploadAvatar()">Upload New Avatar</ion-button>
</ion-card-content>
</ion-card>
</div>
</ion-content>
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,19 @@ ion-content{
padding-top: 1vh;
padding-bottom: 1vh;
border: none;
}

.edit-container{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}

ion-card{
margin: 5vh;
}

ion-button{
--background: var(--ion-color-turquoise-dark);
}
75 changes: 58 additions & 17 deletions Wordle+/ionic/ionic-app/src/app/pages/edit-user/edit-user.page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ApiService } from 'src/app/services/api.service';
Expand All @@ -14,18 +14,23 @@ import { ToastService } from 'src/app/services/toast.service';
export class EditUserPage implements OnInit {
userInfo: any = {};
userInfoForm: FormGroup;
avatarPreview: string | null = null;
@ViewChild('avatarInput', { static: false }) avatarInput!: ElementRef;

constructor(private apiService: ApiService,
private storageService: StorageService,
constructor(
private apiService: ApiService,
private storageService: StorageService,
private router: Router,
private toastService: ToastService,
private formBuilder: FormBuilder) {
this.userInfoForm = this.formBuilder.group({
email: ['', [Validators.required, Validators.email]],
firstName: ['', [Validators.required, Validators.maxLength(20)]],
lastName: ['', [Validators.required, Validators.maxLength(20)]],
});
}
private formBuilder: FormBuilder
) {
this.userInfoForm = this.formBuilder.group({
email: ['', [Validators.required, Validators.email]],
firstName: ['', [Validators.required, Validators.maxLength(20)]],
lastName: ['', [Validators.required, Validators.maxLength(20)]],
avatar: [null]
});
}

ngOnInit() {
this.getUserInfo();
Expand All @@ -40,6 +45,10 @@ export class EditUserPage implements OnInit {
lastName: this.userInfo.last_name,
});
});
const avatarUrl = await this.storageService.getAvatarUrl();
if (avatarUrl) {
this.avatarPreview = avatarUrl;
}
}

async saveUserInfo() {
Expand All @@ -48,23 +57,55 @@ export class EditUserPage implements OnInit {
const firstName = this.userInfoForm.get('firstName').value;
const lastName = this.userInfoForm.get('lastName').value;
const body = {
'email': email,
'first_name': firstName,
'last_name': lastName
email: email,
first_name: firstName,
last_name: lastName
};

(await this.apiService.updateUserInfo(body)).subscribe(
() => {
this.toastService.showToast('Information updated succesfully!', 2000, 'top');
this.toastService.showToast('Information updated successfully!', 2000, 'top');
this.router.navigate(['/tabs/settings']);
},
(error) => {
this.toastService.showToast('An error was generated', 2000, 'top');
this.toastService.showToast('An error occurred', 2000, 'top');
console.error('Error saving the information:', error);
}
);
} else {
console.error('Formulario inválido');
console.error('Invalid form');
}
}

async uploadAvatar() {
const file = this.avatarInput.nativeElement.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
const avatarData = reader.result as string;
console.log(avatarData);
this.saveAvatar(avatarData);
};
reader.readAsDataURL(file);
}
}

async saveAvatar(avatarData: string) {
try {
await (await this.apiService.saveAvatarImage(avatarData)).toPromise();
this.storageService.setAvatarUrl(avatarData);
this.toastService.showToast('Avatar updated successfully!', 2000, 'top');
this.getUserInfo(); // Refresh user info to update avatar preview
this.router.navigate(['/tabs/main'], { queryParams: { avatar: 'true' } });
} catch (error) {
console.error('Error uploading avatar:', error);
}
}

readAndPreviewAvatar(file: File) {
const reader = new FileReader();
reader.onloadend = () => {
this.avatarPreview = reader.result as string;
};
reader.readAsDataURL(file);
}
}
2 changes: 1 addition & 1 deletion Wordle+/ionic/ionic-app/src/app/pages/home/home.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class HomePage {
} else {
this.router.navigateByUrl('/tabs');
}
}, 2000);
}, 4000);


}
Expand Down
4 changes: 3 additions & 1 deletion Wordle+/ionic/ionic-app/src/app/pages/login/login.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class LoginPage implements OnInit {

this.apiService.login(credentials).subscribe(
async (response) => {

// Store the token in the local storage
this.errorMessage = ''
const encryptedToken = this.encryptionService.encryptData(response.token);
Expand All @@ -72,7 +73,8 @@ export class LoginPage implements OnInit {
await this.storageService.setXP(response.xp);
// Rank is calculated in the frontend
}
this.router.navigateByUrl('');
this.isLoading = false;
this.router.navigate(['/tabs/main'], { queryParams: { avatar: 'true' } });
},
(error) => {
console.error('Log in error', error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class RegisterPage implements OnInit{
ngOnInit() {
// Define the fields of the form
this.registerForm = this.formBuilder.group({
username: ['', [Validators.required, Validators.minLength(4)], Validators.maxLength(10)],
username: ['', [Validators.required, Validators.minLength(4),Validators.maxLength(10)]],
email: ['', [Validators.required, Validators.email]],
first_name: ['', Validators.required],
last_name: ['', Validators.required],
Expand Down
5 changes: 3 additions & 2 deletions Wordle+/ionic/ionic-app/src/app/services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ import { EncryptionService } from './encryption.service';
}

async saveAvatarImage(imageData: string): Promise<Observable<any>> {
const url = `${this.baseURL}/api/avatar/`;
const userId = await this.storageService.getUserID();
const url = `${this.baseURL}/api/avatar/${userId}/`;
const accessToken = await this.storageService.getAccessToken();
if (!accessToken) {
return throwError('Access token not found');
Expand All @@ -76,7 +77,7 @@ import { EncryptionService } from './encryption.service';
Authorization: `Token ${decryptedToken}`,
'Content-Type': 'application/json'
});
const body = { image_data: imageData };
const body = { avatar: imageData };
return this.http.post(url, body, { headers });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export class NotificationService {
resolve(this.notifications);
} else {
const storedNotifications = await this.storageService.getNotifications();
console.log(storedNotifications);
if (storedNotifications) {
this.notifications = storedNotifications;
resolve(this.notifications);
Expand Down
4 changes: 2 additions & 2 deletions Wordle+/ionic/ionic-app/src/app/services/storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ export class StorageService {
}

// Avatar
async setAvatarUrl(imageData: string) {
await this._storage?.set('avatarUrl', imageData);
async setAvatarUrl(image: string) {
await this._storage?.set('avatarUrl', image);
}

async getAvatarUrl(): Promise<string | null> {
Expand Down
28 changes: 17 additions & 11 deletions Wordle+/ionic/ionic-app/src/app/tab1/tab1.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class Tab1Page implements OnInit{
private storageService: StorageService,
private apiService: ApiService,
public popoverController: PopoverController,
private notificationService: NotificationService
private notificationService: NotificationService,
) {}

// Change background img depending on the width
Expand All @@ -39,22 +39,19 @@ export class Tab1Page implements OnInit{
} else {
this.backgroundImage = '../../assets/background_wordle_horizontal.png';
}

// Only fetchs the avatar if necessary
const storedAvatarUrl = await this.storageService.getAvatarUrl();
if (storedAvatarUrl) {
this.avatarImage = storedAvatarUrl;
} else {
await this.loadAvatarImage();
}
this.getAvatarImage();

// Optional param to update the player info: useful when
// finishing a game
this.route.queryParams.subscribe(async params => {
const refresh = params['refresh'];
const avatar = params['avatar'];
if (refresh === 'true') {
await this.ionViewWillEnter();
}
if (avatar === 'true') {
this.getAvatarImage();
}
});
}

Expand All @@ -70,6 +67,16 @@ export class Tab1Page implements OnInit{
this.notificationService.refreshNotifications();
}

async getAvatarImage() {
// Only fetchs the avatar if necessary
const storedAvatarUrl = await this.storageService.getAvatarUrl();
if (storedAvatarUrl) {
this.avatarImage = storedAvatarUrl;
} else {
await this.loadAvatarImage();
}
}

// Popover of word length selection
async handleSelectionPopover(event: any) {
const popover = await this.popoverController.create({
Expand All @@ -90,14 +97,13 @@ export class Tab1Page implements OnInit{
});

await popover.present();

}

async loadAvatarImage() {
(await this.apiService.getAvatarImage()).subscribe(
image => {
if (image) {
this.avatarImage = 'data:image/png;base64,' + image;
this.avatarImage = image;
this.storageService.setAvatarUrl(this.avatarImage);
} else {
this.avatarImage = '../../assets/avatar.png'; // Default avatar image
Expand Down

0 comments on commit 3a35a5c

Please sign in to comment.