Browse Source

Search for tweets and users

Carsten Porth 5 years ago
parent
commit
505904b1c0
26 changed files with 417 additions and 31 deletions
  1. 1 1
      app/package.json
  2. 13 1
      app/src/app/app.module.ts
  3. 2 2
      app/src/components/components.module.ts
  4. 3 3
      app/src/components/hashtag/hashtag.ts
  5. 3 3
      app/src/components/mention/mention.ts
  6. 4 9
      app/src/components/tweet-header/tweet-header.ts
  7. 4 0
      app/src/pages/search-results-tweets-popular/search-results-tweets-popular.html
  8. 13 0
      app/src/pages/search-results-tweets-popular/search-results-tweets-popular.module.ts
  9. 5 0
      app/src/pages/search-results-tweets-popular/search-results-tweets-popular.scss
  10. 69 0
      app/src/pages/search-results-tweets-popular/search-results-tweets-popular.ts
  11. 4 0
      app/src/pages/search-results-tweets-recent/search-results-tweets-recent.html
  12. 13 0
      app/src/pages/search-results-tweets-recent/search-results-tweets-recent.module.ts
  13. 5 0
      app/src/pages/search-results-tweets-recent/search-results-tweets-recent.scss
  14. 69 0
      app/src/pages/search-results-tweets-recent/search-results-tweets-recent.ts
  15. 6 0
      app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.html
  16. 13 0
      app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.module.ts
  17. 3 0
      app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.scss
  18. 21 0
      app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.ts
  19. 24 0
      app/src/pages/search-results-users/search-results-users.html
  20. 13 0
      app/src/pages/search-results-users/search-results-users.module.ts
  21. 7 0
      app/src/pages/search-results-users/search-results-users.scss
  22. 58 0
      app/src/pages/search-results-users/search-results-users.ts
  23. 8 5
      app/src/pages/search/search.html
  24. 7 0
      app/src/pages/search/search.scss
  25. 15 7
      app/src/pages/search/search.ts
  26. 34 0
      app/src/providers/twitter-api/twitter-api.ts

+ 1 - 1
app/package.json

@@ -74,4 +74,4 @@
       "android"
     ]
   }
-}
+}

+ 13 - 1
app/src/app/app.module.ts

@@ -29,6 +29,10 @@ import { P2pDatabaseGunProvider } from "../providers/p2p-database-gun/p2p-databa
 import { FeedProvider } from "../providers/feed/feed";
 import { MentionComponent } from "../components/mention/mention";
 import { HashtagComponent } from "../components/hashtag/hashtag";
