Quellcode durchsuchen

News Details page with UI

master
kj1352 vor 4 Jahren
Ursprung
Commit
ef606fe9d3
10 geänderte Dateien mit 522 neuen und 343 gelöschten Zeilen
  1. +4
    -0
      src/app/app-routing.module.ts
  2. +17
    -0
      src/app/home-details/home-details-routing.module.ts
  3. +20
    -0
      src/app/home-details/home-details.module.ts
  4. +74
    -0
      src/app/home-details/home-details.page.html
  5. +297
    -0
      src/app/home-details/home-details.page.scss
  6. +24
    -0
      src/app/home-details/home-details.page.spec.ts
  7. +43
    -0
      src/app/home-details/home-details.page.ts
  8. +5
    -48
      src/app/home/home.page.html
  9. +6
    -256
      src/app/home/home.page.scss
  10. +32
    -39
      src/app/home/home.page.ts

+ 4
- 0
src/app/app-routing.module.ts Datei anzeigen

@@ -17,6 +17,10 @@ const routes: Routes = [
{
path: 'more',
loadChildren: () => import('./more/more.module').then( m => m.MorePageModule)
},
{
path: 'home-details',
loadChildren: () => import('./home-details/home-details.module').then( m => m.HomeDetailsPageModule)
}
];
@NgModule({


+ 17
- 0
src/app/home-details/home-details-routing.module.ts Datei anzeigen

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeDetailsPage } from './home-details.page';

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

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class HomeDetailsPageRoutingModule {}

+ 20
- 0
src/app/home-details/home-details.module.ts Datei anzeigen

@@ -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 { HomeDetailsPageRoutingModule } from './home-details-routing.module';

import { HomeDetailsPage } from './home-details.page';

@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
HomeDetailsPageRoutingModule
],
declarations: [HomeDetailsPage]
})
export class HomeDetailsPageModule {}

+ 74
- 0
src/app/home-details/home-details.page.html Datei anzeigen

@@ -0,0 +1,74 @@
<ion-content>
<button (click)="back()"
class="close-article-button active">
<ion-icon name="close"></ion-icon>
</button>

<div class="image-holder">
<figure>
<img [src]="newsDetails.image">
</figure>

<button *ngIf="newsDetails.type === 'VIDEO'">
<ion-icon name="play"></ion-icon>
</button>
</div>

<section class="content">
<h4> {{ newsDetails.heading }} </h4>

<div class="details">
{{ newsDetails.description }}
</div>
</section>


<section class="comments">
<header> Comments </header>
<ul>
<li *ngFor="let comment of newsDetails.comments">
<p> {{ comment.comment }} <label> - {{ comment.user }} </label> </p>

<button (click)="comment.isLiked = !comment.isLiked"
[ngClass]="{'active' : comment.isLiked}">
<ion-icon *ngIf="!comment.isLiked" name="heart-outline"></ion-icon>
<ion-icon *ngIf="comment.isLiked" name="heart"></ion-icon>
<span> {{ comment.isLiked ? comment.likes + 1 : comment.likes }} </span>
</button>
</li>
<div class="input-holder" id="comment-input">
<input type="text" placeholder="Type your comment" [(ngModel)]="myComment">
<button (click)="postComment()"> <ion-icon name="send"></ion-icon> </button>
</div>
</ul>
</section>

<section class="action-buttons">
<section class="shortcuts">
<button class="wide-button" (click)="newsDetails.isLiked = !newsDetails.isLiked"
[ngClass]="{'active' : newsDetails.isLiked}">
<ion-icon *ngIf="!newsDetails.isLiked" name="heart-outline"></ion-icon>
<ion-icon *ngIf="newsDetails.isLiked" name="heart"></ion-icon>
<span> {{ newsDetails.isLiked ? newsDetails.likes + 1 : newsDetails.likes }} </span>
</button>

<button>
<ion-icon name="share-social-outline"></ion-icon>
</button>
</section>

<section class="shortcuts">
<button (click)="newsDetails.isBookmarked = !newsDetails.isBookmarked"
[ngClass]="{'active' : newsDetails.isBookmarked}">
<ion-icon *ngIf="!newsDetails.isBookmarked" name="bookmark-outline"></ion-icon>
<ion-icon *ngIf="newsDetails.isBookmarked" name="bookmark"></ion-icon>
</button>

