#104 Add Fedora Podcast
Merged 2 years ago by a2batic. Opened 2 years ago by thelittlewonder.
thelittlewonder/Fedora-app podcast  into  master

file modified
+8 -6
@@ -1,8 +1,9 @@ 

  {

    "name": "fedora-app",

-   "app_id": "",

-   "proxies": [

-     {

+   "integrations": {

+     "cordova": {}

+   },

+   "proxies": [{

        "path": "/twitter",

        "proxyUrl": "https://api.twitter.com/1.1/"

      },
@@ -21,10 +22,11 @@ 

      {

        "path": "/community-blog",

        "proxyUrl": "https://communityblog.fedoraproject.org/wp-json/wp/v2/"

+     },

+     {

+       "path": "/podcast",

+       "proxyUrl": "https://api.simplecast.com/v1/"

      }

    ],

-   "integrations": {

-     "cordova": {}

-   },

    "type": "ionic-angular"

  }

file modified
+7 -3
@@ -15,8 +15,8 @@ 

  import { HomePage } from '../pages/home/home';

  import { MagazinePage } from '../pages/magazine/magazine';

  import { AskPage } from '../pages/ask/ask';

+ import { MorePage, AboutDetailPage, AboutPage, BookmarksPage, WomenDiversity, FedoraPodcast, PodcastPlayer } from '../pages/more/more';

  import { CalendarPage, Search, meetingDetails } from '../pages/calendar/calendar';

- import { MorePage, AboutDetailPage, AboutPage, BookmarksPage, WomenDiversity } from '../pages/more/more';

  import { NotificationsPage } from '../pages/notifications/notifications';

  import { PackageSearchPage } from '../pages/package-search/package-search';

  import { ViewPackagePage } from '../pages/view-package/view-package';
@@ -49,7 +49,9 @@ 

      NoBookmarksComponent,

      NoMeetingsComponent,

      NoResultsComponent,

-     NoInternetComponent

+     NoInternetComponent,

+     FedoraPodcast,

+     PodcastPlayer

    ],

    imports: [

      BrowserModule,
@@ -74,7 +76,9 @@ 

      ViewPackagePage,

      Search,

      meetingDetails,

-     WomenDiversity

+     WomenDiversity,

+     FedoraPodcast,

+     PodcastPlayer

    ],

    providers: [

      Browser,

file modified
+201 -129
@@ -19,183 +19,255 @@ 

  // @import "../pages/women/women";

  // App Shared Rules

  .iframeWrapper iframe {

-     width: 100%;

-     height: 100%;

- } 

+   width: 100%;

+   height: 100%;

+ }

  

  //tab icons-active

  ion-icon {

-     &[class*="fedora-"] {

-         mask-size: contain;

-         mask-position: 50% 50%;

-         mask-repeat: no-repeat;

-         background: currentColor;

-         width: 1em;

-         height: 1em;

-     }

-     &[class*="fedora-home"] {

-         mask-image: url(../assets/img/home-active.svg);

-     }

-     &[class*="fedora-mag"] {

-         mask-image: url(../assets/img/mag-active.svg);

-     }

-     &[class*="fedora-cal"] {

-         mask-image: url(../assets/img/cal-active.svg);

-     }

-     &[class*="fedora-ask"] {

-         mask-image: url(../assets/img/ask-active.svg);

-     }

-     &[class*="fedora-more"] {

-         mask-image: url(../assets/img/more-active.svg);

-     }

+   &[class*="fedora-"] {

+     mask-size: contain;

+     mask-position: 50% 50%;

+     mask-repeat: no-repeat;

+     background: currentColor;

+     width: 1em;

+     height: 1em;

+   }

+   &[class*="fedora-home"] {

+     mask-image: url(../assets/img/home-active.svg);

+   }

+   &[class*="fedora-mag"] {

+     mask-image: url(../assets/img/mag-active.svg);

+   }

+   &[class*="fedora-cal"] {

+     mask-image: url(../assets/img/cal-active.svg);

+   }

+   &[class*="fedora-ask"] {

+     mask-image: url(../assets/img/ask-active.svg);

+   }

+   &[class*="fedora-more"] {

+     mask-image: url(../assets/img/more-active.svg);

+   }

  } //tab icons-inactive

  .tabs {

-     a[aria-selected=false] {

-         .tab-button-icon[ng-reflect-name=fedora-home] {

-             mask-image: url(../assets/img/home-inactive.svg);

-         }

+   a[aria-selected=false] {

+     .tab-button-icon[ng-reflect-name=fedora-home] {

+       mask-image: url(../assets/img/home-inactive.svg);

      }

-     a[aria-selected=false] {

-         .tab-button-icon[ng-reflect-name=fedora-mag] {

-             mask-image: url(../assets/img/mag-inactive.svg);

-         }

+   }

+   a[aria-selected=false] {

+     .tab-button-icon[ng-reflect-name=fedora-mag] {

+       mask-image: url(../assets/img/mag-inactive.svg);

      }

-     a[aria-selected=false] {

-         .tab-button-icon[ng-reflect-name=fedora-cal] {

-             mask-image: url(../assets/img/cal-inactive.svg);

-         }

+   }

+   a[aria-selected=false] {

+     .tab-button-icon[ng-reflect-name=fedora-cal] {

+       mask-image: url(../assets/img/cal-inactive.svg);

      }

-     a[aria-selected=false] {

-         .tab-button-icon[ng-reflect-name=fedora-ask] {

-             mask-image: url(../assets/img/ask-inactive.svg);

-         }

+   }

+   a[aria-selected=false] {

+     .tab-button-icon[ng-reflect-name=fedora-ask] {

+       mask-image: url(../assets/img/ask-inactive.svg);

      }

-     a[aria-selected=false] {

-         .tab-button-icon[ng-reflect-name=fedora-more] {

-             mask-image: url(../assets/img/more-inactive.svg);

-         }

+   }

+   a[aria-selected=false] {

+     .tab-button-icon[ng-reflect-name=fedora-more] {

+       mask-image: url(../assets/img/more-inactive.svg);

      }

+   }

  }

  

  ion-buttons {

-     button {

-         background-color: transparent;

-     }

+   button {

+     background-color: transparent;

+   }

  } //tab typography

  .tabs {

-     a[aria-selected=false] {

-         span {

-             font-family: "Montserrat-Regular";

-             font-size: 12px;

-             color: #9A9FA6;

-             text-align: left;

-         }

+   a[aria-selected=false] {

+     span {

+       font-family: "Montserrat-Regular";

+       font-size: 12px;

+       color: #9A9FA6;

+       text-align: left;

      }

-     a[aria-selected=true] {

-         span {

-             /* Home: */

-             font-family: "Montserrat-SemiBold";

-             font-size: 12px;

-             color: #3C6EB4;

-             text-align: left;

-             line-height: 12px;

-         }

+   }

+   a[aria-selected=true] {

+     span {

+       /* Home: */

+       font-family: "Montserrat-SemiBold";

+       font-size: 12px;

+       color: #3C6EB4;

+       text-align: left;

+       line-height: 12px;

      }

+   }

  }

  

  .tabs-md[tabsLayout=icon-top] .has-icon .tab-button-text {

-     margin-top: 8px;

+   margin-top: 8px;

  } //styles of the tabbar

  .tabbar {

-     padding: 12px 0;

-     background-color: #fff !important;

-     box-shadow: 0 2px 4px 0 #565656 !important;

-     justify-content: space-around !important;

+   padding: 12px 0;

+   background-color: #fff !important;

+   box-shadow: 0 2px 4px 0 #565656 !important;

+   justify-content: space-around !important;

  }

  

  .tab-button {

-     overflow: visible !important;

-     flex: none;

+   overflow: visible !important;

+   flex: none;

  } //styles of the top navbar

  ion-navbar {

-     ion-title {

-         text-align: center;

-     }

-     background-color: #fff !important;

-     box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);

+   ion-title {

+     text-align: center;

+   }

+   background-color: #fff !important;

+   box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);

  }

  

  .pkg-lib {

-   color: #A07CBC!important;

+   color: #A07CBC !important;

  }

  

  .pkg-devel {

-   color: #E59728!important;

+   color: #E59728 !important;

  }

  

  //segment styles

  ion-segment {

-     padding: 0px 24px 0 24px;

-     background-color: #fff;

+   padding: 0px 24px 0 24px;

+   background-color: #fff;

  }

  

  .segment-button {

-     font-family: "Montserrat-Light";

-     font-size: 12px;

-     color: #9A9FA6;

-     text-transform: none;

-     padding: 0;

+   font-family: "Montserrat-Light";

+   font-size: 12px;

+   color: #9A9FA6;

+   text-transform: none;

+   padding: 0;

  }

  

  .segment-activated {

-     font-family: "Montserrat-SemiBold";

-     font-size: 12px;

-     color: #3C6EB4;

-     padding: 0;

+   font-family: "Montserrat-SemiBold";

+   font-size: 12px;

+   color: #3C6EB4;

+   padding: 0;

  }

  

  //empty state styles

  .emptystate {

-     display: flex;

-     flex-direction: column;

-     justify-content: center;

-     text-align: center;

-     align-items: center;

-     height: 100%;

+   display: flex;

+   flex-direction: column;

+   justify-content: center;

+   text-align: center;

+   align-items: center;

+   height: 100%;

+   img {

+     margin-bottom: 20px;

+   }

+   h1 {

+     font-family: "Montserrat-SemiBold";

+     font-size: 19px;

+     color: #79818B;

+     margin: 0;

+   }

+   h2 {

+     font-family: "OpenSans-Regular";

+     font-size: 14px;

+     color: #79818B;

+     line-height: 20px;

+     margin: 10px 0 20px 0;

      img {

-         margin-bottom: 20px;

-     }

-     h1 {

-         font-family: "Montserrat-SemiBold";

-         font-size: 19px;

-         color: #79818B;

-         margin: 0;

-     }

-     h2 {

-         font-family: "OpenSans-Regular";

-         font-size: 14px;

-         color: #79818B;

-         line-height: 20px;

-         margin: 10px 0 20px 0;

-         img {

-             margin: 0 2px;

-             display: inline-block;

-             margin-bottom: 0px;

-         }

-     }

-     button {

-         padding: 12px;

-         width: auto;

-         background: transparent;

-         border: 1px solid #3C6EB4;

-         border-radius: 2px;

-         font-family: "Montserrat-SemiBold";

-         font-size: 14px;

-         color: #3C6EB4;

-         line-height: 14px;

+       margin: 0 2px;

+       display: inline-block;

+       margin-bottom: 0px;

      }

+   }

+   button {

+     padding: 12px;

+     width: auto;

+     background: transparent;

+     border: 1px solid #3C6EB4;

+     border-radius: 2px;

+     font-family: "Montserrat-SemiBold";

+     font-size: 14px;

+     color: #3C6EB4;

+     line-height: 14px;

+   }

+ }

+ 

+ //styles for loader

+ .loader-ellipsis {

+   display: inline-block;

+   position: relative;

+   width: 64px;

+   height: 64px;

+   div {

+     position: absolute;

+     top: 27px;

+     width: 11px;

+     height: 11px;

+     border-radius: 50%;

+     background: rgba(#79818B, 0.15);

+     animation-timing-function: cubic-bezier(0, 1, 1, 0);

+   }

+   div:nth-child(1) {

+     left: 6px;

+     animation: loader-ellipsis1 0.6s infinite;

+   }

+   div:nth-child(2) {

+     left: 6px;

+     animation: loader-ellipsis2 0.6s infinite;

+   }

+   div:nth-child(3) {

+     left: 26px;

+     animation: loader-ellipsis2 0.6s infinite;

+   }

+   div:nth-child(4) {

+     left: 45px;

+     animation: loader-ellipsis3 0.6s infinite;

+   }

+ }

+ 

+ @keyframes loader-ellipsis1 {

+   0% {

+     transform: scale(0);

+   }

+   100% {

+     transform: scale(1);

+   }

  }

  

- .hidden{

-     display: none;

- } 

\ No newline at end of file

+ @keyframes loader-ellipsis3 {

+   0% {

+     transform: scale(1);

+   }

+   100% {

+     transform: scale(0);

+   }

+ }

+ 

+ @keyframes loader-ellipsis2 {

+   0% {

+     transform: translate(0, 0);

+   }

+   100% {

+     transform: translate(19px, 0);

+   }

+ }

+ 

+ .loader {

+   height: 100%;

+   width: 100%;

+   background-color: #EFF0F1;

+   position: absolute;

+   top: 0;

+   z-index: 999;

+   display: grid;

+   place-items: center center;

+   justify-content: center;

+ }

+ 

+ .hidden {

+   display: none;

+ }

@@ -0,0 +1,3 @@ 

+ <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">

+ <path d="M20 0C9 0 0 9 0 20C0 31 9 40 20 40C31 40 40 31 40 20C40 9 31 0 20 0ZM16 29V11L28 20L16 29Z" fill="#3B6FBB"/>

+ </svg>

@@ -0,0 +1,3 @@ 

+ <svg width="45" height="31" viewBox="0 0 45 31" fill="none" xmlns="http://www.w3.org/2000/svg">

+ <path d="M21.8571 30.8571V0L0 15.4286L21.8571 30.8571ZM23.1429 15.4286L45 30.8571V0L23.1429 15.4286Z" transform="translate(45) scale(-1 1)" fill="white" fill-opacity="0.75"/>

+ </svg>

@@ -0,0 +1,3 @@ 

+ <svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg">

+ <path d="M45 0C20.25 0 0 20.25 0 45C0 69.75 20.25 90 45 90C69.75 90 90 69.75 90 45C90 20.25 69.75 0 45 0ZM40.5 63H31.5V27H40.5V63ZM58.5 63H49.5V27H58.5V63Z" fill="white"/>

+ </svg>

@@ -0,0 +1,3 @@ 

+ <svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg">

+ <path d="M45 0C20.25 0 0 20.25 0 45C0 69.75 20.25 90 45 90C69.75 90 90 69.75 90 45C90 20.25 69.75 0 45 0ZM36 65.25V24.75L63 45L36 65.25Z" fill="white"/>

+ </svg>

@@ -0,0 +1,3 @@ 

+ <svg width="45" height="31" viewBox="0 0 45 31" fill="none" xmlns="http://www.w3.org/2000/svg">

+ <path d="M21.8571 30.8571V0L0 15.4286L21.8571 30.8571ZM23.1429 15.4286L45 30.8571V0L23.1429 15.4286Z" fill="white" fill-opacity="0.75"/>

+ </svg>

empty or binary file added
@@ -0,0 +1,25 @@ 

+ <!--

+ Template for the Fedora Podcast Page

+ -->

+ 

+ <ion-header>

+   <ion-title>

+     <img src="./assets/img/back.svg" tappable navPop>

+     <span>Fedora Podcast</span>

+   </ion-title>

+ </ion-header>

+ <ion-content>

+   <ion-card *ngFor="let podcast of podcasts" tappable (click)="openPodcastPlayer(podcast)">

+     <div class="icon">

+       <img src="./assets/img/play.svg">

+     </div>

+     <div class="desc">

+       <ion-card-header>

+         {{podcast.title}}

+       </ion-card-header>

+       <ion-card-content>

+         Episode {{podcast.number}} • {{podcast.displayTime}} mins

+       </ion-card-content>

+     </div>

+   </ion-card>

+ </ion-content>

file modified
+203 -89
@@ -51,107 +51,221 @@ 

  

  bookmarks,

  about-us,

- about-detail, women-diversity  {

-   ion-header {

-     padding: 16px 24px;

-     background-color: #fff;

-     span {

-       font-family: "Montserrat-Semibold";

-       font-size: 16px;

-       color: #3C6DB3;

-       letter-spacing: 0.57px;

-       margin: 0 0 16px 16px;

-       text-transform: uppercase;

+ about-detail,

+ women-diversity ,

+   fedora-podcast,

+   podcast-player {

+     ion-header {

+       padding: 16px 24px;

+       background-color: #fff;

+       span {

+         font-family: "Montserrat-Semibold";

+         font-size: 16px;

+         color: #3C6DB3;

+         letter-spacing: 0.57px;

+         margin: 0 0 16px 16px;

+         text-transform: uppercase;

+       }

+       .toolbar-title {

+         padding: 0 !important;

+       }

      }

-     .toolbar-title {

-       padding: 0 !important;

+     .header-md::after {

+       background-color: #fff;

+       box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);

+       background-image: none;

+     }

+     ion-content {

+       background-color: #ECEDEE !important;

      }

    }

-   .header-md::after {

-     background-color: #fff;

-     box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);

-     background-image: none;

-   }

-   ion-content {

-     background-color: #ECEDEE !important;

-   }

- }

- 

- about-us,

- about-detail {

-   ion-content {

-     background-color: #fff !important;

-   }

- }

- 

- about-us {

-   .header-md::after {

-     background-color: #FFF;

-     box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);

-     background-image: none;

-     margin-bottom: 1em;

-   }

-   .item {

-     padding: 0 24px;

-   }

-   .label {

-     font-family: 'OpenSans-Regular';

-     font-size: 16px;

-     color: #535861;

-     text-transform: none;

-     letter-spacing: 0;

-     overflow: visible;

+   about-us,

+   about-detail {

+     ion-content {

+       background-color: #fff !important;

+     }

    }

- }

- 

- about-detail {

-   ion-content {

-     .scroll-content {

-       padding: 16px 24px;

+   about-us {

+     .header-md::after {

+       background-color: #FFF;

+       box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);

+       background-image: none;

+       margin-bottom: 1em;

+     }

+     .item {

+       padding: 0 24px;

+     }

+     .label {

+       font-family: 'OpenSans-Regular';

        font-size: 16px;

-       line-height: 26px;

-       p {

-         margin: 0.5em 0;

-       }

+       color: #535861;

+       text-transform: none;

+       letter-spacing: 0;

+       overflow: visible;

      }

    }

- }

- 

- women-diversity {

-   ion-content {

-     .scroll-content {

-       p {

-         margin:0;

+   about-detail {

+     ion-content {

+       .scroll-content {

          padding: 16px 24px;

-         background-color: #fff;

-       }

-       ion-label{

-         margin: 0 0 4px 0;

-         padding: 12px 24px;

-         background-color: #fff;

+         font-size: 16px;

+         line-height: 26px;

+         p {

+           margin: 0.5em 0;

+         }

        }

      }

    }

-   .card {

-     padding: 12px 24px;

-     background-color: #FFFFFF !important;

-     box-shadow: none;

-     margin: 4px 0;

-     width: 100%;

-   }

-   .card-header {

-     padding: 0 !important;

-   }

-   .card-content {

-     margin-top: 4px;

-     padding: 0 !important;

+   women-diversity {

+     ion-content {

+       .scroll-content {

+         p {

+           margin: 0;

+           padding: 16px 24px;

+           background-color: #fff;

+         }

+         ion-label {

+           margin: 0 0 4px 0;

+           padding: 12px 24px;

+           background-color: #fff;

+         }

+       }

+     }

+     .card {

+       padding: 12px 24px;

+       background-color: #FFFFFF !important;

+       box-shadow: none;

+       margin: 4px 0;

+       width: 100%;

+     }

+     .card-header {

+       padding: 0 !important;

+     }

+     .card-content {

+       margin-top: 4px;

+       padding: 0 !important;

+     }

+     ion-card {

+       span {

+         display: block;

+       }

+       .active-title {

+         margin-bottom: 4px;

+       }

+     }

    }

-   ion-card {

-     span {

-       display: block;

+   fedora-podcast {

+     .header::after {

+       box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);

      }

-     .active-title {

-       margin-bottom: 4px;

+     ion-content {

+       [col-1],

+       [col-11] {

+         padding: 0 !important;

+       }

+       background-color: #EFF0F1 !important;

+       ion-list {

+         margin: 0 !important;

+       }

+       .card {

+         padding: 12px 16px;

+         margin: 0 0 2px 0;

+         width: 100%;

+         background-color: #fff !important;

+         box-shadow: none;

+         display: flex;

+         flex-direction: row;

+         align-items: center;

+         .icon {

+           margin-right: 8px;

+           img {

+             height: 28px;

+           }

+         }

+       }

+       .card-header {

+         font-family: "OpenSans-Semibold";

+         font-size: 14px;

+         color: #3B6FBB;

+         line-height: 20px;

+         padding: 0;

+       }

+       .card-content {

+         font-family: "OpenSans-Regular";

+         font-size: 12px;

+         color: #535961;

+         line-height: 20px;

+         padding: 0;

+       }

+     }

+   }

+   podcast-player {

+     .player {

+       display: flex;

+       flex-direction: column;

+       background: linear-gradient(180deg, #3B6DB3 0%, #284071 100%);

+       padding: 0 1.5em;

+       height: calc( 100vh - 56px);

+       text-align: center;

+       align-items: center;

+       padding-top: 20%;

+       justify-content: flex-start;

+       .banner {

+         width: 50%;

+       }

+       h1 {

+         font-family: "OpenSans-Semibold";

+         font-size: 18px;

+         color: #FFFFFF;

+         margin: 0 0 1em 0;

+       }

+       h2 {

+         font-family: "OpenSans-Regular";

+         line-height: 20px;

+         font-size: 14px;

+         color: #E8EFF8;

+         margin-bottom: 2.5em;

+       }

+       .progress {

+         display: flex;

+         flex-direction: column;

+         width: calc(100% - 3em);

+         color: #fff;

+         progress {

+           width: 100%;

+           height: 6px;

+           -webkit-appearance: none;

+           border: none;

+         }

+         progress::-webkit-progress-bar {

+           background: #637CA6;

+           border-radius: 100px;

+         }

+         progress::-webkit-progress-value {

+           background: #fff;

+           border-radius: 100px;

+         }

+         .time {

+           margin-top: 0.5em;

+           display: flex;

+           flex-direction: row;

+           justify-content: space-between;

+           span {

+             font-family: "OpenSans-Regular";

+             font-size: 12px;

+             color: #FFFFFF;

+           }

+         }

+       }

+       .controls {

+         margin-top: 2em;

+         width: 100%;

+         justify-content: space-around;

+         display: flex;

+         flex-direction: row;

+         button {

+           background: transparent;

+         }

+       }

      }

    }

- }

file modified
+172
@@ -5,6 +5,7 @@ 

  import { Browser } from '../../providers/browser/browser';

  import { Post } from '../../providers/fedora-magazine/fedora-magazine';

  import { CommunityBlogService } from '../../providers/community-blog/community-blog';

+ import { PodcastService, Podcast } from '../../providers/fedora-podcast/podcast';

  

  @Component({

    selector: 'page-more',
@@ -35,6 +36,10 @@ 

          break;

        case 'package':

          this.navCtrl.push(PackageSearchPage, { animate: true, direction: 'forward' });

+         break;

+       case 'podcast':

+         this.navCtrl.push(FedoraPodcast, { animate: true, direction: 'forward' });

+         break;

      }

    }

  
@@ -177,4 +182,171 @@ 

      this.browser.open(update.link);

    }

  }

+ /** Fedora Podcast Component */

+ @Component({

+   selector: 'fedora-podcast',

+   templateUrl: 'fedora-podcast.html',

+   providers: [PodcastService]

+ })

+ export class FedoraPodcast {

+   /**

+    * List of podcasts from Fedora Podcast

+    */

+   private podcasts: Podcast[];

+   constructor(private fedorapodcast: PodcastService, public navCtrl: NavController) {

+     this.podcasts = [];

+   }

+   /**

+    * Fetch podcasts from the Fedora Podcast simplecast channel

+    */

+   private updatePodcasts(): void {

+     this.fedorapodcast.getPodcasts().subscribe(podcasts => {

+       this.podcasts = podcasts

+     });

+   }

+   /**Open player for the podcast

+    * @param podcast clicked podcast

+    */

+   openPodcastPlayer(podcast) {

+     //podcast player for current podcast

+     this.navCtrl.push(PodcastPlayer, { podcast: podcast });

+   }

+   /**

+    * Get latest podcasts on init

+    */

+   ngOnInit() {

+     this.updatePodcasts();

+   }

+ }

+ 

+ /**

+  * Podcast Player Component

+  */

+ @Component({

+   selector: 'podcast-player',

+   templateUrl: 'podcast-player.html',

+ })

+ export class PodcastPlayer {

+   /**

+    * Podcast Player component

+    */

+   private selectedPodcast; //selected podcast number ex-1,2,3..

+   private mp3Track: HTMLAudioElement; //selected podcast mp3 url

+   private seektime: number = 10; //seek time of rewind and forward;

+   private totalLength: string; //total length of the podcast

+   private currTime: string; //current time of the podcast

+   private progressValue;  //progress bar width

+   public loaderState: boolean; //state of loader

+   private playIcon: string = './assets/img/player-play.svg'; //icon for the play button

+   private pauseIcon: string = './assets/img/player-pause.svg'; //icon for the pause button

+   private playerIcon: string; //current icon for the player i.e either play or pause depending on state

+   private currentState: string; //current state of the player i.e playing or not-playing

+   constructor(public navCtrl: NavController, public navParams: NavParams) {

+     this.selectedPodcast = navParams.data.podcast;

+     this.playerIcon = this.playIcon; //set the icon as play

+     this.currentState = 'not-playing';  //set the state as not playing

+     this.progressValue = 0; //initialize progress bar value to 0

+     this.loaderState = true; //start showing the loader

+   }

+   ngOnInit() {

+     this.toggleTabs('none');

+     /**

+      * Initialise the podcast

+      */

+     this.mp3Track = new Audio();

+     this.mp3Track.src = this.selectedPodcast.audioUrl;

+     this.mp3Track.load();

+ 

+     /**

+      * Add Event listeners - on change and on load

+      */

+     this.mp3Track.addEventListener('timeupdate', this.updateProgress.bind(this)); //bind the update button

+     this.mp3Track.addEventListener('canplaythrough', this.hideLoader.bind(this)); //hide loader if track can be played

  

+     /**

+      * Initialise other attributes

+      */

+     this.currTime = this.fancyTimeFormat(this.mp3Track.currentTime); // init current time

+     this.totalLength = this.fancyTimeFormat(this.selectedPodcast.duration); // init total time

+     this.loaderState = true; //set loader state

+   }

+   /**

+    * Start Playing the podcast

+    */

+   playAudio() {

+     this.currentState = 'playing'; //update state

+     this.mp3Track.play(); //start playing the track

+     this.playerIcon = this.pauseIcon; // change the icon

+   }

+   /**

+    * Stop playing the podcast

+    */

+   stopAudio() {

+     this.currentState = 'not-playing'; //update state

+     this.mp3Track.pause(); //stop playing the track

+     this.playerIcon = this.playIcon; // change the icon

+   }

+   /**

+    * Pause the podcast and reset on leave

+    */

+   ngOnDestroy() {

+     if (this.mp3Track) {

+       this.mp3Track.pause();

+       this.mp3Track = null;

+     }

+   }

+   /**

+    * if the track is not being played, start it otherwise vice-versa

+    */

+   togglePlay() {

+     this.currentState === 'not-playing' ? this.playAudio() : this.stopAudio();

+   }

+   /**

+    * seek podcast forward by 10 seconds

+    */

+   forward() {

+     this.mp3Track.currentTime += this.seektime;

+   }

+   /**

+    *  seek podcast back by 10 seconds

+    */

+   rewind() {

+     this.mp3Track.currentTime -= this.seektime;

+   }

+   /**

+    * Change the current Time and width of progress bar on playing the song

+    */

+   updateProgress() {

+     if (this.mp3Track) {

+       this.currTime = this.fancyTimeFormat(this.mp3Track.currentTime); // set current time

+       //set progress value if ratio is defined

+       isNaN((this.mp3Track.currentTime / this.mp3Track.duration)) ? this.progressValue = 0 : this.progressValue = (this.mp3Track.currentTime / this.mp3Track.duration);

+     }

+   }

+   /**

+    * Hide/Show the tabs on the Podcast Player Page

+    */

+   toggleTabs(status) {

+     let elements = document.querySelectorAll(".tabbar");

+     if (elements != null) {

+       Object.keys(elements).map((key) => {

+         elements[key].style.display = status;

+       });

+     }

+   }

+   /**

+    * Hides the loader when the song has loaded

+    */

+   hideLoader() {

+     this.loaderState = false; //stop showing loader

+   }

+   /**

+    * converts time in seconds to MM:SS;

+    * @param time in seconds

+    */

+   fancyTimeFormat(time) {

+     var date = new Date(null);

+     date.setSeconds(time);

+     return date.toISOString().substr(14, 5);

+   }

+ } 

\ No newline at end of file

@@ -0,0 +1,44 @@ 

+ <!--

+ Template for the Fedora Podcast Player

+ -->

+ 

+ <ion-header>

+   <ion-title>

+     <img src="./assets/img/back.svg" tappable navPop (click)="toggleTabs('flex')">

+     <span>Episode {{selectedPodcast.number}}</span>

+   </ion-title>

+ </ion-header>

+ <ion-content>

+   <!--Loader Component-->

+   <div class="loader" *ngIf="loaderState">

+     <div class="loader-ellipsis">

+       <div></div>

+       <div></div>

+     </div>

+   </div>

+ 

+   <!--Player Component-->

+   <div class="player">

+     <img src="./assets/img/podcastbanner.png" class="banner">

+     <h1>{{selectedPodcast.title}}</h1>

+     <h2>{{selectedPodcast.description}}</h2>

+     <div class="progress">

+       <progress [value]=progressValue max="1"></progress>

+       <div class="time">

+         <span>{{currTime}}</span>

+         <span>{{totalLength}}</span>

+       </div>

+     </div>

+     <div class="controls">

+       <button tappable (click)="rewind()">

+         <img src="./assets/img/player-rewind.svg">

+       </button>

+       <button tappable (click)="togglePlay()">

+         <img [src]=playerIcon>

+       </button>

+       <button tappable (click)="forward()">

+         <img src="./assets/img/player-forward.svg">

+       </button>

+     </div>

+   </div>

+ </ion-content>

@@ -0,0 +1,96 @@ 

+ import ENV from '@environment';

+ import { HttpClient } from '@angular/common/http';

+ import { Observable } from 'rxjs/Observable';

+ import { chooseEndpoint } from '../../utils';

+ import { map } from 'rxjs/operators';

+ import { Injectable } from '@angular/core';

+ 

+ /* Endpoint for this service.

+ *

+ * Since Simplecast API does not support CORS, we need to use a proxy when running

+ * on browser platforms.

+ */

+ const ENDPOINT = chooseEndpoint('/podcast', 'https://api.simplecast.com/v1/');

+ 

+ /**

+  * Podcast ID of Fedora Podcast

+  */

+ const PODCAST_ID = 4543

+ 

+ /**

+  * Represents a podcast fetched from Simplecast

+  */

+ export interface Podcast {

+     /**

+      * Unique ID of the podcast, supplied by the Simplecast API

+      */

+     id: number,

+ 

+     /**

+      * The Time duration of the podcast

+      */

+     duration: number,

+ 

+     /**

+      * The Podcast Number

+      */

+ 

+     number: number,

+ 

+     /**

+      * Title of the podcast

+      */

+ 

+     title: string,

+ 

+     /**

+      * Description of the podcast

+      */

+     description: string,

+ 

+     /**

+      * Date of publishing the podcast

+      */

+     publishedAt: Date,

+ 

+     /**

+      * URL of Podcast Source File

+      */

+     audioUrl: string,

+ 

+     /**

+      * Sharing URL of the Podcast Source File

+      */

+     shareUrl: string

+ }

+ 

+ /**

+  * Service for fetching Podcasts from Community  API

+  */

+ @Injectable()

+ export class PodcastService {

+     constructor(private http: HttpClient) {

+     }

+ 

+     /**

+      * Fetch the list of latest podcastss on Fedora Community 

+      *

+      * @returns Observable which emits an array of Podcasts

+      */

+     getPodcasts(): Observable<Podcast[]> {

+         return this.http.get(`${ENDPOINT}/podcasts/${PODCAST_ID}/episodes.json`, { headers: { 'X-API-KEY': ENV.SIMPLECAST_CONFIG.apiKey } })

+             .pipe(

+                 map((data: any[]) => data.map((podcast: any) => ({

+                     id: podcast.id,

+                     number: podcast.number,

+                     title: podcast.title,

+                     duration: podcast.duration,

+                     displayTime: Math.floor(podcast.duration/60),//convert to minutes

+                     description: podcast.description,

+                     publishedAt: podcast.published_at,

+                     audioUrl: podcast.audio_url,

+                     shareUrl: podcast.share_url

+                 })))

+             );

+     }

+ } 

\ No newline at end of file