Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support of twitter account+filtering tweets #769

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
504 changes: 504 additions & 0 deletions lib/WebFlowAuth/webFlowAuth_model.dart

Large diffs are not rendered by default.

405 changes: 257 additions & 148 deletions lib/client.dart

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const optionUserTrendsLocations = 'trends.locations';

const optionNonConfirmationBiasMode = 'other.improve_non_confirmation_bias';

const optionPasswordTwitterAcc = 'passwordTwitterAcc';
const optionLoginNameTwitterAcc = 'loginNameTwitterAcc';
const optionEmailTwitterAcc = 'emailTwitterAcc';

const routeHome = '/';
const routeGroup = '/group';
const routeProfile = '/profile';
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,
),
);
},
),
),
);
});
});
}
}
211 changes: 211 additions & 0 deletions lib/forYou/foryou.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import 'package:extended_image/extended_image.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_triple/flutter_triple.dart';
import 'package:fritter/constants.dart';
import 'package:fritter/database/entities.dart';
import 'package:fritter/generated/l10n.dart';
import 'package:fritter/profile/_filters.dart';
import 'package:fritter/profile/_follows.dart';
import 'package:fritter/profile/_saved.dart';
import 'package:fritter/profile/_tweets.dart';
import 'package:fritter/profile/profile_model.dart';
import 'package:fritter/ui/errors.dart';
import 'package:fritter/ui/physics.dart';
import 'package:fritter/user.dart';
import 'package:fritter/utils/urls.dart';
import 'package:intl/intl.dart';
import 'package:measure_size/measure_size.dart';
import 'package:pref/pref.dart';
import 'package:provider/provider.dart';

import '../home/_saved.dart';
import '../settings/_account.dart';
import '_tweets.dart';

class ForYouScreen extends StatelessWidget {
const ForYouScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(body: ForYouScreenBody());
}
}

class ForYouScreenBody extends StatefulWidget {
const ForYouScreenBody({Key? key}) : super(key: key);

@override
State<StatefulWidget> createState() => _ForYouScreenBodyState();
}

class _ForYouScreenBodyState extends State<ForYouScreenBody> with TickerProviderStateMixin {
final GlobalKey<NestedScrollViewState> nestedScrollViewKey = GlobalKey();
final ScrollController scrollController = ScrollController();
late TabController _tabController;
bool _showBackToTopButton = false;

@override
void initState() {
super.initState();

WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
var nestedScrollViewState = nestedScrollViewKey.currentState;
if (nestedScrollViewState == null) {
return;
}

nestedScrollViewState.innerController.addListener(_listen);
});

_tabController = TabController(length: 3, vsync: this);
}

@override
void dispose() {
nestedScrollViewKey.currentState?.innerController.removeListener(_listen);

super.dispose();
}

void _listen() {
var nestedScrollViewState = nestedScrollViewKey.currentState;
if (nestedScrollViewState == null) {
return;
}

if (!nestedScrollViewState.innerController.hasClients) {
return;
}

// Show the "scroll to top" button if we scroll down a bit, and hide it if we go back above
if (nestedScrollViewState.innerController.positions.any((element) => element.pixels >= 400)) {
if (!_showBackToTopButton) {
setState(() {
_showBackToTopButton = true;
});
}
} else {
if (_showBackToTopButton) {
setState(() {
_showBackToTopButton = false;
});
}
}
}

void _scrollToTop() {
// We scroll the outer controller (the whole nested scroll view and children) to the top
// TODO: No animation due to Flutter crashing on huge lists (https://github.com/flutter/flutter/issues/52207) (#607)
nestedScrollViewKey.currentState?.outerController.jumpTo(0);
}

@override
Widget build(BuildContext context) {
var prefs = PrefService.of(context, listen: false);
UserWithExtra user = UserWithExtra();
user.idStr = "1";
user.possiblySensitive = false;
user.screenName = "ForYou";
return Scaffold(
body: Stack(children: [
ExtendedNestedScrollView(
key: nestedScrollViewKey,
onlyOneScrollInBody: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text( L10n.of(context).foryou),
// This is the title in the app bar.
floating: true,
pinned: true,
snap: false,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
controller: _tabController,
isScrollable: true,
tabs: [
Tab(
child: Text(
L10n.of(context).tweets,
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
L10n.of(context).saved,
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
L10n.of(context).tweetFilters,
textAlign: TextAlign.center,
))
],
),
)
];
},
body: TabBarView(
controller: _tabController,
physics: const LessSensitiveScrollPhysics(),
children: [
MultiProvider(
providers: [
ChangeNotifierProvider<TweetContextState>(
create: (_) => TweetContextState(prefs.get(optionTweetsHideSensitive))),
Provider(create: (context) => ProfileModel()..loadProfileByScreenName("KverulantOrg")),
//ChangeNotifierProvider<TweetContextState>(create: (_) => TweetContextState(PrefService.of(context).get(optionTweetsHideSensitive)))
],
builder: (context, child) {
return Scaffold(
body: ForYouTweets(
user: user,
type: 'profile',
includeReplies: false,
pinnedTweets: [],
pref: PrefService.of(context)),
);
}),
SavedScreen(scrollController: scrollController),
Filters(user: user),
// Filters(user: null),
],
),
),

// If we haven't resized the description widget yet, display an overlay container so we don't see the resize
// TODO: This flickers
// AnimatedSwitcher(
// duration: const Duration(milliseconds: 150),
// child: descriptionResized == true && metadataResized == true
// ? Container(key: const Key('loaded'))
// : Container(
// key: const Key('waiting'),
// height: double.infinity,
// color: theme.backgroundColor,
// ),
// )
// ]),
// floatingActionButton: _showBackToTopButton == false
// ? null
// : FloatingActionButton(
// onPressed: _scrollToTop,
// child: const Icon(Icons.arrow_upward),
// ),
]));
}
}

class TweetContextState extends ChangeNotifier {
bool hideSensitive;

TweetContextState(this.hideSensitive);

void setHideSensitive(bool value) {
hideSensitive = value;
notifyListeners();
}
}
Loading