<button class="wide-button" (click)="scrollToAddComment()">
<ion-icon name="chatbubble-outline"></ion-icon>
<span> {{ newsDetails.comments.length }} </span>
</button>
</section>

</section>
</ion-content>

+ 297
- 0
src/app/home-details/home-details.page.scss Datei anzeigen

@@ -0,0 +1,297 @@
.close-article-button {
width: 40px;
height: 40px;
position: fixed;
top: 10px;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(#ffff, 0.5);
border-radius: 50%;
z-index: 3;
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s, transform 0.3s;

&.active {
animation: fadeInFromBottom 0.5s forwards;
}

ion-icon {
color: white;
font-size: 20px;
}
}

@keyframes fadeInFromBottom {
0% {
opacity: 0;
transform: translateY(30px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}

.image-holder {
position: sticky;
position: -webkit-sticky;
top: 0;
height: 40%;
overflow: hidden;

button {
width: 40px;
height: 40px;
position: absolute;
bottom: 10px;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(var(--brand-red-rgb), 0.5);
border-radius: 50%;
transform: translate(calc(-50vw + 40px), -15vh);

ion-icon {
color: white;
font-size: 20px;
transition: color 0.3s;
}
}

figure {
margin: 0;
display: block;
width: 100%;
height: 100%;
filter: brightness(30%);

img {
height: 100%;
object-fit: cover;
display: block;
width: 100%;
}
}
}

.content {
background-color: white;
padding: 0px 5% 5% 5%;
margin: 0 auto;
border-radius: 0px;
transition: border-radius 0.3s, transform 0.3s, width 0.3s, margin 0.3s, box-shadow 0.3s;
border-bottom: 1px solid rgba(var(--light-grey-rgb), 0.3);
z-index: 2;
width: 90%;
height: auto;
position: relative;
border-radius: 10px;
overflow: auto;
border-width: 0px;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);
animation: showUpContent 0.5s forwards;
}

@keyframes showUpContent {
0% {
transform: translateY(0px);
}
100% {
transform: translateY(-40px);
}
}

h4 {
font-size: 1.2rem;
margin: 0px;
line-height: 1.5;
font-weight: 500;
color: var(--brand-black);
transition: opacity 0.1s;
background-color: white;
padding: 10px 0;
}

.details {
margin: 0px;
font-size: 0.9rem;
line-height: 1.5;
font-weight: 400;
color: var(--light-grey);
overflow: hidden;
transition: opacity 0.1s;
}


.action-buttons {
display: flex;
padding: 0 3%;
z-index: 1;
margin: 0 auto 20px;
width: 90%;
background-color: white;
border-radius: 10px;
position: sticky;
position: -webkit-sticky;
bottom: 10px;
z-index: 2;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);
justify-content: center;
height: 60px;

.shortcuts {
display: flex;
align-items: center;
height: 100%;
width: 50%;

button {
margin-right: 5px;
border-radius: 50%;
background-color: rgba(var(--light-grey-rgb), 0.1);
margin: 0 auto;
width: 40px;
height: 40px;

&.wide-button {
width: auto;
border-radius: 5px;
padding: 0 10px;
}

&.active {
ion-icon, span {
color: var(--brand-red);
}
}

span {
vertical-align: middle;
color: var(--light-grey);
}
ion-icon {
vertical-align: middle;
color: var(--brand-black);
font-size: 1.2rem;
}
}
}
}

.comments {
background-color: white;
width: 90%;
position: relative;
box-shadow: 0px 0px 0px var(--light-grey);
z-index: 1;
margin: -20px auto 20px;
border-radius: 10px;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);
border-radius: 10px;
overflow: hidden;
opacity: 0;
animation: fadeInFromBottom 0.5s forwards;
header {
font-size: 1.1rem;
margin: 0px;
line-height: 1.5;
font-weight: 500;
color: var(--brand-black);
transition: opacity 0.1s;
background-color: white;
padding: 10px 5%;
}

ul {
list-style: none;
padding: 0;
margin: 0;

li {
padding: 5px 5% 10px;
border-top: 1px solid rgba(var(--light-grey-rgb), 0.3);
line-height: 1.5;
}

label {
font-size: 1rem;
font-weight: 500;
letter-spacing: 1px;
color: rgba(var(--brand-red-rgb), 0.8);
}

p {
margin: 3px 0;
font-size: 1rem;
font-weight: 400;
color: var(--light-grey);
}

button {
background-color: #f3f3f3;
margin-right: 5px;
height: 30px;
width: auto;
border-radius: 5px;
padding: 0 5px;

span {
vertical-align: middle;
color: var(--light-grey);
}
ion-icon {
vertical-align: middle;
color: var(--brand-black);
font-size: 1rem;
}

&.active {
ion-icon, span {
color: var(--brand-red);
}
}
}

.input-holder {
display: flex;
width: 100%;
height: 70px;
align-items: center;

input {
width: calc(100% - 80px);
margin: 0 auto;
border: 1px solid rgba(var(--light-grey-rgb), 0.3);
border-radius: 30px;
font-size: 14px;
color: var(--brand-black);
font-weight: 500;
height: 40px;
padding: 0 15px;
}

button {
width: 40px;
height: 40px;
background-color: var(--brand-red);
margin: 0 auto;
border-radius: 50%;

ion-icon {
color: white;
font-size: 16px;
}
}
}
}
}

