Skip to content

Commit

Permalink
Add searching for friends and sending requests
Browse files Browse the repository at this point in the history
Closes #12
  • Loading branch information
salah3x committed Jul 24, 2019
1 parent 08a788a commit 4b65b4e
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 1 deletion.
19 changes: 19 additions & 0 deletions src/app/friends/add-friends/add-friends.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';

import { AddFriendsPage } from './add-friends.page';

const routes: Routes = [
{
path: '',
component: AddFriendsPage
}
];

@NgModule({
imports: [CommonModule, IonicModule, RouterModule.forChild(routes)],
declarations: [AddFriendsPage]
})
export class AddFriendsPageModule {}
60 changes: 60 additions & 0 deletions src/app/friends/add-friends/add-friends.page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-back-button defaultHref="/tabs/friends"></ion-back-button>
</ion-buttons>
<ion-title>Add friends</ion-title>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-grid>
<ion-row>
<ion-col size-md="6" offset-md="3">
<ion-searchbar
(ionChange)="search($event.detail.value)"
#searchBar
></ion-searchbar>
</ion-col>
</ion-row>
<ion-row *ngIf="!searchBar.value">
<ion-col size-md="6" offset-md="3">
<ion-item>
<ion-label text-center>
<p>Type something in the search bar!</p>
</ion-label>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col *ngIf="loading" size-md="6" offset-md="3" text-center>
<ion-spinner></ion-spinner>
</ion-col>
</ion-row>
<ion-row>
<ion-col size-md="6" offset-md="3">
<ion-list *ngIf="friends$ | async as friends">
<ion-item
(click)="showActionSheet(friend)"
*ngFor="let friend of friends"
button
>
<ion-avatar slot="start">
<ion-img [src]="friend.photo || 'assets/user.png'"></ion-img>
</ion-avatar>
<ion-label text-center>{{ friend.name }}</ion-label>
</ion-item>
<ion-item *ngIf="!friends?.length && !loading && searchBar.value">
<ion-label text-center>
<p>Nothing found!</p>
</ion-label>
</ion-item>
</ion-list>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>

<ion-footer text-center>
<p class="footer">Slide to right</p>
</ion-footer>
3 changes: 3 additions & 0 deletions src/app/friends/add-friends/add-friends.page.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.footer {
font-size: 13px;
}
26 changes: 26 additions & 0 deletions src/app/friends/add-friends/add-friends.page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { AddFriendsPage } from './add-friends.page';

describe('AddFriendsPage', () => {
let component: AddFriendsPage;
let fixture: ComponentFixture<AddFriendsPage>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AddFriendsPage],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(AddFriendsPage);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
108 changes: 108 additions & 0 deletions src/app/friends/add-friends/add-friends.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Component, OnInit, AfterViewInit } from '@angular/core';
import {
ActionSheetController,
AlertController,
LoadingController
} from '@ionic/angular';
import { Observable, Subject } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';

import { User } from '../../shared/models';
import { StoreService } from '../../shared/store.service';

@Component({
selector: 'app-add-friends',
templateUrl: './add-friends.page.html',
styleUrls: ['./add-friends.page.scss']
})
export class AddFriendsPage implements OnInit, AfterViewInit {
friends$: Observable<User[]>;
searchTerm = new Subject<string>();
loading = false;

constructor(
private actionSheetCtrl: ActionSheetController,
private alertCtrl: AlertController,
private loadingCtrl: LoadingController,
private store: StoreService
) {}

ngOnInit() {
this.friends$ = this.searchTerm.pipe(
switchMap(s => this.store.searchForFriends(s)),
tap(() => (this.loading = false))
);
}

ngAfterViewInit() {
this.search('');
}

search(s: string) {
this.loading = true;
this.searchTerm.next(s);
}

async showActionSheet(friend: User) {
const actionSheet = await this.actionSheetCtrl.create({
buttons: [
{
text: 'Send request',
icon: 'person-add',
handler: this.addFriend.bind(this, friend)
},
{
text: 'Cancel',
icon: 'close',
role: 'cancel'
}
]
});
await actionSheet.present();
}

private async addFriend(friend: User) {
const alert = await this.alertCtrl.create({
header: 'Are you sure?',
message: `Do you want to send a request to ${friend.name}?`,
buttons: [
{
text: 'No',
role: 'cancel '
},
{
text: 'Yes',
handler: async () => {
if (!(await this.store.canSendRequest(friend.id))) {
await (await this.alertCtrl.create({
header: 'Error',
message: `You are either friend with or already sent or received a request from ${
friend.name
}`,
buttons: [
{
text: 'Close',
role: 'cancel '
}
]
})).present();
return;
}
const loader = await this.loadingCtrl.create({
message: `Sending request to ${friend.name}...`
});
await loader.present();
try {
await this.store.addFriend(friend.id);
} catch (err) {
console.error('Unfriending failed: ', err);
} finally {
await loader.dismiss();
}
}
}
]
});
await alert.present();
}
}
4 changes: 4 additions & 0 deletions src/app/friends/friends.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const routes: Routes = [
{
path: 'requests',
loadChildren: './requests/requests.module#RequestsPageModule'
},
{
path: 'add',
loadChildren: './add-friends/add-friends.module#AddFriendsPageModule'
}
];

Expand Down
2 changes: 1 addition & 1 deletion src/app/friends/friends.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</ion-buttons>
<ion-title text-center>Friends</ion-title>
<ion-buttons slot="end">
<ion-button>
<ion-button routerLink="/tabs/friends/add">
<ion-icon slot="icon-only" name="add"></ion-icon>
</ion-button>
</ion-buttons>
Expand Down
75 changes: 75 additions & 0 deletions src/app/shared/store.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,79 @@ export class StoreService {
declineRequest(id: string): Promise<void> {
return this.db.doc<Request>(`requests/${id}`).delete();
}

searchForFriends(keyword: string): Observable<User[]> {
keyword = keyword.trim().toLowerCase();
return this.authService.user.pipe(
switchMap(user =>
!keyword
? of([] as User[])
: this.db
.collection<User>('users', ref =>
ref
.orderBy('name_lowercase')
.startAt(keyword)
.endAt(keyword + '\uf8ff')
)
.valueChanges({ idField: 'id' })
.pipe(map(users => users.filter(u => u.id !== user.uid)))
)
);
}

addFriend(id: string): Promise<firestore.DocumentReference> {
return this.authService.user
.pipe(
take(1),
switchMap(user =>
this.db.collection<Request>('requests').add({
from: user.uid,
to: id,
timestamp: firestore.Timestamp.now()
})
)
)
.toPromise();
}

canSendRequest(id: string): Promise<boolean> {
return this.authService.user
.pipe(
take(1),
switchMap(user =>
combineLatest([
this.db
.collection('friendships', ref =>
ref.where('members', '==', [user.uid, id])
)
.valueChanges(),
this.db
.collection('friendships', ref =>
ref.where('members', '==', [id, user.uid])
)
.valueChanges(),
this.db
.collection('requests', ref =>
ref.where('from', '==', id).where('to', '==', user.uid)
)
.valueChanges(),
this.db
.collection('requests', ref =>
ref.where('to', '==', id).where('from', '==', user.uid)
)
.valueChanges()
]).pipe(
take(1),
map(
docs =>
docs[0]
.concat(docs[1])
.concat(docs[2])
.concat(docs[3]).length === 0
)
)
)
)
.toPromise();
}
}

0 comments on commit 4b65b4e

Please sign in to comment.