Browse Source

User timeline with private tweets complete refactoring

Carsten Porth 5 years ago
parent
commit
e83688bace

+ 1 - 1
app/src/components/feed/feed.html

@@ -7,7 +7,7 @@
     <tweet *ngFor="let tweet of data" [data]="tweet"></tweet>
   </ion-list>
 
-  <ion-infinite-scroll (ionInfinite)="doInfinite($event)">
+  <ion-infinite-scroll (ionInfinite)="doInfinite($event)" enabled="{{enableInfiniteScroll}}">
     <ion-infinite-scroll-content loadingText="Loading more tweets..."></ion-infinite-scroll-content>
   </ion-infinite-scroll>
 </ion-content>

+ 2 - 0
app/src/components/feed/feed.ts

@@ -23,6 +23,8 @@ export class FeedComponent {
   data: any[];
   @Input()
   enableRefresh: boolean;
+  @Input()
+  enableInfiniteScroll: boolean;
   @Output()
   onRefresh: EventEmitter<any> = new EventEmitter<any>();
   @Output()

+ 10 - 10
app/src/pages/home/home.ts

@@ -42,16 +42,16 @@ export class HomePage {
             .concat(res.data)
             .sort((a, b) => this.sortByDateAsc(a, b)))
       );
-    this.gun
-      .getLastTweetFromUser("username")
-      .then(hash => this.ipfs.fetchTweet(hash))
-      .then(tweet => this.addUserObject(tweet))
-      .then(
-        res =>
-          (this.data = this.data
-            .concat(res)
-            .sort((a, b) => this.sortByDateAsc(a, b)))
-      );
+    // this.gun
+    //   .getLastTweetFromUser("username")
+    //   .then(hash => this.ipfs.fetchTweet(hash))
+    //   .then(tweet => this.addUserObject(tweet))
+    //   .then(
+    //     res =>
+    //       (this.data = this.data
+    //         .concat(res)
+    //         .sort((a, b) => this.sortByDateAsc(a, b)))
+    //   );
     this.twitter
       .fetchFriends(await this.storage.get("userId"))
       .then(res => console.log(res));

+ 2 - 1
app/src/pages/profile/profile.html

@@ -12,5 +12,6 @@
 
 <ion-content fullscreen (ionScroll)="onScroll($event)">
   <profile-header [user]="user"></profile-header>
-  <feed [data]="tweets" (onRefresh)="doRefresh($event)" (onLoadMore)="loadMore($event)" [enableRefresh]="enableRefresh"></feed>
+  <feed [data]="tweets" (onRefresh)="doRefresh($event)" (onLoadMore)="loadMore($event)" [enableRefresh]="enableRefresh"
+    [enableInfiniteScroll]="enableInfiniteScroll"></feed>
 </ion-content>

+ 97 - 20
app/src/pages/profile/profile.ts