+ 24
- 0
src/app/home-details/home-details.page.spec.ts Datei anzeigen

@@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { IonicModule } from '@ionic/angular';

import { HomeDetailsPage } from './home-details.page';

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

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeDetailsPage ],
imports: [IonicModule.forRoot()]
}).compileComponents();

fixture = TestBed.createComponent(HomeDetailsPage);
component = fixture.componentInstance;
fixture.detectChanges();
}));

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 43
- 0
src/app/home-details/home-details.page.ts Datei anzeigen

@@ -0,0 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { INews } from '../home/home.page';

@Component({
selector: 'app-home-details',
templateUrl: './home-details.page.html',
styleUrls: ['./home-details.page.scss'],
})
export class HomeDetailsPage implements OnInit {
newsDetails: INews;
myComment: string = '';

constructor(
private route: ActivatedRoute,
private location: Location
) { }

ngOnInit() {
this.newsDetails = JSON.parse(this.route.snapshot.paramMap.get('news'));
}

back() {
this.location.back();
}

postComment() {
this.newsDetails.comments.push({
user: 'Test',
comment: this.myComment,
isLiked: false,
likes: 0,
});

this.myComment = '';
}

scrollToAddComment() {
document.querySelector('#comment-input').scrollIntoView({behavior: "smooth", block: "start"});
}

}

+ 5
- 48
src/app/home/home.page.html Datei anzeigen

@@ -14,14 +14,7 @@
</section>
</div>

<button (click)="closeArticle()"
class="close-article-button"
[ngClass]="{'active' : selectedArticle !== null }">
<ion-icon name="close"></ion-icon>
</button>

<ion-slides [options]="slideOpts" *ngIf="selectedTab === 'news'"
[ngClass]="{'active' : selectedArticle !== null}" #slides>
<ion-slides [options]="slideOpts" *ngIf="selectedTab === 'news'" #slides>

<ion-slide *ngFor="let news of newsData; let i = index">
<div class="image-holder">
@@ -29,9 +22,7 @@
<img [src]="news.image">
</figure>

<button
[ngClass]="{'active' : selectedArticle !== null}"
*ngIf="news.type === 'VIDEO'">
<button *ngIf="news.type === 'VIDEO'">
<ion-icon name="play"></ion-icon>
</button>

@@ -48,28 +39,7 @@
<div class="details">
{{ news.description }}
</div>
</section>


<section class="comments" *ngIf="selectedArticle !== null">
<header> Comments </header>
<ul>
<li *ngFor="let comment of news.comments">
<p> {{ comment.comment }} <label> - {{ comment.user }} </label> </p>

<button (click)="comment.isLiked = !comment.isLiked"
[ngClass]="{'active' : comment.isLiked}">
<ion-icon *ngIf="!comment.isLiked" name="heart-outline"></ion-icon>
<ion-icon *ngIf="comment.isLiked" name="heart"></ion-icon>
<span> {{ comment.isLiked ? comment.likes + 1 : comment.likes }} </span>
</button>
</li>
<div class="input-holder" id="comment-input">
<input type="text" placeholder="Type your comment" [(ngModel)]="myComment">
<button (click)="postComment()"> <ion-icon name="send"></ion-icon> </button>
</div>
</ul>
</section>
</section>

