Merge branch 'master' into infinite-scroll

This commit is contained in:
Marcin Wojnarowski 2020-09-19 00:42:47 +02:00 committed by GitHub
commit aa5b06a676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 227 additions and 63 deletions

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../pages/settings.dart';
import '../util/goto.dart';
import 'stores.dart';
Function(
Function(Jwt token) action, [
String message,
]) useLoggedInAction(
String instanceUrl,
) {
final context = useContext();
final store = useAccountsStore();
return (Function(Jwt token) action, [message]) {
if (store.isAnonymousFor(instanceUrl)) {
return () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(message ?? 'you have to be logged in to do that'),
action: SnackBarAction(
label: 'log in',
onPressed: () => goTo(context, (_) => AccountsConfigPage())),
));
};
}
final token = store.defaultTokenFor(instanceUrl);
return () => action(token);
};
}

View File

@ -8,6 +8,7 @@ import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
import '../hooks/memo_future.dart';
import '../hooks/stores.dart';
import '../util/extensions/api.dart';
@ -533,28 +534,20 @@ class _FollowButton extends HookWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isSubbed = useState(community.subscribed ?? false);
final token = useAccountsStore().defaultTokenFor(community.instanceUrl);
final delayed = useDelayedLoading(const Duration(milliseconds: 500));
final loggedInAction = useLoggedInAction(community.instanceUrl);
final colorOnTopOfAccent = textColorBasedOnBackground(theme.accentColor);
subscribe() async {
if (token == null) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text("can't sub when you're not logged in")));
return;
}
subscribe(Jwt token) async {
delayed.start();
try {
await LemmyApi(community.instanceUrl).v1.followCommunity(
communityId: community.id,
follow: !isSubbed.value,
auth: token?.raw);
auth: token.raw);
isSubbed.value = !isSubbed.value;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
@ -590,7 +583,7 @@ class _FollowButton extends HookWidget {
)
: RaisedButton.icon(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 20),
onPressed: delayed.pending ? () {} : subscribe,
onPressed: loggedInAction(delayed.pending ? (_) {} : subscribe),
icon: isSubbed.value
? Icon(Icons.remove, size: 18, color: colorOnTopOfAccent)
: Icon(Icons.add, size: 18, color: colorOnTopOfAccent),

View File

@ -3,25 +3,32 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../hooks/memo_future.dart';
import '../hooks/stores.dart';
import '../util/extensions/api.dart';
import '../widgets/comment_section.dart';
import '../widgets/post.dart';
import '../widgets/save_post_button.dart';
class FullPostPage extends HookWidget {
final Future<FullPostView> fullPost;
final int id;
final String instanceUrl;
final PostView post;
FullPostPage({@required int id, @required String instanceUrl})
FullPostPage({@required this.id, @required this.instanceUrl})
: assert(id != null),
assert(instanceUrl != null),
fullPost = LemmyApi(instanceUrl).v1.getPost(id: id),
post = null;
FullPostPage.fromPostView(this.post)
: fullPost = LemmyApi(post.instanceUrl).v1.getPost(id: post.id);
: id = post.id,
instanceUrl = post.instanceUrl;
@override
Widget build(BuildContext context) {
final fullPostSnap = useFuture(this.fullPost);
final accStore = useAccountsStore();
final fullPostSnap = useMemoFuture(() => LemmyApi(instanceUrl)
.v1
.getPost(id: id, auth: accStore.defaultTokenFor(instanceUrl)?.raw));
// FALLBACK VIEW
@ -48,24 +55,16 @@ class FullPostPage extends HookWidget {
final fullPost = fullPostSnap.data;
final savedIcon = (post.saved == null || !post.saved)
? Icons.bookmark_border
: Icons.bookmark;
// FUNCTIONS
sharePost() => Share.text('Share post', post.apId, 'text/plain');
savePost() {
print('SAVE POST');
}
return Scaffold(
appBar: AppBar(
leading: BackButton(),
actions: [
IconButton(icon: Icon(Icons.share), onPressed: sharePost),
IconButton(icon: Icon(savedIcon), onPressed: savePost),
SavePostButton(post),
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () => Post.showMoreMenu(context, post)),
@ -78,6 +77,17 @@ class FullPostPage extends HookWidget {
if (fullPostSnap.hasData)
CommentSection(fullPost.comments,
postCreatorId: fullPost.post.creatorId)
else if (fullPostSnap.hasError)
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 30),
child: Column(
children: [
Icon(Icons.error),
Text('Error: ${fullPostSnap.error}')
],
),
)
else
Container(
child: Center(child: CircularProgressIndicator()),

View File

@ -3,11 +3,14 @@ import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:url_launcher/url_launcher.dart' as ul;
import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
import '../pages/full_post.dart';
import '../url_launcher.dart';
import '../util/extensions/api.dart';
@ -15,6 +18,7 @@ import '../util/goto.dart';
import 'bottom_modal.dart';
import 'fullscreenable_image.dart';
import 'markdown_text.dart';
import 'save_post_button.dart';
enum MediaType {
image,
@ -39,7 +43,7 @@ MediaType whatType(String url) {
return MediaType.other;
}
class Post extends StatelessWidget {
class Post extends HookWidget {
final PostView post;
final String instanceUrl;
final bool fullPost;
@ -48,18 +52,6 @@ class Post extends StatelessWidget {
// == ACTIONS ==
void _savePost() {
print('SAVE POST');
}
void _upvotePost() {
print('UPVOTE POST');
}
void _downvotePost() {
print('DOWNVOTE POST');
}
static void showMoreMenu(BuildContext context, PostView post) {
showModalBottomSheet(
backgroundColor: Colors.transparent,
@ -156,7 +148,6 @@ class Post extends StatelessWidget {
return url;
}();
// TODO: add NSFW, locked, removed, deleted, stickied
/// assemble info section
Widget info() => Column(children: [
Padding(
@ -402,17 +393,8 @@ class Post extends StatelessWidget {
icon: Icon(Icons.share),
onPressed: () => Share.text('Share post url', post.apId,
'text/plain')), // TODO: find a way to mark it as url
if (!fullPost)
IconButton(
icon: post.saved == true
? Icon(Icons.bookmark)
: Icon(Icons.bookmark_border),
onPressed: _savePost),
IconButton(
icon: Icon(Icons.arrow_upward), onPressed: _upvotePost),
Text(NumberFormat.compact().format(post.score)),
IconButton(
icon: Icon(Icons.arrow_downward), onPressed: _downvotePost),
if (!fullPost) SavePostButton(post),
_Voting(post),
],
),
);
@ -449,3 +431,65 @@ class Post extends StatelessWidget {
);
}
}
class _Voting extends HookWidget {
final PostView post;
_Voting(this.post);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final myVote = useState(post.myVote ?? VoteType.none);
final loading = useDelayedLoading(Duration(milliseconds: 500));
final loggedInAction = useLoggedInAction(post.instanceUrl);
vote(VoteType vote, Jwt token) async {
final api = LemmyApi(post.instanceUrl).v1;
loading.start();
try {
final res = await api.createPostLike(
postId: post.id, score: vote, auth: token.raw);
myVote.value = res.myVote;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('voting failed :(')));
return;
}
loading.cancel();
}
return Row(
children: [
IconButton(
icon: Icon(
Icons.arrow_upward,
color: myVote.value == VoteType.up ? theme.accentColor : null,
),
onPressed: loggedInAction(
(token) => vote(
myVote.value == VoteType.up ? VoteType.none : VoteType.up,
token,
),
)),
if (loading.loading)
SizedBox(child: CircularProgressIndicator(), width: 20, height: 20)
else
Text(NumberFormat.compact().format(post.score + myVote.value.value)),
IconButton(
icon: Icon(
Icons.arrow_downward,
color: myVote.value == VoteType.down ? Colors.red : null,
),
onPressed: loggedInAction(
(token) => vote(
myVote.value == VoteType.down ? VoteType.none : VoteType.down,
token,
),
)),
],
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
import '../util/api_extensions.dart';
// TODO: sync this button between post and fullpost. the same with voting
class SavePostButton extends HookWidget {
final PostView post;
SavePostButton(this.post);
@override
Widget build(BuildContext context) {
final isSaved = useState(post.saved ?? false);
final savedIcon = isSaved.value ? Icons.bookmark : Icons.bookmark_border;
final loading = useDelayedLoading(Duration(milliseconds: 500));
final loggedInAction = useLoggedInAction(post.instanceUrl);
savePost(Jwt token) async {
final api = LemmyApi(post.instanceUrl).v1;
loading.start();
try {
final res = await api.savePost(
postId: post.id, save: !isSaved.value, auth: token.raw);
isSaved.value = res.saved;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('saving failed :(')));
}
loading.cancel();
}
if (loading.loading) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 5),
child: SizedBox(
width: 30,
height: 30,
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).iconTheme.color,
)),
);
}
return IconButton(
tooltip: 'Save post',
icon: Icon(savedIcon),
onPressed: loggedInAction(loading.pending ? (_) {} : savePost),
);
}
}

View File

@ -98,7 +98,7 @@ packages:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
version: "2.3.2+1"
characters:
dependency: transitive
description:
@ -204,6 +204,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
file:
dependency: transitive
description:
@ -236,7 +243,7 @@ packages:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.1"
version: "1.4.2"
flutter_hooks:
dependency: "direct main"
description:
@ -358,7 +365,7 @@ packages:
name: lemmy_api_client
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
version: "0.5.0"
logging:
dependency: transitive
description:
@ -435,7 +442,7 @@ packages:
name: octo_image
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1"
version: "0.3.0"
package_config:
dependency: transitive
description:
@ -456,7 +463,7 @@ packages:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.14"
version: "1.6.16"
path_provider_linux:
dependency: transitive
description:
@ -470,7 +477,7 @@ packages:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+3"
version: "0.0.4+4"
path_provider_platform_interface:
dependency: transitive
description:
@ -478,6 +485,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.2"
pedantic:
dependency: transitive
description:
@ -720,7 +734,7 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "5.5.2"
version: "5.6.0"
url_launcher_linux:
dependency: transitive
description:
@ -734,7 +748,7 @@ packages:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+7"
version: "0.0.1+8"
url_launcher_platform_interface:
dependency: transitive
description:
@ -748,7 +762,14 @@ packages:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3+1"
version: "0.1.3+2"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+1"
uuid:
dependency: transitive
description:
@ -777,6 +798,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.1"
xdg_directories:
dependency: transitive
description:
@ -792,5 +820,5 @@ packages:
source: hosted
version: "2.2.1"
sdks:
dart: ">=2.9.0-14.0.dev <3.0.0"
dart: ">=2.9.0 <3.0.0"
flutter: ">=1.20.0 <2.0.0"

View File

@ -29,7 +29,7 @@ dependencies:
flutter_hooks: ^0.13.2
cached_network_image: ^2.2.0+1
timeago: ^2.0.27
lemmy_api_client: ^0.4.0
lemmy_api_client: ^0.5.0
fuzzy: <1.0.0
mobx: ^1.2.1