From 55011818e389c26c698a05b2f6c9ff570b43ed3b Mon Sep 17 00:00:00 2001
From: Amitosh Swain Mahapatra <amitosh.swain@gmail.com>
Date: Jun 27 2018 12:28:49 +0000
Subject: [PATCH 1/2] Add package search


---

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 3ba77d7..6e42183 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -19,6 +19,7 @@ import { CalendarPage } from '../pages/calendar/calendar';
 import { MorePage} from '../pages/more/more';
 import { NotificationsPage} from '../pages/notifications/notifications';
 import { WomenPage } from '../pages/women/women';
+import { PackageSearchPage } from '../pages/package-search/package-search';
 
 import { Browser } from '../providers/browser/browser';
 
@@ -32,7 +33,8 @@ import { Browser } from '../providers/browser/browser';
     CalendarPage,
     MorePage,
     NotificationsPage,
-    WomenPage
+    WomenPage,
+    PackageSearchPage
   ],
   imports: [
     BrowserModule,
@@ -50,7 +52,8 @@ import { Browser } from '../providers/browser/browser';
     HomePage,
     MorePage,
     NotificationsPage,
-    WomenPage
+    WomenPage,
+    PackageSearchPage
   ],
   providers: [
     Browser,
diff --git a/src/app/app.scss b/src/app/app.scss
index 4344684..a36de09 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -122,3 +122,14 @@ ion-navbar {
     background-color: #fff !important;
     box-shadow: 0 2px 4px 0 rgba(86, 86, 86, 0.05);
 }
+
+[padding] ion-list ion-item {
+    h1:first-child,
+    h2:first-child,
+    h3:first-child,
+    h4:first-child,
+    h5:first-child,
+    h6:first-child {
+        margin-top: unset;
+    }
+}
diff --git a/src/pages/more/more.html b/src/pages/more/more.html
index 7fe188f..a129c0e 100644
--- a/src/pages/more/more.html
+++ b/src/pages/more/more.html
@@ -4,15 +4,11 @@
   See http://ionicframework.com/docs/components/#navigation for more info on
   Ionic pages and navigation.
 -->
-<ion-header>
-
-  <ion-navbar>
-    <ion-title>more</ion-title>
-  </ion-navbar>
-
-</ion-header>
-
-
 <ion-content padding>
-
+  <ion-list>
+    <button ion-item (click)="open('packageSearch')">
+      <ion-icon name="search" item-start></ion-icon>
+        Package Search
+    </button>
+  </ion-list>
 </ion-content>
diff --git a/src/pages/more/more.ts b/src/pages/more/more.ts
index 2a9ef14..8dcab3b 100644
--- a/src/pages/more/more.ts
+++ b/src/pages/more/more.ts
@@ -1,5 +1,6 @@
 import { Component } from '@angular/core';
-import { NavController, NavParams } from 'ionic-angular';
+import { NavController } from 'ionic-angular';
+import { PackageSearchPage } from '../package-search/package-search';
 
 @Component({
   selector: 'page-more',
@@ -7,6 +8,14 @@ import { NavController, NavParams } from 'ionic-angular';
 })
 export class MorePage {
 
-  constructor(public navCtrl: NavController, public navParams: NavParams) {
+  pages = {
+    packageSearch: PackageSearchPage
+  };
+
+  constructor(private navCtrl: NavController) {
+  }
+
+  open(page:string) {
+    this.navCtrl.push(this.pages[page]);
   }
 }
diff --git a/src/pages/package-search/package-search.html b/src/pages/package-search/package-search.html
new file mode 100644
index 0000000..0bf676f
--- /dev/null
+++ b/src/pages/package-search/package-search.html
@@ -0,0 +1,28 @@
+<!--
+  Generated template for the SearchPage page.
+
+  See http://ionicframework.com/docs/components/#navigation for more info on
+  Ionic pages and navigation.
+-->
+<ion-header>
+
+  <ion-navbar>
+    <ion-title>Fedora Package Search</ion-title>
+  </ion-navbar>
+
+</ion-header>
+
+
+<ion-content padding>
+  <ion-searchbar [(ngModel)]="searchQuery" (cancel)="clear()" (change)="search()" autocorrect="off"></ion-searchbar>
+  <ion-list>
+    <ion-list-header *ngIf="packages.length > 0">
+        Showing {{ matches }} results
+      </ion-list-header>
+    <ion-item *ngFor="let pkg of packages">
+      <ion-icon name="cube" item-start></ion-icon>
+      <h2>{{ pkg.name }}</h2>
+      <h3>{{ pkg.summary }}</h3>
+    </ion-item>
+  </ion-list>
+</ion-content>
diff --git a/src/pages/package-search/package-search.scss b/src/pages/package-search/package-search.scss
new file mode 100644
index 0000000..dfc5957
--- /dev/null
+++ b/src/pages/package-search/package-search.scss
@@ -0,0 +1,3 @@
+page-search {
+
+}
diff --git a/src/pages/package-search/package-search.ts b/src/pages/package-search/package-search.ts
new file mode 100644
index 0000000..3155cdd
--- /dev/null
+++ b/src/pages/package-search/package-search.ts
@@ -0,0 +1,35 @@
+import { Component } from '@angular/core';
+import { NavController } from 'ionic-angular';
+import { PackageSearchProvider, Package } from '../../providers/package-search/package-search';
+
+const RESULTS_PER_PAGE = 10;
+
+@Component({
+  templateUrl: 'package-search.html',
+  providers: [PackageSearchProvider]
+})
+export class PackageSearchPage {
+
+  private searchQuery:string;
+  private offset = 0;
+  private matches = 0;
+  private packages:Package[] = [];
+
+  constructor(public navCtrl: NavController, private searchProvider:PackageSearchProvider) {
+  }
+
+  search() {
+    this.searchProvider.search(this.searchQuery, RESULTS_PER_PAGE, this.offset)
+      .subscribe( r => {
+        this.packages = r.packages;
+        this.offset = r.offset;
+        this.matches = r.matches;
+      });
+  }
+
+  clear() {
+    this.packages = [];
+    this.offset = 0;
+    this.matches = 0;
+  }
+}
diff --git a/src/providers/package-search/package-search.ts b/src/providers/package-search/package-search.ts
new file mode 100644
index 0000000..b1921a8
--- /dev/null
+++ b/src/providers/package-search/package-search.ts
@@ -0,0 +1,62 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+const API_ENDPOINT = 'https://apps.fedoraproject.org/packages/fcomm_connector/xapian/query/search_packages/';
+
+export interface SearchResult {
+  matches: number,
+  count: number,
+  offset: number,
+  pageSize: number,
+  packages: Package[]
+}
+
+export interface Package {
+  name: string,
+  summary: string,
+  upstreamUrl: string,
+  description: string,
+  branch: string,
+  develOwner: string,
+  subPackages: Package[]
+}
+
+function mapPackage(pkg: any): Package {
+  return {
+    name: pkg.name,
+    summary: pkg.summary,
+    upstreamUrl: pkg.upstream_url,
+    description: pkg.description,
+    branch: pkg.branch,
+    develOwner: pkg.devel_owner,
+    subPackages: pkg.sub_pkgs ? pkg.sub_pkgs.map(mapPackage) : false
+  };
+}
+
+@Injectable()
+export class PackageSearchProvider {
+
+  constructor(private http: HttpClient) {
+  }
+
+  public search(query: string, limit = 10, offset = 0): Observable<SearchResult> {
+    const queryObjectStr = JSON.stringify({
+      filters: {
+        search: query
+      },
+      rows_per_page: limit,
+      start_row: offset
+    });
+
+    return this.http.get(`${API_ENDPOINT}/${queryObjectStr}`)
+      .map((res: any) => ({
+        matches: res.total_rows,
+        count: res.visible_rows,
+        offset: res.start_row,
+        pageSize: res.rows_per_page,
+        packages: res.rows.map(mapPackage)
+      }));
+  }
+
+}

From 9d85cb1d4d1b622205dda96338890f3b2e13ae1f Mon Sep 17 00:00:00 2001
From: Amitosh Swain Mahapatra <amitosh.swain@gmail.com>
Date: Jun 27 2018 12:29:00 +0000
Subject: [PATCH 2/2] Add detailed package view


---

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 6e42183..585596f 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -20,6 +20,7 @@ import { MorePage} from '../pages/more/more';
 import { NotificationsPage} from '../pages/notifications/notifications';
 import { WomenPage } from '../pages/women/women';
 import { PackageSearchPage } from '../pages/package-search/package-search';
+import { ViewPackagePage } from '../pages/view-package/view-package';
 
 import { Browser } from '../providers/browser/browser';
 
@@ -34,7 +35,8 @@ import { Browser } from '../providers/browser/browser';
     MorePage,
     NotificationsPage,
     WomenPage,
-    PackageSearchPage
+    PackageSearchPage,
+    ViewPackagePage
   ],
   imports: [
     BrowserModule,
@@ -53,7 +55,8 @@ import { Browser } from '../providers/browser/browser';
     MorePage,
     NotificationsPage,
     WomenPage,
-    PackageSearchPage
+    PackageSearchPage,
+    ViewPackagePage
   ],
   providers: [
     Browser,
diff --git a/src/app/app.scss b/src/app/app.scss
index a36de09..7d0f64f 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -133,3 +133,11 @@ ion-navbar {
         margin-top: unset;
     }
 }
+
+.pkg-lib {
+  color: #03A9F4
+}
+
+.pkg-devel {
+  color: #FFEB3B;
+}
diff --git a/src/pages/package-search/package-search.html b/src/pages/package-search/package-search.html
index 0bf676f..015bded 100644
--- a/src/pages/package-search/package-search.html
+++ b/src/pages/package-search/package-search.html
@@ -19,8 +19,8 @@
     <ion-list-header *ngIf="packages.length > 0">
         Showing {{ matches }} results
       </ion-list-header>
-    <ion-item *ngFor="let pkg of packages">
-      <ion-icon name="cube" item-start></ion-icon>
+    <ion-item (click)="showPackage(pkg)" *ngFor="let pkg of packages">
+      <ion-icon [ngClass]="getPackageType(pkg)" name="cube" item-start></ion-icon>
       <h2>{{ pkg.name }}</h2>
       <h3>{{ pkg.summary }}</h3>
     </ion-item>
diff --git a/src/pages/package-search/package-search.ts b/src/pages/package-search/package-search.ts
index 3155cdd..684f14e 100644
--- a/src/pages/package-search/package-search.ts
+++ b/src/pages/package-search/package-search.ts
@@ -1,6 +1,8 @@
 import { Component } from '@angular/core';
 import { NavController } from 'ionic-angular';
 import { PackageSearchProvider, Package } from '../../providers/package-search/package-search';
+import { ViewPackagePage } from '../view-package/view-package';
+import { getPackageType } from '../../utils';
 
 const RESULTS_PER_PAGE = 10;
 
@@ -32,4 +34,12 @@ export class PackageSearchPage {
     this.offset = 0;
     this.matches = 0;
   }
+
+  showPackage(pkg:Package) {
+    this.navCtrl.push(ViewPackagePage, { pkg });
+  }
+
+  getPackageType(pkg:Package) {
+    return getPackageType(pkg.name);
+  }
 }