<section class="action-buttons">
<section class="shortcuts">
@@ -83,22 +53,9 @@
<button>
<ion-icon name="share-social-outline"></ion-icon>
</button>
</section>

<section class="shortcuts" *ngIf="selectedArticle !== null">
<button (click)="news.isBookmarked = !news.isBookmarked"
[ngClass]="{'active' : news.isBookmarked}">
<ion-icon *ngIf="!news.isBookmarked" name="bookmark-outline"></ion-icon>
<ion-icon *ngIf="news.isBookmarked" name="bookmark"></ion-icon>
</button>

<button class="wide-button" (click)="scrollToAddComment()">
<ion-icon name="chatbubble-outline"></ion-icon>
<span> {{ news.comments.length }} </span>
</button>
</section>
</section>

<button class="read-more" (click)="expandArticle(i)">
<button class="read-more" (click)="showNewsDetails(news)">
<span> More </span>
<ion-icon name="chevron-forward-outline"></ion-icon>
</button>


+ 6
- 256
src/app/home/home.page.scss Datei anzeigen

@@ -3,10 +3,7 @@ ion-content {
}

.heading-holder {
position: fixed;
top: 0;
left: 0;
width: 100%;
margin-bottom: 3rem;
}

.main-header {
@@ -55,126 +52,15 @@ ion-content {
}
}

.close-article-button {
width: 40px;
height: 40px;
position: fixed;
top: 10px;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(#ffff, 0.5);
border-radius: 50%;
z-index: 3;
opacity: 0;
transform: translateY(10px);
pointer-events: none;
transition: opacity 0.3s, transform 0.3s;

&.active {
opacity: 1;
transition-delay: 1s;
transform: translateY(0px);
pointer-events: all;
}

ion-icon {
color: white;
font-size: 20px;
}
}

ion-slides {
margin-top: 40%;
height: calc(100vh - 40%);
height: calc(100vh - 40% - 16px);
padding: 8px 0;
position: relative;
left: 0;
top: 0;
transition: transform 0.5s, height 0.3s, margin 0.3s, width 0.3s;
z-index: 1;

&.active {
margin: 0px;
width: 100%;
transform: translate(0, 0%);
height: calc(100vh - 56px);

ion-slide {
background-color: white;
border-radius: 0px;
overflow: auto;

.content {
width: 90%;
height: auto;
background-color: white;
transform: translateY(-30px);
position: relative;
border-radius: 10px;
transition-delay: 0.3s;
overflow: auto;
border-width: 0px;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);

h4, .details {
opacity: 0;
animation: showUpContent 0.3s forwards;
animation-delay: 0.3s;
}
}

.action-buttons {
width: 90%;
background-color: white;
border-radius: 10px;
position: sticky;
position: -webkit-sticky;
bottom: 0;
z-index: 2;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);
justify-content: center;
height: 60px;

.shortcuts {
width: 50%;

button {
background-color: rgba(var(--light-grey-rgb), 0.1);
margin: 0 auto;
width: 40px;
height: 40px;
&.wide-button {
width: auto;
padding: 0 10px;
}
}
}

.read-more {
display: none;
}
}
.image-holder figure {
filter: brightness(30%);
}
}
}
@keyframes showUpContent {
0% {
opacity: 0;
transform: translateY(20px);
-webkit-line-clamp: 4;
}
100% {
opacity: 1;
-webkit-line-clamp: unset;
transform: translateY(0px);
}
}
overflow: hidden;

