Skip to content

Commit

Permalink
Screen For-you Added
Browse files Browse the repository at this point in the history
  • Loading branch information
davo3078 committed Aug 22, 2023
1 parent 72aff4b commit 6238f03
Show file tree
Hide file tree
Showing 6 changed files with 520 additions and 42 deletions.
100 changes: 97 additions & 3 deletions lib/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class Twitter {

if (!FilterTweetRegEx(filterModel,tweet)) {
replies.add(TweetChain(
id: result['rest_id'],
id: result['rest_id']??result['tweet']['rest_id'],
tweets: [tweet],
isPinned: isPinned));
}
Expand Down Expand Up @@ -473,6 +473,40 @@ class Twitter {

return List.from(jsonDecode(result)).map((e) => Trends.fromJson(e)).toList(growable: false);
}
static Future<TweetStatus> getTimelineTweets(String id, String type, List<String> pinnedTweets,
{int count = 10, String? cursor, bool includeReplies = true,
bool includeRetweets = true, required int Function() getTweetsCounter,
required void Function() incrementTweetsCounter, required FilterModel filterModel}) async {
bool showPinnedTweet=true;
var query = {
...defaultParams,
'include_tweet_replies': includeReplies ? '1' : '0',
'include_want_retweets': includeRetweets ? '1' : '0', // This may not actually do anything
'count': count.toString(),
};
Map<String,Object> defaultUserTweetsParam=
{
"variables":"{\"userId\":\"160534877\",\"count\":20,\"includePromotedContent\":true,\"withQuickPromoteEligibilityTweetFields\":true,\"withVoice\":true,\"withV2Timeline\":true}",
"features":"{\"rweb_lists_timeline_redesign_enabled\":true,\"responsive_web_graphql_exclude_directive_enabled\":true,\"verified_phone_label_enabled\":false,\"creator_subscriptions_tweet_preview_api_enabled\":true,\"responsive_web_graphql_timeline_navigation_enabled\":true,\"responsive_web_graphql_skip_user_profile_image_extensions_enabled\":false,\"tweetypie_unmention_optimization_enabled\":true,\"responsive_web_edit_tweet_api_enabled\":true,\"graphql_is_translatable_rweb_tweet_is_translatable_enabled\":true,\"view_counts_everywhere_api_enabled\":true,\"longform_notetweets_consumption_enabled\":true,\"responsive_web_twitter_article_tweet_consumption_enabled\":false,\"tweet_awards_web_tipping_enabled\":false,\"freedom_of_speech_not_reach_fetch_enabled\":true,\"standardized_nudges_misinfo\":true,\"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled\":true,\"longform_notetweets_rich_text_read_enabled\":true,\"longform_notetweets_inline_media_enabled\":true,\"responsive_web_media_download_video_enabled\":false,\"responsive_web_enhance_cards_enabled\":false}",
"fieldToggles":"{\"withAuxiliaryUserLabels\":false,\"withArticleRichContentState\":false}"
};

Map<String, dynamic> variables=json.decode(defaultUserTweetsParam["variables"].toString());
variables["userId"]=id;
if (cursor != null) {
variables['cursor'] = cursor;
}
variables['count'] = count;
defaultUserTweetsParam["variables"]=json.encode(variables);

var response = await _twitterApi.client.get(Uri.https('twitter.com', 'i/api/graphql/W4Tpu1uueTGK53paUgxF0Q/HomeTimeline', defaultUserTweetsParam));
var result = json.decode(response.body);
//if this page is not first one on the profile page, dont add pinned tweet
if(variables['cursor'] != null)
showPinnedTweet=false;
return createTimelineChains(result, 'tweet', pinnedTweets, includeReplies == false,
includeReplies,showPinnedTweet,getTweetsCounter,incrementTweetsCounter,filterModel:filterModel);
}
static Future<TweetStatus> getTweets(String id, String type, List<String> pinnedTweets,
{int count = 10, String? cursor, bool includeReplies = true,
bool includeRetweets = true, required int Function() getTweetsCounter,
Expand All @@ -498,6 +532,7 @@ class Twitter {
}
variables['count'] = count;
defaultUserTweetsParam["variables"]=json.encode(variables);

var response = await _twitterApi.client.get(Uri.https('twitter.com', '/i/api/graphql/2GIWTr7XwadIixZDtyXd4A/UserTweets', defaultUserTweetsParam));
var result = json.decode(response.body);
//if this page is not first one on the profile page, dont add pinned tweet
Expand Down Expand Up @@ -589,6 +624,52 @@ class Twitter {
return TweetStatus(chains: chains, cursorBottom: cursorBottom, cursorTop: cursorTop);
}

static TweetStatus createTimelineChains(
Map<String, dynamic> result, String tweetIndicator, List<String> pinnedTweets,
bool mapToThreads, bool includeReplies,bool showPinnedTweet, int Function() getTweetsCounter,
void Function() increaseTweetCounter, {required FilterModel filterModel}
) {
var instructions = List.from(result["data"]["home"]["home_timeline_urt"]['instructions']);
var addEntriesInstructions = instructions.firstWhereOrNull((e) => e['type'] == 'TimelineAddEntries');
if (addEntriesInstructions == null) {
return TweetStatus(chains: [], cursorBottom: null, cursorTop: null);
}
var addPinnedTweetsInstructions = instructions.firstWhereOrNull((e) => e['type'] == 'TimelinePinEntry');
var addEntries = List.from(addEntriesInstructions['entries']);
var repEntries = List.from(instructions.where((e) => e['type'] == 'TimelineReplaceEntry'));
List addPinnedEntries = List<dynamic>.empty(growable: true);
if (addPinnedTweetsInstructions != null) {
addPinnedEntries.add(addPinnedTweetsInstructions['entry'] ?? null);
}

String? cursorBottom = getCursor(addEntries, repEntries, 'cursor-bottom', 'Bottom');
String? cursorTop = getCursor(addEntries, repEntries, 'cursor-top', 'Top');
var chains = createTweets(addEntries,filterModel);
// var debugTweets = json.encode(chains);
//var debugTweets2 = json.encode(addEntries);
var pinnedChains =createTweets(addPinnedEntries,filterModel,true);


// Order all the conversations by newest first (assuming the ID is an incrementing key),
// and create a chain from them
chains.sort((a, b){
return b.id!.compareTo(a.id!);});

//If we want to show pinned tweets, add them before the others that we already have
if (pinnedTweets.isNotEmpty & showPinnedTweet) {
chains.insertAll(0, pinnedChains);
}
//To prevent infinte loading of tweets while filtering via regex , we have to count added tweets.
//(infinite loading originating in paged_silver_builder.dart at line 246)
if(chains.length < 5)
increaseTweetCounter();
//As soon as there is no tweet left that passes regex critera and we also reached maximum attemps
//to find them, than stop loading more.
if(chains.length <= 5 && getTweetsCounter()>filterModel.GetLoadTweetsCounter()) {
cursorBottom=null;
}
return TweetStatus(chains: chains, cursorBottom: cursorBottom, cursorTop: cursorTop);
}
static Future<List<UserWithExtra>> getUsers(Iterable<String> ids) async {
// Split into groups of 100, as the API only supports that many at a time
List<Future<List<UserWithExtra>>> futures = [];
Expand Down Expand Up @@ -716,7 +797,20 @@ class TweetWithCard extends Tweet {
}

factory TweetWithCard.fromGraphqlJson(Map<String, dynamic> result) {
var retweetedStatus = result['legacy']['retweeted_status_result'] == null ? null : TweetWithCard.fromGraphqlJson(result['legacy']['retweeted_status_result']['result']);
var retweetedStatus;
// if(result['tweet']!= null){
// int a=1;
// }
if(result['tweet']!= null && result['tweet']['retweeted_status_result']!=null
&& result['tweet']['retweeted_status_result']['result']!=null){
retweetedStatus=TweetWithCard.fromGraphqlJson(result['tweet']['retweeted_status_result']['result']);
}
else if(result['legacy']?['retweeted_status_result']?['result']!=null) {
retweetedStatus=TweetWithCard.fromGraphqlJson(result['legacy']['retweeted_status_result']['result']);
}
else {
retweetedStatus=null;
}
var quotedStatus = result['quoted_status_result'] == null ||
result['quoted_status_result']['result']["__typename"]=="TweetWithVisibilityResults" ? null : TweetWithCard.fromGraphqlJson(result['quoted_status_result']['result']);
var resCore = result['core']?['user_results']?['result'];
Expand All @@ -730,7 +824,7 @@ class TweetWithCard extends Tweet {
noteEntities = Entities.fromJson(noteResult['entity_set']);
}

TweetWithCard tweet = TweetWithCard.fromData(result['legacy'], noteText, noteEntities, user, retweetedStatus, quotedStatus);
TweetWithCard tweet = TweetWithCard.fromData(result['legacy'] ?? result['tweet']['legacy'], noteText, noteEntities, user, retweetedStatus, quotedStatus);
if (tweet.card == null && result['card']?['legacy'] != null) {
tweet.card = result['card']['legacy'];
List bindingValuesList = tweet.card!['binding_values'] as List;
Expand Down
154 changes: 154 additions & 0 deletions lib/forYou/_tweets.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import 'package:flutter/material.dart';
import 'package:fritter/catcher/errors.dart';
import 'package:fritter/client.dart';
import 'package:fritter/profile/profile.dart';
import 'package:fritter/tweet/conversation.dart';
import 'package:fritter/ui/errors.dart';
import 'package:fritter/user.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:fritter/generated/l10n.dart';
import 'package:pref/pref.dart';
import 'package:provider/provider.dart';

import '../constants.dart';
import '../profile/filter_model.dart';

class ForYouTweets extends StatefulWidget {
final UserWithExtra user;
final String type;
final bool includeReplies;
final List<String> pinnedTweets;
final BasePrefService pref;

const ForYouTweets(
{Key? key,
required this.user,
required this.type,
required this.includeReplies,
required this.pinnedTweets,
required this.pref})
: super(key: key);

@override
State<ForYouTweets> createState() => _ForYouTweetsState();
}

class _ForYouTweetsState extends State<ForYouTweets> with AutomaticKeepAliveClientMixin<ForYouTweets> {
late PagingController<String?, TweetChain> _pagingController;
static const int pageSize = 20;
int loadTweetsCounter = 0;
late FilterModel filterModel;
@override
bool get wantKeepAlive => true;

@override
void initState() {
super.initState();
filterModel = FilterModel(widget.user.idStr!, widget.pref);
_pagingController = PagingController(firstPageKey: null);
_pagingController.addPageRequestListener((cursor) {
_loadTweets(cursor, filterModel);
});
}

@override
void dispose() {
_pagingController.dispose();
super.dispose();
}

void incrementLoadTweetsCounter() {
++loadTweetsCounter;
}

int getLoadTweetsCounter() {
return loadTweetsCounter;
}

Future _loadTweets(String? cursor, FilterModel filterModel) async {
try {
var result = await Twitter.getTimelineTweets(widget.user.idStr!, widget.type, widget.pinnedTweets,
cursor: cursor,
count: pageSize,
includeReplies: widget.includeReplies,
getTweetsCounter: getLoadTweetsCounter,
incrementTweetsCounter: incrementLoadTweetsCounter,
filterModel: filterModel);

if (!mounted) {
return;
}

if (result.cursorBottom == _pagingController.nextPageKey) {
_pagingController.appendLastPage([]);
} else {
_pagingController.appendPage(result.chains, result.cursorBottom);
}
} catch (e, stackTrace) {
Catcher.reportException(e, stackTrace);
if (mounted) {
_pagingController.error = [e, stackTrace];
}
}
}

@override
Widget build(BuildContext context) {
super.build(context);
return MultiProvider(
providers: [
ChangeNotifierProvider<TweetContextState>(
create: (_) => TweetContextState(PrefService.of(context).get(optionTweetsHideSensitive)))
],
builder: (context, child) {
return Consumer<TweetContextState>(builder: (context, model, child) {
if (model.hideSensitive && (widget.user.possiblySensitive ?? false)) {
return EmojiErrorWidget(
emoji: '🍆🙈🍆',
message: L10n.current.possibly_sensitive,
errorMessage: L10n.current.possibly_sensitive_profile,
onRetry: () async => model.setHideSensitive(false),
retryText: L10n.current.yes_please,
);
}

return RefreshIndicator(
onRefresh: () async => _pagingController.refresh(),
child: PagedListView<String?, TweetChain>(
padding: EdgeInsets.zero,
pagingController: _pagingController,
addAutomaticKeepAlives: false,
builderDelegate: PagedChildBuilderDelegate(
itemBuilder: (context, chain, index) {
return TweetConversation(
id: chain.id,
tweets: chain.tweets,
username: widget.user.screenName!,
isPinned: chain.isPinned);
},
firstPageErrorIndicatorBuilder: (context) => FullPageErrorWidget(
error: _pagingController.error[0],
stackTrace: _pagingController.error[1],
prefix: L10n.of(context).unable_to_load_the_tweets,
onRetry: () => _loadTweets(_pagingController.firstPageKey, this.filterModel),
),
newPageErrorIndicatorBuilder: (context) => FullPageErrorWidget(
error: _pagingController.error[0],
stackTrace: _pagingController.error[1],
prefix: L10n.of(context).unable_to_load_the_next_page_of_tweets,
onRetry: () => _loadTweets(_pagingController.nextPageKey, this.filterModel),
),
noItemsFoundIndicatorBuilder: (context) {
return Center(
child: Text(
L10n.of(context).could_not_find_any_tweets_by_this_user,
),
);
},
),
),
);
});
});
}
}
Loading

0 comments on commit 6238f03

Please sign in to comment.