@@ -23,10 +23,11 @@ import { P2pDatabaseGunProvider } from "../../providers/p2p-database-gun/p2p-dat
   templateUrl: "profile.html"
 })
 export class ProfilePage {
-  userId: string;
   user: any = [];
   tweets: any[];
+  oldestLoadedTweetId;
   enableRefresh: boolean = true;
+  enableInfiniteScroll: boolean = true;
 
   @ViewChild(Content)
   content: Content;
@@ -40,39 +41,115 @@ export class ProfilePage {
   ) {}
 
   ionViewDidLoad() {
-    this.userId = this.navParams.get("userId");
+    const userId = this.navParams.get("userId");
 
-    this.twitter.fetchUser(this.userId).then(res => (this.user = res));
+    this.twitter.fetchUser(userId).then(res => (this.user = res));
 
-    this.twitter
-      .fetchUserTimeline(this.userId)
-      .then(res => (this.tweets = res));
+    this.loadTimeline(userId, null).then(res => {
+      if (res.length > 0) {
+        // Save tweets
+        this.tweets = res;
 
-    this.checkP2pTweets("username");
+        // Save oldest tweet's id for next load more
+        this.oldestLoadedTweetId = res
+          .filter(tweet => !tweet.private_tweet)
+          .reduce((acc, cur) => (acc.id < cur.id ? acc : cur))["id"];
+      }
+    });
   }
 
   doRefresh(refresher) {
-    this.twitter.fetchUserTimeline(this.userId).then(res => {
-      this.tweets = res;
+    this.loadTimeline(this.user.id, null).then(res => {
+      if (res.length > 0) {
+        // Replace tweets
+        this.tweets = res;
+
+        // Save oldest tweet's id for next load more
+        this.oldestLoadedTweetId = res
+          .filter(tweet => !tweet.private_tweet)
+          .reduce((acc, cur) => (acc.id < cur.id ? acc : cur))["id"];
+      }
+
+      // Hide loading icon
       refresher.complete();
     });
   }
 
   loadMore(infiniteScroll: InfiniteScroll) {
-    this.twitter
-      .fetchUserTimelineSince(
-        this.userId,
-        this.tweets[this.tweets.length - 1].id
-      )
-      .then(res => {
+    this.loadTimeline(this.user.id, this.oldestLoadedTweetId).then(res => {
+      if (res.length > 0) {
+        // Append loaded tweets
         this.tweets = this.tweets.concat(res);
-        infiniteScroll.complete();
-      });
+
+        // Save oldest tweet's id for next load more
+        this.oldestLoadedTweetId = res
+          .filter(tweet => !tweet.private_tweet)
+          .reduce((acc, cur) => (acc.id < cur.id ? acc : cur))["id"];
+      }
+
+      // Hide loading icon
+      infiniteScroll.complete();
+    });
+  }
+
+  private async loadTimeline(userId, oldestLoadedTweetId) {
+    // Fetch tweets from Twitter
+    const tweets =
+      oldestLoadedTweetId == null
+        ? await this.twitter.fetchUserTimeline(userId)
+        : await this.twitter.fetchUserTimelineSince(
+            userId,
+            oldestLoadedTweetId
+          );
+
+    // Determine end of time interval to look for private tweets
+    let intervalEnd;
+    if (tweets.length < 20) {
+      // End of timeline is reached
+      this.enableInfiniteScroll = false;
+      intervalEnd = new Date("2018-04-01T00:00:00");
+    } else {
+      const lastTweetTimestamp = tweets[tweets.length - 1].created_at;
+      intervalEnd = new Date(lastTweetTimestamp);
+    }
+
+    // Fetch private tweet hashs from P2P DB for corresponding interval
+    const privateTweetHashs: string[] = await this.gun.fetchPrivateTweetHashsForUserInInterval(
+      userId,
+      new Date(),
+      intervalEnd
+    );
+
+    // Load private tweets from P2P storage
+    let privateTweets = await this.ipfs.fetchTweets(privateTweetHashs);
+
+    // Add user object to private tweets
+    privateTweets = await Promise.all(
+      privateTweets.map(async tweet => await this.addUserToTweet(tweet))
+    );
+
+    // Combine and sort tweets
+    return tweets
+      .concat(privateTweets)
+      .sort((a, b) => this.sortByDateAsc(a, b));
+  }
+
+  private sortByDateAsc(a, b) {
+    const dateA = new Date(a.created_at);
+    const dateB = new Date(b.created_at);
+
+    if (dateA > dateB) {
+      return -1;
+    } else if (dateA < dateB) {
+      return 1;
+    } else {
+      return 0;
+    }
   }
 
-  private async checkP2pTweets(userId) {
-    const lastTweetHash = await this.gun.getLastTweetFromUser(userId);
-    const res = await this.ipfs.fetchTweet(lastTweetHash);
+  private async addUserToTweet(tweet) {
+    tweet.user = await this.twitter.fetchUser(tweet.user_id);
+    return tweet;
   }
 
   onScroll(event) {

+ 9 - 0
app/src/providers/p2p-database-gun/p2p-database-gun.ts

@@ -27,4 +27,13 @@ export class P2pDatabaseGunProvider {
         .once(resolve)
     );
   }
+
+  public fetchPrivateTweetHashsForUserInInterval(
+    userId,
+    intervalStart,
+    intervalEnd
+  ): string[] {
+    // return ["QmU37grV5ZoK4d5d2Cooaf429LuD31WyFbAMw2f1DHtvaS"];
+    return [];
+  }
 }

+ 9 - 1
app/src/providers/p2p-storage-ipfs/p2p-storage-ipfs.ts

@@ -20,10 +20,18 @@ export class P2pStorageIpfsProvider {
     return this.http.post(this.infuraUrl + "add", formData).toPromise();
   }
 
-  public fetchTweet(hash) {
+  public fetchTweet(hash: string) {
     const options = {
       params: { arg: hash }
     };
     return this.http.get(this.infuraUrl + "cat", options).toPromise();
   }
+
+  public async fetchTweets(hashs: Array<string>) {
+    let res = [];
+    hashs.forEach(element => {
+      res.push(this.fetchTweet(element));
+    });
+    return await Promise.all(res);
+  }
 }

+ 6 - 3
app/src/providers/twitter-api/twitter-api.ts

@@ -49,18 +49,21 @@ export class TwitterApiProvider {
     const res = await this.client.get("statuses/user_timeline", {
       user_id: userId,
       include_entities: true,
-      tweet_mode: "extended"
+      tweet_mode: "extended",
+      count: 20
     });
     return res.data;
   }
 
   public async fetchUserTimelineSince(userId, maxId) {
-    return await this.client.get("statuses/user_timeline", {
+    const res = await this.client.get("statuses/user_timeline", {
       user_id: userId,
       max_id: maxId,
       include_entities: true,
-      tweet_mode: "extended"
+      tweet_mode: "extended",
+      count: 20
     });
+    return res.data;
   }
 
   public async destroyFriendship(userId) {