ion-slide {
display: block;
@@ -183,13 +69,11 @@ ion-slides {
text-align: left;
align-self: start;
height: 100%;
transition: width 0.5s, transform 0.3s, margin 0.3s;
box-shadow: 2px 2px 5px black;
}

.image-holder {
position: sticky;
position: -webkit-sticky;
top: 0;
position: relative;
height: 40%;
overflow: hidden;

@@ -211,30 +95,6 @@ ion-slides {
pointer-events: none;
}

&.active {
transform: translate(calc(-50vw + 40px), -15vh);
animation: ripple 1s infinite;
background-color: rgba(var(--brand-red-rgb), 0.5);

ion-icon {
color: white;
}
}

@keyframes ripple {
0% {
box-shadow: 0px 0px 0px var(--brand-red);
}
50% {
box-shadow: 0px 0px 5px var(--brand-red);
}

100% {
box-shadow: 0px 0px 0px var(--brand-red);
}
}

ion-icon {
color: white;
font-size: 20px;
@@ -377,114 +237,4 @@ ion-slides {
}
}
}

.comments {
background-color: white;
width: 90%;
position: relative;
box-shadow: 0px 0px 0px var(--light-grey);
z-index: 1;
margin: 0px auto 20px;
border-radius: 10px;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);
border-radius: 10px;
overflow: hidden;
header {
font-size: 1.1rem;
margin: 0px;
line-height: 1.5;
font-weight: 500;
color: var(--brand-black);
transition: opacity 0.1s;
background-color: white;
padding: 10px 5%;
}

ul {
list-style: none;
padding: 0;
margin: 0;

li {
padding: 5px 5% 10px;
border-top: 1px solid rgba(var(--light-grey-rgb), 0.3);
line-height: 1.5;
}

label {
font-size: 1rem;
font-weight: 500;
letter-spacing: 1px;
color: rgba(var(--brand-red-rgb), 0.8);
}

p {
margin: 3px 0;
font-size: 1rem;
font-weight: 400;
color: var(--light-grey);
}

button {
background-color: #f3f3f3;
margin-right: 5px;
height: 30px;
width: auto;
border-radius: 5px;
padding: 0 5px;

span {
vertical-align: middle;
color: var(--light-grey);
}
ion-icon {
vertical-align: middle;
color: var(--brand-black);
font-size: 1rem;
}

&.active {
ion-icon, span {
color: var(--brand-red);
}
}
}

.input-holder {
display: flex;
width: 100%;
height: 70px;
align-items: center;

input {
width: calc(100% - 80px);
margin: 0 auto;
border: 1px solid rgba(var(--light-grey-rgb), 0.3);
border-radius: 30px;
font-size: 14px;
color: var(--brand-black);
font-weight: 500;
height: 40px;
padding: 0 15px;
}

button {
width: 40px;
height: 40px;
background-color: var(--brand-red);
margin: 0 auto;
border-radius: 50%;

ion-icon {
color: white;
font-size: 16px;
}
}
}
}
}

}

+ 32
- 39
src/app/home/home.page.ts Datei anzeigen

@@ -1,38 +1,43 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { IonSlides } from '@ionic/angular';
import { Router } from '@angular/router';

export type INews = {
id: string | number,
image: string,
heading: string,
description: string,
type: 'VIDEO' | 'ARTICLE',
likes: number,
isBookmarked: boolean,
isLiked: boolean,
comments: Array<{
user: string,
comment: string,
likes: number,
isLiked: boolean
}>,
};

@Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})

export class HomePage implements OnInit {
@ViewChild('slides', {static: false}) slides: IonSlides;
selectedTab: string = 'news';
selectedArticle: number = null;
myComment: string = '';
selectedArticle: number = null;

slideOpts = { };

newsData: Array<{
id: string | number,
image: string,
heading: string,
description: string,
type: 'VIDEO' | 'ARTICLE',
likes: number,
isBookmarked: boolean,
isLiked: boolean,
comments: Array<{
user: string,
comment: string,
likes: number,
isLiked: boolean
}>,
}> = [];

constructor() { }
newsData: Array<INews> = [];

constructor(
private router: Router
) { }

ngOnInit() {

@@ -94,9 +99,13 @@ export class HomePage implements OnInit {
slidesOffsetAfter: 0,
};

setTimeout(() => {
this.slides.slideTo(index);
}, 200);
// setTimeout(() => {
// this.slides.slideTo(index);
// }, 200);
}

showNewsDetails(news: any) {
this.router.navigate(['/home-details', { news: JSON.stringify(news) }])
}

closeArticle() {
@@ -109,20 +118,4 @@ export class HomePage implements OnInit {
slidesOffsetAfter: 30,
};
}

postComment() {
this.newsData[this.selectedArticle].comments.push({
user: 'Test',
comment: this.myComment,
isLiked: false,
likes: 0,
});

this.myComment = '';
}

scrollToAddComment() {
document.querySelector('#comment-input').scrollIntoView({behavior: "smooth", block: "start"});
}

}

Laden…
Abbrechen
Speichern