+import { SearchResultsUsersPage } from "../pages/search-results-users/search-results-users";
+import { SearchResultsTweetsPopularPage } from "../pages/search-results-tweets-popular/search-results-tweets-popular";
+import { SearchResultsTweetsRecentPage } from "../pages/search-results-tweets-recent/search-results-tweets-recent";
+import { SearchResultsTweetsTabsPage } from "../pages/search-results-tweets-tabs/search-results-tweets-tabs";
 
 @NgModule({
   declarations: [
@@ -39,6 +43,10 @@ import { HashtagComponent } from "../components/hashtag/hashtag";
     LoginPage,
     ProfilePage,
     WriteTweetPage,
+    SearchResultsTweetsTabsPage,
+    SearchResultsTweetsRecentPage,
+    SearchResultsTweetsPopularPage,
+    SearchResultsUsersPage,
     FeedComponent,
     TweetComponent,
     TweetHeaderComponent,
@@ -64,7 +72,11 @@ import { HashtagComponent } from "../components/hashtag/hashtag";
     SettingsPage,
     LoginPage,
     ProfilePage,
-    WriteTweetPage
+    WriteTweetPage,
+    SearchResultsTweetsTabsPage,
+    SearchResultsTweetsRecentPage,
+    SearchResultsTweetsPopularPage,
+    SearchResultsUsersPage
   ],
   providers: [
     StatusBar,

+ 2 - 2
app/src/components/components.module.ts

@@ -6,8 +6,8 @@ import { TweetBodyComponent } from "./tweet-body/tweet-body";
 import { TweetActionsComponent } from "./tweet-actions/tweet-actions";
 import { ProfileHeaderComponent } from "./profile-header/profile-header";
 import { QuotedStatusComponent } from "./quoted-status/quoted-status";
-import { HashtagComponent } from './hashtag/hashtag';
-import { MentionComponent } from './mention/mention';
+import { HashtagComponent } from "./hashtag/hashtag";
+import { MentionComponent } from "./mention/mention";
 @NgModule({
   declarations: [
     FeedComponent,

+ 3 - 3
app/src/components/hashtag/hashtag.ts

@@ -1,5 +1,5 @@
 import { Component, Input } from "@angular/core";
-import { NavController } from "ionic-angular";
+import { App } from "ionic-angular";
 import { SearchPage } from "../../pages/search/search";
 
 @Component({
@@ -10,9 +10,9 @@ export class HashtagComponent {
   @Input()
   hashtag;
 
-  constructor(public navCtrl: NavController) {}
+  constructor(private appCtrl: App) {}
 
   search(hashtag) {
-    this.navCtrl.push(SearchPage, { hashtag });
+    this.appCtrl.getRootNav().push(SearchPage, { keyword: hashtag });
   }
 }

+ 3 - 3
app/src/components/mention/mention.ts

@@ -1,5 +1,5 @@
 import { Component, Input } from "@angular/core";
-import { NavController } from "ionic-angular";
+import { App } from "ionic-angular";
 import { ProfilePage } from "../../pages/profile/profile";
 
 @Component({
@@ -12,9 +12,9 @@ export class MentionComponent {
   @Input()
   userId: string;
 
-  constructor(public navCtrl: NavController) {}
+  constructor(private appCtrl: App) {}
 
   showProfile(userId) {
-    this.navCtrl.push(ProfilePage, { userId });
+    this.appCtrl.getRootNav().push(ProfilePage, { userId });
   }
 }

+ 4 - 9
app/src/components/tweet-header/tweet-header.ts

@@ -1,13 +1,8 @@
 import { Component, Input } from "@angular/core";
-import { NavController, ActionSheetController } from "ionic-angular";
+import { ActionSheetController, App } from "ionic-angular";
 import { ProfilePage } from "../../pages/profile/profile";
 import { TwitterApiProvider } from "../../providers/twitter-api/twitter-api";
-/**
- * Generated class for the TweetHeaderComponent component.
- *
- * See https://angular.io/api/core/Component for more info on Angular
- * Components.
- */
+
 @Component({
   selector: "tweet-header",
   templateUrl: "tweet-header.html"
@@ -19,13 +14,13 @@ export class TweetHeaderComponent {
   tweetCreatedAt: string;
 
   constructor(
-    public navCtrl: NavController,
+    private appCtrl: App,
     public actionSheetCtrl: ActionSheetController,
     private twitter: TwitterApiProvider
   ) {}
 
   showProfile(userId) {
-    this.navCtrl.push(ProfilePage, { userId });
+    this.appCtrl.getRootNav().push(ProfilePage, { userId });
   }
 
   showActions(userId) {

+ 4 - 0
app/src/pages/search-results-tweets-popular/search-results-tweets-popular.html

@@ -0,0 +1,4 @@
+<ion-content no-padding>
+  <feed [data]="tweets.statuses" (onRefresh)="doRefresh($event)" (onLoadMore)="loadMore($event)" [enableRefresh]="enableRefresh"
+    [enableInfiniteScroll]="enableInfiniteScroll"></feed>
+</ion-content>

+ 13 - 0
app/src/pages/search-results-tweets-popular/search-results-tweets-popular.module.ts

@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { SearchResultsTweetsPopularPage } from './search-results-tweets-popular';
+
+@NgModule({
+  declarations: [
+    SearchResultsTweetsPopularPage,
+  ],
+  imports: [
+    IonicPageModule.forChild(SearchResultsTweetsPopularPage),
+  ],
+})
+export class SearchResultsTweetsPopularPageModule {}

+ 5 - 0
app/src/pages/search-results-tweets-popular/search-results-tweets-popular.scss

@@ -0,0 +1,5 @@
+page-search-results-tweets-popular {
+    feed .scroll-content {
+        margin-top: 0 !important;
+    }
+}

+ 69 - 0
app/src/pages/search-results-tweets-popular/search-results-tweets-popular.ts

@@ -0,0 +1,69 @@
+import { Component } from "@angular/core";
+import {
+  IonicPage,
+  NavController,
+  NavParams,
+  Refresher,
+  InfiniteScroll
+} from "ionic-angular";
+import { TwitterApiProvider } from "../../providers/twitter-api/twitter-api";
+
+@IonicPage()
+@Component({
+  selector: "page-search-results-tweets-popular",
+  templateUrl: "search-results-tweets-popular.html"
+})
+export class SearchResultsTweetsPopularPage {
+  keyword: string;
+  tweets = [];
+
+  constructor(
+    public navCtrl: NavController,
+    public navParams: NavParams,
+    private twitter: TwitterApiProvider
+  ) {
+    this.keyword = navParams.data;
+  }
+
+  async ionViewDidLoad() {
+    if (this.keyword.length) {
+      this.tweets = await this.twitter.searchPopularTweets(this.keyword);
+    }
+  }
+
+  doRefresh(refresher: Refresher) {
+    this.twitter.searchPopularTweets(this.keyword).then(tweets => {
+      this.tweets = tweets;
+      refresher.complete();
+    });
+  }
+
+  loadMore(infiniteScroll: InfiniteScroll) {
+    this.twitter
+      .searchPopularTweets(this.keyword, this.oldestTweet)
+      .then(tweets => {
+        this.tweets["statuses"] = this.tweets["statuses"].concat(
+          tweets["statuses"]
+        );
+        infiniteScroll.complete();
+      });
+  }
+
+  get oldestTweet() {
+    if (this.tweets.length > 0) {
+      return this.tweets.reduce((acc, cur) => (acc.id < cur.id ? acc : cur))[
+        "id_str"
+      ];
+    } else {
+      return undefined;
+    }
+  }
+
+  get enableRefresh() {
+    return this.keyword.length > 0;
+  }
+
+  get enableInfiniteScroll() {
+    return this.keyword.length > 0;
+  }
+}

+ 4 - 0
app/src/pages/search-results-tweets-recent/search-results-tweets-recent.html

@@ -0,0 +1,4 @@
+<ion-content no-padding>
+  <feed [data]="tweets.statuses" (onRefresh)="doRefresh($event)" (onLoadMore)="loadMore($event)" [enableRefresh]="enableRefresh"
+    [enableInfiniteScroll]="enableInfiniteScroll"></feed>
+</ion-content>

+ 13 - 0
app/src/pages/search-results-tweets-recent/search-results-tweets-recent.module.ts

@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { SearchResultsTweetsRecentPage } from './search-results-tweets-recent';
+
+@NgModule({
+  declarations: [
+    SearchResultsTweetsRecentPage,
+  ],
+  imports: [
+    IonicPageModule.forChild(SearchResultsTweetsRecentPage),
+  ],
+})
+export class SearchResultsTweetsRecentPageModule {}

+ 5 - 0
app/src/pages/search-results-tweets-recent/search-results-tweets-recent.scss

@@ -0,0 +1,5 @@
+page-search-results-tweets-recent {
+    feed .scroll-content {
+        margin-top: 0 !important;
+    }
+}

+ 69 - 0
app/src/pages/search-results-tweets-recent/search-results-tweets-recent.ts

@@ -0,0 +1,69 @@
+import { Component } from "@angular/core";
+import {
+  IonicPage,
+  NavController,
+  NavParams,
+  Refresher,
+  InfiniteScroll
+} from "ionic-angular";
+import { TwitterApiProvider } from "../../providers/twitter-api/twitter-api";
+
+@IonicPage()
+@Component({
+  selector: "page-search-results-tweets-recent",
+  templateUrl: "search-results-tweets-recent.html"
+})
+export class SearchResultsTweetsRecentPage {
+  keyword: string;
+  tweets = [];
+
+  constructor(
+    public navCtrl: NavController,
+    public navParams: NavParams,
+    private twitter: TwitterApiProvider
+  ) {
+    this.keyword = navParams.data;
+  }
+
+  async ionViewDidLoad() {
+    if (this.keyword.length) {
+      this.tweets = await this.twitter.searchRecentTweets(this.keyword);
+    }
+  }
+
+  doRefresh(refresher: Refresher) {
+    this.twitter.searchRecentTweets(this.keyword).then(tweets => {
+      this.tweets = tweets;
+      refresher.complete();
+    });
+  }
+
+  loadMore(infiniteScroll: InfiniteScroll) {
+    this.twitter
+      .searchRecentTweets(this.keyword, this.oldestTweet)
+      .then(tweets => {
+        this.tweets["statuses"] = this.tweets["statuses"].concat(
+          tweets["statuses"]
+        );
+        infiniteScroll.complete();
+      });
+  }
+
+  get oldestTweet() {
+    if (this.tweets.length > 0) {
+      return this.tweets.reduce((acc, cur) => (acc.id < cur.id ? acc : cur))[
+        "id_str"
+      ];
+    } else {
+      return undefined;
+    }
+  }
+
+  get enableRefresh() {
+    return this.keyword.length > 0;
+  }
+
+  get enableInfiniteScroll() {
+    return this.keyword.length > 0;
+  }
+}

+ 6 - 0
app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.html

@@ -0,0 +1,6 @@
+<ion-content no-padding>
+  <ion-tabs selectedIndex="0" tabsPlacement="top">
+    <ion-tab [root]="searchResultsRecentTweets" [rootParams]="keyword" tabTitle="Recent"></ion-tab>
+    <ion-tab [root]="searchResultsPopularTweets" [rootParams]="keyword" tabTitle="Popular"></ion-tab>
+  </ion-tabs>
+</ion-content>

+ 13 - 0
app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.module.ts

@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { SearchResultsTweetsTabsPage } from './search-results-tweets-tabs';
+
+@NgModule({
+  declarations: [
+    SearchResultsTweetsTabsPage,
+  ],
+  imports: [
+    IonicPageModule.forChild(SearchResultsTweetsTabsPage),
+  ],
+})
+export class SearchResultsTweetsTabsPageModule {}

+ 3 - 0
app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.scss

@@ -0,0 +1,3 @@
+page-search-results-tweets-tabs {
+    margin-top: 56px;
+}

+ 21 - 0
app/src/pages/search-results-tweets-tabs/search-results-tweets-tabs.ts

@@ -0,0 +1,21 @@
+import { Component } from "@angular/core";
+import { IonicPage, NavController, NavParams } from "ionic-angular";
+import { SearchResultsTweetsRecentPage } from "../search-results-tweets-recent/search-results-tweets-recent";
+import { SearchResultsTweetsPopularPage } from "../search-results-tweets-popular/search-results-tweets-popular";
+
+@IonicPage()
+@Component({
+  selector: "page-search-results-tweets-tabs",
+  templateUrl: "search-results-tweets-tabs.html"
+})
+export class SearchResultsTweetsTabsPage {
+  searchResultsRecentTweets = SearchResultsTweetsRecentPage;
+  searchResultsPopularTweets = SearchResultsTweetsPopularPage;
+  keyword: string;
+
+  constructor(public navCtrl: NavController, public navParams: NavParams) {
+    this.keyword = navParams.data;
+  }
+
+  ionViewDidLoad() {}
+}

+ 24 - 0
app/src/pages/search-results-users/search-results-users.html

@@ -0,0 +1,24 @@
+<ion-content no-padding fullscreen>
+  <ion-refresher (ionRefresh)="doRefresh($event)" enabled="keyword.length">
+    <ion-refresher-content pullingText=" Pull to refresh" refreshingText="Refreshing...">
+    </ion-refresher-content>
+  </ion-refresher>
+
+  <ion-list>
+    <ion-item *ngFor="let user of users" (click)="showProfile(user.id_str)">
+      <ion-avatar item-start>
+        <img src="{{ user.profile_image_url_https }}">
+      </ion-avatar>
+      <h2>
+        {{ user.name }}
+        <ion-icon name="ios-checkmark-circle" *ngIf="user.verified" class="icon-verified"></ion-icon>
+        <ion-icon name="ios-lock-outline" *ngIf="user.protected" class="icon-protected"></ion-icon>
+      </h2>
+      <p>@{{ user.screen_name }}</p>
+    </ion-item>
+  </ion-list>
+
+  <ion-infinite-scroll (ionInfinite)="loadMore($event)" enabled="keyword.length">
+    <ion-infinite-scroll-content loadingText="Loading more users..."></ion-infinite-scroll-content>
+  </ion-infinite-scroll>
+</ion-content>

+ 13 - 0
app/src/pages/search-results-users/search-results-users.module.ts

@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { SearchResultsUsersPage } from './search-results-users';
+
+@NgModule({
+  declarations: [
+    SearchResultsUsersPage,
+  ],
+  imports: [
+    IonicPageModule.forChild(SearchResultsUsersPage),
+  ],
+})
+export class SearchResultsUsersPageModule {}

+ 7 - 0
app/src/pages/search-results-users/search-results-users.scss

@@ -0,0 +1,7 @@
+page-search-results-users {
+  margin-top: 56px;
+  .icon-verified,
+  .icon-protected {
+    font-size: 1em;
+  }
+}

+ 58 - 0
app/src/pages/search-results-users/search-results-users.ts

@@ -0,0 +1,58 @@
+import { Component } from "@angular/core";
+import {
+  IonicPage,
+  NavController,
+  NavParams,
+  Refresher,
+  InfiniteScroll,
+  App
+} from "ionic-angular";
+import { TwitterApiProvider } from "../../providers/twitter-api/twitter-api";
+import { ProfilePage } from "../profile/profile";
+
+@IonicPage()
+@Component({
+  selector: "page-search-results-users",
+  templateUrl: "search-results-users.html"
+})
+export class SearchResultsUsersPage {
+  keyword: string;
+  nextPage: number = 2;
+  users: any[] = [];
+
+  constructor(
+    public navCtrl: NavController,
+    public navParams: NavParams,
+    private appCtrl: App,
+    private twitter: TwitterApiProvider
+  ) {
+    this.keyword = navParams.data;
+  }
+
+  async ionViewDidLoad() {
+    if (this.keyword.length) {
+      this.users = await this.twitter.searchUsers(this.keyword);
+    }
+  }
+
+  showProfile(userId) {
+    this.appCtrl.getRootNav().push(ProfilePage, { userId });
+    this.nextPage = 2;
+  }
+
+  doRefresh(refresher: Refresher) {
+    this.twitter.searchUsers(this.keyword).then(users => {
+      this.users = users;
+      this.nextPage = 2;
+      refresher.complete();
+    });
+  }
+
+  loadMore(infiniteScroll: InfiniteScroll) {
+    this.twitter.searchUsers(this.keyword, this.nextPage).then(users => {
+      this.users = this.users.concat(users);
+      infiniteScroll.complete();
+      this.nextPage = this.nextPage + 1;
+    });
+  }
+}

+ 8 - 5
app/src/pages/search/search.html

@@ -1,12 +1,15 @@
 <ion-header>
-    <ion-navbar>
-        <button ion-button menuToggle>
+    <ion-navbar><button ion-button menuToggle>
             <ion-icon name="menu"></ion-icon>
         </button>
         <ion-title>Search</ion-title>
     </ion-navbar>
 </ion-header>
-
-<ion-content padding>
-    <p>Search...</p>
+<ion-content no-padding>
+    <ion-searchbar [(ngModel)]="keyword" (ionInput)="onInput($event)" (ionCancel)="onCancel($event)" class="searchbar"
+        debounce="500"></ion-searchbar>
+    <ion-tabs selectedIndex="0" tabsLayout="icon-start" tabsPlacement="bottom">
+        <ion-tab [root]="searchResultsTweets" [rootParams]="keyword" tabTitle="Tweets" tabIcon="logo-twitter"></ion-tab>
+        <ion-tab [root]="searchResultsUsers" [rootParams]="keyword" tabTitle="Users" tabIcon="person"></ion-tab>
+    </ion-tabs>
 </ion-content>

+ 7 - 0
app/src/pages/search/search.scss

@@ -0,0 +1,7 @@
+.searchbar {
+    z-index: 10;
+}
+
+ion-content.content-md {
+    background-color: #f8f8f8;
+}

+ 15 - 7
app/src/pages/search/search.ts

@@ -1,14 +1,22 @@
-import { Component } from '@angular/core';
-import { NavController } from 'ionic-angular';
+import { Component } from "@angular/core";
+import { NavController, NavParams } from "ionic-angular";
+import { SearchResultsUsersPage } from "../search-results-users/search-results-users";
+import { SearchResultsTweetsTabsPage } from "../search-results-tweets-tabs/search-results-tweets-tabs";
 
 @Component({
-    selector: 'page-search',
-    templateUrl: 'search.html'
+  selector: "page-search",
+  templateUrl: "search.html"
 })
 export class SearchPage {
+  searchResultsTweets = SearchResultsTweetsTabsPage;
+  searchResultsUsers = SearchResultsUsersPage;
+  keyword: string;
 
-    constructor(public navCtrl: NavController) {
-
-    }
+  constructor(public navCtrl: NavController, private navParams: NavParams) {
+    this.keyword = this.navParams.get("keyword");
+  }
 
+  onInput(event) {
+    console.log(event, this.keyword);
+  }
 }

+ 34 - 0
app/src/providers/twitter-api/twitter-api.ts

@@ -131,4 +131,38 @@ export class TwitterApiProvider {
       tweet_mode: "extended"
     });
   }
+
+  public async searchRecentTweets(keyword: string, maxId?: string) {
+    const res = await this.client.get("search/tweets", {
+      q: keyword,
+      result_type: "recent",
+      count: 20,
+      include_entities: true,
+      tweet_mode: "extended",
+      max_id: maxId
+    });
+    return res.data;
+  }
+
+  public async searchPopularTweets(keyword: string, maxId?: string) {
+    const res = await this.client.get("search/tweets", {
+      q: keyword,
+      result_type: "popular",
+      count: 20,
+      include_entities: true,
+      tweet_mode: "extended",
+      max_id: maxId
+    });
+    return res.data;
+  }
+
+  public async searchUsers(keyword: string, page?: number) {
+    const res = await this.client.get("users/search", {
+      q: keyword,
+      count: 10,
+      include_entities: true,
+      page: page
+    });
+    return res.data;
+  }
 }