diff --git a/src/pages/view-package/view-package.html b/src/pages/view-package/view-package.html
new file mode 100644
index 0000000..77955b4
--- /dev/null
+++ b/src/pages/view-package/view-package.html
@@ -0,0 +1,36 @@
+<!--
+  Generated template for the ViewPackagePage page.
+
+  See http://ionicframework.com/docs/components/#navigation for more info on
+  Ionic pages and navigation.
+-->
+<ion-header>
+
+  <ion-navbar>
+    <ion-title>Package {{ pkg.name }}</ion-title>
+  </ion-navbar>
+
+</ion-header>
+
+
+<ion-content padding>
+  <h1>{{ pkg.name }}</h1>
+  <p>{{ pkg.summary }}</p>
+  <p *ngIf="pkg.upstreamUrl">
+    <a href="{{ pkg.upstreamUrl }}">{{ pkg.upstreamUrl }}</a>
+  </p>
+  <h3>Description</h3>
+  <p>{{ pkg.description }}</p>
+  <h3>Install</h3>
+  <p class="code-block" padding>
+    <code>$ <span>sudo dnf install {{ pkg.name }}</span></code>
+  </p>
+  <h3 *ngIf="pkg.subPackages.length > 0">Sub packages</h3>
+  <ion-list *ngIf="pkg.subPackages.length > 0">
+    <ion-item (click)="showPackage(pkg)" *ngFor="let pkg of pkg.subPackages">
+      <ion-icon [ngClass]="getPackageType(pkg)" name="cube" item-start></ion-icon>
+      <h2>{{ pkg.name }}</h2>
+      <h3>{{ pkg.summary }}</h3>
+    </ion-item>
+  </ion-list>
+</ion-content>
diff --git a/src/pages/view-package/view-package.scss b/src/pages/view-package/view-package.scss
new file mode 100644
index 0000000..82f4cdb
--- /dev/null
+++ b/src/pages/view-package/view-package.scss
@@ -0,0 +1,6 @@
+page-view-package {
+  .code-block {
+    background: #f0f0f0;
+    border-radius: 3px;
+  }
+}
diff --git a/src/pages/view-package/view-package.ts b/src/pages/view-package/view-package.ts
new file mode 100644
index 0000000..2d27ad9
--- /dev/null
+++ b/src/pages/view-package/view-package.ts
@@ -0,0 +1,32 @@
+import { Component } from '@angular/core';
+import { NavController, NavParams } from 'ionic-angular';
+import { Package } from 'providers/package-search/package-search';
+import { getPackageType } from '../../utils';
+
+/**
+ * Generated class for the ViewPackagePage page.
+ *
+ * See https://ionicframework.com/docs/components/#navigation for more info on
+ * Ionic pages and navigation.
+ */
+
+@Component({
+  selector: 'page-view-package',
+  templateUrl: 'view-package.html',
+})
+export class ViewPackagePage {
+
+  private pkg: Package;
+
+  constructor(public navCtrl: NavController, public navParams: NavParams) {
+    this.pkg = this.navParams.get('pkg') || {};
+  }
+
+  showPackage(pkg: Package) {
+    this.navCtrl.push(ViewPackagePage, { pkg });
+  }
+
+  getPackageType(pkg:Package) {
+    return getPackageType(pkg.name);
+  }
+}
diff --git a/src/utils.ts b/src/utils.ts
index a541bfe..8ea8f46 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -38,3 +38,27 @@ export function beautifyDate(postDate: any, type: string): string {
     return jsDate[1] + ' ' + jsDate[2] + ', ' + jsDate[3];
   }
 }
+
+/**
+ * Return the type of package from it names.
+ *
+ * Matches the name against the Fedora packaging guidelines
+ * Taken from: https://fedoraproject.org/wiki/Packaging:Naming?rd=Packaging:NamingGuidelines
+ * @param pkgName package name
+ */
+export function getPackageType(pkgName:string) {
+  if (pkgName.endsWith('-devel')) {
+    return 'pkg-devel';
+  } else if (pkgName.endsWith('-libs')) {
+    return 'pkg-lib';
+  } else if(pkgName.endsWith('-doc')) {
+    return 'pkg-doc';
+  } else if(/^\w*-langpack(-[a-zA-Z]{2})?$/.test(pkgName)) {
+    return 'pkg-langpack';
+  } else if(pkgName.endsWith('-fonts')) {
+    return 'pkg-font';
+  }
+
+  return '';
+}
+

