Skip to content

Commit 9d02290

Browse files
committed
✨ Tweet link can be shared with tweet thumbnail image.
1 parent 1519eff commit 9d02290

File tree

6 files changed

+273
-14
lines changed

6 files changed

+273
-14
lines changed

lib/helper/utility.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,20 @@ Future<Uri> createLinkToShare(BuildContext context, String id,
241241

242242
createLinkAndShare(BuildContext context, String id,
243243
{SocialMetaTagParameters socialMetaTagParameters}) async {
244-
var url = createLinkToShare(context, id,
244+
var url = await createLinkToShare(context, id,
245245
socialMetaTagParameters: socialMetaTagParameters);
246246

247247
share(url.toString(), subject: "Tweet");
248248
}
249249

250+
shareFile(List<String> path, {String text = ""}) {
251+
try {
252+
Share.shareFiles(path, text: text);
253+
} catch (error) {
254+
print(error);
255+
}
256+
}
257+
250258
void copyToClipBoard({
251259
GlobalKey<ScaffoldState> scaffoldKey,
252260
String text,

lib/page/notification/notificationPage.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,12 @@ class NotificationTile extends StatelessWidget {
193193

194194
@override
195195
Widget build(BuildContext context) {
196-
var description = model.description.length > 150
197-
? model.description.substring(0, 150) + '...'
198-
: model.description;
196+
String description = "";
197+
if (model.description != null) {
198+
description = model.description.length > 150
199+
? model.description.substring(0, 150) + '...'
200+
: model.description;
201+
}
199202
return Column(
200203
children: <Widget>[
201204
Container(

lib/widgets/customFlatButton.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import 'package:flutter/material.dart';
2+
3+
class CustomFlatButton extends StatelessWidget {
4+
const CustomFlatButton({
5+
Key key,
6+
this.onPressed,
7+
this.label,
8+
this.isLoading,
9+
this.color,
10+
this.labelStyle,
11+
this.isWraped = false,
12+
this.isColored = true,
13+
this.padding = const EdgeInsets.all(16),
14+
}) : super(key: key);
15+
final Function onPressed;
16+
final String label;
17+
final TextStyle labelStyle;
18+
final ValueNotifier<bool> isLoading;
19+
final bool isWraped;
20+
final bool isColored;
21+
final Color color;
22+
final EdgeInsetsGeometry padding;
23+
@override
24+
Widget build(BuildContext context) {
25+
return Container(
26+
width: isWraped ? null : double.infinity,
27+
child: ValueListenableBuilder<bool>(
28+
valueListenable: isLoading ?? ValueNotifier(false),
29+
builder: (context, loading, child) {
30+
return FlatButton(
31+
disabledColor: Theme.of(context).disabledColor,
32+
padding: padding,
33+
shape:
34+
RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
35+
color: !isColored ? null : color ?? Theme.of(context).primaryColor,
36+
splashColor: Theme.of(context).colorScheme.background,
37+
textColor: Theme.of(context).colorScheme.onPrimary,
38+
onPressed: loading ? null : onPressed,
39+
child: loading
40+
? SizedBox(
41+
height: 15,
42+
width: 15,
43+
child: FittedBox(
44+
child: CircularProgressIndicator(
45+
valueColor: AlwaysStoppedAnimation(
46+
color ?? Theme.of(context).primaryColor),
47+
),
48+
),
49+
)
50+
: child,
51+
);
52+
},
53+
child: Text(label,
54+
style: labelStyle ??
55+
Theme.of(context)
56+
.textTheme
57+
.button
58+
.copyWith(color: Colors.white)),
59+
),
60+
);
61+
}
62+
}

lib/widgets/share_widget.dart

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import 'dart:io';
2+
import 'dart:typed_data';
3+
import 'dart:ui' as ui;
4+
import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter/rendering.dart';
7+
import 'package:flutter_twitter_clone/helper/utility.dart';
8+
import 'package:flutter_twitter_clone/widgets/customFlatButton.dart';
9+
import 'package:path_provider/path_provider.dart';
10+
11+
class ShareWidget extends StatefulWidget {
12+
const ShareWidget(
13+
{Key key, this.child, this.socialMetaTagParameters, this.id})
14+
: super(key: key);
15+
16+
final SocialMetaTagParameters socialMetaTagParameters;
17+
final String id;
18+
static MaterialPageRoute getRoute(
19+
{Widget child,
20+
SocialMetaTagParameters socialMetaTagParameters,
21+
String id}) {
22+
return MaterialPageRoute(
23+
builder: (_) => ShareWidget(
24+
child: child,
25+
id: id,
26+
socialMetaTagParameters: socialMetaTagParameters),
27+
);
28+
}
29+
30+
final Widget child;
31+
@override
32+
_MyHomePageState createState() => new _MyHomePageState();
33+
}
34+
35+
class _MyHomePageState extends State<ShareWidget> {
36+
GlobalKey _globalKey = new GlobalKey();
37+
ValueNotifier<bool> isLoading = ValueNotifier<bool>(false);
38+
39+
Future _capturePng() async {
40+
try {
41+
isLoading.value = true;
42+
RenderRepaintBoundary boundary =
43+
_globalKey.currentContext.findRenderObject();
44+
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
45+
ByteData byteData =
46+
await image.toByteData(format: ui.ImageByteFormat.png);
47+
var path = await _localPath + "/${DateTime.now().toIso8601String()}.png";
48+
await writeToFile(byteData, path);
49+
50+
var shareUrl = await createLinkToShare(context, widget.id,
51+
socialMetaTagParameters: widget.socialMetaTagParameters);
52+
var message =
53+
"*${widget.socialMetaTagParameters.title}*\n${widget.socialMetaTagParameters.description}\n$shareUrl";
54+
shareFile([path], text: message);
55+
isLoading.value = false;
56+
} catch (e) {
57+
print(e);
58+
}
59+
}
60+
61+
Future<File> writeToFile(ByteData data, String path) {
62+
final buffer = data.buffer;
63+
return new File(path).writeAsBytes(
64+
buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
65+
}
66+
67+
Future<String> get _localPath async {
68+
final directory = await getApplicationDocumentsDirectory();
69+
70+
return directory.path;
71+
}
72+
73+
@override
74+
Widget build(BuildContext context) {
75+
return new Scaffold(
76+
appBar: new AppBar(
77+
title: new Text('Share'),
78+
),
79+
body: SingleChildScrollView(
80+
child: new Column(
81+
mainAxisAlignment: MainAxisAlignment.start,
82+
children: <Widget>[
83+
RepaintBoundary(
84+
key: _globalKey,
85+
child: Container(
86+
color: Theme.of(context).colorScheme.onPrimary,
87+
padding: EdgeInsets.symmetric(vertical: 16),
88+
child: AbsorbPointer(
89+
child: widget.child,
90+
),
91+
)),
92+
Padding(
93+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
94+
child: CustomFlatButton(
95+
label: "Share",
96+
onPressed: _capturePng,
97+
isLoading: isLoading,
98+
labelStyle: TextStyle(
99+
fontSize: 18,
100+
color: Theme.of(context).colorScheme.onPrimary,
101+
fontWeight: FontWeight.bold),
102+
),
103+
)
104+
],
105+
),
106+
),
107+
);
108+
}
109+
}

lib/widgets/tweet/widgets/tweetBottomSheet.dart

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import 'package:flutter_twitter_clone/model/user.dart';
99
import 'package:flutter_twitter_clone/state/authState.dart';
1010
import 'package:flutter_twitter_clone/state/feedState.dart';
1111
import 'package:flutter_twitter_clone/widgets/customWidgets.dart';
12+
import 'package:flutter_twitter_clone/widgets/share_widget.dart';
13+
import 'package:flutter_twitter_clone/widgets/tweet/tweet.dart';
1214
import 'package:provider/provider.dart';
1315

1416
class TweetBottomSheet {
@@ -404,4 +406,88 @@ class TweetBottomSheet {
404406
],
405407
);
406408
}
409+
410+
void openShareTweetBottomSheet(
411+
BuildContext context, FeedModel model, TweetType type) async {
412+
await showModalBottomSheet(
413+
backgroundColor: Colors.transparent,
414+
context: context,
415+
builder: (context) {
416+
return Container(
417+
padding: EdgeInsets.only(top: 5, bottom: 0),
418+
height: 130,
419+
width: fullWidth(context),
420+
decoration: BoxDecoration(
421+
color: Theme.of(context).bottomSheetTheme.backgroundColor,
422+
borderRadius: BorderRadius.only(
423+
topLeft: Radius.circular(20),
424+
topRight: Radius.circular(20),
425+
),
426+
),
427+
child: _shareTweet(context, model, type));
428+
},
429+
);
430+
}
431+
432+
Widget _shareTweet(BuildContext context, FeedModel model, TweetType type) {
433+
var socialMetaTagParameters = SocialMetaTagParameters(
434+
description: model.description ??
435+
"${model.user.displayName} posted a tweet on Fwitter.",
436+
title: "Tweet on Fwitter app",
437+
imageUrl: Uri.parse(
438+
"https://play-lh.googleusercontent.com/e66XMuvW5hZ7HnFf8R_lcA3TFgkxm0SuyaMsBs3KENijNHZlogUAjxeu9COqsejV5w=s180-rw"));
439+
return Column(
440+
children: <Widget>[
441+
Container(
442+
width: fullWidth(context) * .1,
443+
height: 5,
444+
decoration: BoxDecoration(
445+
color: Theme.of(context).dividerColor,
446+
borderRadius: BorderRadius.all(
447+
Radius.circular(10),
448+
),
449+
),
450+
),
451+
_widgetBottomSheetRow(
452+
context,
453+
AppIcon.link,
454+
isEnable: true,
455+
text: 'Share Link',
456+
onPressed: () async {
457+
var url = createLinkToShare(
458+
context,
459+
"tweet/${model.key}",
460+
socialMetaTagParameters: socialMetaTagParameters,
461+
);
462+
var uri = await url;
463+
share(uri.toString(), subject: "Tweet");
464+
Navigator.pop(context);
465+
},
466+
),
467+
_widgetBottomSheetRow(
468+
context,
469+
AppIcon.image,
470+
text: 'Share with Tweet thumbnail',
471+
isEnable: true,
472+
onPressed: () {
473+
socialMetaTagParameters = SocialMetaTagParameters(
474+
title: "${model.user.displayName} posted a tweet on Fwitter.",
475+
imageUrl: Uri.parse(
476+
"https://play-lh.googleusercontent.com/e66XMuvW5hZ7HnFf8R_lcA3TFgkxm0SuyaMsBs3KENijNHZlogUAjxeu9COqsejV5w=s180-rw"));
477+
Navigator.pop(context);
478+
Navigator.push(
479+
context,
480+
ShareWidget.getRoute(
481+
child: Tweet(
482+
model: model,
483+
type: type,
484+
),
485+
id: "tweet/${model.key}",
486+
socialMetaTagParameters: socialMetaTagParameters),
487+
);
488+
},
489+
)
490+
],
491+
);
492+
}
407493
}

lib/widgets/tweet/widgets/tweetIconsRow.dart

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,7 @@ class TweetIconsRow extends StatelessWidget {
243243
}
244244

245245
void shareTweet(BuildContext context) async {
246-
createLinkAndShare(
247-
context,
248-
"tweet/${model.key}",
249-
socialMetaTagParameters: SocialMetaTagParameters(
250-
description: model.description ??
251-
"${model.user.displayName} posted a tweet on Fwitter.",
252-
title: "Tweet on Fwitter app",
253-
imageUrl: Uri.parse(
254-
"https://play-lh.googleusercontent.com/e66XMuvW5hZ7HnFf8R_lcA3TFgkxm0SuyaMsBs3KENijNHZlogUAjxeu9COqsejV5w=s180-rw")),
255-
);
246+
TweetBottomSheet().openShareTweetBottomSheet(context, model, type);
256247
}
257248

258249
@override

0 commit comments

Comments
 (0)