Benjamin Arhen
3 years ago
114 changed files with 342 additions and 13672 deletions
@ -0,0 +1,19 @@ |
|||
<component name="libraryTable"> |
|||
<library name="Dart SDK"> |
|||
<CLASSES> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/async" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/collection" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/convert" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/core" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/developer" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/html" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/io" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/isolate" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/math" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/mirrors" /> |
|||
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/typed_data" /> |
|||
</CLASSES> |
|||
<JAVADOC /> |
|||
<SOURCES /> |
|||
</library> |
|||
</component> |
@ -0,0 +1,15 @@ |
|||
<component name="libraryTable"> |
|||
<library name="KotlinJavaRuntime"> |
|||
<CLASSES> |
|||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" /> |
|||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" /> |
|||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" /> |
|||
</CLASSES> |
|||
<JAVADOC /> |
|||
<SOURCES> |
|||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" /> |
|||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" /> |
|||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" /> |
|||
</SOURCES> |
|||
</library> |
|||
</component> |
@ -0,0 +1,9 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="ProjectModuleManager"> |
|||
<modules> |
|||
<module fileurl="file://$PROJECT_DIR$/teso.iml" filepath="$PROJECT_DIR$/teso.iml" /> |
|||
<module fileurl="file://$PROJECT_DIR$/android/teso_android.iml" filepath="$PROJECT_DIR$/android/teso_android.iml" /> |
|||
</modules> |
|||
</component> |
|||
</project> |
@ -0,0 +1,6 @@ |
|||
<component name="ProjectRunConfigurationManager"> |
|||
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter"> |
|||
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" /> |
|||
<method /> |
|||
</configuration> |
|||
</component> |
@ -0,0 +1,36 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="FileEditorManager"> |
|||
<leaf> |
|||
<file leaf-file-name="main.dart" pinned="false" current-in-tab="true"> |
|||
<entry file="file://$PROJECT_DIR$/lib/main.dart"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="0"> |
|||
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
</file> |
|||
</leaf> |
|||
</component> |
|||
<component name="ToolWindowManager"> |
|||
<editor active="true" /> |
|||
<layout> |
|||
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" /> |
|||
</layout> |
|||
</component> |
|||
<component name="ProjectView"> |
|||
<navigator currentView="ProjectPane" proportions="" version="1"> |
|||
</navigator> |
|||
<panes> |
|||
<pane id="ProjectPane"> |
|||
<option name="show-excluded-files" value="false" /> |
|||
</pane> |
|||
</panes> |
|||
</component> |
|||
<component name="PropertiesComponent"> |
|||
<property name="last_opened_file_path" value="$PROJECT_DIR$" /> |
|||
<property name="dart.analysis.tool.window.force.activate" value="true" /> |
|||
<property name="show.migrate.to.gradle.popup" value="false" /> |
|||
</component> |
|||
</project> |
@ -0,0 +1,10 @@ |
|||
# This file tracks properties of this Flutter project. |
|||
# Used by Flutter tool to assess capabilities and perform upgrades etc. |
|||
# |
|||
# This file should be version controlled and should not be manually edited. |
|||
|
|||
version: |
|||
revision: db747aa1331bd95bc9b3874c842261ca2d302cd5 |
|||
channel: stable |
|||
|
|||
project_type: app |
@ -0,0 +1,29 @@ |
|||
# This file configures the analyzer, which statically analyzes Dart code to |
|||
# check for errors, warnings, and lints. |
|||
# |
|||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled |
|||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be |
|||
# invoked from the command line by running `flutter analyze`. |
|||
|
|||
# The following line activates a set of recommended lints for Flutter apps, |
|||
# packages, and plugins designed to encourage good coding practices. |
|||
#include: package:flutter_lints/flutter.yaml |
|||
|
|||
linter: |
|||
# The lint rules applied to this project can be customized in the |
|||
# section below to disable rules from the `package:flutter_lints/flutter.yaml` |
|||
# included above or to enable additional rules. A list of all available lints |
|||
# and their documentation is published at |
|||
# https://dart-lang.github.io/linter/lints/index.html. |
|||
# |
|||
# Instead of disabling a lint rule for the entire project in the |
|||
# section below, it can also be suppressed for a single line of code |
|||
# or a specific dart file by using the `// ignore: name_of_lint` and |
|||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file |
|||
# producing the lint. |
|||
rules: |
|||
# avoid_print: false # Uncomment to disable the `avoid_print` rule |
|||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule |
|||
|
|||
# Additional information about this file can be found at |
|||
# https://dart.dev/guides/language/analysis-options |
@ -1,31 +0,0 @@ |
|||
import 'package:flutter_upchunk/flutter_upchunk.dart'; |
|||
|
|||
class Uploading { |
|||
String id; |
|||
String title; |
|||
String path; |
|||
String aspect; |
|||
String thumbnail; |
|||
UpChunk token; |
|||
double pending; |
|||
String campaignID; |
|||
String muxuploadID; |
|||
String muxuploadURL; |
|||
String muxassetID; |
|||
bool isProcessing; |
|||
|
|||
Uploading({ |
|||
this.id, |
|||
this.title, |
|||
this.path, |
|||
this.aspect, |
|||
this.thumbnail, |
|||
this.token, |
|||
this.pending, |
|||
this.campaignID, |
|||
this.isProcessing, |
|||
this.muxuploadID, |
|||
this.muxuploadURL, |
|||
this.muxassetID, |
|||
}); |
|||
} |
@ -1,48 +0,0 @@ |
|||
import 'package:better_player/better_player.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Classes/Firebase/Posts.dart'; |
|||
|
|||
const ASPECT_RATIO = 16 / 9; |
|||
|
|||
class VideoPlayerWidget extends StatefulWidget { |
|||
final BetterPlayerController controller; |
|||
final FBPosts ad; |
|||
|
|||
const VideoPlayerWidget({ |
|||
Key key, |
|||
@required this.controller, |
|||
@required this.ad, |
|||
}) : assert(controller != null), |
|||
assert(ad != null), |
|||
super(key: key); |
|||
|
|||
@override |
|||
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState(); |
|||
} |
|||
|
|||
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> { |
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return FittedBox( |
|||
clipBehavior: Clip.hardEdge, |
|||
child: SizedBox( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: BetterPlayer( |
|||
controller: widget.controller, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
widget.controller.dispose(); |
|||
super.dispose(); |
|||
} |
|||
} |
@ -1,104 +0,0 @@ |
|||
import 'package:better_player/better_player.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/CouponDetails.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/ProductDetails/CouponList.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
|
|||
const ASPECT_RATIO = 16 / 9; |
|||
|
|||
class VideoPlayerWidget extends StatefulWidget { |
|||
final BetterPlayerController controller; |
|||
final Post ad; |
|||
final bool play; |
|||
final List<CouponDetails> details; |
|||
|
|||
const VideoPlayerWidget({ |
|||
Key key, |
|||
@required this.controller, |
|||
@required this.ad, |
|||
@required this.play, |
|||
this.details, |
|||
}) : assert(controller != null), |
|||
assert(ad != null), |
|||
super(key: key); |
|||
|
|||
@override |
|||
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState(); |
|||
} |
|||
|
|||
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> { |
|||
bool displayed = false; |
|||
|
|||
@override |
|||
void initState() { |
|||
widget.controller.videoPlayerController.addListener(() => checkVideo()); |
|||
super.initState(); |
|||
|
|||
// if (widget.play) { |
|||
// _chewieController.play(); |
|||
// } |
|||
} |
|||
|
|||
checkVideo() async { |
|||
// Implement your calls inside these conditions' bodies : |
|||
if (widget.controller.videoPlayerController.value.position == |
|||
Duration(seconds: 0, minutes: 0, hours: 0)) { |
|||
// print('video Started'); |
|||
Provider.of<UserProvider>(context, listen: false).viewPost(widget.ad); |
|||
} |
|||
|
|||
if (widget.controller.videoPlayerController.value.position.inSeconds > |
|||
(widget.controller.videoPlayerController.value.duration.inSeconds) / |
|||
3) { |
|||
// print('video Ended'); |
|||
if (!displayed && widget.details.length > 0) { |
|||
setState(() { |
|||
displayed = true; |
|||
}); |
|||
await Navigator.of(context).push( |
|||
PageRouteBuilder( |
|||
opaque: false, |
|||
pageBuilder: (_, __, ___) => CouponList( |
|||
couponsList: widget.details, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
// _chewieController.play(); |
|||
} |
|||
} |
|||
|
|||
// @override |
|||
// void didUpdateWidget(VideoPlayerWidget oldWidget) { |
|||
// if (oldWidget.play != widget.play) { |
|||
// if (widget.play) { |
|||
// _chewieController.play(); |
|||
// } else { |
|||
// _chewieController.pause(); |
|||
// } |
|||
// } |
|||
// super.didUpdateWidget(oldWidget); |
|||
// } |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return FittedBox( |
|||
clipBehavior: Clip.hardEdge, |
|||
child: SizedBox( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: BetterPlayer( |
|||
controller: widget.controller, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
widget.controller.dispose(); |
|||
super.dispose(); |
|||
} |
|||
} |
@ -1 +0,0 @@ |
|||
export 'video_player_widget.dart'; |
@ -1,141 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'PageWidgets/Campaigns/header.dart'; |
|||
import 'package:teso/Classes/API Clasess/Campaign.dart'; |
|||
import 'PageWidgets/Campaigns/campaignTile.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:http/http.dart' as http; |
|||
import 'dart:convert'; |
|||
import 'dart:async'; |
|||
|
|||
class Campaigns extends StatefulWidget { |
|||
@override |
|||
_CampaignsState createState() => _CampaignsState(); |
|||
} |
|||
|
|||
class _CampaignsState extends State<Campaigns> { |
|||
TextEditingController searchkey; |
|||
List<Campaign> campaignMain; |
|||
List<Campaign> campaign; |
|||
var _future; |
|||
|
|||
void clearText() { |
|||
setState(() { |
|||
searchkey.clear(); |
|||
}); |
|||
} |
|||
|
|||
Future<List<Campaign>> getCampaigns() async { |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
|
|||
Map<String, String> requestHeaders = { |
|||
'Content-type': 'application/json', |
|||
'Authorization': prefs.getString('tokensTeso') |
|||
}; |
|||
|
|||
var register2 = serverLocation + 'adverts/businesscampaigns'; |
|||
var client1 = await http.post(Uri.parse(register2), |
|||
body: json.encode(searchkey.text), headers: requestHeaders); |
|||
|
|||
if (client1.statusCode == 200) { |
|||
var details = jsonDecode(client1.body); |
|||
if (mounted) |
|||
setState(() { |
|||
campaign = List<Campaign>.from( |
|||
details.map((model) => Campaign.fromJSON(model)).toList()); |
|||
}); |
|||
if (campaignMain == null) { |
|||
setState(() { |
|||
campaignMain = campaign; |
|||
}); |
|||
} |
|||
} |
|||
return campaign; |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
searchkey = new TextEditingController(); |
|||
_future = getCampaigns(); |
|||
searchkey.addListener(() async { |
|||
if (searchkey.text.isNotEmpty) { |
|||
getCampaigns(); |
|||
} else { |
|||
setState(() { |
|||
campaign = campaignMain; |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: AppBar( |
|||
backgroundColor: Colors.transparent, |
|||
automaticallyImplyLeading: true, |
|||
title: Text("Join a Campaign"), |
|||
centerTitle: true, |
|||
), |
|||
body: Container( |
|||
// padding: EdgeInsets.only( |
|||
// left: 10, |
|||
// right: 10, |
|||
// ), |
|||
child: Column( |
|||
children: [ |
|||
buildCampaignHead(context, searchkey, clearText), |
|||
SingleChildScrollView( |
|||
scrollDirection: Axis.vertical, |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
// height: MediaQuery.of(context).size.height, |
|||
child: FutureBuilder( |
|||
future: _future, |
|||
builder: (context, snapshot) { |
|||
if (snapshot.data == null && |
|||
snapshot.connectionState == ConnectionState.waiting) { |
|||
return Container( |
|||
child: Center( |
|||
child: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
); |
|||
} else if (snapshot.data == null && |
|||
snapshot.connectionState == ConnectionState.done) { |
|||
return Container( |
|||
height: MediaQuery.of(context).size.width, |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Center( |
|||
child: Text( |
|||
"Sorry there are no open campaigns at the moment"), |
|||
), |
|||
); |
|||
} else { |
|||
return ListView.builder( |
|||
primary: true, |
|||
scrollDirection: Axis.vertical, |
|||
shrinkWrap: true, |
|||
itemCount: campaign.length, |
|||
itemBuilder: (context, index) { |
|||
return buildCampaign( |
|||
context, |
|||
campaign.elementAt(index), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
}, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,142 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Classes/API Clasess/Campaign.dart'; |
|||
import 'package:teso/providers/pageAnimations.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:jiffy/jiffy.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/AuditionPage.dart'; |
|||
|
|||
buildCampaign(BuildContext context, Campaign campaignItem) { |
|||
return Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
//height: 120, |
|||
// padding: EdgeInsets.only( |
|||
// left: 10, |
|||
// right: 10, |
|||
// ), |
|||
child: Material( |
|||
elevation: 2.5, |
|||
child: SingleChildScrollView( |
|||
scrollDirection: Axis.horizontal, |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
Container( |
|||
width: 90, |
|||
height: 95, |
|||
decoration: BoxDecoration( |
|||
border: Border.all( |
|||
color: Colors.grey, |
|||
width: 0.5, |
|||
), |
|||
borderRadius: BorderRadius.only( |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(30.0), |
|||
topRight: Radius.circular(30.0), |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
), |
|||
child: Image( |
|||
width: MediaQuery.of(context).size.width * 0.28, |
|||
height: 110, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage(campaignItem.targetProduct != null |
|||
? productURL + campaignItem.targetProduct |
|||
: ""), |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
width: MediaQuery.of(context).size.width - 105, |
|||
//height: 110, |
|||
padding: EdgeInsets.all(5), |
|||
child: Column( |
|||
children: [ |
|||
Container( |
|||
width: double.infinity, |
|||
child: Wrap( |
|||
direction: Axis.horizontal, |
|||
children: [ |
|||
Text( |
|||
"Campaign Title : ", |
|||
style: TextStyle(fontWeight: FontWeight.bold), |
|||
), |
|||
Text(campaignItem.title) |
|||
], |
|||
), |
|||
), |
|||
Container( |
|||
width: double.infinity, |
|||
child: Wrap( |
|||
direction: Axis.horizontal, |
|||
children: [ |
|||
Text( |
|||
"Description : ", |
|||
style: TextStyle(fontWeight: FontWeight.bold), |
|||
), |
|||
Text( |
|||
campaignItem.description.length > 90 |
|||
? campaignItem.description.substring(0, 90) + |
|||
"...." |
|||
: campaignItem.description, |
|||
) |
|||
], |
|||
), |
|||
), |
|||
Container( |
|||
width: double.infinity, |
|||
child: Wrap( |
|||
direction: Axis.horizontal, |
|||
children: [ |
|||
Text( |
|||
"Start Date : ", |
|||
style: TextStyle(fontWeight: FontWeight.bold), |
|||
), |
|||
Text(Jiffy(campaignItem.startDate).yMMMMd), |
|||
], |
|||
), |
|||
), |
|||
Container( |
|||
width: double.infinity, |
|||
child: Align( |
|||
alignment: Alignment.centerRight, |
|||
child: Container( |
|||
width: 90, |
|||
child: ElevatedButton( |
|||
style: ElevatedButton.styleFrom( |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular(20.0), |
|||
), |
|||
), |
|||
primary: accentMain, |
|||
), |
|||
onPressed: () => Navigator.push( |
|||
context, |
|||
PageTransition( |
|||
child: Audition( |
|||
campaign: campaignItem, |
|||
), |
|||
type: PageTransitionType.rightToLeft, |
|||
), |
|||
), |
|||
child: Text("Join"), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
@ -1,45 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
buildCampaignHead( |
|||
BuildContext context, TextEditingController searchkey, Function filter) { |
|||
return Container( |
|||
height: 60.0, |
|||
//margin: EdgeInsets.all(10.0), |
|||
padding: EdgeInsets.all(10.0), |
|||
child: Material( |
|||
elevation: 4.0, |
|||
borderRadius: BorderRadius.circular(12.0), |
|||
shadowColor: Theme.of(context).backgroundColor, |
|||
child: Row( |
|||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|||
children: [ |
|||
//buildSmartSearch(context), |
|||
new Expanded( |
|||
child: InkWell( |
|||
onTap: () => print("Searching"), |
|||
child: TextField( |
|||
autofocus: false, |
|||
enabled: true, |
|||
textAlign: TextAlign.start, |
|||
controller: searchkey, |
|||
onChanged: (String v) => filter(v), |
|||
style: TextStyle( |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
decoration: InputDecoration( |
|||
border: InputBorder.none, |
|||
prefixIcon: Icon( |
|||
Icons.search, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
hintText: "Search", |
|||
hintStyle: TextStyle(color: Colors.grey), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
@ -1,137 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
buildCommentTile(BuildContext context, bool available, Uint8List bytes, |
|||
TextEditingController controller) { |
|||
return Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: 170, |
|||
margin: EdgeInsets.only(bottom: 20), |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(30.0), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(30.0), |
|||
topRight: Radius.circular(30.0), |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
), |
|||
child: Material( |
|||
elevation: 50.0, |
|||
borderRadius: BorderRadius.circular(12.0), |
|||
child: InkWell( |
|||
onTap: () { |
|||
// Navigator.push( |
|||
// context, |
|||
// PageTransition( |
|||
// type: PageTransitionType.rightToLeft, |
|||
// child: CommentSection(), |
|||
// ), |
|||
// ); |
|||
}, |
|||
child: Column( |
|||
children: [ |
|||
Container( |
|||
margin: EdgeInsets.only(top: 10), |
|||
height: 30, |
|||
width: double.infinity, |
|||
child: Center( |
|||
child: Text( |
|||
"Comments", |
|||
style: TextStyle( |
|||
fontSize: 16, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
Divider(), |
|||
Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 10), |
|||
width: double.infinity, |
|||
child: Text("Love this post ? Say something!")), |
|||
GestureDetector( |
|||
onTap: () => print("hello"), |
|||
child: Container( |
|||
padding: EdgeInsets.symmetric( |
|||
vertical: 08, |
|||
horizontal: 6, |
|||
), |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Row( |
|||
children: [ |
|||
Container( |
|||
height: 45.0, |
|||
width: 50.0, |
|||
margin: EdgeInsets.only(right: 8), |
|||
decoration: new BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
color: Colors.grey, |
|||
), |
|||
child: !available |
|||
? Center( |
|||
child: Text("B"), |
|||
) |
|||
: Image( |
|||
fit: BoxFit.fill, |
|||
image: MemoryImage(bytes), |
|||
), |
|||
), |
|||
Container( |
|||
width: MediaQuery.of(context).size.width * 0.55, |
|||
height: 50, |
|||
child: TextField( |
|||
maxLines: 2, |
|||
autofocus: false, |
|||
enabled: true, |
|||
textAlign: TextAlign.start, |
|||
controller: controller, |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
decoration: InputDecoration( |
|||
border: InputBorder.none, |
|||
//contentPadding: EdgeInsets.only(top: 14.0), |
|||
hintText: "Add a comment", |
|||
hintStyle: TextStyle(color: Colors.grey), |
|||
), |
|||
), |
|||
), |
|||
GestureDetector( |
|||
onTap: () { |
|||
print("send comment"); |
|||
}, |
|||
child: Container( |
|||
margin: EdgeInsets.all(20), |
|||
height: 30, |
|||
width: 30, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(30.0), |
|||
color: Color.fromRGBO(0, 0, 0, 0.4), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(30.0), |
|||
topRight: Radius.circular(30.0), |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
), |
|||
child: Align( |
|||
alignment: Alignment.center, |
|||
child: Icon( |
|||
Icons.send, |
|||
)), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
@ -1,40 +0,0 @@ |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/SpecialPosts.dart'; |
|||
import 'package:teso/providers/pageAnimations.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
buildPosted(BuildContext context, Post post, double height, TesoUser user, |
|||
bool addable) { |
|||
return Container( |
|||
margin: EdgeInsets.all(3), |
|||
width: MediaQuery.of(context).size.width * 0.5, |
|||
height: MediaQuery.of(context).size.width * height, |
|||
color: Colors.black, |
|||
child: GestureDetector( |
|||
onTap: () { |
|||
Navigator.push( |
|||
context, |
|||
PageTransition( |
|||
child: new ViewPost( |
|||
postedAd: post, |
|||
play: true, |
|||
), |
|||
type: PageTransitionType.fade)); |
|||
}, |
|||
child: CachedNetworkImage( |
|||
imageUrl: tesoPostThumb(post.playbackID), |
|||
imageBuilder: (context, imageProvider) => FadeInImage( |
|||
width: double.infinity, |
|||
fit: BoxFit.fill, |
|||
image: imageProvider, |
|||
placeholder: AssetImage("assets/images/blank.jpg"), |
|||
), |
|||
errorWidget: (context, url, error) => |
|||
Image.asset("assets/images/blank.jpg"), |
|||
), |
|||
), |
|||
); |
|||
} |
@ -1,56 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:time_elapsed/time_elapsed.dart'; |
|||
|
|||
buildPostTile3P(BuildContext context, TesoUser user, Post postedAd) { |
|||
return ListTile( |
|||
leading: Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
)), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage( |
|||
serverLocation + "api/pulldp/" + postedAd.publisherID, |
|||
), |
|||
placeholder: AssetImage("assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
title: new RichText( |
|||
text: new TextSpan( |
|||
style: new TextStyle( |
|||
fontSize: 14.0, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
children: <TextSpan>[ |
|||
new TextSpan( |
|||
text: user.username + " ", |
|||
style: new TextStyle(fontWeight: FontWeight.bold)), |
|||
new TextSpan( |
|||
text: postedAd.title, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
subtitle: Text( |
|||
TimeElapsed.fromDateTime(postedAd.timestamp), |
|||
), |
|||
); |
|||
} |
@ -1,69 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/PostedAd.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:time_elapsed/time_elapsed.dart'; |
|||
|
|||
buildPostTile(BuildContext context, PostedAd postedAd) { |
|||
return Consumer<UserProvider>( |
|||
builder: (context, value, child) { |
|||
return ListTile( |
|||
leading: Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
)), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: value.currentUser.thumbnail_dp == null |
|||
? Center( |
|||
child: Text( |
|||
value.currentUser.username.characters |
|||
.characterAt(0) |
|||
.toString() |
|||
.toUpperCase(), |
|||
), |
|||
) |
|||
: FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage( |
|||
userdpURL + value.currentUser.thumbnail_dp), |
|||
placeholder: AssetImage("assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
title: new RichText( |
|||
text: new TextSpan( |
|||
style: new TextStyle( |
|||
fontSize: 14.0, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
children: <TextSpan>[ |
|||
new TextSpan( |
|||
text: value.currentUser.username + " ", |
|||
style: new TextStyle(fontWeight: FontWeight.bold)), |
|||
new TextSpan( |
|||
text: postedAd.post.title, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
subtitle: Text( |
|||
TimeElapsed.fromDateTime(postedAd.post.timestamp), |
|||
), |
|||
); |
|||
}, |
|||
); |
|||
} |
@ -1,35 +0,0 @@ |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Classes/Firebase/Posts.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/UserPosts.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
buildPosted(BuildContext context, FBPosts post, double height) { |
|||
return Container( |
|||
margin: EdgeInsets.all(3), |
|||
width: MediaQuery.of(context).size.width * 0.5, |
|||
height: MediaQuery.of(context).size.width * height, |
|||
color: Colors.black, |
|||
child: GestureDetector( |
|||
onTap: () { |
|||
Navigator.of(context).push(new PageRouteBuilder( |
|||
pageBuilder: (_, __, ___) => new UserPosts(postedAd: post), |
|||
)); |
|||
}, |
|||
child: CachedNetworkImage( |
|||
imageUrl: tesoPostThumb( |
|||
post.playbackID, |
|||
), |
|||
imageBuilder: (context, imageProvider) => FadeInImage( |
|||
width: double.infinity, |
|||
fit: BoxFit.fill, |
|||
image: imageProvider, |
|||
placeholder: AssetImage("assets/images/blank.jpg"), |
|||
), |
|||
errorWidget: (context, url, error) => Container( |
|||
color: Colors.grey[800], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
@ -1,89 +0,0 @@ |
|||
import 'dart:convert'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:loading_indicator/loading_indicator.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:teso/Classes/Uploading.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
uploadTile(BuildContext context, Uploading pendingUpload) { |
|||
return Card( |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: 70, |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
Container( |
|||
//constraints: BoxConstraints(minHeight: 80, maxHeight: 150), |
|||
width: 60, |
|||
height: 60, |
|||
child: pendingUpload != null && pendingUpload.thumbnail != null |
|||
? Image( |
|||
width: double.infinity, |
|||
height: double.infinity, |
|||
fit: BoxFit.cover, |
|||
image: MemoryImage(base64Decode(pendingUpload.thumbnail)), |
|||
gaplessPlayback: true, |
|||
) |
|||
: Image( |
|||
width: double.infinity, |
|||
height: double.infinity, |
|||
fit: BoxFit.cover, |
|||
image: AssetImage( |
|||
"assets/images/blank.jpg", |
|||
), |
|||
gaplessPlayback: true, |
|||
), |
|||
), |
|||
pendingUpload.isProcessing |
|||
? new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Text("Preparing post.."), |
|||
LoadingIndicator( |
|||
indicatorType: Indicator.ballPulse, |
|||
|
|||
/// Required, The loading type of the widget |
|||
colors: [tesoAsh, tesoBlue, tesoGold], |
|||
|
|||
/// Optional, The color collections |
|||
strokeWidth: 2, |
|||
|
|||
/// Optional, The stroke of the line, only applicable to widget which contains line |
|||
) |
|||
], |
|||
) |
|||
: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Text("Processing.."), |
|||
Container( |
|||
width: MediaQuery.of(context).size.width * 0.65, |
|||
height: 10, |
|||
child: LinearProgressIndicator( |
|||
value: pendingUpload.pending, |
|||
backgroundColor: tesoAsh, |
|||
valueColor: new AlwaysStoppedAnimation<Color>(tesoBlue), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
pendingUpload.isProcessing |
|||
? Container() |
|||
: Container( |
|||
width: 40, |
|||
height: 40, |
|||
child: InkWell( |
|||
onTap: () => |
|||
Provider.of<UserProvider>(context, listen: false) |
|||
.cancelUpload(pendingUpload), |
|||
child: Icon(Icons.close), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
@ -1,212 +0,0 @@ |
|||
// import 'package:flutter/material.dart'; |
|||
|
|||
// import 'package:teso/Classes/TextE.dart'; |
|||
// import 'package:teso/util/SizeConfig.dart'; |
|||
// import 'textstyler/src/toolbar_action.dart'; |
|||
// import 'textstyler/text_style_editor.dart'; |
|||
|
|||
// // ignore: must_be_immutable |
|||
// class TextEdit extends StatefulWidget { |
|||
// Textted content; |
|||
// TextEdit({Key key, this.content}) : super(key: key); |
|||
|
|||
// @override |
|||
// _TextEditState createState() => _TextEditState(); |
|||
// } |
|||
|
|||
// class _TextEditState extends State<TextEdit> { |
|||
// TextStyle textStyle; |
|||
// TextAlign textAlign; |
|||
// List<String> fonts = [ |
|||
// 'Billabong', |
|||
// 'AlexBrush', |
|||
// 'Allura', |
|||
// 'Arizonia', |
|||
// 'ChunkFive', |
|||
// 'GrandHotel', |
|||
// 'GreatVibes', |
|||
// 'Lobster', |
|||
// 'OpenSans', |
|||
// 'OstrichSans', |
|||
// 'Oswald', |
|||
// 'Pacifico', |
|||
// 'Quicksand', |
|||
// 'Roboto', |
|||
// 'SEASRN', |
|||
// 'Windsong', |
|||
// ]; |
|||
// List<Color> paletteColors = [ |
|||
// Colors.black, |
|||
// Colors.white, |
|||
// Color(int.parse('0xffEA2027')), |
|||
// Color(int.parse('0xff006266')), |
|||
// Color(int.parse('0xff1B1464')), |
|||
// Color(int.parse('0xff5758BB')), |
|||
// Color(int.parse('0xff6F1E51')), |
|||
// Color(int.parse('0xffB53471')), |
|||
// Color(int.parse('0xffEE5A24')), |
|||
// Color(int.parse('0xff009432')), |
|||
// Color(int.parse('0xff0652DD')), |
|||
// Color(int.parse('0xff9980FA')), |
|||
// Color(int.parse('0xff833471')), |
|||
// Color(int.parse('0xff112CBC4')), |
|||
// Color(int.parse('0xffFDA7DF')), |
|||
// Color(int.parse('0xffED4C67')), |
|||
// Color(int.parse('0xffF79F1F')), |
|||
// Color(int.parse('0xffA3CB38')), |
|||
// Color(int.parse('0xff1289A7')), |
|||
// Color(int.parse('0xffD980FA')) |
|||
// ]; |
|||
// FocusNode _focus = new FocusNode(); |
|||
// TextEditingController controller; |
|||
// @override |
|||
// void initState() { |
|||
// controller = new TextEditingController(); |
|||
// textStyle = TextStyle( |
|||
// fontSize: 15, |
|||
// color: Colors.white, |
|||
// fontFamily: 'OpenSans', |
|||
// ); |
|||
// textAlign = TextAlign.left; |
|||
// _focus.addListener(_onFocusChange); |
|||
|
|||
// controller.text = widget.content.text != null ? widget.content.text : ""; |
|||
// textStyle = |
|||
// widget.content.textStyle != null ? widget.content.textStyle : null; |
|||
// textAlign = widget.content.textAlign != null |
|||
// ? widget.content.textAlign |
|||
// : TextAlign.center; |
|||
// super.initState(); |
|||
// } |
|||
|
|||
// @override |
|||
// void dispose() { |
|||
// _focus.removeListener(_onFocusChange); |
|||
// _focus.dispose(); |
|||
// super.dispose(); |
|||
// } |
|||
|
|||
// void _onFocusChange() { |
|||
// debugPrint("Focus: " + _focus.hasFocus.toString()); |
|||
// } |
|||
|
|||
// void verify() { |
|||
// if (_focus.hasFocus) { |
|||
// _focus.unfocus(); |
|||
// } else { |
|||
// Navigator.pop( |
|||
// context, |
|||
// new Textted( |
|||
// text: controller.text, |
|||
// textAlign: textAlign, |
|||
// textStyle: textStyle, |
|||
// )); |
|||
// } |
|||
// } |
|||
|
|||
// @override |
|||
// Widget build(BuildContext context) { |
|||
// SizeConfig().init(context); |
|||
// return Scaffold( |
|||
// resizeToAvoidBottomInset: false, |
|||
// backgroundColor: Color.fromRGBO(0, 0, 0, 0.8), |
|||
// appBar: AppBar( |
|||
// backgroundColor: Colors.transparent, |
|||
// leading: IconButton( |
|||
// onPressed: () => Navigator.pop(context, widget.content), |
|||
// icon: Icon( |
|||
// Feather.x, |
|||
// color: Colors.white, |
|||
// ), |
|||
// ), |
|||
// actions: [ |
|||
// IconButton( |
|||
// onPressed: verify, |
|||
// icon: Icon( |
|||
// AntDesign.check, |
|||
// color: Colors.white, |
|||
// ), |
|||
// ), |
|||
// ], |
|||
// ), |
|||
// body: Container( |
|||
// height: SizeConfig.safeBlockVertical * 40, |
|||
// child: Center( |
|||
// child: TextField( |
|||
// controller: controller, |
|||
// // enabled: false, |
|||
// focusNode: _focus, |
|||
// style: textStyle, |
|||
// textAlign: textAlign, |
|||
// // maxLines: 4, |
|||
// decoration: new InputDecoration( |
|||
// filled: true, |
|||
// enabledBorder: OutlineInputBorder( |
|||
// borderRadius: BorderRadius.all( |
|||
// Radius.circular(10.0), |
|||
// ), |
|||
// borderSide: BorderSide( |
|||
// color: Colors.grey.shade400, |
|||
// width: 2, |
|||
// ), |
|||
// ), |
|||
// focusedBorder: OutlineInputBorder( |
|||
// borderRadius: BorderRadius.all( |
|||
// Radius.circular(10.0), |
|||
// ), |
|||
// borderSide: BorderSide( |
|||
// color: Colors.blue.shade300, |
|||
// width: 0, |
|||
// ), |
|||
// ), |
|||
// contentPadding: EdgeInsets.all(15), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// extendBody: false, |
|||
// extendBodyBehindAppBar: false, |
|||
// bottomSheet: Container( |
|||
// height: SizeConfig.safeBlockVertical * 60, |
|||
// child: Container( |
|||
// padding: EdgeInsets.all(10), |
|||
// decoration: BoxDecoration( |
|||
// color: Theme.of(context).backgroundColor, |
|||
// border: Border.symmetric( |
|||
// horizontal: BorderSide( |
|||
// color: Theme.of(context).backgroundColor, |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// child: Align( |
|||
// alignment: Alignment.topCenter, |
|||
// child: SingleChildScrollView( |
|||
// scrollDirection: Axis.vertical, |
|||
// child: TextStyleEditor( |
|||
// fonts: fonts, |
|||
// paletteColors: paletteColors, |
|||
// textStyle: textStyle, |
|||
// textAlign: textAlign, |
|||
// initialTool: EditorToolbarAction.fontFamilyTool, |
|||
// onTextAlignEdited: (align) { |
|||
// setState(() { |
|||
// textAlign = align; |
|||
// }); |
|||
// }, |
|||
// onTextStyleEdited: (style) { |
|||
// setState(() { |
|||
// textStyle = textStyle.merge(style); |
|||
// }); |
|||
// }, |
|||
// onCpasLockTaggle: (caps) { |
|||
// print(caps); |
|||
// }, |
|||
// //onToolbarActionChanged: (fu) => , |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// } |
|||
// } |
@ -1,840 +0,0 @@ |
|||
// import 'dart:typed_data'; |
|||
// import 'package:firebase_crashlytics/firebase_crashlytics.dart'; |
|||
// import 'package:flutter/material.dart'; |
|||
// import 'package:flutter/rendering.dart'; |
|||
// import 'package:flutter/services.dart'; |
|||
// import 'dart:io'; |
|||
// |
|||
// import 'package:image_gallery_saver/image_gallery_saver.dart'; |
|||
// import 'package:page_transition/page_transition.dart'; |
|||
// import 'package:share_plus/share_plus.dart'; |
|||
// import 'package:teso/Classes/TextE.dart'; |
|||
// import 'package:teso/Pages/PageWidgets/Editors/SampleThumbnail.dart'; |
|||
// import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Editor/TextEditor.dart'; |
|||
// import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trim_editor.dart'; |
|||
// import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trimmer.dart'; |
|||
// import 'package:teso/Pages/Sub_Pages/Posts/CreatePost.dart'; |
|||
// import 'package:teso/util/SizeConfig.dart'; |
|||
// import 'package:video_player/video_player.dart'; |
|||
// import 'package:teso/util/consts.dart'; |
|||
// import 'dart:async'; |
|||
// import 'package:path_provider/path_provider.dart'; |
|||
// import 'package:teso/Classes/TesoUser.dart'; |
|||
// import 'package:provider/provider.dart'; |
|||
// import 'package:teso/providers/user_provider.dart'; |
|||
// import 'package:flutter/cupertino.dart'; |
|||
// import 'package:image/image.dart' as IMG; |
|||
// import 'package:video_thumbnail/video_thumbnail.dart'; |
|||
// import 'package:teso/Classes/ColorFilters.dart'; |
|||
|
|||
// class VideoReview extends StatefulWidget { |
|||
// final video; |
|||
// final bool recorded; |
|||
|
|||
// const VideoReview({Key key, @required this.video, @required this.recorded}) |
|||
// : super(key: key); |
|||
// @override |
|||
// _VideoReviewState createState() => _VideoReviewState(); |
|||
// } |
|||
|
|||
// class _VideoReviewState extends State<VideoReview> |
|||
// with TickerProviderStateMixin { |
|||
// Trimmer _trimmer = new Trimmer(); |
|||
// VideoPlayerController videoController; |
|||
// VoidCallback videoPlayerListener; |
|||
// bool muted = false; |
|||
// String readyVideo; |
|||
// Color textColor = Colors.white; |
|||
// double _startValue = 0.15; |
|||
// double _endValue = 60000.0; |
|||
// var _future; |
|||
// bool _isPlaying = false; |
|||
// Duration _duration; |
|||
// Duration _position; |
|||
// ByteData bytes; |
|||
// Uint8List imageBitmap; |
|||
// Uint8List thumbnail; |
|||
// Directory tempDirectory; |
|||
// TesoUser user; |
|||
// bool processing = false; |
|||
// bool downloaded = false; |
|||
// bool processed = false; |
|||
// final key = new GlobalKey(); |
|||
// double currentOffset = 0; |
|||
|
|||
// // ScreenshotController screenshotController = ScreenshotController(); |
|||
// Offset offset = Offset(0, SizeConfig.safeBlockVertical * 50); |
|||
// var indexFilter = 0; |
|||
// ScrollController controller; |
|||
// TextStyle textStyle; |
|||
// TextAlign textAlign; |
|||
// bool showFilter = false; |
|||
// Textted editting = new Textted(); |
|||
// List<String> fonts = [ |
|||
// 'Billabong', |
|||
// 'AlexBrush', |
|||
// 'Allura', |
|||
// 'Arizonia', |
|||
// 'ChunkFive', |
|||
// 'GrandHotel', |
|||
// 'GreatVibes', |
|||
// 'Lobster', |
|||
// 'OpenSans', |
|||
// 'OstrichSans', |
|||
// 'Oswald', |
|||
// 'Pacifico', |
|||
// 'Quicksand', |
|||
// 'Roboto', |
|||
// 'SEASRN', |
|||
// 'Windsong', |
|||
// ]; |
|||
// List<Color> paletteColors = [ |
|||
// Colors.black, |
|||
// Colors.white, |
|||
// Color(int.parse('0xffEA2027')), |
|||
// Color(int.parse('0xff006266')), |
|||
// Color(int.parse('0xff1B1464')), |
|||
// Color(int.parse('0xff5758BB')), |
|||
// Color(int.parse('0xff6F1E51')), |
|||
// Color(int.parse('0xffB53471')), |
|||
// Color(int.parse('0xffEE5A24')), |
|||
// Color(int.parse('0xff009432')), |
|||
// Color(int.parse('0xff0652DD')), |
|||
// Color(int.parse('0xff9980FA')), |
|||
// Color(int.parse('0xff833471')), |
|||
// Color(int.parse('0xff112CBC4')), |
|||
// Color(int.parse('0xffFDA7DF')), |
|||
// Color(int.parse('0xffED4C67')), |
|||
// Color(int.parse('0xffF79F1F')), |
|||
// Color(int.parse('0xffA3CB38')), |
|||
// Color(int.parse('0xff1289A7')), |
|||
// Color(int.parse('0xffD980FA')) |
|||
// ]; |
|||
// List<ColorFilter> colorFilters = [ |
|||
// new ColorFilter(code: Color(0xFFffffff), name: "Original"), |
|||
// new ColorFilter(code: Color(0xFFffffff), name: "White"), |
|||
// new ColorFilter(code: Color(0xFF5E2612), name: "Sepia"), |
|||
// new ColorFilter(code: Color(0xFF8BA446), name: "Martini Olive"), |
|||
// new ColorFilter(code: Color(0xFFFFF8DC), name: "Cornsilk"), |
|||
// new ColorFilter(code: Color(0xFFCDB7B5), name: "Mistyrose"), |
|||
// new ColorFilter(code: Color(0xFFEEE9E9), name: "Snow"), |
|||
// new ColorFilter(code: Color(0xFF856363), name: "Dusty"), |
|||
// new ColorFilter(code: Color(0xFF8C1717), name: "Scarlet"), |
|||
// new ColorFilter(code: Color(0xFF615E3F), name: "Tank"), |
|||
// ]; |
|||
|
|||
// void _scrollListener() { |
|||
// setState(() { |
|||
// indexFilter = |
|||
// (controller.offset / MediaQuery.of(context).size.width).round() + 1; |
|||
// if (controller.offset > currentOffset) { |
|||
// controller.animateTo(currentOffset + MediaQuery.of(context).size.width, |
|||
// duration: Duration(microseconds: 2), curve: Curves.easeIn); |
|||
// currentOffset = controller.offset; |
|||
// } else { |
|||
// controller.animateTo(currentOffset - MediaQuery.of(context).size.width, |
|||
// duration: Duration(microseconds: 2), curve: Curves.easeIn); |
|||
// currentOffset = controller.offset; |
|||
// } |
|||
// }); |
|||
// print(indexFilter); |
|||
// } |
|||
|
|||
// Future<void> _startVideoPlayer() async { |
|||
// await videoController.play(); |
|||
// } |
|||
|
|||
// Future<void> initializeController(String fileLocation) async { |
|||
// videoController = VideoPlayerController.file(File(fileLocation)); |
|||
|
|||
// videoPlayerListener = () async { |
|||
// Timer.run(() { |
|||
// this.setState(() { |
|||
// _position = videoController.value.position; |
|||
// }); |
|||
// setState(() { |
|||
// _duration = Duration(milliseconds: _endValue.round()); |
|||
// }); |
|||
// if (_duration?.compareTo(_position) == 0 || |
|||
// _duration?.compareTo(_position) == -1) { |
|||
// this.setState(() { |
|||
// _isPlaying = false; |
|||
// }); |
|||
// videoController.pause(); |
|||
// videoController.seekTo(Duration(milliseconds: _startValue.round())); |
|||
// } else {} |
|||
// }); |
|||
// }; |
|||
// videoController.addListener(videoPlayerListener); |
|||
// await videoController.setLooping(true); |
|||
// await videoController.initialize(); |
|||
// await _trimmer.loadVideo(videoFile: File(fileLocation)); |
|||
// } |
|||
|
|||
// @override |
|||
// void initState() { |
|||
// // textStyle = TextStyle( |
|||
// // fontSize: 15, |
|||
// // color: Colors.white, |
|||
// // fontFamily: 'OpenSans', |
|||
// // ); |
|||
// controller = new ScrollController(); |
|||
// controller.addListener(_scrollListener); |
|||
// readyVideo = widget.video; |
|||
// if (readyVideo != null) _future = initializeController(readyVideo); |
|||
// rootBundle.load("assets/images/rawLogo.png").then((value) => setState(() { |
|||
// imageBitmap = value.buffer.asUint8List(); |
|||
// IMG.Image img = IMG.decodeImage(imageBitmap); |
|||
// IMG.Image resized = IMG.copyResize(img, width: 50, height: 60); |
|||
// imageBitmap = IMG.encodePng(resized); |
|||
// })); |
|||
// super.initState(); |
|||
// editting = |
|||
// new Textted(text: "", textAlign: textAlign, textStyle: textStyle); |
|||
// } |
|||
|
|||
// Future<Uint8List> saveTextOverlay() async { |
|||
// return await screenshotController.capture( |
|||
// pixelRatio: videoController.value.aspectRatio); |
|||
// } |
|||
|
|||
// @override |
|||
// void dispose() { |
|||
// videoController?.dispose(); |
|||
// controller.dispose(); |
|||
// super.dispose(); |
|||
// } |
|||
|
|||
// void postVideo(context) async { |
|||
// setState(() { |
|||
// processing = true; |
|||
// }); |
|||
// if (processed) { |
|||
// await Navigator.pushReplacement( |
|||
// context, |
|||
// PageTransition( |
|||
// type: PageTransitionType.leftToRight, |
|||
// child: CreatePost( |
|||
// video: readyVideo, |
|||
// aspectRatio: videoController.value.aspectRatio.toString(), |
|||
// thumbnail: this.thumbnail, |
|||
// ), |
|||
// )); |
|||
// } else { |
|||
// readyVideo = await processVideo(context, false); |
|||
// await Navigator.pushReplacement( |
|||
// context, |
|||
// PageTransition( |
|||
// type: PageTransitionType.leftToRight, |
|||
// child: CreatePost( |
|||
// video: readyVideo, |
|||
// aspectRatio: videoController.value.aspectRatio.toString(), |
|||
// thumbnail: this.thumbnail, |
|||
// ), |
|||
// )); |
|||
// } |
|||
// } |
|||
|
|||
// Future<void> downloadVideo(context) async { |
|||
// try { |
|||
// setState(() { |
|||
// processing = true; |
|||
// }); |
|||
// String output = await processVideo(context, true); |
|||
// await ImageGallerySaver.saveFile(output).catchError((error, stackTrace) { |
|||
// setState(() { |
|||
// processing = false; |
|||
// downloaded = false; |
|||
// }); |
|||
// }).then((value) { |
|||
// setState(() { |
|||
// processing = false; |
|||
// downloaded = true; |
|||
// }); |
|||
// }); |
|||
// } catch (e) { |
|||
// print(e); |
|||
// } |
|||
// } |
|||
|
|||
// Future<String> processVideo(context, bool watermark) async { |
|||
// user = Provider.of<UserProvider>(context, listen: false).currentUser; |
|||
// String location = await getTemporaryDirectory().then((value) => |
|||
// value.path + |
|||
// "/" + |
|||
// DateTime.now().millisecondsSinceEpoch.toString() + |
|||
// ".mp4"); |
|||
// String initial = await _trimmer.saveTrimmedVideo( |
|||
// applyVideoEncoding: false, |
|||
// startValue: _startValue, |
|||
// endValue: videoController.value.duration.inMilliseconds > 5900 && |
|||
// videoController.value.duration.inMilliseconds >= _endValue |
|||
// ? _endValue |
|||
// : double.parse( |
|||
// videoController.value.duration.inMilliseconds.toString()), |
|||
// ); |
|||
// this.thumbnail = await generateThumbnail(); |
|||
// if (widget.recorded) { |
|||
// try { |
|||
// // Uint8List textBytes; |
|||
// // if (editting.text.isNotEmpty) textBytes = await saveTextOverlay(); |
|||
// // int xposition = ScaledPosition.getWidth( |
|||
// // SizeConfig.safeBlockHorizontal * 100, |
|||
// // videoController.value.size.width, |
|||
// // offset.dx); |
|||
// // int yposition = ScaledPosition.getHeight( |
|||
// // SizeConfig.safeBlockVertical * 100, |
|||
// // videoController.value.size.height, |
|||
// // offset.dy); |
|||
// // if (watermark) { |
|||
// // final tapiocaBalls = [ |
|||
// // if (indexFilter > 1) |
|||
// // TapiocaBall.filterFromColor(colorFilters[indexFilter - 1].code), |
|||
// // TapiocaBall.imageOverlay(imageBitmap, 0, 0), |
|||
// // if (textBytes != null) |
|||
// // TapiocaBall.imageOverlay(textBytes, xposition, yposition), |
|||
// // ]; |
|||
|
|||
// // final cup = Cup(Content(initial), tapiocaBalls); |
|||
// // await cup.suckUp(location); |
|||
|
|||
// // setState(() { |
|||
// // processed = true; |
|||
// // }); |
|||
// // } else if (!watermark && indexFilter == 1) { |
|||
// // setState(() { |
|||
// // processed = true; |
|||
// // }); |
|||
// // final tapiocaBalls = [ |
|||
// // if (textBytes != null) |
|||
// // TapiocaBall.imageOverlay(textBytes, xposition, yposition), |
|||
// // ]; |
|||
// // final cup = Cup(Content(initial), tapiocaBalls); |
|||
// // await cup.suckUp(location); |
|||
// // } else { |
|||
// // final tapiocaBalls = [ |
|||
// // if (indexFilter != 1 && indexFilter != 0) |
|||
// // TapiocaBall.filterFromColor(colorFilters[indexFilter - 1].code), |
|||
// // if (textBytes != null) |
|||
// // TapiocaBall.imageOverlay(textBytes, xposition, yposition), |
|||
// // ]; |
|||
// // final cup = Cup(Content(initial), tapiocaBalls); |
|||
// // await cup.suckUp(location); |
|||
|
|||
// // setState(() { |
|||
// // processed = true; |
|||
// // }); |
|||
// // } |
|||
// } catch (e) { |
|||
// print(e); |
|||
// FirebaseCrashlytics.instance.recordError( |
|||
// e, |
|||
// e, |
|||
// reason: "Video Editor", |
|||
// ); |
|||
// setState(() { |
|||
// processed = true; |
|||
// }); |
|||
// } |
|||
// } else { |
|||
// location = initial; |
|||
// } |
|||
// return location; |
|||
// } |
|||
|
|||
// Future<Uint8List> generateThumbnail() async { |
|||
// try { |
|||
// Uint8List thumbnail; |
|||
|
|||
// thumbnail = await VideoThumbnail.thumbnailData( |
|||
// video: widget.video, |
|||
// imageFormat: ImageFormat.JPEG, |
|||
// maxWidth: 0, |
|||
// maxHeight: 0, |
|||
// timeMs: 100, |
|||
// quality: 100, |
|||
// ); |
|||
// return thumbnail; |
|||
// } catch (e) { |
|||
// print("Error :::: " + e); |
|||
// return null; |
|||
// } |
|||
// } |
|||
|
|||
// Future<void> shareVideo(context) async { |
|||
// setState(() { |
|||
// processing = true; |
|||
// }); |
|||
// if (readyVideo == widget.video) { |
|||
// readyVideo = await processVideo(context, true); |
|||
// Share.shareFiles([readyVideo]); |
|||
// } else { |
|||
// Share.shareFiles([readyVideo]); |
|||
// } |
|||
// setState(() { |
|||
// processing = false; |
|||
// }); |
|||
// } |
|||
|
|||
// void setText() async { |
|||
// Textted ed = await Navigator.push( |
|||
// context, |
|||
// PageRouteBuilder( |
|||
// opaque: false, |
|||
// pageBuilder: (_, __, ___) => TextEdit( |
|||
// content: editting, |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// if (ed != null) { |
|||
// setState(() { |
|||
// editting = ed; |
|||
// }); |
|||
// } |
|||
// } |
|||
|
|||
// void showFilters() async { |
|||
// if (thumbnail == null) this.thumbnail = await generateThumbnail(); |
|||
// setState(() { |
|||
// showFilter = !showFilter; |
|||
// }); |
|||
// } |
|||
|
|||
// void setFilter(index) { |
|||
// setState(() { |
|||
// indexFilter = index; |
|||
// }); |
|||
// } |
|||
|
|||
// @override |
|||
// Widget build(BuildContext context) { |
|||
// SizeConfig().init(context); |
|||
// return Scaffold( |
|||
// body: FutureBuilder( |
|||
// future: _future, |
|||
// builder: (context, snapshot) { |
|||
// if (snapshot.connectionState == ConnectionState.waiting) { |
|||
// return Container( |
|||
// color: Colors.black, |
|||
// width: MediaQuery.of(context).size.width, |
|||
// height: MediaQuery.of(context).size.height, |
|||
// child: Center( |
|||
// child: CircularProgressIndicator( |
|||
// backgroundColor: Colors.red, |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// } else { |
|||
// return Stack( |
|||
// children: [ |
|||
// videoContent(context), |
|||
// // //Filters |
|||
// filterWidget(context), |
|||
// textWidget(context), |
|||
// // Video trimmer |
|||
// trimmerWidget(context), |
|||
// // Pop button |
|||
// Align( |
|||
// alignment: Alignment.topLeft, |
|||
// child: InkWell( |
|||
// onTap: () => Navigator.pop(context), |
|||
// child: Container( |
|||
// margin: EdgeInsets.symmetric( |
|||
// horizontal: MediaQuery.of(context).size.width * 0.07, |
|||
// vertical: MediaQuery.of(context).size.width * 0.1, |
|||
// ), |
|||
// height: 35, |
|||
// width: 35, |
|||
// decoration: BoxDecoration( |
|||
// color: Color.fromRGBO(0, 0, 0, 0.4), |
|||
// shape: BoxShape.circle), |
|||
// child: Icon( |
|||
// EvilIcons.close, |
|||
// color: Colors.white, |
|||
// size: 20, |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// //Filter Buttons |
|||
// showFilter ? listFilters(context) : Container(), |
|||
// // Bottom buttons |
|||
// bottomButtons(context), |
|||
// Visibility( |
|||
// visible: processing, |
|||
// child: Container( |
|||
// width: MediaQuery.of(context).size.width, |
|||
// height: MediaQuery.of(context).size.height, |
|||
// color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
// padding: EdgeInsets.only( |
|||
// top: MediaQuery.of(context).size.width * 0.7), |
|||
// child: Center( |
|||
// child: Column( |
|||
// children: [ |
|||
// Image.asset(cupertinoActivityIndicatorSmall), |
|||
// Text( |
|||
// "Processing.....", |
|||
// style: TextStyle( |
|||
// color: Colors.white, |
|||
// ), |
|||
// ), |
|||
// ], |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ], |
|||
// ); |
|||
// } |
|||
// }), |
|||
// ); |
|||
// } |
|||
|
|||
// Widget trimmerWidget(context) { |
|||
// return Container( |
|||
// margin: EdgeInsets.symmetric( |
|||
// horizontal: MediaQuery.of(context).size.width * 0.01, |
|||
// vertical: MediaQuery.of(context).size.width * 0.20, |
|||
// ), |
|||
// width: MediaQuery.of(context).size.width, |
|||
// child: TrimEditor( |
|||
// borderPaintColor: tesoGold, |
|||
// circlePaintColor: tesoBlue, |
|||
// thumbnailQuality: 100, |
|||
// showDuration: true, |
|||
// viewerHeight: 50.0, |
|||
// maxVideoLength: Duration(seconds: 60), |
|||
// viewerWidth: MediaQuery.of(context).size.width, |
|||
// onChangeStart: (value) { |
|||
// if (!mounted) { |
|||
// setState(() { |
|||
// _startValue = value; |
|||
// }); |
|||
// } else { |
|||
// _startValue = value; |
|||
// } |
|||
// videoController.seekTo(Duration(milliseconds: value.round())); |
|||
// }, |
|||
// onChangeEnd: (value) { |
|||
// if (!mounted) { |
|||
// setState(() { |
|||
// _endValue = value; |
|||
// }); |
|||
// } else { |
|||
// _endValue = value; |
|||
// } |
|||
// }, |
|||
// onChangePlaybackState: (isPlaying) { |
|||
// if (mounted) |
|||
// setState(() { |
|||
// _isPlaying = isPlaying; |
|||
// }); |
|||
// }, |
|||
// )); |
|||
// } |
|||
|
|||
// Widget filterWidget(context) { |
|||
// return GestureDetector( |
|||
// onTap: () { |
|||
// !_isPlaying ? _startVideoPlayer() : videoController.pause(); |
|||
// setState(() { |
|||
// _isPlaying = !_isPlaying; |
|||
// }); |
|||
// }, |
|||
// child: Container( |
|||
// width: MediaQuery.of(context).size.width, |
|||
// height: MediaQuery.of(context).size.height, |
|||
// color: |
|||
// colorFilters.elementAt(indexFilter).name.toLowerCase() == "original" |
|||
// ? colorFilters.elementAt(indexFilter).code.withOpacity(0) |
|||
// : colorFilters.elementAt(indexFilter).code.withOpacity(0.5), |
|||
// ), |
|||
// ); |
|||
// } |
|||
|
|||
// Widget videoContent(context) { |
|||
// print(videoController.value.size.width); |
|||
|
|||
// return Container( |
|||
// width: MediaQuery.of(context).size.width, |
|||
// height: MediaQuery.of(context).size.height, |
|||
// color: Colors.black, |
|||
// child: Center( |
|||
// child: AspectRatio( |
|||
// aspectRatio: videoController.value.size != null |
|||
// ? videoController.value.aspectRatio |
|||
// : 1.0, |
|||
// child: Stack( |
|||
// children: [ |
|||
// InkWell( |
|||
// onTap: () { |
|||
// !_isPlaying ? _startVideoPlayer() : videoController.pause(); |
|||
// setState(() { |
|||
// _isPlaying = !_isPlaying; |
|||
// }); |
|||
// }, |
|||
// child: VideoPlayer( |
|||
// videoController, |
|||
// ), |
|||
// ), |
|||
// Container( |
|||
// width: double.infinity, |
|||
// height: double.infinity, |
|||
// child: GestureDetector( |
|||
// child: !_isPlaying |
|||
// ? Icon( |
|||
// Ionicons.md_play_circle, |
|||
// size: 60, |
|||
// color: Colors.white, |
|||
// ) |
|||
// : Container(), |
|||
// onTap: () { |
|||
// !_isPlaying |
|||
// ? _startVideoPlayer() |
|||
// : videoController.pause(); |
|||
// setState(() { |
|||
// _isPlaying = !_isPlaying; |
|||
// }); |
|||
// }, |
|||
// ), |
|||
// ), |
|||
// ], |
|||
// )), |
|||
// ), |
|||
// ); |
|||
// } |
|||
|
|||
// Widget bottomButtons(context) { |
|||
// if (widget.recorded) { |
|||
// return Align( |
|||
// alignment: Alignment.bottomLeft, |
|||
// child: Container( |
|||
// margin: EdgeInsets.symmetric( |
|||
// horizontal: MediaQuery.of(context).size.width * 0.05, |
|||
// vertical: SizeConfig.safeBlockVertical * 2.5, |
|||
// ), |
|||
// width: MediaQuery.of(context).size.width, |
|||
// child: Row( |
|||
// mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
// mainAxisSize: MainAxisSize.min, |
|||
// children: [ |
|||
// Container( |
|||
// width: 55, |
|||
// height: 40, |
|||
// padding: EdgeInsets.all(5), |
|||
// decoration: BoxDecoration( |
|||
// color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
// borderRadius: BorderRadius.only( |
|||
// bottomLeft: Radius.circular(30), |
|||
// bottomRight: Radius.circular(30), |
|||
// topRight: Radius.circular(30), |
|||
// topLeft: Radius.circular(30), |
|||
// ), |
|||
// border: Border.all(color: Colors.white, width: 0.5)), |
|||
// child: InkWell( |
|||
// onTap: () async => |
|||
// !downloaded ? await downloadVideo(context) : null, |
|||
// child: Icon( |
|||
// !downloaded ? Feather.download : MaterialIcons.check, |
|||
// color: !downloaded ? Colors.white : Colors.green, |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// // InkWell( |
|||
// // onTap: setText, |
|||
// // child: Container( |
|||
// // width: 50, |
|||
// // padding: EdgeInsets.symmetric(horizontal: 18), |
|||
// // decoration: BoxDecoration( |
|||
// // color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
// // borderRadius: BorderRadius.only( |
|||
// // bottomLeft: Radius.circular(30), |
|||
// // bottomRight: Radius.circular(30), |
|||
// // topRight: Radius.circular(30), |
|||
// // topLeft: Radius.circular(30), |
|||
// // ), |
|||
// // border: Border.all(color: Colors.white, width: 0.5)), |
|||
// // child: Text( |
|||
// // "T", |
|||
// // style: TextStyle( |
|||
// // color: Colors.white, |
|||
// // fontWeight: FontWeight.bold, |
|||
// // fontSize: SizeConfig.safeBlockHorizontal * 8, |
|||
// // fontFamily: 'DeadheadScript', |
|||
// // ), |
|||
// // ), |
|||
// // ), |
|||
// // ), |
|||
// // Container( |
|||
// // width: 55, |
|||
// // height: 40, |
|||
// // padding: EdgeInsets.all(5), |
|||
// // decoration: BoxDecoration( |
|||
// // color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
// // borderRadius: BorderRadius.only( |
|||
// // bottomLeft: Radius.circular(30), |
|||
// // bottomRight: Radius.circular(30), |
|||
// // topRight: Radius.circular(30), |
|||
// // topLeft: Radius.circular(30), |
|||
// // ), |
|||
// // border: Border.all(color: Colors.white, width: 0.5)), |
|||
// // child: InkWell( |
|||
// // onTap: showFilters, |
|||
// // child: Image( |
|||
// // image: AssetImage("assets/images/color-filters.png"), |
|||
// // ), |
|||
// // ), |
|||
// // ), |
|||
|
|||
// Container( |
|||
// width: 55, |
|||
// height: 40, |
|||
// padding: EdgeInsets.all(5), |
|||
// decoration: BoxDecoration( |
|||
// color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
// borderRadius: BorderRadius.only( |
|||
// bottomLeft: Radius.circular(30), |
|||
// bottomRight: Radius.circular(30), |
|||
// topRight: Radius.circular(30), |
|||
// topLeft: Radius.circular(30), |
|||
// ), |
|||
// border: Border.all(color: Colors.white, width: 0.5)), |
|||
// child: InkWell( |
|||
// onTap: () async => await shareVideo(context), |
|||
// child: Icon( |
|||
// Entypo.share, |
|||
// color: Colors.white, |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// Container( |
|||
// padding: EdgeInsets.all(5), |
|||
// width: 100, |
|||
// height: 40, |
|||
// decoration: BoxDecoration( |
|||
// color: tesoGold, |
|||
// borderRadius: BorderRadius.only( |
|||
// bottomLeft: Radius.circular(30), |
|||
// bottomRight: Radius.circular(30), |
|||
// topRight: Radius.circular(30), |
|||
// topLeft: Radius.circular(30), |
|||
// ), |
|||
// ), |
|||
// child: InkWell( |
|||
// onTap: () => postVideo(context), |
|||
// child: Row( |
|||
// mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
// children: [ |
|||
// Text( |
|||
// "Post", |
|||
// style: TextStyle( |
|||
// fontWeight: FontWeight.bold, |
|||
// ), |
|||
// ), |
|||
// Icon( |
|||
// Ionicons.md_send, |
|||
// color: tesoBlue, |
|||
// ), |
|||
// ], |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ], |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// } else { |
|||
// return Align( |
|||
// alignment: Alignment.bottomRight, |
|||
// child: Container( |
|||
// padding: EdgeInsets.all(5), |
|||
// width: 100, |
|||
// height: 40, |
|||
// margin: EdgeInsets.symmetric( |
|||
// vertical: 10, |
|||
// horizontal: 20, |
|||
// ), |
|||
// decoration: BoxDecoration( |
|||
// color: tesoGold, |
|||
// borderRadius: BorderRadius.only( |
|||
// bottomLeft: Radius.circular(30), |
|||
// bottomRight: Radius.circular(30), |
|||
// topRight: Radius.circular(30), |
|||
// topLeft: Radius.circular(30), |
|||
// ), |
|||
// ), |
|||
// child: InkWell( |
|||
// onTap: () => postVideo(context), |
|||
// child: Row( |
|||
// mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
// children: [ |
|||
// Text( |
|||
// "Post", |
|||
// style: TextStyle( |
|||
// fontWeight: FontWeight.bold, |
|||
// ), |
|||
// ), |
|||
// Icon( |
|||
// Ionicons.md_send, |
|||
// color: tesoBlue, |
|||
// ), |
|||
// ], |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// } |
|||
// } |
|||
|
|||
// Widget listFilters(context) { |
|||
// return Align( |
|||
// alignment: Alignment.bottomLeft, |
|||
// child: Container( |
|||
// margin: EdgeInsets.symmetric( |
|||
// horizontal: MediaQuery.of(context).size.width * 0.05, |
|||
// vertical: SizeConfig.safeBlockVertical * 10, |
|||
// ), |
|||
// width: MediaQuery.of(context).size.width, |
|||
// height: SizeConfig.safeBlockVertical * 17, |
|||
// child: ListView.builder( |
|||
// scrollDirection: Axis.horizontal, |
|||
// itemCount: colorFilters.length, |
|||
// //controller: controller, |
|||
// itemBuilder: (context, index) { |
|||
// return InkWell( |
|||
// onTap: () => setFilter(index), |
|||
// child: buildFilterThumb( |
|||
// context, colorFilters[index], thumbnail)); |
|||
// }), |
|||
// ), |
|||
// ); |
|||
// } |
|||
|
|||
// Widget textWidget(context) { |
|||
// return Container( |
|||
// child: Positioned( |
|||
// left: offset.dx, |
|||
// top: offset.dy, |
|||
// child: GestureDetector( |
|||
// onTap: setText, |
|||
// onPanUpdate: (details) { |
|||
// setState(() { |
|||
// offset = Offset( |
|||
// offset.dx + details.delta.dx, offset.dy + details.delta.dy); |
|||
// }); |
|||
// }, |
|||
// child: Screenshot( |
|||
// controller: screenshotController, |
|||
// child: editting.text != null |
|||
// ? Text( |
|||
// editting.text, |
|||
// style: editting.textStyle, |
|||
// textAlign: editting.textAlign, |
|||
// ) |
|||
// : Container(), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// } |
|||
// } |
@ -1,554 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/rendering.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'dart:io'; |
|||
|
|||
import 'package:image_gallery_saver/image_gallery_saver.dart'; |
|||
import 'package:page_transition/page_transition.dart'; |
|||
import 'package:share_plus/share_plus.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/file_formats.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trim_editor.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trimmer.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/CreatePost.dart'; |
|||
import 'package:teso/util/SizeConfig.dart'; |
|||
import 'package:video_player/video_player.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'dart:async'; |
|||
import 'package:path_provider/path_provider.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:image/image.dart' as IMG; |
|||
import 'package:video_thumbnail/video_thumbnail.dart'; |
|||
|
|||
class VideoReview extends StatefulWidget { |
|||
final video; |
|||
final bool recorded; |
|||
final double aspect; |
|||
|
|||
const VideoReview( |
|||
{Key key, @required this.video, @required this.recorded, this.aspect}) |
|||
: super(key: key); |
|||
@override |
|||
_VideoReviewState createState() => _VideoReviewState(); |
|||
} |
|||
|
|||
class _VideoReviewState extends State<VideoReview> |
|||
with TickerProviderStateMixin { |
|||
Trimmer _trimmer = new Trimmer(); |
|||
VideoPlayerController videoController; |
|||
VoidCallback videoPlayerListener; |
|||
bool muted = false; |
|||
String readyVideo; |
|||
Color textColor = Colors.white; |
|||
double _startValue = 0.15; |
|||
double _endValue = 60000.0; |
|||
var _future; |
|||
bool _isPlaying = false; |
|||
Duration _duration; |
|||
Duration _position; |
|||
ByteData bytes; |
|||
Uint8List imageBitmap; |
|||
Uint8List thumbnail; |
|||
Directory tempDirectory; |
|||
TesoUser user; |
|||
bool processing = false; |
|||
bool downloaded = false; |
|||
bool processed = false; |
|||
final key = new GlobalKey(); |
|||
double currentOffset = 0; |
|||
|
|||
Future<void> _startVideoPlayer() async { |
|||
await videoController.play(); |
|||
} |
|||
|
|||
Future<void> initializeController(String fileLocation) async { |
|||
videoController = VideoPlayerController.file(File(fileLocation)); |
|||
|
|||
videoPlayerListener = () async { |
|||
Timer.run(() { |
|||
this.setState(() { |
|||
_position = videoController.value.position; |
|||
}); |
|||
setState(() { |
|||
_duration = Duration(milliseconds: _endValue.round()); |
|||
}); |
|||
if (_duration?.compareTo(_position) == 0 || |
|||
_duration?.compareTo(_position) == -1) { |
|||
this.setState(() { |
|||
_isPlaying = false; |
|||
}); |
|||
videoController.pause(); |
|||
videoController.seekTo(Duration(milliseconds: _startValue.round())); |
|||
} else {} |
|||
}); |
|||
}; |
|||
videoController.addListener(videoPlayerListener); |
|||
await videoController.setLooping(true); |
|||
await videoController.initialize(); |
|||
await _trimmer.loadVideo(videoFile: File(fileLocation)); |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
readyVideo = widget.video; |
|||
if (readyVideo != null) _future = initializeController(readyVideo); |
|||
rootBundle.load("assets/images/rawLogo.png").then((value) => setState(() { |
|||
imageBitmap = value.buffer.asUint8List(); |
|||
IMG.Image img = IMG.decodeImage(imageBitmap); |
|||
IMG.Image resized = IMG.copyResize(img, width: 50, height: 60); |
|||
imageBitmap = IMG.encodePng(resized); |
|||
})); |
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
super.dispose(); |
|||
videoController.dispose(); |
|||
} |
|||
|
|||
void postVideo(context) async { |
|||
setState(() { |
|||
processing = true; |
|||
}); |
|||
if (processed) { |
|||
await Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
type: PageTransitionType.leftToRight, |
|||
child: CreatePost( |
|||
video: readyVideo, |
|||
aspectRatio: widget.recorded |
|||
? "0.5625" |
|||
: videoController.value.aspectRatio.toString(), |
|||
thumbnail: this.thumbnail, |
|||
), |
|||
)); |
|||
} else { |
|||
readyVideo = await processVideo(context, false); |
|||
await Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
type: PageTransitionType.leftToRight, |
|||
child: CreatePost( |
|||
video: readyVideo, |
|||
aspectRatio: widget.recorded |
|||
? "0.5625" |
|||
: videoController.value.aspectRatio.toString(), |
|||
thumbnail: this.thumbnail, |
|||
), |
|||
)); |
|||
} |
|||
} |
|||
|
|||
Future<void> downloadVideo(context) async { |
|||
try { |
|||
setState(() { |
|||
processing = true; |
|||
}); |
|||
String output = await processVideo(context, true); |
|||
await ImageGallerySaver.saveFile(output).catchError((error, stackTrace) { |
|||
setState(() { |
|||
processing = false; |
|||
downloaded = false; |
|||
}); |
|||
}).then((value) { |
|||
setState(() { |
|||
processing = false; |
|||
downloaded = true; |
|||
}); |
|||
}); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
} |
|||
|
|||
Future<String> processVideo(context, bool watermark) async { |
|||
user = Provider.of<UserProvider>(context, listen: false).currentUser; |
|||
String location = await getTemporaryDirectory().then((value) => |
|||
value.path + |
|||
"/" + |
|||
DateTime.now().millisecondsSinceEpoch.toString() + |
|||
".mp4"); |
|||
if (widget.recorded) { |
|||
String initial = await _trimmer.saveTrimmedVideo( |
|||
applyVideoEncoding: false, |
|||
ffmpegCommand: "-vf setsar=1:1 -aspect 9:16", |
|||
customVideoFormat: ".mp4", |
|||
startValue: _startValue, |
|||
endValue: videoController.value.duration.inMilliseconds > 5900 && |
|||
videoController.value.duration.inMilliseconds >= _endValue |
|||
? _endValue |
|||
: double.parse( |
|||
videoController.value.duration.inMilliseconds.toString()), |
|||
); |
|||
this.thumbnail = await generateThumbnail(); |
|||
|
|||
location = initial; |
|||
} else { |
|||
String initial = await _trimmer.saveTrimmedVideo( |
|||
startValue: _startValue, |
|||
endValue: videoController.value.duration.inMilliseconds > 5900 && |
|||
videoController.value.duration.inMilliseconds >= _endValue |
|||
? _endValue |
|||
: double.parse( |
|||
videoController.value.duration.inMilliseconds.toString()), |
|||
outputFormat: FileFormat.mp4, |
|||
); |
|||
this.thumbnail = await generateThumbnail(); |
|||
|
|||
location = initial; |
|||
} |
|||
return location; |
|||
} |
|||
|
|||
Future<Uint8List> generateThumbnail() async { |
|||
try { |
|||
Uint8List thumbnail; |
|||
|
|||
thumbnail = await VideoThumbnail.thumbnailData( |
|||
video: widget.video, |
|||
imageFormat: ImageFormat.JPEG, |
|||
maxWidth: 0, |
|||
maxHeight: 0, |
|||
timeMs: 100, |
|||
quality: 100, |
|||
); |
|||
return thumbnail; |
|||
} catch (e) { |
|||
print("Error :::: " + e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
Future<void> shareVideo(context) async { |
|||
setState(() { |
|||
processing = true; |
|||
}); |
|||
if (readyVideo == widget.video) { |
|||
readyVideo = await processVideo(context, true); |
|||
Share.shareFiles([readyVideo]); |
|||
} else { |
|||
Share.shareFiles([readyVideo]); |
|||
} |
|||
setState(() { |
|||
processing = false; |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
SizeConfig().init(context); |
|||
return Scaffold( |
|||
body: FutureBuilder( |
|||
future: _future, |
|||
builder: (context, snapshot) { |
|||
if (snapshot.connectionState == ConnectionState.waiting) { |
|||
return Container( |
|||
color: Colors.black, |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: Center( |
|||
child: CircularProgressIndicator( |
|||
backgroundColor: Colors.red, |
|||
), |
|||
), |
|||
); |
|||
} else { |
|||
return Stack( |
|||
children: [ |
|||
videoContent(context), |
|||
// Video trimmer |
|||
trimmerWidget(context), |
|||
// Pop button |
|||
Align( |
|||
alignment: Alignment.topLeft, |
|||
child: InkWell( |
|||
onTap: () => Navigator.pop(context), |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.07, |
|||
vertical: MediaQuery.of(context).size.width * 0.1, |
|||
), |
|||
height: 35, |
|||
width: 35, |
|||
decoration: BoxDecoration( |
|||
color: Color.fromRGBO(0, 0, 0, 0.4), |
|||
shape: BoxShape.circle), |
|||
child: Icon( |
|||
Icons.close, |
|||
color: Colors.white, |
|||
size: 20, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
// Bottom buttons |
|||
bottomButtons(context), |
|||
Visibility( |
|||
visible: processing, |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
padding: EdgeInsets.only( |
|||
top: MediaQuery.of(context).size.width * 0.7), |
|||
child: Center( |
|||
child: Column( |
|||
children: [ |
|||
CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
Text( |
|||
"Processing.....", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
}), |
|||
); |
|||
} |
|||
|
|||
Widget trimmerWidget(context) { |
|||
return Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.01, |
|||
vertical: MediaQuery.of(context).size.width * 0.20, |
|||
), |
|||
width: MediaQuery.of(context).size.width, |
|||
child: TrimEditor( |
|||
borderPaintColor: tesoGold, |
|||
circlePaintColor: tesoBlue, |
|||
thumbnailQuality: 100, |
|||
showDuration: true, |
|||
viewerHeight: 50.0, |
|||
maxVideoLength: Duration(seconds: 60), |
|||
viewerWidth: MediaQuery.of(context).size.width, |
|||
onChangeStart: (value) { |
|||
if (!mounted) { |
|||
setState(() { |
|||
_startValue = value; |
|||
}); |
|||
} else { |
|||
_startValue = value; |
|||
} |
|||
videoController.seekTo(Duration(milliseconds: value.round())); |
|||
}, |
|||
onChangeEnd: (value) { |
|||
if (!mounted) { |
|||
setState(() { |
|||
_endValue = value; |
|||
}); |
|||
} else { |
|||
_endValue = value; |
|||
} |
|||
}, |
|||
onChangePlaybackState: (isPlaying) { |
|||
if (mounted) |
|||
setState(() { |
|||
_isPlaying = isPlaying; |
|||
}); |
|||
}, |
|||
)); |
|||
} |
|||
|
|||
Widget videoContent(context) { |
|||
print(videoController.value.size.width); |
|||
|
|||
return Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
color: Colors.black, |
|||
child: Center( |
|||
child: AspectRatio( |
|||
aspectRatio: videoController.value.size != null |
|||
? videoController.value.aspectRatio |
|||
: 1.0, |
|||
child: Stack( |
|||
children: [ |
|||
InkWell( |
|||
onTap: () { |
|||
!_isPlaying ? _startVideoPlayer() : videoController.pause(); |
|||
setState(() { |
|||
_isPlaying = !_isPlaying; |
|||
}); |
|||
}, |
|||
child: VideoPlayer( |
|||
videoController, |
|||
), |
|||
), |
|||
Container( |
|||
width: double.infinity, |
|||
height: double.infinity, |
|||
child: GestureDetector( |
|||
child: !_isPlaying |
|||
? Icon( |
|||
Icons.play_circle, |
|||
size: 60, |
|||
color: Colors.white, |
|||
) |
|||
: Container(), |
|||
onTap: () { |
|||
!_isPlaying |
|||
? _startVideoPlayer() |
|||
: videoController.pause(); |
|||
setState(() { |
|||
_isPlaying = !_isPlaying; |
|||
}); |
|||
}, |
|||
), |
|||
), |
|||
], |
|||
)), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget bottomButtons(context) { |
|||
if (widget.recorded) { |
|||
return Align( |
|||
alignment: Alignment.bottomLeft, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.05, |
|||
vertical: SizeConfig.safeBlockVertical * 2.5, |
|||
), |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
Container( |
|||
width: 55, |
|||
height: 40, |
|||
padding: EdgeInsets.all(5), |
|||
decoration: BoxDecoration( |
|||
color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
border: Border.all(color: Colors.white, width: 0.5)), |
|||
child: InkWell( |
|||
onTap: () async => |
|||
!downloaded ? await downloadVideo(context) : null, |
|||
child: Icon( |
|||
!downloaded ? Icons.download : Icons.check, |
|||
color: !downloaded ? Colors.white : Colors.green, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
width: 55, |
|||
height: 40, |
|||
padding: EdgeInsets.all(5), |
|||
decoration: BoxDecoration( |
|||
color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
border: Border.all(color: Colors.white, width: 0.5)), |
|||
child: InkWell( |
|||
onTap: () async => await shareVideo(context), |
|||
child: Icon( |
|||
Icons.share, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
padding: EdgeInsets.all(5), |
|||
width: 100, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
color: tesoGold, |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
), |
|||
child: InkWell( |
|||
onTap: () => postVideo(context), |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
Text( |
|||
"Post", |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
Icon( |
|||
Icons.send, |
|||
color: tesoBlue, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} else { |
|||
return Align( |
|||
alignment: Alignment.bottomRight, |
|||
child: Container( |
|||
padding: EdgeInsets.all(5), |
|||
width: 100, |
|||
height: 40, |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: 10, |
|||
horizontal: 20, |
|||
), |
|||
decoration: BoxDecoration( |
|||
color: tesoGold, |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
), |
|||
child: InkWell( |
|||
onTap: () => postVideo(context), |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
Text( |
|||
"Post", |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
Icon( |
|||
Icons.send, |
|||
color: tesoBlue, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
} |
@ -1,92 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class ColorPalette extends StatefulWidget { |
|||
final Color activeColor; |
|||
final List<Color> colors; |
|||
final Function(Color) onColorPicked; |
|||
|
|||
ColorPalette({ |
|||
this.activeColor, |
|||
this.onColorPicked, |
|||
this.colors, |
|||
}); |
|||
|
|||
@override |
|||
_ColorPaletteState createState() => _ColorPaletteState(); |
|||
} |
|||
|
|||
class _ColorPaletteState extends State<ColorPalette> { |
|||
Color _activeColor; |
|||
|
|||
@override |
|||
void initState() { |
|||
_activeColor = widget.activeColor ?? widget.colors[0]; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Padding( |
|||
padding: EdgeInsets.all(16), |
|||
child: Wrap( |
|||
spacing: 16, |
|||
runSpacing: 16, |
|||
children: widget.colors |
|||
.map( |
|||
(color) => _ColorHolder( |
|||
color: color, |
|||
active: color == _activeColor, |
|||
onTap: (color) { |
|||
setState(() => _activeColor = color); |
|||
widget.onColorPicked(color); |
|||
}, |
|||
), |
|||
) |
|||
.toList(), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _ColorHolder extends StatelessWidget { |
|||
final Color color; |
|||
final Function(Color) onTap; |
|||
final bool active; |
|||
|
|||
_ColorHolder({ |
|||
this.color, |
|||
this.onTap, |
|||
this.active = false, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Container( |
|||
height: 40, |
|||
width: 40, |
|||
decoration: BoxDecoration( |
|||
border: active |
|||
? Border.fromBorderSide( |
|||
BorderSide(color: Theme.of(context).colorScheme.onSurface)) |
|||
: null, |
|||
borderRadius: BorderRadius.circular(50), |
|||
), |
|||
child: Center( |
|||
child: GestureDetector( |
|||
onTap: () => onTap(color), |
|||
child: Container( |
|||
height: 35, |
|||
width: 35, |
|||
decoration: BoxDecoration( |
|||
border: Border.fromBorderSide( |
|||
BorderSide(color: Theme.of(context).colorScheme.onSurface)), |
|||
borderRadius: BorderRadius.circular(50), |
|||
color: color, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,30 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class OptionButton extends StatelessWidget { |
|||
final bool isActive; |
|||
final Function() onPressed; |
|||
final Widget child; |
|||
final Size size; |
|||
|
|||
OptionButton({ |
|||
this.onPressed, |
|||
this.child, |
|||
this.isActive = false, |
|||
this.size, |
|||
}); |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return RawMaterialButton( |
|||
constraints: BoxConstraints.tight(size ?? Size(45, 45)), |
|||
highlightColor: Theme.of(context).colorScheme.background, |
|||
splashColor: Theme.of(context).colorScheme.background, |
|||
fillColor: isActive ? Theme.of(context).colorScheme.background : null, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(12), |
|||
side: BorderSide(color: Theme.of(context).colorScheme.surface), |
|||
), |
|||
child: child, |
|||
onPressed: onPressed, |
|||
); |
|||
} |
|||
} |
@ -1,87 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import 'option_button.dart'; |
|||
import 'toolbar_action.dart'; |
|||
|
|||
class Toolbar extends StatefulWidget { |
|||
final EditorToolbarAction initialTool; |
|||
final Function(EditorToolbarAction) onToolSelect; |
|||
|
|||
Toolbar({ |
|||
this.initialTool = EditorToolbarAction.editor, |
|||
this.onToolSelect, |
|||
}); |
|||
|
|||
@override |
|||
_ToolbarState createState() => _ToolbarState(); |
|||
} |
|||
|
|||
class _ToolbarState extends State<Toolbar> { |
|||
EditorToolbarAction _selectedAction; |
|||
@override |
|||
void initState() { |
|||
_selectedAction = widget.initialTool; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
// OptionButton( |
|||
// isActive: _selectedAction == EditorToolbarAction.editor, |
|||
// child: Icon(Icons.keyboard), |
|||
// onPressed: () { |
|||
// setState(() => _selectedAction = EditorToolbarAction.editor); |
|||
// widget.onToolSelect(EditorToolbarAction.editor); |
|||
// }, |
|||
// ), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontFamilyTool, |
|||
child: Icon(Icons.title), |
|||
onPressed: () { |
|||
setState( |
|||
() => _selectedAction = EditorToolbarAction.fontFamilyTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontFamilyTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontOptionTool, |
|||
child: Icon(Icons.strikethrough_s), |
|||
onPressed: () { |
|||
setState( |
|||
() => _selectedAction = EditorToolbarAction.fontOptionTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontOptionTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontSizeTool, |
|||
child: Icon(Icons.format_size), |
|||
onPressed: () { |
|||
setState(() => _selectedAction = EditorToolbarAction.fontSizeTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontSizeTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontColorTool, |
|||
child: Icon(Icons.format_color_text), |
|||
onPressed: () { |
|||
setState(() => _selectedAction = EditorToolbarAction.fontColorTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontColorTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.backgroundColorTool, |
|||
child: Icon(Icons.format_color_fill), |
|||
onPressed: () { |
|||
setState(() => |
|||
_selectedAction = EditorToolbarAction.backgroundColorTool); |
|||
widget.onToolSelect(EditorToolbarAction.backgroundColorTool); |
|||
}, |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
@ -1,8 +0,0 @@ |
|||
enum EditorToolbarAction { |
|||
editor, |
|||
fontFamilyTool, |
|||
fontOptionTool, |
|||
fontSizeTool, |
|||
fontColorTool, |
|||
backgroundColorTool, |
|||
} |
@ -1,24 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../color_palette.dart'; |
|||
|
|||
class BackgroundColorTool extends StatelessWidget { |
|||
final List<Color> colors; |
|||
final Color activeColor; |
|||
final Function(Color) onColorPicked; |
|||
|
|||
BackgroundColorTool({ |
|||
this.colors, |
|||
this.onColorPicked, |
|||
this.activeColor, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return ColorPalette( |
|||
activeColor: activeColor, |
|||
onColorPicked: onColorPicked, |
|||
colors: colors, |
|||
); |
|||
} |
|||
} |
@ -1,24 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../color_palette.dart'; |
|||
|
|||
class FontColorTool extends StatelessWidget { |
|||
final List<Color> colors; |
|||
final Color activeColor; |
|||
final Function(Color) onColorPicked; |
|||
|
|||
FontColorTool({ |
|||
this.colors, |
|||
this.onColorPicked, |
|||
this.activeColor, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return ColorPalette( |
|||
activeColor: activeColor, |
|||
onColorPicked: onColorPicked, |
|||
colors: colors, |
|||
); |
|||
} |
|||
} |
@ -1,66 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../option_button.dart'; |
|||
|
|||
class FontFamilyTool extends StatefulWidget { |
|||
final List<String> fonts; |
|||
final Function(String) onSelectFont; |
|||
final String selectedFont; |
|||
|
|||
FontFamilyTool({ |
|||
this.fonts, |
|||
this.onSelectFont, |
|||
this.selectedFont, |
|||
}); |
|||
|
|||
@override |
|||
_FontFamilyToolState createState() => _FontFamilyToolState(); |
|||
} |
|||
|
|||
class _FontFamilyToolState extends State<FontFamilyTool> { |
|||
String _selectedFont; |
|||
|
|||
@override |
|||
void initState() { |
|||
_selectedFont = widget.selectedFont ?? widget.fonts[0]; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Wrap( |
|||
spacing: 12, |
|||
runSpacing: 12, |
|||
children: widget.fonts |
|||
.map<_FontFamily>( |
|||
(font) => _FontFamily( |
|||
font, |
|||
isSelected: _selectedFont == font, |
|||
onSelect: (selectedFont) { |
|||
setState(() => _selectedFont = selectedFont); |
|||
widget.onSelectFont(selectedFont); |
|||
}, |
|||
), |
|||
) |
|||
.toList(), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _FontFamily extends StatelessWidget { |
|||
final String font; |
|||
final bool isSelected; |
|||
final Function(String) onSelect; |
|||
|
|||
_FontFamily(this.font, {this.onSelect, this.isSelected = false}); |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return OptionButton( |
|||
isActive: isSelected, |
|||
size: Size(90, 45), |
|||
onPressed: () => onSelect(font), |
|||
child: Center(child: Text(font, style: TextStyle(fontFamily: font))), |
|||
); |
|||
} |
|||
} |
@ -1,123 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class FontSizeTool extends StatelessWidget { |
|||
final double fontSize; |
|||
final double letterSpacing; |
|||
final double letterHeight; |
|||
final Function( |
|||
double fontSize, |
|||
double letterSpacing, |
|||
double letterHeight, |
|||
) onFontSizeEdited; |
|||
|
|||
FontSizeTool({ |
|||
this.onFontSizeEdited, |
|||
this.fontSize = 0, |
|||
this.letterSpacing = 0, |
|||
this.letterHeight = 0, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
double _fontSize = fontSize; |
|||
double _letterSpacing = letterSpacing; |
|||
double _letterHeight = letterHeight; |
|||
|
|||
return Padding( |
|||
padding: EdgeInsets.all(16), |
|||
child: Column( |
|||
children: [ |
|||
_ResizeSlider( |
|||
value: _fontSize, |
|||
icon: Icons.format_size, |
|||
max: 45, |
|||
onChange: (value) { |
|||
_fontSize = value; |
|||
onFontSizeEdited(_fontSize, _letterSpacing, _letterHeight); |
|||
}, |
|||
), |
|||
_ResizeSlider( |
|||
value: _letterHeight, |
|||
icon: Icons.format_line_spacing, |
|||
max: 10, |
|||
onChange: (value) { |
|||
_letterHeight = value; |
|||
onFontSizeEdited(_fontSize, _letterSpacing, _letterHeight); |
|||
}, |
|||
), |
|||
_ResizeSlider( |
|||
value: _letterSpacing, |
|||
icon: Icons.settings_ethernet, |
|||
max: 10, |
|||
onChange: (value) { |
|||
_letterSpacing = value; |
|||
onFontSizeEdited(_fontSize, _letterSpacing, _letterHeight); |
|||
}, |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _ResizeSlider extends StatefulWidget { |
|||
final double value; |
|||
final double min; |
|||
final double max; |
|||
final IconData icon; |
|||
final Function(double) onChange; |
|||
|
|||
_ResizeSlider({ |
|||
this.value, |
|||
this.icon, |
|||
this.onChange, |
|||
this.min = 0, |
|||
this.max = 100, |
|||
}); |
|||
|
|||
@override |
|||
_ResizeSliderState createState() => _ResizeSliderState(); |
|||
} |
|||
|
|||
class _ResizeSliderState extends State<_ResizeSlider> { |
|||
double _value; |
|||
|
|||
@override |
|||
void initState() { |
|||
_value = widget.value; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
children: [ |
|||
Icon(widget.icon), |
|||
Expanded( |
|||
child: SliderTheme( |
|||
data: SliderThemeData( |
|||
activeTrackColor: Theme.of(context).colorScheme.background, |
|||
inactiveTrackColor: Theme.of(context).colorScheme.background, |
|||
thumbColor: Theme.of(context).colorScheme.background, |
|||
overlayColor: |
|||
Theme.of(context).colorScheme.background.withOpacity(0.2), |
|||
trackHeight: 2, |
|||
), |
|||
child: Slider( |
|||
value: _value, |
|||
onChanged: (value) { |
|||
setState(() => _value = value); |
|||
|
|||
widget.onChange(value); |
|||
}, |
|||
min: widget.min, |
|||
max: widget.max, |
|||
), |
|||
), |
|||
), |
|||
Text(_value.toStringAsFixed(1)), |
|||
], |
|||
); |
|||
} |
|||
} |
@ -1,237 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../option_button.dart'; |
|||
|
|||
class TextFormatTool extends StatelessWidget { |
|||
final Function( |
|||
bool bold, |
|||
bool italic, |
|||
) onTextFormatEdited; |
|||
final Function(bool caps) onCpasLockTaggle; |
|||
final Function(TextAlign textAlign) onTextAlignEdited; |
|||
final TextAlign textAlign; |
|||
final bool bold; |
|||
final bool italic; |
|||
final bool caps; |
|||
|
|||
TextFormatTool({ |
|||
this.onTextFormatEdited, |
|||
this.onTextAlignEdited, |
|||
this.onCpasLockTaggle, |
|||
this.textAlign = TextAlign.left, |
|||
this.bold = false, |
|||
this.italic = false, |
|||
this.caps = false, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Container( |
|||
margin: EdgeInsets.only(top: 36), |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.center, |
|||
children: [ |
|||
_TextFormatEditor( |
|||
bold: bold, |
|||
italic: italic, |
|||
caps: caps, |
|||
onFormatEdited: onTextFormatEdited, |
|||
onCpasLockTaggle: onCpasLockTaggle, |
|||
), |
|||
SizedBox(height: 36), |
|||
_TextAlignEditor( |
|||
textAlign: textAlign, |
|||
onTextAlignEdited: onTextAlignEdited, |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextAlignEditor extends StatefulWidget { |
|||
final TextAlign textAlign; |
|||
final Function(TextAlign textAlign) onTextAlignEdited; |
|||
|
|||
_TextAlignEditor({ |
|||
this.onTextAlignEdited, |
|||
this.textAlign = TextAlign.left, |
|||
}); |
|||
|
|||
@override |
|||
_TextAlignEditorState createState() => _TextAlignEditorState(); |
|||
} |
|||
|
|||
class _TextAlignEditorState extends State<_TextAlignEditor> { |
|||
TextAlign _textAlign; |
|||
|
|||
@override |
|||
void initState() { |
|||
_textAlign = widget.textAlign; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_left, |
|||
isActive: _textAlign == TextAlign.left, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.left); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_center, |
|||
isActive: _textAlign == TextAlign.center, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.center); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_right, |
|||
isActive: _textAlign == TextAlign.right, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.right); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_justify, |
|||
isActive: _textAlign == TextAlign.justify, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.justify); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextAlignOption extends StatelessWidget { |
|||
final IconData icon; |
|||
final Function() onPressed; |
|||
final bool isActive; |
|||
|
|||
_TextAlignOption({ |
|||
this.icon, |
|||
this.onPressed, |
|||
this.isActive = false, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return IconButton( |
|||
iconSize: 32, |
|||
icon: Icon(icon), |
|||
color: isActive |
|||
? Theme.of(context).iconTheme.color |
|||
: Theme.of(context).disabledColor, |
|||
onPressed: onPressed, |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextFormatEditor extends StatefulWidget { |
|||
final Function(bool bold, bool italic) onFormatEdited; |
|||
final Function(bool caps) onCpasLockTaggle; |
|||
final bool bold; |
|||
final bool italic; |
|||
final bool caps; |
|||
|
|||
_TextFormatEditor({ |
|||
this.onFormatEdited, |
|||
this.onCpasLockTaggle, |
|||
this.bold = false, |
|||
this.italic = false, |
|||
this.caps = false, |
|||
}); |
|||
|
|||
@override |
|||
_TextFormatEditorState createState() => _TextFormatEditorState(); |
|||
} |
|||
|
|||
class _TextFormatEditorState extends State<_TextFormatEditor> { |
|||
bool _bold; |
|||
bool _italic; |
|||
bool _caps; |
|||
|
|||
@override |
|||
void initState() { |
|||
_bold = widget.bold; |
|||
_italic = widget.italic; |
|||
_caps = widget.caps; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
_TextFormatOption( |
|||
title: 'BOLD', |
|||
icon: Icons.format_bold, |
|||
isActive: _bold, |
|||
onPressed: () { |
|||
setState(() => _bold = !_bold); |
|||
widget.onFormatEdited(_bold, _italic); |
|||
}, |
|||
), |
|||
_TextFormatOption( |
|||
title: 'ITALIC', |
|||
icon: Icons.format_italic, |
|||
isActive: _italic, |
|||
onPressed: () { |
|||
setState(() => _italic = !_italic); |
|||
widget.onFormatEdited(_bold, _italic); |
|||
}, |
|||
), |
|||
_TextFormatOption( |
|||
title: 'CAPS', |
|||
icon: Icons.keyboard_capslock, |
|||
isActive: _caps, |
|||
onPressed: () { |
|||
setState(() => _caps = !_caps); |
|||
widget.onCpasLockTaggle(_caps); |
|||
}, |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextFormatOption extends StatelessWidget { |
|||
final String title; |
|||
final IconData icon; |
|||
final Function() onPressed; |
|||
final bool isActive; |
|||
|
|||
_TextFormatOption({ |
|||
this.title, |
|||
this.icon, |
|||
this.onPressed, |
|||
this.isActive = false, |
|||
}); |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Column( |
|||
children: [ |
|||
OptionButton( |
|||
isActive: isActive, |
|||
onPressed: onPressed, |
|||
child: Icon(icon), |
|||
), |
|||
SizedBox(height: 12), |
|||
Text(title), |
|||
], |
|||
); |
|||
} |
|||
} |
@ -1,223 +0,0 @@ |
|||
library text_style_editor; |
|||
|
|||
export 'src/toolbar_action.dart'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'src/toolbar_action.dart'; |
|||
import 'src/tools/background_color_tool.dart'; |
|||
import 'src/color_palette.dart'; |
|||
import 'src/tools/font_family_tool.dart'; |
|||
import 'src/tools/font_size_tool.dart'; |
|||
import 'src/tools/text_format_tool.dart'; |
|||
import 'src/toolbar.dart'; |
|||
|
|||
/// Text style editor |
|||
/// A flutter widget that edit text style and text alignment |
|||
/// |
|||
/// You can pass your text style or alignment to the widget |
|||
/// and then get the edited text style |
|||
class TextStyleEditor extends StatefulWidget { |
|||
/// Editor's font families |
|||
final List<String> fonts; |
|||
|
|||
/// The text style |
|||
final TextStyle textStyle; |
|||
|
|||
/// The text alignment |
|||
final TextAlign textAlign; |
|||
|
|||
/// The inithial editor tool |
|||
final EditorToolbarAction initialTool; |
|||
|
|||
/// Editor's palette colors |
|||
final List<Color> paletteColors; |
|||
|
|||
/// [onTextStyleEdited] will be called after [textStyle] prop has changed |
|||
final Function(TextStyle) onTextStyleEdited; |
|||
|
|||
/// [onTextAlignEdited] will be called after [textAlingment] prop has changed |
|||
final Function(TextAlign) onTextAlignEdited; |
|||
|
|||
/// [onCpasLockTaggle] will be called after caps lock has changed |
|||
final Function(bool) onCpasLockTaggle; |
|||
|
|||
/// [onToolbarActionChanged] will be called after editor's tool has changed |
|||
final Function(EditorToolbarAction) onToolbarActionChanged; |
|||
|
|||
/// Create a [TextStyleEditor] widget |
|||
/// |
|||
/// [fonts] list of font families that you want to use in editor. |
|||
/// [textStyle] initiate text style. |
|||
/// [textAlign] initiate text alignment. |
|||
/// |
|||
/// [onTextStyleEdited] callback will be called every time [textStyle] has changed. |
|||
/// [onTextAlignEdited] callback will be called every time [textAlign] has changed. |
|||
/// [onCpasLockTaggle] callback will be called every time caps lock has changed to off or on. |
|||
/// [onToolbarActionChanged] callback will be called every time editor's tool has changed. |
|||
TextStyleEditor({ |
|||
this.fonts, |
|||
this.textStyle, |
|||
this.textAlign, |
|||
this.paletteColors, |
|||
this.initialTool = EditorToolbarAction.editor, |
|||
this.onTextStyleEdited, |
|||
this.onTextAlignEdited, |
|||
this.onCpasLockTaggle, |
|||
this.onToolbarActionChanged, |
|||
}); |
|||
|
|||
@override |
|||
_TextStyleEditorState createState() => _TextStyleEditorState(); |
|||
} |
|||
|
|||
class _TextStyleEditorState extends State<TextStyleEditor> { |
|||
EditorToolbarAction _currentTool; |
|||
TextStyle _textStyle; |
|||
TextAlign _textAlign; |
|||
List<Color> _paletteColors; |
|||
|
|||
@override |
|||
void initState() { |
|||
_currentTool = widget.initialTool; |
|||
_textStyle = widget.textStyle; |
|||
_textAlign = widget.textAlign; |
|||
|
|||
// Set default palette's colors |
|||
_paletteColors = widget.paletteColors ?? |
|||
[ |
|||
Colors.black, |
|||
Colors.white, |
|||
Colors.red, |
|||
Colors.blue, |
|||
Colors.blueAccent, |
|||
Colors.brown, |
|||
Colors.green, |
|||
Colors.indigoAccent, |
|||
Colors.lime, |
|||
]; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
Toolbar( |
|||
initialTool: _currentTool, |
|||
onToolSelect: (action) { |
|||
setState(() => _currentTool = action); |
|||
if (widget.onToolbarActionChanged != null) { |
|||
widget.onToolbarActionChanged(action); |
|||
} |
|||
}, |
|||
), |
|||
Divider(), |
|||
Container( |
|||
child: SingleChildScrollView( |
|||
child: () { |
|||
// Choice tools |
|||
switch (_currentTool) { |
|||
case EditorToolbarAction.fontFamilyTool: |
|||
return FontFamilyTool( |
|||
fonts: widget.fonts, |
|||
selectedFont: _textStyle.fontFamily, |
|||
onSelectFont: (fontFamily) { |
|||
setState(() => _textStyle = |
|||
_textStyle.copyWith(fontFamily: fontFamily)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.fontOptionTool: |
|||
return TextFormatTool( |
|||
bold: _textStyle.fontWeight == FontWeight.bold, |
|||
italic: _textStyle.fontStyle == FontStyle.italic, |
|||
textAlign: _textAlign, |
|||
onTextFormatEdited: (bold, italic) { |
|||
setState(() => _textStyle = _textStyle.copyWith( |
|||
fontWeight: |
|||
bold ? FontWeight.bold : FontWeight.normal, |
|||
fontStyle: |
|||
italic ? FontStyle.italic : FontStyle.normal, |
|||
)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
onTextAlignEdited: (align) { |
|||
setState(() => _textAlign = align); |
|||
|
|||
if (widget.onTextAlignEdited != null) { |
|||
widget.onTextAlignEdited(align); |
|||
} |
|||
}, |
|||
onCpasLockTaggle: (caps) { |
|||
if (widget.onCpasLockTaggle != null) { |
|||
widget.onCpasLockTaggle(caps); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.fontSizeTool: |
|||
return FontSizeTool( |
|||
fontSize: _textStyle.fontSize ?? 0, |
|||
letterHeight: _textStyle.height ?? 1.2, |
|||
letterSpacing: _textStyle.letterSpacing ?? 1, |
|||
onFontSizeEdited: ( |
|||
fontSize, |
|||
letterSpacing, |
|||
letterHeight, |
|||
) { |
|||
setState(() => _textStyle = _textStyle.copyWith( |
|||
fontSize: fontSize, |
|||
height: letterHeight, |
|||
letterSpacing: letterSpacing, |
|||
)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.fontColorTool: |
|||
return BackgroundColorTool( |
|||
activeColor: _textStyle.color, |
|||
colors: _paletteColors, |
|||
onColorPicked: (color) { |
|||
setState( |
|||
() => _textStyle = _textStyle.copyWith(color: color)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.backgroundColorTool: |
|||
return ColorPalette( |
|||
activeColor: _textStyle.backgroundColor, |
|||
colors: _paletteColors, |
|||
onColorPicked: (color) { |
|||
setState(() => _textStyle = |
|||
_textStyle.copyWith(backgroundColor: color)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.editor: |
|||
return Container(); |
|||
default: |
|||
return Container(); |
|||
} |
|||
}(), |
|||
), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
@ -1,499 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:camera/camera.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
|
|||
import 'package:image_picker/image_picker.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:page_transition/page_transition.dart'; |
|||
import 'dart:async'; |
|||
import 'package:circular_countdown_timer/circular_countdown_timer.dart'; |
|||
import 'package:video_thumbnail/video_thumbnail.dart' as thumb; |
|||
|
|||
import 'Editor/VideoReview.dart'; |
|||
|
|||
// ignore: must_be_immutable |
|||
class RecordVideo extends StatefulWidget { |
|||
List<CameraDescription> connectedCameras; |
|||
|
|||
RecordVideo({Key key, this.connectedCameras}) : super(key: key); |
|||
@override |
|||
_RecordVideoState createState() => _RecordVideoState(); |
|||
} |
|||
|
|||
class _RecordVideoState extends State<RecordVideo> |
|||
with TickerProviderStateMixin { |
|||
CameraController _controller; |
|||
int selectedCamera = 0; |
|||
bool flash = false; |
|||
bool frontFlash = false; |
|||
bool recording = false; |
|||
AnimationController _recordingAnimationController; |
|||
XFile video; |
|||
String filePath; |
|||
int recordEnd = 60; |
|||
CountDownController _controllerCountDown = CountDownController(); |
|||
final interval = const Duration(seconds: 1); |
|||
final picker = ImagePicker(); |
|||
bool gallery = false; |
|||
|
|||
final int timerMaxSeconds = 60; |
|||
|
|||
int currentSeconds = 0; |
|||
|
|||
flipCamera() { |
|||
selectedCamera++; |
|||
if (selectedCamera < widget.connectedCameras.length) { |
|||
onNewCameraSelected(widget.connectedCameras.elementAt(selectedCamera)); |
|||
} else { |
|||
selectedCamera = 0; |
|||
onNewCameraSelected(widget.connectedCameras.elementAt(selectedCamera)); |
|||
} |
|||
} |
|||
|
|||
flashCamera() { |
|||
try { |
|||
if (!flash && |
|||
_controller.description.lensDirection == CameraLensDirection.back) { |
|||
_controller.setFlashMode(FlashMode.torch); |
|||
setState(() { |
|||
flash = true; |
|||
frontFlash = false; |
|||
}); |
|||
} else if (!flash && |
|||
_controller.description.lensDirection == CameraLensDirection.front) { |
|||
setState(() { |
|||
flash = true; |
|||
frontFlash = true; |
|||
}); |
|||
} else if (flash && |
|||
_controller.description.lensDirection == CameraLensDirection.back) { |
|||
_controller.setFlashMode(FlashMode.off); |
|||
setState(() { |
|||
flash = false; |
|||
}); |
|||
} else { |
|||
setState(() { |
|||
flash = false; |
|||
frontFlash = false; |
|||
}); |
|||
} |
|||
} catch (e) {} |
|||
} |
|||
|
|||
haltRecord() async { |
|||
double aspect = _controller.value.aspectRatio; |
|||
XFile recorded = await stopVideoRecording(); |
|||
if (recorded != null) |
|||
Navigator.of(context).pushReplacement( |
|||
PageRouteBuilder( |
|||
opaque: false, |
|||
pageBuilder: (_, __, ___) => VideoReview( |
|||
video: recorded.path, |
|||
aspect: aspect, |
|||
// campaignID: widget.campaignID, |
|||
recorded: true, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Future<Uint8List> generateThumbnail(video) async { |
|||
try { |
|||
Uint8List thumbnail; |
|||
|
|||
thumbnail = await thumb.VideoThumbnail.thumbnailData( |
|||
video: video, |
|||
imageFormat: thumb.ImageFormat.JPEG, |
|||
maxWidth: 0, |
|||
maxHeight: 0, |
|||
timeMs: 1, |
|||
quality: 100, |
|||
); |
|||
return thumbnail; |
|||
} catch (e) { |
|||
print("Error :::: " + e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
if (widget.connectedCameras == null || |
|||
widget.connectedCameras.length == 0) { |
|||
availableCameras().then((value) { |
|||
widget.connectedCameras = value; |
|||
onNewCameraSelected(widget.connectedCameras.first); |
|||
}); |
|||
} else { |
|||
onNewCameraSelected(widget.connectedCameras.first); |
|||
} |
|||
_recordingAnimationController = |
|||
new AnimationController(vsync: this, duration: Duration(seconds: 1)); |
|||
|
|||
_recordingAnimationController.repeat(reverse: true); |
|||
super.initState(); |
|||
} |
|||
|
|||
sayCheese() async { |
|||
try { |
|||
if (flash && !frontFlash) |
|||
await _controller.setFlashMode(FlashMode.always); |
|||
await _controller.startVideoRecording(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
} |
|||
|
|||
Future<XFile> stopVideoRecording() async { |
|||
if (!_controller.value.isRecordingVideo) { |
|||
return null; |
|||
} |
|||
|
|||
try { |
|||
return _controller.stopVideoRecording(); |
|||
} on CameraException catch (e) { |
|||
print(e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
void onNewCameraSelected(CameraDescription cameraDescription) async { |
|||
if (_controller != null) { |
|||
await _controller.dispose(); |
|||
} |
|||
_controller = CameraController( |
|||
cameraDescription, |
|||
ResolutionPreset.max, |
|||
enableAudio: true, |
|||
imageFormatGroup: ImageFormatGroup.jpeg, |
|||
); |
|||
|
|||
// If the controller is updated then update the UI. |
|||
_controller.addListener(() { |
|||
if (mounted) setState(() {}); |
|||
if (_controller.value.hasError) { |
|||
print('Camera error ${_controller.value.errorDescription}'); |
|||
} |
|||
}); |
|||
|
|||
try { |
|||
await _controller.initialize(); |
|||
_controller.lockCaptureOrientation(DeviceOrientation.portraitUp); |
|||
_controller.setFocusMode(FocusMode.auto); |
|||
} on CameraException catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
if (mounted) { |
|||
setState(() {}); |
|||
} |
|||
} |
|||
|
|||
void onHocusFocus(TapDownDetails details, BoxConstraints constraints) { |
|||
final offset = Offset( |
|||
details.localPosition.dx / constraints.maxWidth, |
|||
details.localPosition.dy / constraints.maxHeight, |
|||
); |
|||
_controller.setExposurePoint(offset); |
|||
_controller.setFocusPoint(offset); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
_controller?.dispose(); |
|||
_recordingAnimationController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
if (_controller == null || !_controller.value.isInitialized) { |
|||
return Container( |
|||
color: Colors.black, |
|||
); |
|||
} else { |
|||
return Scaffold( |
|||
body: !gallery |
|||
? Stack( |
|||
children: [ |
|||
cameraWidget(context), |
|||
flashWidget(context), |
|||
cameraFlip(context), |
|||
cameraFlash(context), |
|||
recordingAnimation(context), |
|||
recordingCircle(context), |
|||
recorderWidget(context), |
|||
galleryPicker(context), |
|||
], |
|||
) |
|||
: Container(), |
|||
); |
|||
} |
|||
} |
|||
|
|||
imgFromGallery() async { |
|||
try { |
|||
setState(() { |
|||
gallery = true; |
|||
}); |
|||
final pickedFile = await picker.pickVideo( |
|||
source: ImageSource.gallery, |
|||
maxDuration: Duration(minutes: 1), |
|||
); |
|||
|
|||
if (pickedFile != null) { |
|||
return pickedFile.path; |
|||
} else { |
|||
onNewCameraSelected(widget.connectedCameras.first); |
|||
print('No image selected.'); |
|||
} |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
setState(() { |
|||
gallery = false; |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
Widget recordingCircle(context) { |
|||
return Align( |
|||
alignment: Alignment.bottomCenter, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 70, |
|||
width: 70, |
|||
child: CircularCountDownTimer( |
|||
duration: recordEnd, |
|||
initialDuration: 0, |
|||
controller: _controllerCountDown, |
|||
width: MediaQuery.of(context).size.width / 2, |
|||
height: MediaQuery.of(context).size.height / 2, |
|||
ringColor: Colors.grey[300], |
|||
fillColor: Colors.red, |
|||
backgroundColor: Colors.transparent, |
|||
autoStart: false, |
|||
strokeWidth: 5.5, |
|||
isTimerTextShown: false, |
|||
strokeCap: StrokeCap.round, |
|||
//onStart: startTimeout, |
|||
onComplete: haltRecord, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget recorderWidget(context) { |
|||
return Align( |
|||
alignment: Alignment.bottomCenter, |
|||
child: InkWell( |
|||
onTap: recording |
|||
? haltRecord |
|||
: () async { |
|||
await _controller.startVideoRecording(); |
|||
setState(() { |
|||
_controllerCountDown.start(); |
|||
recording = !recording; |
|||
}); |
|||
}, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 70, |
|||
width: 70, |
|||
child: Icon( |
|||
recording ? Icons.stop : Icons.video_camera_back, |
|||
color: Colors.white, |
|||
size: 25, |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget galleryPicker(context) { |
|||
return Align( |
|||
alignment: Alignment.bottomLeft, |
|||
child: recording |
|||
? Container() |
|||
: InkWell( |
|||
onTap: () async { |
|||
String result = await imgFromGallery(); |
|||
if (result != null) { |
|||
// _controller.dispose(); |
|||
Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
type: PageTransitionType.leftToRight, |
|||
child: VideoReview( |
|||
video: result, |
|||
recorded: false, |
|||
// campaignID: widget.campaignID, |
|||
), |
|||
)); |
|||
} |
|||
}, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.05, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 70, |
|||
width: 70, |
|||
child: Icon( |
|||
Icons.photo, |
|||
color: Colors.white, |
|||
size: 27, |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget recordingAnimation(context) { |
|||
if (!recording) |
|||
return Align( |
|||
alignment: Alignment.topLeft, |
|||
child: InkWell( |
|||
onTap: () { |
|||
Navigator.pop(context); |
|||
}, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.08, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 35, |
|||
width: 35, |
|||
decoration: BoxDecoration( |
|||
//color: ColorFilterEngineLayer (0, 0, 0, 0.4), |
|||
shape: BoxShape.circle), |
|||
child: Icon( |
|||
Icons.arrow_back_ios, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
)); |
|||
else |
|||
return Align( |
|||
alignment: Alignment.topLeft, |
|||
child: Container( |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
children: [ |
|||
Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: 5, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
padding: EdgeInsets.all(2.5), |
|||
height: 20, |
|||
width: 20, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.red, |
|||
width: 2, |
|||
)), |
|||
child: FadeTransition( |
|||
opacity: _recordingAnimationController, |
|||
child: Container( |
|||
width: 20, |
|||
height: 20, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
color: Colors.red, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget cameraFlash(context) { |
|||
return !recording |
|||
? Align( |
|||
alignment: Alignment.topRight, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.07, |
|||
vertical: MediaQuery.of(context).size.width * 0.25, |
|||
), |
|||
child: InkWell( |
|||
onTap: flashCamera, |
|||
child: Icon( |
|||
flash ? Icons.flash_on : Icons.flash_off, |
|||
color: flash ? tesoGold : Colors.white, |
|||
size: 30, |
|||
), |
|||
), |
|||
), |
|||
) |
|||
: Container(); |
|||
} |
|||
|
|||
Widget cameraFlip(context) { |
|||
return !recording |
|||
? Align( |
|||
alignment: Alignment.topRight, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.06, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
child: InkWell( |
|||
onTap: flipCamera, |
|||
child: Icon( |
|||
Icons.cameraswitch_outlined, |
|||
color: Colors.white, |
|||
size: 40, |
|||
), |
|||
), |
|||
), |
|||
) |
|||
: Container(); |
|||
} |
|||
|
|||
Widget flashWidget(context) { |
|||
return Visibility( |
|||
visible: frontFlash, |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
decoration: BoxDecoration( |
|||
color: Colors.white.withOpacity(0.4), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget cameraWidget(context) { |
|||
var camera = _controller.value; |
|||
final size = MediaQuery.of(context).size; |
|||
var scale = size.aspectRatio * camera.aspectRatio; |
|||
if (scale < 1) scale = 1 / scale; |
|||
|
|||
return Transform.scale( |
|||
scale: scale, |
|||
child: Center( |
|||
child: CameraPreview( |
|||
_controller, |
|||
child: LayoutBuilder( |
|||
builder: (BuildContext context, BoxConstraints constraints) { |
|||
return GestureDetector( |
|||
behavior: HitTestBehavior.opaque, |
|||
onTapDown: (details) => onHocusFocus(details, constraints), |
|||
); |
|||
}), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,45 +0,0 @@ |
|||
/// The video file formats available for |
|||
/// generating the output trimmed video. |
|||
/// |
|||
/// The available formats are `mp4`, `mkv`, |
|||
/// `mov`, `flv`, `avi`, `wmv`& `gif`. |
|||
/// |
|||
/// If you define a custom `FFmpeg` command |
|||
/// then this will be ignored. |
|||
/// |
|||
class FileFormat { |
|||
const FileFormat._(this.index); |
|||
|
|||
final int index; |
|||
|
|||
static const FileFormat mp4 = FileFormat._(0); |
|||
static const FileFormat mkv = FileFormat._(1); |
|||
static const FileFormat mov = FileFormat._(2); |
|||
static const FileFormat flv = FileFormat._(3); |
|||
static const FileFormat avi = FileFormat._(4); |
|||
static const FileFormat wmv = FileFormat._(5); |
|||
static const FileFormat gif = FileFormat._(6); |
|||
|
|||
static const List<FileFormat> values = <FileFormat>[ |
|||
mp4, |
|||
mkv, |
|||
mov, |
|||
flv, |
|||
avi, |
|||
wmv, |
|||
gif, |
|||
]; |
|||
|
|||
@override |
|||
String toString() { |
|||
return const <int, String>{ |
|||
0: '.mp4', |
|||
1: '.mkv', |
|||
2: '.mov', |
|||
3: '.flv', |
|||
4: '.avi', |
|||
5: '.wmv', |
|||
6: '.gif', |
|||
}[index]; |
|||
} |
|||
} |
@ -1,32 +0,0 @@ |
|||
/// Supported storage locations. |
|||
/// |
|||
/// * [temporaryDirectory] |
|||
/// |
|||
/// * [applicationDocumentsDirectory] |
|||
/// |
|||
/// * [externalStorageDirectory] |
|||
/// |
|||
class StorageDir { |
|||
const StorageDir._(this.index); |
|||
|
|||
final int index; |
|||
|
|||
static const StorageDir temporaryDirectory = StorageDir._(0); |
|||
static const StorageDir applicationDocumentsDirectory = StorageDir._(1); |
|||
static const StorageDir externalStorageDirectory = StorageDir._(2); |
|||
|
|||
static const List<StorageDir> values = <StorageDir>[ |
|||
temporaryDirectory, |
|||
applicationDocumentsDirectory, |
|||
externalStorageDirectory, |
|||
]; |
|||
|
|||
@override |
|||
String toString() { |
|||
return const <int, String>{ |
|||
0: 'temporaryDirectory', |
|||
1: 'applicationDocumentsDirectory', |
|||
2: 'externalStorageDirectory', |
|||
}[index]; |
|||
} |
|||
} |
@ -1,81 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:video_thumbnail/video_thumbnail.dart'; |
|||
|
|||
class ThumbnailViewer extends StatelessWidget { |
|||
final videoFile; |
|||
final videoDuration; |
|||
final thumbnailHeight; |
|||
final fit; |
|||
final int numberOfThumbnails; |
|||
final int quality; |
|||
|
|||
/// For showing the thumbnails generated from the video, |
|||
/// like a frame by frame preview |
|||
ThumbnailViewer({ |
|||
@required this.videoFile, |
|||
@required this.videoDuration, |
|||
@required this.thumbnailHeight, |
|||
@required this.numberOfThumbnails, |
|||
@required this.fit, |
|||
this.quality = 75, |
|||
}) : assert(videoFile != null), |
|||
assert(videoDuration != null), |
|||
assert(thumbnailHeight != null), |
|||
assert(numberOfThumbnails != null), |
|||
assert(quality != null); |
|||
|
|||
Stream<List<Uint8List>> generateThumbnail() async* { |
|||
final String _videoPath = videoFile.path; |
|||
|
|||
double _eachPart = videoDuration / numberOfThumbnails; |
|||
|
|||
List<Uint8List> _byteList = []; |
|||
|
|||
for (int i = 1; i <= numberOfThumbnails; i++) { |
|||
Uint8List _bytes; |
|||
_bytes = await VideoThumbnail.thumbnailData( |
|||
video: _videoPath, |
|||
imageFormat: ImageFormat.JPEG, |
|||
timeMs: (_eachPart * i).toInt(), |
|||
quality: quality, |
|||
); |
|||
|
|||
_byteList.add(_bytes); |
|||
|
|||
yield _byteList; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return StreamBuilder( |
|||
stream: generateThumbnail(), |
|||
builder: (context, snapshot) { |
|||
if (snapshot.hasData) { |
|||
List<Uint8List> _imageBytes = snapshot.data; |
|||
return ListView.builder( |
|||
scrollDirection: Axis.horizontal, |
|||
itemCount: snapshot.data.length, |
|||
itemBuilder: (context, index) { |
|||
return Container( |
|||
height: thumbnailHeight, |
|||
width: thumbnailHeight, |
|||
child: Image( |
|||
image: MemoryImage(_imageBytes[index]), |
|||
fit: fit, |
|||
), |
|||
); |
|||
}); |
|||
} else { |
|||
return Container( |
|||
color: Colors.grey[900], |
|||
height: thumbnailHeight, |
|||
width: double.maxFinite, |
|||
); |
|||
} |
|||
}, |
|||
); |
|||
} |
|||
} |
@ -1,537 +0,0 @@ |
|||
import 'dart:io'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/thumbnail_viewer.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trim_editor_painter.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trimmer.dart'; |
|||
import 'package:video_player/video_player.dart'; |
|||
|
|||
VideoPlayerController videoPlayerController; |
|||
|
|||
class TrimEditor extends StatefulWidget { |
|||
/// For defining the total trimmer area width |
|||
final double viewerWidth; |
|||
|
|||
/// For defining the total trimmer area height |
|||
final double viewerHeight; |
|||
|
|||
/// For defining the image fit type of each thumbnail image. |
|||
/// |
|||
/// By default it is set to `BoxFit.fitHeight`. |
|||
final BoxFit fit; |
|||
|
|||
/// For defining the maximum length of the output video. |
|||
final Duration maxVideoLength; |
|||
|
|||
/// For specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// |
|||
/// By default it is set to `5.0`. |
|||
final double circleSize; |
|||
|
|||
/// For specifying a size to the holder at |
|||
/// the two ends of the video trimmer area, while it is being |
|||
/// `dragged`. |
|||
/// |
|||
/// By default it is set to `8.0`. |
|||
final double circleSizeOnDrag; |
|||
|
|||
/// For specifying a color to the circle. |
|||
/// |
|||
/// By default it is set to `Colors.white`. |
|||
final Color circlePaintColor; |
|||
|
|||
/// For specifying a color to the border of |
|||
/// the trim area. |
|||
/// |
|||
/// By default it is set to `Colors.white`. |
|||
final Color borderPaintColor; |
|||
|
|||
/// For specifying a color to the video |
|||
/// scrubber inside the trim area. |
|||
/// |
|||
/// By default it is set to `Colors.white`. |
|||
final Color scrubberPaintColor; |
|||
|
|||
/// For specifying the quality of each |
|||
/// generated image thumbnail, to be displayed in the trimmer |
|||
/// area. |
|||
final int thumbnailQuality; |
|||
|
|||
/// For showing the start and the end point of the |
|||
/// video on top of the trimmer area. |
|||
/// |
|||
/// By default it is set to `true`. |
|||
final bool showDuration; |
|||
|
|||
/// For providing a `TextStyle` to the |
|||
/// duration text. |
|||
/// |
|||
/// By default it is set to `TextStyle(color: Colors.white)` |
|||
final TextStyle durationTextStyle; |
|||
|
|||
/// Callback to the video start position |
|||
/// |
|||
/// Returns the selected video start position in `milliseconds`. |
|||
final Function(double startValue) onChangeStart; |
|||
|
|||
/// Callback to the video end position. |
|||
/// |
|||
/// Returns the selected video end position in `milliseconds`. |
|||
final Function(double endValue) onChangeEnd; |
|||
|
|||
/// Callback to the video playback |
|||
/// state to know whether it is currently playing or paused. |
|||
/// |
|||
/// Returns a `boolean` value. If `true`, video is currently |
|||
/// playing, otherwise paused. |
|||
final Function(bool isPlaying) onChangePlaybackState; |
|||
|
|||
/// Widget for displaying the video trimmer. |
|||
/// |
|||
/// This has frame wise preview of the video with a |
|||
/// slider for selecting the part of the video to be |
|||
/// trimmed. |
|||
/// |
|||
/// The required parameters are [viewerWidth] & [viewerHeight] |
|||
/// |
|||
/// * [viewerWidth] to define the total trimmer area width. |
|||
/// |
|||
/// |
|||
/// * [viewerHeight] to define the total trimmer area height. |
|||
/// |
|||
/// |
|||
/// The optional parameters are: |
|||
/// |
|||
/// * [fit] for specifying the image fit type of each thumbnail image. |
|||
/// By default it is set to `BoxFit.fitHeight`. |
|||
/// |
|||
/// |
|||
/// * [maxVideoLength] for specifying the maximum length of the |
|||
/// output video. |
|||
/// |
|||
/// |
|||
/// * [circleSize] for specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// By default it is set to `5.0`. |
|||
/// |
|||
/// |
|||
/// * [circleSizeOnDrag] for specifying a size to the holder at |
|||
/// the two ends of the video trimmer area, while it is being |
|||
/// `dragged`. By default it is set to `8.0`. |
|||
/// |
|||
/// |
|||
/// * [circlePaintColor] for specifying a color to the circle. |
|||
/// By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [borderPaintColor] for specifying a color to the border of |
|||
/// the trim area. By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [scrubberPaintColor] for specifying a color to the video |
|||
/// scrubber inside the trim area. By default it is set to |
|||
/// `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [thumbnailQuality] for specifying the quality of each |
|||
/// generated image thumbnail, to be displayed in the trimmer |
|||
/// area. |
|||
/// |
|||
/// |
|||
/// * [showDuration] for showing the start and the end point of the |
|||
/// video on top of the trimmer area. By default it is set to `true`. |
|||
/// |
|||
/// |
|||
/// * [durationTextStyle] is for providing a `TextStyle` to the |
|||
/// duration text. By default it is set to |
|||
/// `TextStyle(color: Colors.white)` |
|||
/// |
|||
/// |
|||
/// * [onChangeStart] is a callback to the video start position. |
|||
/// |
|||
/// |
|||
/// * [onChangeEnd] is a callback to the video end position. |
|||
/// |
|||
/// |
|||
/// * [onChangePlaybackState] is a callback to the video playback |
|||
/// state to know whether it is currently playing or paused. |
|||
/// |
|||
TrimEditor({ |
|||
@required this.viewerWidth, |
|||
@required this.viewerHeight, |
|||
this.fit = BoxFit.fitHeight, |
|||
this.maxVideoLength = const Duration(milliseconds: 0), |
|||
this.circleSize = 5.0, |
|||
this.circleSizeOnDrag = 8.0, |
|||
this.circlePaintColor = Colors.white, |
|||
this.borderPaintColor = Colors.white, |
|||
this.scrubberPaintColor = Colors.white, |
|||
this.thumbnailQuality = 75, |
|||
this.showDuration = true, |
|||
this.durationTextStyle = const TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
this.onChangeStart, |
|||
this.onChangeEnd, |
|||
this.onChangePlaybackState, |
|||
}) : assert(viewerWidth != null), |
|||
assert(viewerHeight != null), |
|||
assert(fit != null), |
|||
assert(maxVideoLength != null), |
|||
assert(circleSize != null), |
|||
assert(circleSizeOnDrag != null), |
|||
assert(circlePaintColor != null), |
|||
assert(borderPaintColor != null), |
|||
assert(scrubberPaintColor != null), |
|||
assert(thumbnailQuality != null), |
|||
assert(showDuration != null), |
|||
assert(durationTextStyle != null); |
|||
|
|||
@override |
|||
_TrimEditorState createState() => _TrimEditorState(); |
|||
} |
|||
|
|||
class _TrimEditorState extends State<TrimEditor> with TickerProviderStateMixin { |
|||
File _videoFile; |
|||
|
|||
double _videoStartPos = 0.0; |
|||
double _videoEndPos = 0.0; |
|||
|
|||
bool _canUpdateStart = true; |
|||
bool _isLeftDrag = true; |
|||
|
|||
Offset _startPos = Offset(0, 0); |
|||
Offset _endPos = Offset(0, 0); |
|||
|
|||
double _startFraction = 0.0; |
|||
double _endFraction = 1.0; |
|||
|
|||
int _videoDuration = 0; |
|||
int _currentPosition = 0; |
|||
|
|||
double _thumbnailViewerW = 0.0; |
|||
double _thumbnailViewerH = 0.0; |
|||
|
|||
int _numberOfThumbnails = 0; |
|||
|
|||
double _circleSize; |
|||
|
|||
double fraction; |
|||
double maxLengthPixels; |
|||
|
|||
ThumbnailViewer thumbnailWidget; |
|||
|
|||
Animation<double> _scrubberAnimation; |
|||
AnimationController _animationController; |
|||
Tween<double> _linearTween; |
|||
|
|||
Future<void> _initializeVideoController() async { |
|||
if (_videoFile != null) { |
|||
videoPlayerController.addListener(() { |
|||
final bool isPlaying = videoPlayerController.value.isPlaying; |
|||
|
|||
if (isPlaying) { |
|||
widget.onChangePlaybackState(true); |
|||
setState(() { |
|||
_currentPosition = |
|||
videoPlayerController.value.position.inMilliseconds; |
|||
|
|||
if (_currentPosition > _videoEndPos.toInt()) { |
|||
widget.onChangePlaybackState(false); |
|||
videoPlayerController.pause(); |
|||
_animationController.stop(); |
|||
} else { |
|||
if (!_animationController.isAnimating) { |
|||
widget.onChangePlaybackState(true); |
|||
_animationController.forward(); |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
if (videoPlayerController.value.isInitialized) { |
|||
if (_animationController != null) { |
|||
if ((_scrubberAnimation.value).toInt() == (_endPos.dx).toInt()) { |
|||
_animationController.reset(); |
|||
} |
|||
_animationController.stop(); |
|||
widget.onChangePlaybackState(false); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
videoPlayerController.setVolume(1.0); |
|||
_videoDuration = videoPlayerController.value.duration.inMilliseconds; |
|||
print(_videoFile.path); |
|||
|
|||
_videoEndPos = fraction != null |
|||
? _videoDuration.toDouble() * fraction |
|||
: _videoDuration.toDouble(); |
|||
|
|||
widget.onChangeEnd(_videoEndPos); |
|||
|
|||
final ThumbnailViewer _thumbnailWidget = ThumbnailViewer( |
|||
videoFile: _videoFile, |
|||
videoDuration: _videoDuration, |
|||
fit: widget.fit, |
|||
thumbnailHeight: _thumbnailViewerH, |
|||
numberOfThumbnails: _numberOfThumbnails, |
|||
quality: widget.thumbnailQuality, |
|||
); |
|||
thumbnailWidget = _thumbnailWidget; |
|||
} |
|||
} |
|||
|
|||
void _setVideoStartPosition(DragUpdateDetails details) async { |
|||
if (!(_startPos.dx + details.delta.dx < 0) && |
|||
!(_startPos.dx + details.delta.dx > _thumbnailViewerW) && |
|||
!(_startPos.dx + details.delta.dx > _endPos.dx)) { |
|||
if (maxLengthPixels != null) { |
|||
if (!(_endPos.dx - _startPos.dx - details.delta.dx > maxLengthPixels)) { |
|||
setState(() { |
|||
if (!(_startPos.dx + details.delta.dx < 0)) |
|||
_startPos += details.delta; |
|||
|
|||
_startFraction = (_startPos.dx / _thumbnailViewerW); |
|||
|
|||
_videoStartPos = _videoDuration * _startFraction; |
|||
widget.onChangeStart(_videoStartPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoStartPos.toInt())); |
|||
_linearTween.begin = _startPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} else { |
|||
setState(() { |
|||
if (!(_startPos.dx + details.delta.dx < 0)) |
|||
_startPos += details.delta; |
|||
|
|||
_startFraction = (_startPos.dx / _thumbnailViewerW); |
|||
|
|||
_videoStartPos = _videoDuration * _startFraction; |
|||
widget.onChangeStart(_videoStartPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoStartPos.toInt())); |
|||
_linearTween.begin = _startPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void _setVideoEndPosition(DragUpdateDetails details) async { |
|||
if (!(_endPos.dx + details.delta.dx > _thumbnailViewerW) && |
|||
!(_endPos.dx + details.delta.dx < 0) && |
|||
!(_endPos.dx + details.delta.dx < _startPos.dx)) { |
|||
if (maxLengthPixels != null) { |
|||
if (!(_endPos.dx - _startPos.dx + details.delta.dx > maxLengthPixels)) { |
|||
setState(() { |
|||
_endPos += details.delta; |
|||
_endFraction = _endPos.dx / _thumbnailViewerW; |
|||
|
|||
_videoEndPos = _videoDuration * _endFraction; |
|||
widget.onChangeEnd(_videoEndPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoEndPos.toInt())); |
|||
_linearTween.end = _endPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} else { |
|||
setState(() { |
|||
_endPos += details.delta; |
|||
_endFraction = _endPos.dx / _thumbnailViewerW; |
|||
|
|||
_videoEndPos = _videoDuration * _endFraction; |
|||
widget.onChangeEnd(_videoEndPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoEndPos.toInt())); |
|||
_linearTween.end = _endPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_circleSize = widget.circleSize; |
|||
|
|||
_videoFile = Trimmer.currentVideoFile; |
|||
_thumbnailViewerH = widget.viewerHeight; |
|||
|
|||
_numberOfThumbnails = widget.viewerWidth ~/ _thumbnailViewerH; |
|||
|
|||
_thumbnailViewerW = _numberOfThumbnails * _thumbnailViewerH; |
|||
|
|||
Duration totalDuration = videoPlayerController.value.duration; |
|||
|
|||
if (widget.maxVideoLength > Duration(milliseconds: 0) && |
|||
widget.maxVideoLength < totalDuration) { |
|||
if (widget.maxVideoLength < totalDuration) { |
|||
fraction = |
|||
widget.maxVideoLength.inMilliseconds / totalDuration.inMilliseconds; |
|||
|
|||
maxLengthPixels = _thumbnailViewerW * fraction; |
|||
} |
|||
} |
|||
|
|||
_initializeVideoController(); |
|||
_endPos = Offset( |
|||
maxLengthPixels != null ? maxLengthPixels : _thumbnailViewerW, |
|||
_thumbnailViewerH, |
|||
); |
|||
|
|||
// Defining the tween points |
|||
_linearTween = Tween(begin: _startPos.dx, end: _endPos.dx); |
|||
|
|||
_animationController = AnimationController( |
|||
vsync: this, |
|||
duration: Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()), |
|||
); |
|||
|
|||
_scrubberAnimation = _linearTween.animate(_animationController) |
|||
..addListener(() { |
|||
setState(() {}); |
|||
}) |
|||
..addStatusListener((status) { |
|||
if (status == AnimationStatus.completed) { |
|||
_animationController.stop(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
videoPlayerController.pause(); |
|||
widget.onChangePlaybackState(false); |
|||
if (_videoFile != null) { |
|||
videoPlayerController.setVolume(0.0); |
|||
videoPlayerController.pause(); |
|||
videoPlayerController.dispose(); |
|||
widget.onChangePlaybackState(false); |
|||
} |
|||
super.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return GestureDetector( |
|||
onHorizontalDragStart: (DragStartDetails details) { |
|||
print("START"); |
|||
print(details.localPosition); |
|||
print((_startPos.dx - details.localPosition.dx).abs()); |
|||
print((_endPos.dx - details.localPosition.dx).abs()); |
|||
|
|||
if (_endPos.dx >= _startPos.dx) { |
|||
if ((_startPos.dx - details.localPosition.dx).abs() > |
|||
(_endPos.dx - details.localPosition.dx).abs()) { |
|||
setState(() { |
|||
_canUpdateStart = false; |
|||
}); |
|||
} else { |
|||
setState(() { |
|||
_canUpdateStart = true; |
|||
}); |
|||
} |
|||
} else { |
|||
if (_startPos.dx > details.localPosition.dx) { |
|||
_isLeftDrag = true; |
|||
} else { |
|||
_isLeftDrag = false; |
|||
} |
|||
} |
|||
}, |
|||
onHorizontalDragEnd: (DragEndDetails details) { |
|||
setState(() { |
|||
_circleSize = widget.circleSize; |
|||
}); |
|||
}, |
|||
onHorizontalDragUpdate: (DragUpdateDetails details) { |
|||
_circleSize = widget.circleSizeOnDrag; |
|||
|
|||
if (_endPos.dx >= _startPos.dx) { |
|||
_isLeftDrag = false; |
|||
if (_canUpdateStart && _startPos.dx + details.delta.dx > 0) { |
|||
_isLeftDrag = false; // To prevent from scrolling over |
|||
_setVideoStartPosition(details); |
|||
} else if (!_canUpdateStart && |
|||
_endPos.dx + details.delta.dx < _thumbnailViewerW) { |
|||
_isLeftDrag = true; // To prevent from scrolling over |
|||
_setVideoEndPosition(details); |
|||
} |
|||
} else { |
|||
if (_isLeftDrag && _startPos.dx + details.delta.dx > 0) { |
|||
_setVideoStartPosition(details); |
|||
} else if (!_isLeftDrag && |
|||
_endPos.dx + details.delta.dx < _thumbnailViewerW) { |
|||
_setVideoEndPosition(details); |
|||
} |
|||
} |
|||
}, |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: <Widget>[ |
|||
widget.showDuration |
|||
? Container( |
|||
width: _thumbnailViewerW, |
|||
child: Padding( |
|||
padding: const EdgeInsets.only(bottom: 8.0), |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
mainAxisSize: MainAxisSize.max, |
|||
children: <Widget>[ |
|||
Text( |
|||
Duration(milliseconds: _videoStartPos.toInt()) |
|||
.toString() |
|||
.split('.')[0], |
|||
style: widget.durationTextStyle, |
|||
), |
|||
Text( |
|||
Duration(milliseconds: _videoEndPos.toInt()) |
|||
.toString() |
|||
.split('.')[0], |
|||
style: widget.durationTextStyle, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
) |
|||
: Container(), |
|||
CustomPaint( |
|||
foregroundPainter: TrimEditorPainter( |
|||
startPos: _startPos, |
|||
endPos: _endPos, |
|||
scrubberAnimationDx: _scrubberAnimation.value, |
|||
circleSize: _circleSize, |
|||
circlePaintColor: widget.circlePaintColor, |
|||
borderPaintColor: widget.borderPaintColor, |
|||
scrubberPaintColor: widget.scrubberPaintColor, |
|||
), |
|||
child: Container( |
|||
color: Colors.grey[900], |
|||
height: _thumbnailViewerH, |
|||
width: _thumbnailViewerW, |
|||
child: thumbnailWidget == null ? Column() : thumbnailWidget, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,150 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class TrimEditorPainter extends CustomPainter { |
|||
/// To define the start offset |
|||
final Offset startPos; |
|||
|
|||
/// To define the end offset |
|||
final Offset endPos; |
|||
|
|||
/// To define the horizontal length of the selected video area |
|||
final double scrubberAnimationDx; |
|||
|
|||
/// For specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// By default it is set to `0.5`. |
|||
final double circleSize; |
|||
|
|||
/// For specifying the width of the border around |
|||
/// the trim area. By default it is set to `3`. |
|||
final double borderWidth; |
|||
|
|||
/// For specifying the width of the video scrubber |
|||
final double scrubberWidth; |
|||
|
|||
/// For specifying whether to show the scrubber |
|||
final bool showScrubber; |
|||
|
|||
/// For specifying a color to the border of |
|||
/// the trim area. By default it is set to `Colors.white`. |
|||
final Color borderPaintColor; |
|||
|
|||
/// For specifying a color to the circle. |
|||
/// By default it is set to `Colors.white` |
|||
final Color circlePaintColor; |
|||
|
|||
/// For specifying a color to the video |
|||
/// scrubber inside the trim area. By default it is set to |
|||
/// `Colors.white`. |
|||
final Color scrubberPaintColor; |
|||
|
|||
/// For drawing the trim editor slider |
|||
/// |
|||
/// The required parameters are [startPos], [endPos] |
|||
/// & [scrubberAnimationDx] |
|||
/// |
|||
/// * [startPos] to define the start offset |
|||
/// |
|||
/// |
|||
/// * [endPos] to define the end offset |
|||
/// |
|||
/// |
|||
/// * [scrubberAnimationDx] to define the horizontal length of the |
|||
/// selected video area |
|||
/// |
|||
/// |
|||
/// The optional parameters are: |
|||
/// |
|||
/// * [circleSize] for specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// By default it is set to `0.5`. |
|||
/// |
|||
/// |
|||
/// * [borderWidth] for specifying the width of the border around |
|||
/// the trim area. By default it is set to `3`. |
|||
/// |
|||
/// |
|||
/// * [scrubberWidth] for specifying the width of the video scrubber |
|||
/// |
|||
/// |
|||
/// * [showScrubber] for specifying whether to show the scrubber |
|||
/// |
|||
/// |
|||
/// * [borderPaintColor] for specifying a color to the border of |
|||
/// the trim area. By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [circlePaintColor] for specifying a color to the circle. |
|||
/// By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [scrubberPaintColor] for specifying a color to the video |
|||
/// scrubber inside the trim area. By default it is set to |
|||
/// `Colors.white`. |
|||
/// |
|||
TrimEditorPainter({ |
|||
@required this.startPos, |
|||
@required this.endPos, |
|||
@required this.scrubberAnimationDx, |
|||
this.circleSize = 0.5, |
|||
this.borderWidth = 3, |
|||
this.scrubberWidth = 1, |
|||
this.showScrubber = true, |
|||
this.borderPaintColor = Colors.white, |
|||
this.circlePaintColor = Colors.white, |
|||
this.scrubberPaintColor = Colors.white, |
|||
}) : assert(startPos != null), |
|||
assert(endPos != null), |
|||
assert(scrubberAnimationDx != null), |
|||
assert(circleSize != null), |
|||
assert(borderWidth != null), |
|||
assert(scrubberWidth != null), |
|||
assert(showScrubber != null), |
|||
assert(borderPaintColor != null), |
|||
assert(circlePaintColor != null), |
|||
assert(scrubberPaintColor != null); |
|||
|
|||
@override |
|||
void paint(Canvas canvas, Size size) { |
|||
var borderPaint = Paint() |
|||
..color = borderPaintColor |
|||
..strokeWidth = borderWidth |
|||
..style = PaintingStyle.stroke |
|||
..strokeCap = StrokeCap.round; |
|||
|
|||
var circlePaint = Paint() |
|||
..color = circlePaintColor |
|||
..strokeWidth = 1 |
|||
..style = PaintingStyle.fill |
|||
..strokeCap = StrokeCap.round; |
|||
|
|||
var scrubberPaint = Paint() |
|||
..color = scrubberPaintColor |
|||
..strokeWidth = scrubberWidth |
|||
..style = PaintingStyle.stroke |
|||
..strokeCap = StrokeCap.round; |
|||
|
|||
final rect = Rect.fromPoints(startPos, endPos); |
|||
|
|||
if (showScrubber) { |
|||
if (scrubberAnimationDx.toInt() > startPos.dx.toInt()) { |
|||
canvas.drawLine( |
|||
Offset(scrubberAnimationDx, 0), |
|||
Offset(scrubberAnimationDx, 0) + Offset(0, endPos.dy), |
|||
scrubberPaint, |
|||
); |
|||
} |
|||
} |
|||
|
|||
canvas.drawRect(rect, borderPaint); |
|||
canvas.drawCircle( |
|||
startPos + Offset(0, endPos.dy / 2), circleSize, circlePaint); |
|||
canvas.drawCircle( |
|||
endPos + Offset(0, -endPos.dy / 2), circleSize, circlePaint); |
|||
} |
|||
|
|||
@override |
|||
bool shouldRepaint(CustomPainter oldDelegate) { |
|||
return true; |
|||
} |
|||
} |
@ -1,300 +0,0 @@ |
|||
import 'dart:io'; |
|||
import 'package:path/path.dart'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; |
|||
import 'package:intl/intl.dart'; |
|||
import 'package:path_provider/path_provider.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/file_formats.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/storage_dir.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trim_editor.dart'; |
|||
import 'package:video_player/video_player.dart'; |
|||
|
|||
/// Helps in loading video from file, saving trimmed video to a file |
|||
/// and gives video playback controls. Some of the helpful methods |
|||
/// are: |
|||
/// * [loadVideo()] |
|||
/// * [saveTrimmedVideo()] |
|||
/// * [videPlaybackControl()] |
|||
class Trimmer { |
|||
static File currentVideoFile; |
|||
|
|||
final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg(); |
|||
|
|||
/// Loads a video using the path provided. |
|||
/// |
|||
/// Returns the loaded video file. |
|||
Future<void> loadVideo({@required File videoFile}) async { |
|||
currentVideoFile = videoFile; |
|||
if (currentVideoFile != null) { |
|||
videoPlayerController = VideoPlayerController.file(currentVideoFile); |
|||
await videoPlayerController.initialize().then((_) { |
|||
TrimEditor( |
|||
viewerHeight: 50, |
|||
viewerWidth: 50.0 * 8, |
|||
// currentVideoFile: currentVideoFile, |
|||
); |
|||
}); |
|||
// TrimEditor( |
|||
// viewerHeight: 50, |
|||
// viewerWidth: 50.0 * 8, |
|||
// // currentVideoFile: currentVideoFile, |
|||
// ); |
|||
} |
|||
} |
|||
|
|||
Future<String> _createFolderInAppDocDir( |
|||
String folderName, |
|||
StorageDir storageDir, |
|||
) async { |
|||
Directory _directory; |
|||
|
|||
if (storageDir == null) { |
|||
_directory = await getApplicationDocumentsDirectory(); |
|||
} else { |
|||
switch (storageDir.toString()) { |
|||
case 'temporaryDirectory': |
|||
_directory = await getTemporaryDirectory(); |
|||
break; |
|||
|
|||
case 'applicationDocumentsDirectory': |
|||
_directory = await getApplicationDocumentsDirectory(); |
|||
break; |
|||
|
|||
case 'externalStorageDirectory': |
|||
_directory = await getExternalStorageDirectory(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Directory + folder name |
|||
final Directory _directoryFolder = |
|||
Directory('${_directory.path}/$folderName/'); |
|||
|
|||
if (await _directoryFolder.exists()) { |
|||
// If folder already exists return path |
|||
print('Exists'); |
|||
return _directoryFolder.path; |
|||
} else { |
|||
print('Creating'); |
|||
// If folder does not exists create folder and then return its path |
|||
final Directory _directoryNewFolder = |
|||
await _directoryFolder.create(recursive: true); |
|||
return _directoryNewFolder.path; |
|||
} |
|||
} |
|||
|
|||
/// Saves the trimmed video to file system. |
|||
/// |
|||
/// Returns the output video path |
|||
/// |
|||
/// The required parameters are [startValue] & [endValue]. |
|||
/// |
|||
/// The optional parameters are [videoFolderName], [videoFileName], |
|||
/// [outputFormat], [fpsGIF], [scaleGIF], [applyVideoEncoding]. |
|||
/// |
|||
/// The `@required` parameter [startValue] is for providing a starting point |
|||
/// to the trimmed video. To be specified in `milliseconds`. |
|||
/// |
|||
/// The `@required` parameter [endValue] is for providing an ending point |
|||
/// to the trimmed video. To be specified in `milliseconds`. |
|||
/// |
|||
/// The parameter [videoFolderName] is used to |
|||
/// pass a folder name which will be used for creating a new |
|||
/// folder in the selected directory. The default value for |
|||
/// it is `Trimmer`. |
|||
/// |
|||
/// The parameter [videoFileName] is used for giving |
|||
/// a new name to the trimmed video file. By default the |
|||
/// trimmed video is named as `<original_file_name>_trimmed.mp4`. |
|||
/// |
|||
/// The parameter [outputFormat] is used for providing a |
|||
/// file format to the trimmed video. This only accepts value |
|||
/// of [FileFormat] type. By default it is set to `FileFormat.mp4`, |
|||
/// which is for `mp4` files. |
|||
/// |
|||
/// The parameter [storageDir] can be used for providing a storage |
|||
/// location option. It accepts only [StorageDir] values. By default |
|||
/// it is set to [applicationDocumentsDirectory]. Some of the |
|||
/// storage types are: |
|||
/// |
|||
/// * [temporaryDirectory] (Only accessible from inside the app, can be |
|||
/// cleared at anytime) |
|||
/// |
|||
/// * [applicationDocumentsDirectory] (Only accessible from inside the app) |
|||
/// |
|||
/// * [externalStorageDirectory] (Supports only `Android`, accessible externally) |
|||
/// |
|||
/// The parameters [fpsGIF] & [scaleGIF] are used only if the |
|||
/// selected output format is `FileFormat.gif`. |
|||
/// |
|||
/// * [fpsGIF] for providing a FPS value (by default it is set |
|||
/// to `10`) |
|||
/// |
|||
/// |
|||
/// * [scaleGIF] for proving a width to output GIF, the height |
|||
/// is selected by maintaining the aspect ratio automatically (by |
|||
/// default it is set to `480`) |
|||
/// |
|||
/// |
|||
/// * [applyVideoEncoding] for specifying whether to apply video |
|||
/// encoding (by default it is set to `false`). |
|||
/// |
|||
/// |
|||
/// ADVANCED OPTION: |
|||
/// |
|||
/// If you want to give custom `FFmpeg` command, then define |
|||
/// [ffmpegCommand] & [customVideoFormat] strings. The `input path`, |
|||
/// `output path`, `start` and `end` position is already define. |
|||
/// |
|||
/// NOTE: The advanced option does not provide any safety check, so if wrong |
|||
/// video format is passed in [customVideoFormat], then the app may |
|||
/// crash. |
|||
/// |
|||
Future<String> saveTrimmedVideo({ |
|||
@required double startValue, |
|||
@required double endValue, |
|||
bool applyVideoEncoding = false, |
|||
FileFormat outputFormat, |
|||
String ffmpegCommand, |
|||
String customVideoFormat, |
|||
int fpsGIF, |
|||
int scaleGIF, |
|||
String videoFolderName, |
|||
String videoFileName, |
|||
StorageDir storageDir, |
|||
}) async { |
|||
final String _videoPath = currentVideoFile.path; |
|||
final String _videoName = basename(_videoPath).split('.')[0]; |
|||
|
|||
String _command; |
|||
|
|||
// Formatting Date and Time |
|||
String dateTime = DateFormat.yMMMd() |
|||
.addPattern('-') |
|||
.add_Hms() |
|||
.format(DateTime.now()) |
|||
.toString(); |
|||
|
|||
// String _resultString; |
|||
String _outputPath; |
|||
String _outputFormatString; |
|||
String formattedDateTime = dateTime.replaceAll(' ', ''); |
|||
|
|||
print("DateTime: $dateTime"); |
|||
print("Formatted: $formattedDateTime"); |
|||
|
|||
if (videoFolderName == null) { |
|||
videoFolderName = "Trimmer"; |
|||
} |
|||
|
|||
if (videoFileName == null) { |
|||
videoFileName = "${_videoName}_trimmed:$formattedDateTime"; |
|||
} |
|||
|
|||
videoFileName = videoFileName.replaceAll(' ', '_'); |
|||
|
|||
String path = await _createFolderInAppDocDir( |
|||
videoFolderName, |
|||
storageDir, |
|||
).whenComplete( |
|||
() => print("Retrieved Trimmer folder"), |
|||
); |
|||
|
|||
Duration startPoint = Duration(milliseconds: startValue.toInt()); |
|||
Duration endPoint = Duration(milliseconds: endValue.toInt()); |
|||
|
|||
// Checking the start and end point strings |
|||
print("Start: ${startPoint.toString()} & End: ${endPoint.toString()}"); |
|||
|
|||
print(path); |
|||
|
|||
if (outputFormat == null) { |
|||
if (Platform.isIOS) { |
|||
outputFormat = FileFormat.mp4; |
|||
} else { |
|||
outputFormat = FileFormat.mkv; |
|||
} |
|||
_outputFormatString = outputFormat.toString(); |
|||
print('OUTPUT: $_outputFormatString'); |
|||
} else { |
|||
_outputFormatString = outputFormat.toString(); |
|||
} |
|||
|
|||
String _trimLengthCommand = |
|||
' -ss $startPoint -i "$_videoPath" -t ${endPoint - startPoint} -avoid_negative_ts make_zero '; |
|||
|
|||
if (ffmpegCommand == null) { |
|||
_command = '$_trimLengthCommand -c:a copy '; |
|||
|
|||
if (!applyVideoEncoding) { |
|||
_command += '-c:v copy '; |
|||
} |
|||
|
|||
if (outputFormat == FileFormat.gif) { |
|||
if (fpsGIF == null) { |
|||
fpsGIF = 10; |
|||
} |
|||
if (scaleGIF == null) { |
|||
scaleGIF = 480; |
|||
} |
|||
_command = |
|||
'$_trimLengthCommand -vf "fps=$fpsGIF,scale=$scaleGIF:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 '; |
|||
} |
|||
} else { |
|||
_command = '$_trimLengthCommand $ffmpegCommand '; |
|||
_outputFormatString = customVideoFormat; |
|||
} |
|||
|
|||
_outputPath = '$path$videoFileName$_outputFormatString'; |
|||
|
|||
_command += '"$_outputPath"'; |
|||
print(_command); |
|||
await _flutterFFmpeg.execute(_command).whenComplete(() { |
|||
print('Got value'); |
|||
debugPrint('Video successfuly saved'); |
|||
// _resultString = 'Video successfuly saved'; |
|||
}).catchError((error) { |
|||
print('Error'); |
|||
// _resultString = 'Couldn\'t save the video'; |
|||
debugPrint('Couldn\'t save the video'); |
|||
}); |
|||
|
|||
return _outputPath; |
|||
} |
|||
|
|||
/// For getting the video controller state, to know whether the |
|||
/// video is playing or paused currently. |
|||
/// |
|||
/// The two required parameters are [startValue] & [endValue] |
|||
/// |
|||
/// * [startValue] is the current starting point of the video. |
|||
/// * [endValue] is the current ending point of the video. |
|||
/// |
|||
/// Returns a `Future<bool>`, if `true` then video is playing |
|||
/// otherwise paused. |
|||
Future<bool> videPlaybackControl({ |
|||
@required double startValue, |
|||
@required double endValue, |
|||
}) async { |
|||
if (videoPlayerController.value.isPlaying) { |
|||
await videoPlayerController.pause(); |
|||
return false; |
|||
} else { |
|||
if (videoPlayerController.value.position.inMilliseconds >= |
|||
endValue.toInt()) { |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: startValue.toInt())); |
|||
await videoPlayerController.play(); |
|||
return true; |
|||
} else { |
|||
await videoPlayerController.play(); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
File getVideoFile() { |
|||
return currentVideoFile; |
|||
} |
|||
} |
@ -1,153 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Campaign.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/Video/RecordVideo.dart'; |
|||
import 'package:teso/providers/pageAnimations.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
class Audition extends StatefulWidget { |
|||
final Campaign campaign; |
|||
|
|||
const Audition({Key key, this.campaign}) : super(key: key); |
|||
@override |
|||
_AuditionState createState() => _AuditionState(); |
|||
} |
|||
|
|||
class _AuditionState extends State<Audition> { |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: AppBar( |
|||
title: Text( |
|||
widget.campaign.title, |
|||
), |
|||
centerTitle: true, |
|||
), |
|||
body: Padding( |
|||
padding: const EdgeInsets.all(8.0), |
|||
child: ListView( |
|||
scrollDirection: Axis.vertical, |
|||
children: [ |
|||
Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: 150, |
|||
child: Center( |
|||
child: Container( |
|||
width: 150, |
|||
height: 150, |
|||
decoration: BoxDecoration( |
|||
border: Border.all( |
|||
color: Colors.grey, |
|||
width: 0.5, |
|||
), |
|||
borderRadius: BorderRadius.only( |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(30.0), |
|||
topRight: Radius.circular(30.0), |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
), |
|||
child: Image( |
|||
width: MediaQuery.of(context).size.width * 0.28, |
|||
height: 110, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage(widget.campaign.targetProduct != null |
|||
? productURL + widget.campaign.targetProduct |
|||
: ""), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Center( |
|||
child: new RichText( |
|||
text: new TextSpan( |
|||
style: new TextStyle( |
|||
fontSize: 14.0, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
children: <TextSpan>[ |
|||
new TextSpan( |
|||
text: "Publisher : ", |
|||
style: new TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
new TextSpan( |
|||
text: widget.campaign.businessID, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 10, |
|||
), |
|||
Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Center( |
|||
child: Text( |
|||
"Description", |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
fontSize: 16.5, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Center( |
|||
child: Text(widget.campaign.description), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 30, |
|||
), |
|||
Container( |
|||
width: double.infinity, |
|||
child: Align( |
|||
alignment: Alignment.center, |
|||
child: Container( |
|||
width: 100, |
|||
child: ElevatedButton( |
|||
style: ElevatedButton.styleFrom( |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular(20.0), |
|||
), |
|||
), |
|||
primary: accentMain, |
|||
), |
|||
onPressed: () => Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
child: RecordVideo( |
|||
campaignID: widget.campaign.campaignID, |
|||
), |
|||
type: PageTransitionType.fade, |
|||
), |
|||
), |
|||
child: Text("Submit Ad"), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,200 +0,0 @@ |
|||
import 'dart:convert'; |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/Uploading.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:http/http.dart' as http; |
|||
|
|||
class CreateCampaignPost extends StatefulWidget { |
|||
final String video; |
|||
final Uint8List thumbnail; |
|||
final String campaignID; |
|||
final String aspectRatio; |
|||
|
|||
const CreateCampaignPost({ |
|||
Key key, |
|||
this.video, |
|||
this.thumbnail, |
|||
this.campaignID, |
|||
this.aspectRatio, |
|||
}) : super(key: key); |
|||
@override |
|||
_CreateCampaignPostState createState() => _CreateCampaignPostState(); |
|||
} |
|||
|
|||
class _CreateCampaignPostState extends State<CreateCampaignPost> { |
|||
String aspectRatio; |
|||
TextEditingController controller; |
|||
SharedPreferences prefs; |
|||
bool sending = false; |
|||
|
|||
void postVideo(context) async { |
|||
setState(() { |
|||
sending = true; |
|||
}); |
|||
try { |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
String token = prefs.getString("tokensTeso"); |
|||
Map<String, String> requestHeaders = {'Authorization': token}; |
|||
String urlLocation = tesoStreaming + "api/mobile/upload/authurl"; |
|||
var client = |
|||
await http.get(Uri.parse(urlLocation), headers: requestHeaders); |
|||
if (client.statusCode == 200) { |
|||
var details = jsonDecode(client.body); |
|||
String muxuploadsID = details["data"]["id"]; |
|||
String muxuploadsURL = details["data"]["url"]; |
|||
|
|||
Provider.of<UserProvider>(context, listen: false).uploadPost(Uploading( |
|||
id: DateTime.now().toString() + |
|||
widget.video.replaceAll("file://", ""), |
|||
aspect: widget.aspectRatio, |
|||
path: widget.video.replaceAll("file://", ""), |
|||
thumbnail: |
|||
widget.thumbnail != null ? base64Encode(widget.thumbnail) : null, |
|||
title: controller.text, |
|||
pending: 0, |
|||
campaignID: widget.campaignID, |
|||
muxuploadID: muxuploadsID, |
|||
muxuploadURL: muxuploadsURL, |
|||
)); |
|||
Navigator.pop(context); |
|||
} |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
setState(() { |
|||
sending = false; |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
controller = new TextEditingController(); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
super.dispose(); |
|||
controller.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: PreferredSize( |
|||
child: AppBar( |
|||
automaticallyImplyLeading: true, |
|||
title: Text("Post"), |
|||
centerTitle: true, |
|||
), |
|||
preferredSize: Size.fromHeight(70.0), |
|||
), |
|||
body: SingleChildScrollView( |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
padding: EdgeInsets.all(MediaQuery.of(context).size.width * 0.025), |
|||
child: Column( |
|||
children: [ |
|||
Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
Container( |
|||
width: MediaQuery.of(context).size.width * 0.25, |
|||
height: MediaQuery.of(context).size.width * 0.35, |
|||
color: Colors.black, |
|||
child: widget.thumbnail != null |
|||
? Image.memory(widget.thumbnail) |
|||
: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
Container( |
|||
width: (MediaQuery.of(context).size.width) - |
|||
(MediaQuery.of(context).size.width * 0.35), |
|||
height: MediaQuery.of(context).size.width * 0.35, |
|||
child: TextField( |
|||
decoration: InputDecoration( |
|||
border: OutlineInputBorder( |
|||
borderSide: BorderSide.none, |
|||
), |
|||
filled: true, |
|||
isDense: true, |
|||
labelText: "Say Something..", |
|||
labelStyle: TextStyle( |
|||
color: Colors.black54, |
|||
), |
|||
fillColor: Colors.white70, |
|||
), |
|||
controller: controller, |
|||
maxLines: null, |
|||
keyboardType: TextInputType.text, |
|||
textInputAction: TextInputAction.done, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Divider(), |
|||
Container( |
|||
margin: EdgeInsets.only( |
|||
top: 10, |
|||
), |
|||
child: Text( |
|||
"Teso businesses and other Teso users can see your post in their feeds and on your profile.", |
|||
textAlign: TextAlign.center, |
|||
style: TextStyle( |
|||
color: Colors.grey, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
floatingActionButton: !sending |
|||
? Container( |
|||
margin: EdgeInsets.all(20), |
|||
width: MediaQuery.of(context).size.width, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(15.0), |
|||
color: tesoBlue, |
|||
), |
|||
child: InkWell( |
|||
onTap: () => postVideo(context), |
|||
child: Center( |
|||
child: Text( |
|||
"NEXT", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
height: 50, |
|||
) |
|||
: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: 50, |
|||
child: Center( |
|||
child: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
), |
|||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, |
|||
); |
|||
} |
|||
} |
@ -1,195 +0,0 @@ |
|||
import 'dart:convert'; |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/Uploading.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:http/http.dart' as http; |
|||
|
|||
class SubmitAdvert extends StatefulWidget { |
|||
final String video; |
|||
final String aspectRatio; |
|||
final Uint8List thumbnail; |
|||
final String campaignID; |
|||
|
|||
const SubmitAdvert( |
|||
{Key key, this.video, this.aspectRatio, this.thumbnail, this.campaignID}) |
|||
: super(key: key); |
|||
@override |
|||
_SubmitAdvertState createState() => _SubmitAdvertState(); |
|||
} |
|||
|
|||
class _SubmitAdvertState extends State<SubmitAdvert> { |
|||
TextEditingController controller; |
|||
SharedPreferences prefs; |
|||
bool sending = false; |
|||
|
|||
void postVideo(context) async { |
|||
setState(() { |
|||
sending = true; |
|||
}); |
|||
try { |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
String token = prefs.getString("tokensTeso"); |
|||
Map<String, String> requestHeaders = {'Authorization': token}; |
|||
String urlLocation = tesoStreaming + "api/mobile/upload/authurl"; |
|||
var client = |
|||
await http.get(Uri.parse(urlLocation), headers: requestHeaders); |
|||
if (client.statusCode == 200) { |
|||
var details = jsonDecode(client.body); |
|||
String muxuploadsID = details["data"]["id"]; |
|||
String muxuploadsURL = details["data"]["url"]; |
|||
|
|||
Provider.of<UserProvider>(context, listen: false).uploadPost(Uploading( |
|||
id: DateTime.now().toString() + |
|||
widget.video.replaceAll("file://", ""), |
|||
aspect: widget.aspectRatio, |
|||
path: widget.video.replaceAll("file://", ""), |
|||
thumbnail: |
|||
widget.thumbnail != null ? base64Encode(widget.thumbnail) : null, |
|||
title: controller.text, |
|||
pending: 0, |
|||
campaignID: widget.campaignID, |
|||
muxuploadID: muxuploadsID, |
|||
muxuploadURL: muxuploadsURL, |
|||
)); |
|||
Navigator.pop(context); |
|||
} |
|||
} catch (e) { |
|||
print("Something is " + e.toString()); |
|||
} |
|||
setState(() { |
|||
sending = false; |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
controller = new TextEditingController(); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
super.dispose(); |
|||
controller.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: PreferredSize( |
|||
child: AppBar( |
|||
automaticallyImplyLeading: true, |
|||
title: Text("Post"), |
|||
centerTitle: true, |
|||
), |
|||
preferredSize: Size.fromHeight(70.0), |
|||
), |
|||
body: SingleChildScrollView( |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
padding: EdgeInsets.all(MediaQuery.of(context).size.width * 0.025), |
|||
child: Column( |
|||
children: [ |
|||
Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
Container( |
|||
width: MediaQuery.of(context).size.width * 0.25, |
|||
height: MediaQuery.of(context).size.width * 0.35, |
|||
color: Colors.black, |
|||
child: widget.thumbnail != null |
|||
? Image.memory(widget.thumbnail) |
|||
: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
Container( |
|||
width: (MediaQuery.of(context).size.width) - |
|||
(MediaQuery.of(context).size.width * 0.35), |
|||
height: MediaQuery.of(context).size.width * 0.35, |
|||
child: TextField( |
|||
decoration: InputDecoration( |
|||
border: OutlineInputBorder( |
|||
borderSide: BorderSide.none, |
|||
), |
|||
filled: true, |
|||
isDense: true, |
|||
labelText: "Say Something..", |
|||
labelStyle: TextStyle( |
|||
color: Colors.black54, |
|||
), |
|||
fillColor: Colors.white70, |
|||
), |
|||
controller: controller, |
|||
maxLines: null, |
|||
keyboardType: TextInputType.url, |
|||
textInputAction: TextInputAction.done, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Divider(), |
|||
Container( |
|||
margin: EdgeInsets.only( |
|||
top: 10, |
|||
), |
|||
child: Text( |
|||
"Teso businesses and other Teso users can only see campaign post on your profile after they have been approved by the campaign owners.", |
|||
textAlign: TextAlign.center, |
|||
style: TextStyle( |
|||
color: Colors.grey, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
floatingActionButton: !sending |
|||
? Container( |
|||
margin: EdgeInsets.all(20), |
|||
width: MediaQuery.of(context).size.width, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(15.0), |
|||
color: tesoBlue, |
|||
), |
|||
child: InkWell( |
|||
onTap: () => postVideo(context), |
|||
child: Center( |
|||
child: Text( |
|||
"NEXT", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
height: 50, |
|||
) |
|||
: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: 50, |
|||
child: Center( |
|||
child: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
), |
|||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, |
|||
); |
|||
} |
|||
} |
@ -1,212 +0,0 @@ |
|||
// import 'package:flutter/material.dart'; |
|||
|
|||
// import 'package:teso/Classes/TextE.dart'; |
|||
// import 'package:teso/util/SizeConfig.dart'; |
|||
// import 'textstyler/src/toolbar_action.dart'; |
|||
// import 'textstyler/text_style_editor.dart'; |
|||
|
|||
// // ignore: must_be_immutable |
|||
// class TextEdit extends StatefulWidget { |
|||
// Textted content; |
|||
// TextEdit({Key key, this.content}) : super(key: key); |
|||
|
|||
// @override |
|||
// _TextEditState createState() => _TextEditState(); |
|||
// } |
|||
|
|||
// class _TextEditState extends State<TextEdit> { |
|||
// TextStyle textStyle; |
|||
// TextAlign textAlign; |
|||
// List<String> fonts = [ |
|||
// 'Billabong', |
|||
// 'AlexBrush', |
|||
// 'Allura', |
|||
// 'Arizonia', |
|||
// 'ChunkFive', |
|||
// 'GrandHotel', |
|||
// 'GreatVibes', |
|||
// 'Lobster', |
|||
// 'OpenSans', |
|||
// 'OstrichSans', |
|||
// 'Oswald', |
|||
// 'Pacifico', |
|||
// 'Quicksand', |
|||
// 'Roboto', |
|||
// 'SEASRN', |
|||
// 'Windsong', |
|||
// ]; |
|||
// List<Color> paletteColors = [ |
|||
// Colors.black, |
|||
// Colors.white, |
|||
// Color(int.parse('0xffEA2027')), |
|||
// Color(int.parse('0xff006266')), |
|||
// Color(int.parse('0xff1B1464')), |
|||
// Color(int.parse('0xff5758BB')), |
|||
// Color(int.parse('0xff6F1E51')), |
|||
// Color(int.parse('0xffB53471')), |
|||
// Color(int.parse('0xffEE5A24')), |
|||
// Color(int.parse('0xff009432')), |
|||
// Color(int.parse('0xff0652DD')), |
|||
// Color(int.parse('0xff9980FA')), |
|||
// Color(int.parse('0xff833471')), |
|||
// Color(int.parse('0xff112CBC4')), |
|||
// Color(int.parse('0xffFDA7DF')), |
|||
// Color(int.parse('0xffED4C67')), |
|||
// Color(int.parse('0xffF79F1F')), |
|||
// Color(int.parse('0xffA3CB38')), |
|||
// Color(int.parse('0xff1289A7')), |
|||
// Color(int.parse('0xffD980FA')) |
|||
// ]; |
|||
// FocusNode _focus = new FocusNode(); |
|||
// TextEditingController controller; |
|||
// @override |
|||
// void initState() { |
|||
// controller = new TextEditingController(); |
|||
// textStyle = TextStyle( |
|||
// fontSize: 15, |
|||
// color: Colors.white, |
|||
// fontFamily: 'OpenSans', |
|||
// ); |
|||
// textAlign = TextAlign.left; |
|||
// _focus.addListener(_onFocusChange); |
|||
|
|||
// controller.text = widget.content.text != null ? widget.content.text : ""; |
|||
// textStyle = |
|||
// widget.content.textStyle != null ? widget.content.textStyle : null; |
|||
// textAlign = widget.content.textAlign != null |
|||
// ? widget.content.textAlign |
|||
// : TextAlign.center; |
|||
// super.initState(); |
|||
// } |
|||
|
|||
// @override |
|||
// void dispose() { |
|||
// _focus.removeListener(_onFocusChange); |
|||
// _focus.dispose(); |
|||
// super.dispose(); |
|||
// } |
|||
|
|||
// void _onFocusChange() { |
|||
// debugPrint("Focus: " + _focus.hasFocus.toString()); |
|||
// } |
|||
|
|||
// void verify() { |
|||
// if (_focus.hasFocus) { |
|||
// _focus.unfocus(); |
|||
// } else { |
|||
// Navigator.pop( |
|||
// context, |
|||
// new Textted( |
|||
// text: controller.text, |
|||
// textAlign: textAlign, |
|||
// textStyle: textStyle, |
|||
// )); |
|||
// } |
|||
// } |
|||
|
|||
// @override |
|||
// Widget build(BuildContext context) { |
|||
// SizeConfig().init(context); |
|||
// return Scaffold( |
|||
// resizeToAvoidBottomInset: false, |
|||
// backgroundColor: Color.fromRGBO(0, 0, 0, 0.8), |
|||
// appBar: AppBar( |
|||
// backgroundColor: Colors.transparent, |
|||
// leading: IconButton( |
|||
// onPressed: () => Navigator.pop(context, widget.content), |
|||
// icon: Icon( |
|||
// Icons.x, |
|||
// color: Colors.white, |
|||
// ), |
|||
// ), |
|||
// actions: [ |
|||
// IconButton( |
|||
// onPressed: verify, |
|||
// icon: Icon( |
|||
// AntDesign.check, |
|||
// color: Colors.white, |
|||
// ), |
|||
// ), |
|||
// ], |
|||
// ), |
|||
// body: Container( |
|||
// height: SizeConfig.safeBlockVertical * 40, |
|||
// child: Center( |
|||
// child: TextField( |
|||
// controller: controller, |
|||
// // enabled: false, |
|||
// focusNode: _focus, |
|||
// style: textStyle, |
|||
// textAlign: textAlign, |
|||
// // maxLines: 4, |
|||
// decoration: new InputDecoration( |
|||
// filled: true, |
|||
// enabledBorder: OutlineInputBorder( |
|||
// borderRadius: BorderRadius.all( |
|||
// Radius.circular(10.0), |
|||
// ), |
|||
// borderSide: BorderSide( |
|||
// color: Colors.grey.shade400, |
|||
// width: 2, |
|||
// ), |
|||
// ), |
|||
// focusedBorder: OutlineInputBorder( |
|||
// borderRadius: BorderRadius.all( |
|||
// Radius.circular(10.0), |
|||
// ), |
|||
// borderSide: BorderSide( |
|||
// color: Colors.blue.shade300, |
|||
// width: 0, |
|||
// ), |
|||
// ), |
|||
// contentPadding: EdgeInsets.all(15), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// extendBody: false, |
|||
// extendBodyBehindAppBar: false, |
|||
// bottomSheet: Container( |
|||
// height: SizeConfig.safeBlockVertical * 60, |
|||
// child: Container( |
|||
// padding: EdgeInsets.all(10), |
|||
// decoration: BoxDecoration( |
|||
// color: Theme.of(context).backgroundColor, |
|||
// border: Border.symmetric( |
|||
// horizontal: BorderSide( |
|||
// color: Theme.of(context).backgroundColor, |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// child: Align( |
|||
// alignment: Alignment.topCenter, |
|||
// child: SingleChildScrollView( |
|||
// scrollDirection: Axis.vertical, |
|||
// child: TextStyleEditor( |
|||
// fonts: fonts, |
|||
// paletteColors: paletteColors, |
|||
// textStyle: textStyle, |
|||
// textAlign: textAlign, |
|||
// initialTool: EditorToolbarAction.fontFamilyTool, |
|||
// onTextAlignEdited: (align) { |
|||
// setState(() { |
|||
// textAlign = align; |
|||
// }); |
|||
// }, |
|||
// onTextStyleEdited: (style) { |
|||
// setState(() { |
|||
// textStyle = textStyle.merge(style); |
|||
// }); |
|||
// }, |
|||
// onCpasLockTaggle: (caps) { |
|||
// print(caps); |
|||
// }, |
|||
// //onToolbarActionChanged: (fu) => , |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// } |
|||
// } |
@ -1,560 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/rendering.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'dart:io'; |
|||
|
|||
import 'package:image_gallery_saver/image_gallery_saver.dart'; |
|||
import 'package:page_transition/page_transition.dart'; |
|||
import 'package:share_plus/share_plus.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/file_formats.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trim_editor.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/Trimmer/trimmer.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/SubmitAdvert.dart'; |
|||
import 'package:teso/util/SizeConfig.dart'; |
|||
import 'package:video_player/video_player.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'dart:async'; |
|||
import 'package:path_provider/path_provider.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:image/image.dart' as IMG; |
|||
import 'package:video_thumbnail/video_thumbnail.dart'; |
|||
|
|||
class VideoReview extends StatefulWidget { |
|||
final video; |
|||
final bool recorded; |
|||
final String campaignID; |
|||
final double aspect; |
|||
|
|||
@override |
|||
const VideoReview( |
|||
{Key key, |
|||
this.video, |
|||
@required this.campaignID, |
|||
@required this.recorded, |
|||
this.aspect}) |
|||
: super(key: key); |
|||
@override |
|||
_VideoReviewState createState() => _VideoReviewState(); |
|||
} |
|||
|
|||
class _VideoReviewState extends State<VideoReview> |
|||
with TickerProviderStateMixin { |
|||
Trimmer _trimmer = new Trimmer(); |
|||
VideoPlayerController videoController; |
|||
VoidCallback videoPlayerListener; |
|||
bool muted = false; |
|||
String readyVideo; |
|||
Color textColor = Colors.white; |
|||
double _startValue = 0.15; |
|||
double _endValue = 60000.0; |
|||
var _future; |
|||
bool _isPlaying = false; |
|||
Duration _duration; |
|||
Duration _position; |
|||
ByteData bytes; |
|||
Uint8List imageBitmap; |
|||
Uint8List thumbnail; |
|||
Directory tempDirectory; |
|||
TesoUser user; |
|||
bool processing = false; |
|||
bool downloaded = false; |
|||
bool processed = false; |
|||
final key = new GlobalKey(); |
|||
double currentOffset = 0; |
|||
|
|||
Future<void> _startVideoPlayer() async { |
|||
await videoController.play(); |
|||
} |
|||
|
|||
Future<void> initializeController(String fileLocation) async { |
|||
videoController = VideoPlayerController.file(File(fileLocation)); |
|||
|
|||
videoPlayerListener = () async { |
|||
Timer.run(() { |
|||
this.setState(() { |
|||
_position = videoController.value.position; |
|||
}); |
|||
setState(() { |
|||
_duration = Duration(milliseconds: _endValue.round()); |
|||
}); |
|||
if (_duration?.compareTo(_position) == 0 || |
|||
_duration?.compareTo(_position) == -1) { |
|||
this.setState(() { |
|||
_isPlaying = false; |
|||
}); |
|||
videoController.pause(); |
|||
videoController.seekTo(Duration(milliseconds: _startValue.round())); |
|||
} else {} |
|||
}); |
|||
}; |
|||
videoController.addListener(videoPlayerListener); |
|||
await videoController.setLooping(true); |
|||
await videoController.initialize(); |
|||
await _trimmer.loadVideo(videoFile: File(fileLocation)); |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
readyVideo = widget.video; |
|||
if (readyVideo != null) _future = initializeController(readyVideo); |
|||
rootBundle.load("assets/images/rawLogo.png").then((value) => setState(() { |
|||
imageBitmap = value.buffer.asUint8List(); |
|||
IMG.Image img = IMG.decodeImage(imageBitmap); |
|||
IMG.Image resized = IMG.copyResize(img, width: 50, height: 60); |
|||
imageBitmap = IMG.encodePng(resized); |
|||
})); |
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
videoController?.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
void postVideo(context) async { |
|||
setState(() { |
|||
processing = true; |
|||
}); |
|||
if (processed) { |
|||
await Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
type: PageTransitionType.leftToRight, |
|||
child: SubmitAdvert( |
|||
video: readyVideo, |
|||
aspectRatio: widget.recorded |
|||
? "0.5625" |
|||
: videoController.value.aspectRatio.toString(), |
|||
thumbnail: this.thumbnail, |
|||
campaignID: widget.campaignID, |
|||
), |
|||
)); |
|||
} else { |
|||
readyVideo = await processVideo(context, false); |
|||
await Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
type: PageTransitionType.leftToRight, |
|||
child: SubmitAdvert( |
|||
video: readyVideo, |
|||
aspectRatio: widget.recorded |
|||
? "0.5625" |
|||
: videoController.value.aspectRatio.toString(), |
|||
thumbnail: this.thumbnail, |
|||
campaignID: widget.campaignID, |
|||
), |
|||
)); |
|||
} |
|||
} |
|||
|
|||
Future<void> downloadVideo(context) async { |
|||
try { |
|||
setState(() { |
|||
processing = true; |
|||
}); |
|||
String output = await processVideo(context, true); |
|||
await ImageGallerySaver.saveFile(output).catchError((error, stackTrace) { |
|||
setState(() { |
|||
processing = false; |
|||
downloaded = false; |
|||
}); |
|||
}).then((value) { |
|||
setState(() { |
|||
processing = false; |
|||
downloaded = true; |
|||
}); |
|||
}); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
} |
|||
|
|||
Future<String> processVideo(context, bool watermark) async { |
|||
user = Provider.of<UserProvider>(context, listen: false).currentUser; |
|||
String location = await getTemporaryDirectory().then((value) => |
|||
value.path + |
|||
"/" + |
|||
DateTime.now().millisecondsSinceEpoch.toString() + |
|||
".mp4"); |
|||
if (widget.recorded) { |
|||
String initial = await _trimmer.saveTrimmedVideo( |
|||
applyVideoEncoding: false, |
|||
ffmpegCommand: "-vf setsar=1:1 -aspect 9:16", |
|||
customVideoFormat: ".mp4", |
|||
startValue: _startValue, |
|||
endValue: videoController.value.duration.inMilliseconds > 5900 && |
|||
videoController.value.duration.inMilliseconds >= _endValue |
|||
? _endValue |
|||
: double.parse( |
|||
videoController.value.duration.inMilliseconds.toString()), |
|||
); |
|||
this.thumbnail = await generateThumbnail(); |
|||
|
|||
location = initial; |
|||
} else { |
|||
String initial = await _trimmer.saveTrimmedVideo( |
|||
startValue: _startValue, |
|||
endValue: videoController.value.duration.inMilliseconds > 5900 && |
|||
videoController.value.duration.inMilliseconds >= _endValue |
|||
? _endValue |
|||
: double.parse( |
|||
videoController.value.duration.inMilliseconds.toString()), |
|||
outputFormat: FileFormat.mp4, |
|||
); |
|||
this.thumbnail = await generateThumbnail(); |
|||
|
|||
location = initial; |
|||
} |
|||
return location; |
|||
} |
|||
|
|||
Future<Uint8List> generateThumbnail() async { |
|||
try { |
|||
Uint8List thumbnail; |
|||
|
|||
thumbnail = await VideoThumbnail.thumbnailData( |
|||
video: widget.video, |
|||
imageFormat: ImageFormat.JPEG, |
|||
maxWidth: 0, |
|||
maxHeight: 0, |
|||
timeMs: 100, |
|||
quality: 100, |
|||
); |
|||
return thumbnail; |
|||
} catch (e) { |
|||
print("Error :::: " + e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
Future<void> shareVideo(context) async { |
|||
setState(() { |
|||
processing = true; |
|||
}); |
|||
if (readyVideo == widget.video) { |
|||
readyVideo = await processVideo(context, true); |
|||
Share.shareFiles([readyVideo]); |
|||
} else { |
|||
Share.shareFiles([readyVideo]); |
|||
} |
|||
setState(() { |
|||
processing = false; |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
SizeConfig().init(context); |
|||
return Scaffold( |
|||
body: FutureBuilder( |
|||
future: _future, |
|||
builder: (context, snapshot) { |
|||
if (snapshot.connectionState == ConnectionState.waiting) { |
|||
return Container( |
|||
color: Colors.black, |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: Center( |
|||
child: CircularProgressIndicator( |
|||
backgroundColor: Colors.red, |
|||
), |
|||
), |
|||
); |
|||
} else { |
|||
return Stack( |
|||
children: [ |
|||
videoContent(context), |
|||
// Video trimmer |
|||
trimmerWidget(context), |
|||
// Pop button |
|||
Align( |
|||
alignment: Alignment.topLeft, |
|||
child: InkWell( |
|||
onTap: () => Navigator.pop(context), |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.07, |
|||
vertical: MediaQuery.of(context).size.width * 0.1, |
|||
), |
|||
height: 35, |
|||
width: 35, |
|||
decoration: BoxDecoration( |
|||
color: Color.fromRGBO(0, 0, 0, 0.4), |
|||
shape: BoxShape.circle), |
|||
child: Icon( |
|||
Icons.close, |
|||
color: Colors.white, |
|||
size: 20, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
// Bottom buttons |
|||
bottomButtons(context), |
|||
Visibility( |
|||
visible: processing, |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
padding: EdgeInsets.only( |
|||
top: MediaQuery.of(context).size.width * 0.7), |
|||
child: Center( |
|||
child: Column( |
|||
children: [ |
|||
CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
Text( |
|||
"Processing.....", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
}), |
|||
); |
|||
} |
|||
|
|||
Widget trimmerWidget(context) { |
|||
return Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.01, |
|||
vertical: MediaQuery.of(context).size.width * 0.20, |
|||
), |
|||
width: MediaQuery.of(context).size.width, |
|||
child: TrimEditor( |
|||
borderPaintColor: tesoGold, |
|||
circlePaintColor: tesoBlue, |
|||
thumbnailQuality: 100, |
|||
showDuration: true, |
|||
viewerHeight: 50.0, |
|||
maxVideoLength: Duration(seconds: 60), |
|||
viewerWidth: MediaQuery.of(context).size.width, |
|||
onChangeStart: (value) { |
|||
if (!mounted) { |
|||
setState(() { |
|||
_startValue = value; |
|||
}); |
|||
} else { |
|||
_startValue = value; |
|||
} |
|||
videoController.seekTo(Duration(milliseconds: value.round())); |
|||
}, |
|||
onChangeEnd: (value) { |
|||
if (!mounted) { |
|||
setState(() { |
|||
_endValue = value; |
|||
}); |
|||
} else { |
|||
_endValue = value; |
|||
} |
|||
}, |
|||
onChangePlaybackState: (isPlaying) { |
|||
if (mounted) |
|||
setState(() { |
|||
_isPlaying = isPlaying; |
|||
}); |
|||
}, |
|||
)); |
|||
} |
|||
|
|||
Widget videoContent(context) { |
|||
return Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
color: Colors.black, |
|||
child: Center( |
|||
child: AspectRatio( |
|||
aspectRatio: videoController.value.size != null |
|||
? videoController.value.aspectRatio |
|||
: 1.0, |
|||
child: Stack( |
|||
children: [ |
|||
InkWell( |
|||
onTap: () { |
|||
!_isPlaying ? _startVideoPlayer() : videoController.pause(); |
|||
setState(() { |
|||
_isPlaying = !_isPlaying; |
|||
}); |
|||
}, |
|||
child: VideoPlayer( |
|||
videoController, |
|||
), |
|||
), |
|||
Container( |
|||
width: double.infinity, |
|||
height: double.infinity, |
|||
child: GestureDetector( |
|||
child: !_isPlaying |
|||
? Icon( |
|||
Icons.play_circle, |
|||
size: 60, |
|||
color: Colors.white, |
|||
) |
|||
: Container(), |
|||
onTap: () { |
|||
!_isPlaying |
|||
? _startVideoPlayer() |
|||
: videoController.pause(); |
|||
setState(() { |
|||
_isPlaying = !_isPlaying; |
|||
}); |
|||
}, |
|||
), |
|||
), |
|||
], |
|||
)), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget bottomButtons(context) { |
|||
if (widget.recorded) { |
|||
return Align( |
|||
alignment: Alignment.bottomLeft, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.05, |
|||
vertical: SizeConfig.safeBlockVertical * 2.5, |
|||
), |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
Container( |
|||
width: 55, |
|||
height: 40, |
|||
padding: EdgeInsets.all(5), |
|||
decoration: BoxDecoration( |
|||
color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
border: Border.all(color: Colors.white, width: 0.5)), |
|||
child: InkWell( |
|||
onTap: () async => |
|||
!downloaded ? await downloadVideo(context) : null, |
|||
child: Icon( |
|||
!downloaded ? Icons.download : Icons.check, |
|||
color: !downloaded ? Colors.white : Colors.green, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
width: 55, |
|||
height: 40, |
|||
padding: EdgeInsets.all(5), |
|||
decoration: BoxDecoration( |
|||
color: Color.fromRGBO(0, 0, 0, 0.6), |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
border: Border.all(color: Colors.white, width: 0.5)), |
|||
child: InkWell( |
|||
onTap: () async => await shareVideo(context), |
|||
child: Icon( |
|||
Icons.share, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
padding: EdgeInsets.all(5), |
|||
width: 100, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
color: tesoGold, |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
), |
|||
child: InkWell( |
|||
onTap: () => postVideo(context), |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
Text( |
|||
"Post", |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
Icon( |
|||
Icons.send, |
|||
color: tesoBlue, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} else { |
|||
return Align( |
|||
alignment: Alignment.bottomRight, |
|||
child: Container( |
|||
padding: EdgeInsets.all(5), |
|||
width: 100, |
|||
height: 40, |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: 10, |
|||
horizontal: 20, |
|||
), |
|||
decoration: BoxDecoration( |
|||
color: tesoGold, |
|||
borderRadius: BorderRadius.only( |
|||
bottomLeft: Radius.circular(30), |
|||
bottomRight: Radius.circular(30), |
|||
topRight: Radius.circular(30), |
|||
topLeft: Radius.circular(30), |
|||
), |
|||
), |
|||
child: InkWell( |
|||
onTap: () => postVideo(context), |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
Text( |
|||
"Post", |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
Icon( |
|||
Icons.send, |
|||
color: tesoBlue, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
} |
@ -1,92 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class ColorPalette extends StatefulWidget { |
|||
final Color activeColor; |
|||
final List<Color> colors; |
|||
final Function(Color) onColorPicked; |
|||
|
|||
ColorPalette({ |
|||
this.activeColor, |
|||
this.onColorPicked, |
|||
this.colors, |
|||
}); |
|||
|
|||
@override |
|||
_ColorPaletteState createState() => _ColorPaletteState(); |
|||
} |
|||
|
|||
class _ColorPaletteState extends State<ColorPalette> { |
|||
Color _activeColor; |
|||
|
|||
@override |
|||
void initState() { |
|||
_activeColor = widget.activeColor ?? widget.colors[0]; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Padding( |
|||
padding: EdgeInsets.all(16), |
|||
child: Wrap( |
|||
spacing: 16, |
|||
runSpacing: 16, |
|||
children: widget.colors |
|||
.map( |
|||
(color) => _ColorHolder( |
|||
color: color, |
|||
active: color == _activeColor, |
|||
onTap: (color) { |
|||
setState(() => _activeColor = color); |
|||
widget.onColorPicked(color); |
|||
}, |
|||
), |
|||
) |
|||
.toList(), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _ColorHolder extends StatelessWidget { |
|||
final Color color; |
|||
final Function(Color) onTap; |
|||
final bool active; |
|||
|
|||
_ColorHolder({ |
|||
this.color, |
|||
this.onTap, |
|||
this.active = false, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Container( |
|||
height: 40, |
|||
width: 40, |
|||
decoration: BoxDecoration( |
|||
border: active |
|||
? Border.fromBorderSide( |
|||
BorderSide(color: Theme.of(context).colorScheme.onSurface)) |
|||
: null, |
|||
borderRadius: BorderRadius.circular(50), |
|||
), |
|||
child: Center( |
|||
child: GestureDetector( |
|||
onTap: () => onTap(color), |
|||
child: Container( |
|||
height: 35, |
|||
width: 35, |
|||
decoration: BoxDecoration( |
|||
border: Border.fromBorderSide( |
|||
BorderSide(color: Theme.of(context).colorScheme.onSurface)), |
|||
borderRadius: BorderRadius.circular(50), |
|||
color: color, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,30 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class OptionButton extends StatelessWidget { |
|||
final bool isActive; |
|||
final Function() onPressed; |
|||
final Widget child; |
|||
final Size size; |
|||
|
|||
OptionButton({ |
|||
this.onPressed, |
|||
this.child, |
|||
this.isActive = false, |
|||
this.size, |
|||
}); |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return RawMaterialButton( |
|||
constraints: BoxConstraints.tight(size ?? Size(45, 45)), |
|||
highlightColor: Theme.of(context).colorScheme.background, |
|||
splashColor: Theme.of(context).colorScheme.background, |
|||
fillColor: isActive ? Theme.of(context).colorScheme.background : null, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(12), |
|||
side: BorderSide(color: Theme.of(context).colorScheme.surface), |
|||
), |
|||
child: child, |
|||
onPressed: onPressed, |
|||
); |
|||
} |
|||
} |
@ -1,87 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import 'option_button.dart'; |
|||
import 'toolbar_action.dart'; |
|||
|
|||
class Toolbar extends StatefulWidget { |
|||
final EditorToolbarAction initialTool; |
|||
final Function(EditorToolbarAction) onToolSelect; |
|||
|
|||
Toolbar({ |
|||
this.initialTool = EditorToolbarAction.editor, |
|||
this.onToolSelect, |
|||
}); |
|||
|
|||
@override |
|||
_ToolbarState createState() => _ToolbarState(); |
|||
} |
|||
|
|||
class _ToolbarState extends State<Toolbar> { |
|||
EditorToolbarAction _selectedAction; |
|||
@override |
|||
void initState() { |
|||
_selectedAction = widget.initialTool; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
// OptionButton( |
|||
// isActive: _selectedAction == EditorToolbarAction.editor, |
|||
// child: Icon(Icons.keyboard), |
|||
// onPressed: () { |
|||
// setState(() => _selectedAction = EditorToolbarAction.editor); |
|||
// widget.onToolSelect(EditorToolbarAction.editor); |
|||
// }, |
|||
// ), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontFamilyTool, |
|||
child: Icon(Icons.title), |
|||
onPressed: () { |
|||
setState( |
|||
() => _selectedAction = EditorToolbarAction.fontFamilyTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontFamilyTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontOptionTool, |
|||
child: Icon(Icons.strikethrough_s), |
|||
onPressed: () { |
|||
setState( |
|||
() => _selectedAction = EditorToolbarAction.fontOptionTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontOptionTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontSizeTool, |
|||
child: Icon(Icons.format_size), |
|||
onPressed: () { |
|||
setState(() => _selectedAction = EditorToolbarAction.fontSizeTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontSizeTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.fontColorTool, |
|||
child: Icon(Icons.format_color_text), |
|||
onPressed: () { |
|||
setState(() => _selectedAction = EditorToolbarAction.fontColorTool); |
|||
widget.onToolSelect(EditorToolbarAction.fontColorTool); |
|||
}, |
|||
), |
|||
OptionButton( |
|||
isActive: _selectedAction == EditorToolbarAction.backgroundColorTool, |
|||
child: Icon(Icons.format_color_fill), |
|||
onPressed: () { |
|||
setState(() => |
|||
_selectedAction = EditorToolbarAction.backgroundColorTool); |
|||
widget.onToolSelect(EditorToolbarAction.backgroundColorTool); |
|||
}, |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
@ -1,8 +0,0 @@ |
|||
enum EditorToolbarAction { |
|||
editor, |
|||
fontFamilyTool, |
|||
fontOptionTool, |
|||
fontSizeTool, |
|||
fontColorTool, |
|||
backgroundColorTool, |
|||
} |
@ -1,24 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../color_palette.dart'; |
|||
|
|||
class BackgroundColorTool extends StatelessWidget { |
|||
final List<Color> colors; |
|||
final Color activeColor; |
|||
final Function(Color) onColorPicked; |
|||
|
|||
BackgroundColorTool({ |
|||
this.colors, |
|||
this.onColorPicked, |
|||
this.activeColor, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return ColorPalette( |
|||
activeColor: activeColor, |
|||
onColorPicked: onColorPicked, |
|||
colors: colors, |
|||
); |
|||
} |
|||
} |
@ -1,24 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../color_palette.dart'; |
|||
|
|||
class FontColorTool extends StatelessWidget { |
|||
final List<Color> colors; |
|||
final Color activeColor; |
|||
final Function(Color) onColorPicked; |
|||
|
|||
FontColorTool({ |
|||
this.colors, |
|||
this.onColorPicked, |
|||
this.activeColor, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return ColorPalette( |
|||
activeColor: activeColor, |
|||
onColorPicked: onColorPicked, |
|||
colors: colors, |
|||
); |
|||
} |
|||
} |
@ -1,66 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../option_button.dart'; |
|||
|
|||
class FontFamilyTool extends StatefulWidget { |
|||
final List<String> fonts; |
|||
final Function(String) onSelectFont; |
|||
final String selectedFont; |
|||
|
|||
FontFamilyTool({ |
|||
this.fonts, |
|||
this.onSelectFont, |
|||
this.selectedFont, |
|||
}); |
|||
|
|||
@override |
|||
_FontFamilyToolState createState() => _FontFamilyToolState(); |
|||
} |
|||
|
|||
class _FontFamilyToolState extends State<FontFamilyTool> { |
|||
String _selectedFont; |
|||
|
|||
@override |
|||
void initState() { |
|||
_selectedFont = widget.selectedFont ?? widget.fonts[0]; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Wrap( |
|||
spacing: 12, |
|||
runSpacing: 12, |
|||
children: widget.fonts |
|||
.map<_FontFamily>( |
|||
(font) => _FontFamily( |
|||
font, |
|||
isSelected: _selectedFont == font, |
|||
onSelect: (selectedFont) { |
|||
setState(() => _selectedFont = selectedFont); |
|||
widget.onSelectFont(selectedFont); |
|||
}, |
|||
), |
|||
) |
|||
.toList(), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _FontFamily extends StatelessWidget { |
|||
final String font; |
|||
final bool isSelected; |
|||
final Function(String) onSelect; |
|||
|
|||
_FontFamily(this.font, {this.onSelect, this.isSelected = false}); |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return OptionButton( |
|||
isActive: isSelected, |
|||
size: Size(90, 45), |
|||
onPressed: () => onSelect(font), |
|||
child: Center(child: Text(font, style: TextStyle(fontFamily: font))), |
|||
); |
|||
} |
|||
} |
@ -1,123 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class FontSizeTool extends StatelessWidget { |
|||
final double fontSize; |
|||
final double letterSpacing; |
|||
final double letterHeight; |
|||
final Function( |
|||
double fontSize, |
|||
double letterSpacing, |
|||
double letterHeight, |
|||
) onFontSizeEdited; |
|||
|
|||
FontSizeTool({ |
|||
this.onFontSizeEdited, |
|||
this.fontSize = 0, |
|||
this.letterSpacing = 0, |
|||
this.letterHeight = 0, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
double _fontSize = fontSize; |
|||
double _letterSpacing = letterSpacing; |
|||
double _letterHeight = letterHeight; |
|||
|
|||
return Padding( |
|||
padding: EdgeInsets.all(16), |
|||
child: Column( |
|||
children: [ |
|||
_ResizeSlider( |
|||
value: _fontSize, |
|||
max: 45, |
|||
icon: Icons.format_size, |
|||
onChange: (value) { |
|||
_fontSize = value; |
|||
onFontSizeEdited(_fontSize, _letterSpacing, _letterHeight); |
|||
}, |
|||
), |
|||
_ResizeSlider( |
|||
value: _letterHeight, |
|||
icon: Icons.format_line_spacing, |
|||
max: 10, |
|||
onChange: (value) { |
|||
_letterHeight = value; |
|||
onFontSizeEdited(_fontSize, _letterSpacing, _letterHeight); |
|||
}, |
|||
), |
|||
_ResizeSlider( |
|||
value: _letterSpacing, |
|||
icon: Icons.settings_ethernet, |
|||
max: 10, |
|||
onChange: (value) { |
|||
_letterSpacing = value; |
|||
onFontSizeEdited(_fontSize, _letterSpacing, _letterHeight); |
|||
}, |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _ResizeSlider extends StatefulWidget { |
|||
final double value; |
|||
final double min; |
|||
final double max; |
|||
final IconData icon; |
|||
final Function(double) onChange; |
|||
|
|||
_ResizeSlider({ |
|||
this.value, |
|||
this.icon, |
|||
this.onChange, |
|||
this.min = 0, |
|||
this.max = 100, |
|||
}); |
|||
|
|||
@override |
|||
_ResizeSliderState createState() => _ResizeSliderState(); |
|||
} |
|||
|
|||
class _ResizeSliderState extends State<_ResizeSlider> { |
|||
double _value; |
|||
|
|||
@override |
|||
void initState() { |
|||
_value = widget.value; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
children: [ |
|||
Icon(widget.icon), |
|||
Expanded( |
|||
child: SliderTheme( |
|||
data: SliderThemeData( |
|||
activeTrackColor: Theme.of(context).colorScheme.background, |
|||
inactiveTrackColor: Theme.of(context).colorScheme.background, |
|||
thumbColor: Theme.of(context).colorScheme.background, |
|||
overlayColor: |
|||
Theme.of(context).colorScheme.background.withOpacity(0.2), |
|||
trackHeight: 2, |
|||
), |
|||
child: Slider( |
|||
value: _value, |
|||
onChanged: (value) { |
|||
setState(() => _value = value); |
|||
|
|||
widget.onChange(value); |
|||
}, |
|||
min: widget.min, |
|||
max: widget.max, |
|||
), |
|||
), |
|||
), |
|||
Text(_value.toStringAsFixed(1)), |
|||
], |
|||
); |
|||
} |
|||
} |
@ -1,237 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import '../option_button.dart'; |
|||
|
|||
class TextFormatTool extends StatelessWidget { |
|||
final Function( |
|||
bool bold, |
|||
bool italic, |
|||
) onTextFormatEdited; |
|||
final Function(bool caps) onCpasLockTaggle; |
|||
final Function(TextAlign textAlign) onTextAlignEdited; |
|||
final TextAlign textAlign; |
|||
final bool bold; |
|||
final bool italic; |
|||
final bool caps; |
|||
|
|||
TextFormatTool({ |
|||
this.onTextFormatEdited, |
|||
this.onTextAlignEdited, |
|||
this.onCpasLockTaggle, |
|||
this.textAlign = TextAlign.left, |
|||
this.bold = false, |
|||
this.italic = false, |
|||
this.caps = false, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Container( |
|||
margin: EdgeInsets.only(top: 36), |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.center, |
|||
children: [ |
|||
_TextFormatEditor( |
|||
bold: bold, |
|||
italic: italic, |
|||
caps: caps, |
|||
onFormatEdited: onTextFormatEdited, |
|||
onCpasLockTaggle: onCpasLockTaggle, |
|||
), |
|||
SizedBox(height: 36), |
|||
_TextAlignEditor( |
|||
textAlign: textAlign, |
|||
onTextAlignEdited: onTextAlignEdited, |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextAlignEditor extends StatefulWidget { |
|||
final TextAlign textAlign; |
|||
final Function(TextAlign textAlign) onTextAlignEdited; |
|||
|
|||
_TextAlignEditor({ |
|||
this.onTextAlignEdited, |
|||
this.textAlign = TextAlign.left, |
|||
}); |
|||
|
|||
@override |
|||
_TextAlignEditorState createState() => _TextAlignEditorState(); |
|||
} |
|||
|
|||
class _TextAlignEditorState extends State<_TextAlignEditor> { |
|||
TextAlign _textAlign; |
|||
|
|||
@override |
|||
void initState() { |
|||
_textAlign = widget.textAlign; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_left, |
|||
isActive: _textAlign == TextAlign.left, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.left); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_center, |
|||
isActive: _textAlign == TextAlign.center, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.center); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_right, |
|||
isActive: _textAlign == TextAlign.right, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.right); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
_TextAlignOption( |
|||
icon: Icons.format_align_justify, |
|||
isActive: _textAlign == TextAlign.justify, |
|||
onPressed: () { |
|||
setState(() => _textAlign = TextAlign.justify); |
|||
widget.onTextAlignEdited(_textAlign); |
|||
}, |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextAlignOption extends StatelessWidget { |
|||
final IconData icon; |
|||
final Function() onPressed; |
|||
final bool isActive; |
|||
|
|||
_TextAlignOption({ |
|||
this.icon, |
|||
this.onPressed, |
|||
this.isActive = false, |
|||
}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return IconButton( |
|||
iconSize: 32, |
|||
icon: Icon(icon), |
|||
color: isActive |
|||
? Theme.of(context).iconTheme.color |
|||
: Theme.of(context).disabledColor, |
|||
onPressed: onPressed, |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextFormatEditor extends StatefulWidget { |
|||
final Function(bool bold, bool italic) onFormatEdited; |
|||
final Function(bool caps) onCpasLockTaggle; |
|||
final bool bold; |
|||
final bool italic; |
|||
final bool caps; |
|||
|
|||
_TextFormatEditor({ |
|||
this.onFormatEdited, |
|||
this.onCpasLockTaggle, |
|||
this.bold = false, |
|||
this.italic = false, |
|||
this.caps = false, |
|||
}); |
|||
|
|||
@override |
|||
_TextFormatEditorState createState() => _TextFormatEditorState(); |
|||
} |
|||
|
|||
class _TextFormatEditorState extends State<_TextFormatEditor> { |
|||
bool _bold; |
|||
bool _italic; |
|||
bool _caps; |
|||
|
|||
@override |
|||
void initState() { |
|||
_bold = widget.bold; |
|||
_italic = widget.italic; |
|||
_caps = widget.caps; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
_TextFormatOption( |
|||
title: 'BOLD', |
|||
icon: Icons.format_bold, |
|||
isActive: _bold, |
|||
onPressed: () { |
|||
setState(() => _bold = !_bold); |
|||
widget.onFormatEdited(_bold, _italic); |
|||
}, |
|||
), |
|||
_TextFormatOption( |
|||
title: 'ITALIC', |
|||
icon: Icons.format_italic, |
|||
isActive: _italic, |
|||
onPressed: () { |
|||
setState(() => _italic = !_italic); |
|||
widget.onFormatEdited(_bold, _italic); |
|||
}, |
|||
), |
|||
_TextFormatOption( |
|||
title: 'CAPS', |
|||
icon: Icons.keyboard_capslock, |
|||
isActive: _caps, |
|||
onPressed: () { |
|||
setState(() => _caps = !_caps); |
|||
widget.onCpasLockTaggle(_caps); |
|||
}, |
|||
), |
|||
], |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _TextFormatOption extends StatelessWidget { |
|||
final String title; |
|||
final IconData icon; |
|||
final Function() onPressed; |
|||
final bool isActive; |
|||
|
|||
_TextFormatOption({ |
|||
this.title, |
|||
this.icon, |
|||
this.onPressed, |
|||
this.isActive = false, |
|||
}); |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Column( |
|||
children: [ |
|||
OptionButton( |
|||
isActive: isActive, |
|||
onPressed: onPressed, |
|||
child: Icon(icon), |
|||
), |
|||
SizedBox(height: 12), |
|||
Text(title), |
|||
], |
|||
); |
|||
} |
|||
} |
@ -1,226 +0,0 @@ |
|||
library text_style_editor; |
|||
|
|||
export 'src/toolbar_action.dart'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'src/toolbar_action.dart'; |
|||
import 'src/tools/background_color_tool.dart'; |
|||
import 'src/color_palette.dart'; |
|||
import 'src/tools/font_family_tool.dart'; |
|||
import 'src/tools/font_size_tool.dart'; |
|||
import 'src/tools/text_format_tool.dart'; |
|||
import 'src/toolbar.dart'; |
|||
|
|||
/// Text style editor |
|||
/// A flutter widget that edit text style and text alignment |
|||
/// |
|||
/// You can pass your text style or alignment to the widget |
|||
/// and then get the edited text style |
|||
class TextStyleEditor extends StatefulWidget { |
|||
/// Editor's font families |
|||
final List<String> fonts; |
|||
|
|||
/// The text style |
|||
final TextStyle textStyle; |
|||
|
|||
/// The text alignment |
|||
final TextAlign textAlign; |
|||
|
|||
/// The inithial editor tool |
|||
final EditorToolbarAction initialTool; |
|||
|
|||
/// Editor's palette colors |
|||
final List<Color> paletteColors; |
|||
|
|||
/// [onTextStyleEdited] will be called after [textStyle] prop has changed |
|||
final Function(TextStyle) onTextStyleEdited; |
|||
|
|||
/// [onTextAlignEdited] will be called after [textAlingment] prop has changed |
|||
final Function(TextAlign) onTextAlignEdited; |
|||
|
|||
/// [onCpasLockTaggle] will be called after caps lock has changed |
|||
final Function(bool) onCpasLockTaggle; |
|||
|
|||
/// [onToolbarActionChanged] will be called after editor's tool has changed |
|||
final Function(EditorToolbarAction) onToolbarActionChanged; |
|||
|
|||
/// Create a [TextStyleEditor] widget |
|||
/// |
|||
/// [fonts] list of font families that you want to use in editor. |
|||
/// [textStyle] initiate text style. |
|||
/// [textAlign] initiate text alignment. |
|||
/// |
|||
/// [onTextStyleEdited] callback will be called every time [textStyle] has changed. |
|||
/// [onTextAlignEdited] callback will be called every time [textAlign] has changed. |
|||
/// [onCpasLockTaggle] callback will be called every time caps lock has changed to off or on. |
|||
/// [onToolbarActionChanged] callback will be called every time editor's tool has changed. |
|||
TextStyleEditor({ |
|||
this.fonts, |
|||
this.textStyle, |
|||
this.textAlign, |
|||
this.paletteColors, |
|||
this.initialTool = EditorToolbarAction.editor, |
|||
this.onTextStyleEdited, |
|||
this.onTextAlignEdited, |
|||
this.onCpasLockTaggle, |
|||
this.onToolbarActionChanged, |
|||
}); |
|||
|
|||
@override |
|||
_TextStyleEditorState createState() => _TextStyleEditorState(); |
|||
} |
|||
|
|||
class _TextStyleEditorState extends State<TextStyleEditor> { |
|||
EditorToolbarAction _currentTool; |
|||
TextStyle _textStyle; |
|||
TextAlign _textAlign; |
|||
List<Color> _paletteColors; |
|||
|
|||
@override |
|||
void initState() { |
|||
_currentTool = widget.initialTool; |
|||
_textStyle = widget.textStyle; |
|||
_textAlign = widget.textAlign; |
|||
|
|||
// Set default palette's colors |
|||
_paletteColors = widget.paletteColors ?? |
|||
[ |
|||
Colors.black, |
|||
Colors.white, |
|||
Colors.red, |
|||
Colors.blue, |
|||
Colors.blueAccent, |
|||
Colors.brown, |
|||
Colors.green, |
|||
Colors.indigoAccent, |
|||
Colors.lime, |
|||
]; |
|||
|
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Container( |
|||
color: Theme.of(context).backgroundColor, |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
Toolbar( |
|||
initialTool: _currentTool, |
|||
onToolSelect: (action) { |
|||
setState(() => _currentTool = action); |
|||
if (widget.onToolbarActionChanged != null) { |
|||
widget.onToolbarActionChanged(action); |
|||
} |
|||
}, |
|||
), |
|||
Divider(), |
|||
Container( |
|||
child: SingleChildScrollView( |
|||
child: () { |
|||
// Choice tools |
|||
switch (_currentTool) { |
|||
case EditorToolbarAction.fontFamilyTool: |
|||
return FontFamilyTool( |
|||
fonts: widget.fonts, |
|||
selectedFont: _textStyle.fontFamily, |
|||
onSelectFont: (fontFamily) { |
|||
setState(() => _textStyle = |
|||
_textStyle.copyWith(fontFamily: fontFamily)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.fontOptionTool: |
|||
return TextFormatTool( |
|||
bold: _textStyle.fontWeight == FontWeight.bold, |
|||
italic: _textStyle.fontStyle == FontStyle.italic, |
|||
textAlign: _textAlign, |
|||
onTextFormatEdited: (bold, italic) { |
|||
setState(() => _textStyle = _textStyle.copyWith( |
|||
fontWeight: |
|||
bold ? FontWeight.bold : FontWeight.normal, |
|||
fontStyle: |
|||
italic ? FontStyle.italic : FontStyle.normal, |
|||
)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
onTextAlignEdited: (align) { |
|||
setState(() => _textAlign = align); |
|||
|
|||
if (widget.onTextAlignEdited != null) { |
|||
widget.onTextAlignEdited(align); |
|||
} |
|||
}, |
|||
onCpasLockTaggle: (caps) { |
|||
if (widget.onCpasLockTaggle != null) { |
|||
widget.onCpasLockTaggle(caps); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.fontSizeTool: |
|||
return FontSizeTool( |
|||
fontSize: _textStyle.fontSize ?? 0, |
|||
letterHeight: _textStyle.height ?? 1.2, |
|||
letterSpacing: _textStyle.letterSpacing ?? 1, |
|||
onFontSizeEdited: ( |
|||
fontSize, |
|||
letterSpacing, |
|||
letterHeight, |
|||
) { |
|||
setState(() => _textStyle = _textStyle.copyWith( |
|||
fontSize: fontSize, |
|||
height: letterHeight, |
|||
letterSpacing: letterSpacing, |
|||
)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.fontColorTool: |
|||
return BackgroundColorTool( |
|||
activeColor: _textStyle.color, |
|||
colors: _paletteColors, |
|||
onColorPicked: (color) { |
|||
setState(() => |
|||
_textStyle = _textStyle.copyWith(color: color)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.backgroundColorTool: |
|||
return ColorPalette( |
|||
activeColor: _textStyle.backgroundColor, |
|||
colors: _paletteColors, |
|||
onColorPicked: (color) { |
|||
setState(() => _textStyle = |
|||
_textStyle.copyWith(backgroundColor: color)); |
|||
|
|||
if (widget.onTextStyleEdited != null) { |
|||
widget.onTextStyleEdited(_textStyle); |
|||
} |
|||
}, |
|||
); |
|||
case EditorToolbarAction.editor: |
|||
return Container(); |
|||
default: |
|||
return Container(); |
|||
} |
|||
}(), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,500 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:camera/camera.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
|
|||
import 'package:image_picker/image_picker.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:page_transition/page_transition.dart'; |
|||
import 'dart:async'; |
|||
import 'package:circular_countdown_timer/circular_countdown_timer.dart'; |
|||
import 'package:video_thumbnail/video_thumbnail.dart' as thumb; |
|||
|
|||
import 'Editor/VideoReview.dart'; |
|||
|
|||
// ignore: must_be_immutable |
|||
class RecordVideo extends StatefulWidget { |
|||
final String campaignID; |
|||
List<CameraDescription> connectedCameras; |
|||
|
|||
RecordVideo({Key key, this.connectedCameras, @required this.campaignID}) |
|||
: super(key: key); |
|||
@override |
|||
_RecordVideoState createState() => _RecordVideoState(); |
|||
} |
|||
|
|||
class _RecordVideoState extends State<RecordVideo> |
|||
with TickerProviderStateMixin { |
|||
CameraController _controller; |
|||
int selectedCamera = 0; |
|||
bool flash = false; |
|||
bool frontFlash = false; |
|||
bool recording = false; |
|||
AnimationController _recordingAnimationController; |
|||
XFile video; |
|||
String filePath; |
|||
int recordEnd = 60; |
|||
CountDownController _controllerCountDown = CountDownController(); |
|||
final interval = const Duration(seconds: 1); |
|||
final picker = ImagePicker(); |
|||
bool gallery = false; |
|||
|
|||
final int timerMaxSeconds = 60; |
|||
|
|||
int currentSeconds = 0; |
|||
|
|||
flipCamera() { |
|||
selectedCamera++; |
|||
if (selectedCamera < widget.connectedCameras.length) { |
|||
onNewCameraSelected(widget.connectedCameras.elementAt(selectedCamera)); |
|||
} else { |
|||
selectedCamera = 0; |
|||
onNewCameraSelected(widget.connectedCameras.elementAt(selectedCamera)); |
|||
} |
|||
} |
|||
|
|||
flashCamera() { |
|||
try { |
|||
if (!flash && |
|||
_controller.description.lensDirection == CameraLensDirection.back) { |
|||
_controller.setFlashMode(FlashMode.torch); |
|||
setState(() { |
|||
flash = true; |
|||
frontFlash = false; |
|||
}); |
|||
} else if (!flash && |
|||
_controller.description.lensDirection == CameraLensDirection.front) { |
|||
setState(() { |
|||
flash = true; |
|||
frontFlash = true; |
|||
}); |
|||
} else if (flash && |
|||
_controller.description.lensDirection == CameraLensDirection.back) { |
|||
_controller.setFlashMode(FlashMode.off); |
|||
setState(() { |
|||
flash = false; |
|||
}); |
|||
} else { |
|||
setState(() { |
|||
flash = false; |
|||
frontFlash = false; |
|||
}); |
|||
} |
|||
} catch (e) {} |
|||
} |
|||
|
|||
haltRecord() async { |
|||
XFile recorded = await stopVideoRecording(); |
|||
if (recorded != null) |
|||
Navigator.of(context).pushReplacement( |
|||
PageRouteBuilder( |
|||
opaque: false, |
|||
pageBuilder: (_, __, ___) => VideoReview( |
|||
video: recorded.path, |
|||
campaignID: widget.campaignID, |
|||
recorded: true, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Future<Uint8List> generateThumbnail(video) async { |
|||
try { |
|||
Uint8List thumbnail; |
|||
|
|||
thumbnail = await thumb.VideoThumbnail.thumbnailData( |
|||
video: video, |
|||
imageFormat: thumb.ImageFormat.JPEG, |
|||
maxWidth: 0, |
|||
maxHeight: 0, |
|||
timeMs: 1, |
|||
quality: 100, |
|||
); |
|||
return thumbnail; |
|||
} catch (e) { |
|||
print("Error :::: " + e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
if (widget.connectedCameras == null || |
|||
widget.connectedCameras.length == 0) { |
|||
availableCameras().then((value) { |
|||
widget.connectedCameras = value; |
|||
onNewCameraSelected(widget.connectedCameras.first); |
|||
}); |
|||
} else { |
|||
onNewCameraSelected(widget.connectedCameras.first); |
|||
} |
|||
_recordingAnimationController = |
|||
new AnimationController(vsync: this, duration: Duration(seconds: 1)); |
|||
|
|||
_recordingAnimationController.repeat(reverse: true); |
|||
super.initState(); |
|||
} |
|||
|
|||
sayCheese() async { |
|||
try { |
|||
if (flash && !frontFlash) |
|||
await _controller.setFlashMode(FlashMode.always); |
|||
await _controller.startVideoRecording(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
} |
|||
|
|||
Future<XFile> stopVideoRecording() async { |
|||
if (!_controller.value.isRecordingVideo) { |
|||
return null; |
|||
} |
|||
|
|||
try { |
|||
return _controller.stopVideoRecording(); |
|||
} on CameraException catch (e) { |
|||
print(e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
void onNewCameraSelected(CameraDescription cameraDescription) async { |
|||
if (_controller != null) { |
|||
await _controller.dispose(); |
|||
} |
|||
_controller = CameraController( |
|||
cameraDescription, |
|||
ResolutionPreset.high, |
|||
enableAudio: true, |
|||
imageFormatGroup: ImageFormatGroup.jpeg, |
|||
); |
|||
|
|||
// If the controller is updated then update the UI. |
|||
_controller.addListener(() { |
|||
if (mounted) setState(() {}); |
|||
if (_controller.value.hasError) { |
|||
print('Camera error ${_controller.value.errorDescription}'); |
|||
} |
|||
}); |
|||
|
|||
try { |
|||
await _controller.initialize(); |
|||
_controller.lockCaptureOrientation(DeviceOrientation.portraitUp); |
|||
_controller.setFocusMode(FocusMode.auto); |
|||
} on CameraException catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
if (mounted) { |
|||
setState(() {}); |
|||
} |
|||
} |
|||
|
|||
void onHocusFocus(TapDownDetails details, BoxConstraints constraints) { |
|||
final offset = Offset( |
|||
details.localPosition.dx / constraints.maxWidth, |
|||
details.localPosition.dy / constraints.maxHeight, |
|||
); |
|||
_controller.setExposurePoint(offset); |
|||
_controller.setFocusPoint(offset); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
_controller?.dispose(); |
|||
_recordingAnimationController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
if (_controller == null || !_controller.value.isInitialized) { |
|||
return Container( |
|||
color: Colors.black, |
|||
); |
|||
} else { |
|||
return Scaffold( |
|||
body: !gallery |
|||
? Stack( |
|||
children: [ |
|||
cameraWidget(context), |
|||
flashWidget(context), |
|||
cameraFlip(context), |
|||
cameraFlash(context), |
|||
recordingAnimation(context), |
|||
recordingCircle(context), |
|||
recorderWidget(context), |
|||
galleryPicker(context), |
|||
], |
|||
) |
|||
: Container(), |
|||
); |
|||
} |
|||
} |
|||
|
|||
imgFromGallery() async { |
|||
try { |
|||
_controller?.dispose(); |
|||
setState(() { |
|||
gallery = true; |
|||
}); |
|||
final pickedFile = await picker.pickVideo( |
|||
source: ImageSource.gallery, |
|||
maxDuration: Duration(minutes: 1), |
|||
); |
|||
|
|||
if (pickedFile != null) { |
|||
return pickedFile.path; |
|||
} else { |
|||
onNewCameraSelected(widget.connectedCameras.first); |
|||
print('No image selected.'); |
|||
} |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
setState(() { |
|||
gallery = false; |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
Widget recordingCircle(context) { |
|||
return Align( |
|||
alignment: Alignment.bottomCenter, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 70, |
|||
width: 70, |
|||
child: CircularCountDownTimer( |
|||
duration: recordEnd, |
|||
initialDuration: 0, |
|||
controller: _controllerCountDown, |
|||
width: MediaQuery.of(context).size.width / 2, |
|||
height: MediaQuery.of(context).size.height / 2, |
|||
ringColor: Colors.grey[300], |
|||
fillColor: Colors.red, |
|||
backgroundColor: Colors.transparent, |
|||
autoStart: false, |
|||
strokeWidth: 5.5, |
|||
isTimerTextShown: false, |
|||
strokeCap: StrokeCap.round, |
|||
//onStart: startTimeout, |
|||
onComplete: haltRecord, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget recorderWidget(context) { |
|||
return Align( |
|||
alignment: Alignment.bottomCenter, |
|||
child: InkWell( |
|||
onTap: recording |
|||
? haltRecord |
|||
: () async { |
|||
await _controller.startVideoRecording(); |
|||
setState(() { |
|||
_controllerCountDown.start(); |
|||
recording = !recording; |
|||
}); |
|||
}, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 70, |
|||
width: 70, |
|||
child: Icon( |
|||
recording ? Icons.stop : Icons.video_camera_back, |
|||
color: Colors.white, |
|||
size: 25, |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget galleryPicker(context) { |
|||
return Align( |
|||
alignment: Alignment.bottomLeft, |
|||
child: recording |
|||
? Container() |
|||
: InkWell( |
|||
onTap: () async { |
|||
String result = await imgFromGallery(); |
|||
if (result != null) { |
|||
// _controller.dispose(); |
|||
Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
type: PageTransitionType.leftToRight, |
|||
child: VideoReview( |
|||
video: result, |
|||
recorded: false, |
|||
campaignID: widget.campaignID, |
|||
), |
|||
)); |
|||
} |
|||
}, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.05, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 70, |
|||
width: 70, |
|||
child: Icon( |
|||
Icons.photo, |
|||
color: Colors.white, |
|||
size: 27, |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget recordingAnimation(context) { |
|||
if (!recording) |
|||
return Align( |
|||
alignment: Alignment.topLeft, |
|||
child: InkWell( |
|||
onTap: () { |
|||
Navigator.pop(context); |
|||
}, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.08, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
height: 35, |
|||
width: 35, |
|||
decoration: BoxDecoration( |
|||
//color: ColorFilterEngineLayer (0, 0, 0, 0.4), |
|||
shape: BoxShape.circle), |
|||
child: Icon( |
|||
Icons.arrow_back_ios, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
)); |
|||
else |
|||
return Align( |
|||
alignment: Alignment.topLeft, |
|||
child: Container( |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
children: [ |
|||
Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: 5, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
padding: EdgeInsets.all(2.5), |
|||
height: 20, |
|||
width: 20, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.red, |
|||
width: 2, |
|||
)), |
|||
child: FadeTransition( |
|||
opacity: _recordingAnimationController, |
|||
child: Container( |
|||
width: 20, |
|||
height: 20, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
color: Colors.red, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget cameraFlash(context) { |
|||
return !recording |
|||
? Align( |
|||
alignment: Alignment.topRight, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.07, |
|||
vertical: MediaQuery.of(context).size.width * 0.25, |
|||
), |
|||
child: InkWell( |
|||
onTap: flashCamera, |
|||
child: Icon( |
|||
flash ? Icons.flash_on : Icons.flash_off, |
|||
color: flash ? tesoGold : Colors.white, |
|||
size: 30, |
|||
), |
|||
), |
|||
), |
|||
) |
|||
: Container(); |
|||
} |
|||
|
|||
Widget cameraFlip(context) { |
|||
return !recording |
|||
? Align( |
|||
alignment: Alignment.topRight, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: MediaQuery.of(context).size.width * 0.06, |
|||
vertical: MediaQuery.of(context).size.width * 0.11, |
|||
), |
|||
child: InkWell( |
|||
onTap: flipCamera, |
|||
child: Icon( |
|||
Icons.cameraswitch_outlined, |
|||
color: Colors.white, |
|||
size: 40, |
|||
), |
|||
), |
|||
), |
|||
) |
|||
: Container(); |
|||
} |
|||
|
|||
Widget flashWidget(context) { |
|||
return Visibility( |
|||
visible: frontFlash, |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
decoration: BoxDecoration( |
|||
color: Colors.white.withOpacity(0.4), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget cameraWidget(context) { |
|||
var camera = _controller.value; |
|||
final size = MediaQuery.of(context).size; |
|||
var scale = size.aspectRatio * camera.aspectRatio; |
|||
if (scale < 1) scale = 1 / scale; |
|||
|
|||
return Transform.scale( |
|||
scale: scale, |
|||
child: Center( |
|||
child: CameraPreview( |
|||
_controller, |
|||
child: LayoutBuilder( |
|||
builder: (BuildContext context, BoxConstraints constraints) { |
|||
return GestureDetector( |
|||
behavior: HitTestBehavior.opaque, |
|||
onTapDown: (details) => onHocusFocus(details, constraints), |
|||
); |
|||
}), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,45 +0,0 @@ |
|||
/// The video file formats available for |
|||
/// generating the output trimmed video. |
|||
/// |
|||
/// The available formats are `mp4`, `mkv`, |
|||
/// `mov`, `flv`, `avi`, `wmv`& `gif`. |
|||
/// |
|||
/// If you define a custom `FFmpeg` command |
|||
/// then this will be ignored. |
|||
/// |
|||
class FileFormat { |
|||
const FileFormat._(this.index); |
|||
|
|||
final int index; |
|||
|
|||
static const FileFormat mp4 = FileFormat._(0); |
|||
static const FileFormat mkv = FileFormat._(1); |
|||
static const FileFormat mov = FileFormat._(2); |
|||
static const FileFormat flv = FileFormat._(3); |
|||
static const FileFormat avi = FileFormat._(4); |
|||
static const FileFormat wmv = FileFormat._(5); |
|||
static const FileFormat gif = FileFormat._(6); |
|||
|
|||
static const List<FileFormat> values = <FileFormat>[ |
|||
mp4, |
|||
mkv, |
|||
mov, |
|||
flv, |
|||
avi, |
|||
wmv, |
|||
gif, |
|||
]; |
|||
|
|||
@override |
|||
String toString() { |
|||
return const <int, String>{ |
|||
0: '.mp4', |
|||
1: '.mkv', |
|||
2: '.mov', |
|||
3: '.flv', |
|||
4: '.avi', |
|||
5: '.wmv', |
|||
6: '.gif', |
|||
}[index]; |
|||
} |
|||
} |
@ -1,32 +0,0 @@ |
|||
/// Supported storage locations. |
|||
/// |
|||
/// * [temporaryDirectory] |
|||
/// |
|||
/// * [applicationDocumentsDirectory] |
|||
/// |
|||
/// * [externalStorageDirectory] |
|||
/// |
|||
class StorageDir { |
|||
const StorageDir._(this.index); |
|||
|
|||
final int index; |
|||
|
|||
static const StorageDir temporaryDirectory = StorageDir._(0); |
|||
static const StorageDir applicationDocumentsDirectory = StorageDir._(1); |
|||
static const StorageDir externalStorageDirectory = StorageDir._(2); |
|||
|
|||
static const List<StorageDir> values = <StorageDir>[ |
|||
temporaryDirectory, |
|||
applicationDocumentsDirectory, |
|||
externalStorageDirectory, |
|||
]; |
|||
|
|||
@override |
|||
String toString() { |
|||
return const <int, String>{ |
|||
0: 'temporaryDirectory', |
|||
1: 'applicationDocumentsDirectory', |
|||
2: 'externalStorageDirectory', |
|||
}[index]; |
|||
} |
|||
} |
@ -1,81 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:video_thumbnail/video_thumbnail.dart'; |
|||
|
|||
class ThumbnailViewer extends StatelessWidget { |
|||
final videoFile; |
|||
final videoDuration; |
|||
final thumbnailHeight; |
|||
final fit; |
|||
final int numberOfThumbnails; |
|||
final int quality; |
|||
|
|||
/// For showing the thumbnails generated from the video, |
|||
/// like a frame by frame preview |
|||
ThumbnailViewer({ |
|||
@required this.videoFile, |
|||
@required this.videoDuration, |
|||
@required this.thumbnailHeight, |
|||
@required this.numberOfThumbnails, |
|||
@required this.fit, |
|||
this.quality = 75, |
|||
}) : assert(videoFile != null), |
|||
assert(videoDuration != null), |
|||
assert(thumbnailHeight != null), |
|||
assert(numberOfThumbnails != null), |
|||
assert(quality != null); |
|||
|
|||
Stream<List<Uint8List>> generateThumbnail() async* { |
|||
final String _videoPath = videoFile.path; |
|||
|
|||
double _eachPart = videoDuration / numberOfThumbnails; |
|||
|
|||
List<Uint8List> _byteList = []; |
|||
|
|||
for (int i = 1; i <= numberOfThumbnails; i++) { |
|||
Uint8List _bytes; |
|||
_bytes = await VideoThumbnail.thumbnailData( |
|||
video: _videoPath, |
|||
imageFormat: ImageFormat.JPEG, |
|||
timeMs: (_eachPart * i).toInt(), |
|||
quality: quality, |
|||
); |
|||
|
|||
_byteList.add(_bytes); |
|||
|
|||
yield _byteList; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return StreamBuilder( |
|||
stream: generateThumbnail(), |
|||
builder: (context, snapshot) { |
|||
if (snapshot.hasData) { |
|||
List<Uint8List> _imageBytes = snapshot.data; |
|||
return ListView.builder( |
|||
scrollDirection: Axis.horizontal, |
|||
itemCount: snapshot.data.length, |
|||
itemBuilder: (context, index) { |
|||
return Container( |
|||
height: thumbnailHeight, |
|||
width: thumbnailHeight, |
|||
child: Image( |
|||
image: MemoryImage(_imageBytes[index]), |
|||
fit: fit, |
|||
), |
|||
); |
|||
}); |
|||
} else { |
|||
return Container( |
|||
color: Colors.grey[900], |
|||
height: thumbnailHeight, |
|||
width: double.maxFinite, |
|||
); |
|||
} |
|||
}, |
|||
); |
|||
} |
|||
} |
@ -1,537 +0,0 @@ |
|||
import 'dart:io'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/Video/Trimmer/thumbnail_viewer.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/Video/Trimmer/trim_editor_painter.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/Video/Trimmer/trimmer.dart'; |
|||
import 'package:video_player/video_player.dart'; |
|||
|
|||
VideoPlayerController videoPlayerController; |
|||
|
|||
class TrimEditor extends StatefulWidget { |
|||
/// For defining the total trimmer area width |
|||
final double viewerWidth; |
|||
|
|||
/// For defining the total trimmer area height |
|||
final double viewerHeight; |
|||
|
|||
/// For defining the image fit type of each thumbnail image. |
|||
/// |
|||
/// By default it is set to `BoxFit.fitHeight`. |
|||
final BoxFit fit; |
|||
|
|||
/// For defining the maximum length of the output video. |
|||
final Duration maxVideoLength; |
|||
|
|||
/// For specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// |
|||
/// By default it is set to `5.0`. |
|||
final double circleSize; |
|||
|
|||
/// For specifying a size to the holder at |
|||
/// the two ends of the video trimmer area, while it is being |
|||
/// `dragged`. |
|||
/// |
|||
/// By default it is set to `8.0`. |
|||
final double circleSizeOnDrag; |
|||
|
|||
/// For specifying a color to the circle. |
|||
/// |
|||
/// By default it is set to `Colors.white`. |
|||
final Color circlePaintColor; |
|||
|
|||
/// For specifying a color to the border of |
|||
/// the trim area. |
|||
/// |
|||
/// By default it is set to `Colors.white`. |
|||
final Color borderPaintColor; |
|||
|
|||
/// For specifying a color to the video |
|||
/// scrubber inside the trim area. |
|||
/// |
|||
/// By default it is set to `Colors.white`. |
|||
final Color scrubberPaintColor; |
|||
|
|||
/// For specifying the quality of each |
|||
/// generated image thumbnail, to be displayed in the trimmer |
|||
/// area. |
|||
final int thumbnailQuality; |
|||
|
|||
/// For showing the start and the end point of the |
|||
/// video on top of the trimmer area. |
|||
/// |
|||
/// By default it is set to `true`. |
|||
final bool showDuration; |
|||
|
|||
/// For providing a `TextStyle` to the |
|||
/// duration text. |
|||
/// |
|||
/// By default it is set to `TextStyle(color: Colors.white)` |
|||
final TextStyle durationTextStyle; |
|||
|
|||
/// Callback to the video start position |
|||
/// |
|||
/// Returns the selected video start position in `milliseconds`. |
|||
final Function(double startValue) onChangeStart; |
|||
|
|||
/// Callback to the video end position. |
|||
/// |
|||
/// Returns the selected video end position in `milliseconds`. |
|||
final Function(double endValue) onChangeEnd; |
|||
|
|||
/// Callback to the video playback |
|||
/// state to know whether it is currently playing or paused. |
|||
/// |
|||
/// Returns a `boolean` value. If `true`, video is currently |
|||
/// playing, otherwise paused. |
|||
final Function(bool isPlaying) onChangePlaybackState; |
|||
|
|||
/// Widget for displaying the video trimmer. |
|||
/// |
|||
/// This has frame wise preview of the video with a |
|||
/// slider for selecting the part of the video to be |
|||
/// trimmed. |
|||
/// |
|||
/// The required parameters are [viewerWidth] & [viewerHeight] |
|||
/// |
|||
/// * [viewerWidth] to define the total trimmer area width. |
|||
/// |
|||
/// |
|||
/// * [viewerHeight] to define the total trimmer area height. |
|||
/// |
|||
/// |
|||
/// The optional parameters are: |
|||
/// |
|||
/// * [fit] for specifying the image fit type of each thumbnail image. |
|||
/// By default it is set to `BoxFit.fitHeight`. |
|||
/// |
|||
/// |
|||
/// * [maxVideoLength] for specifying the maximum length of the |
|||
/// output video. |
|||
/// |
|||
/// |
|||
/// * [circleSize] for specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// By default it is set to `5.0`. |
|||
/// |
|||
/// |
|||
/// * [circleSizeOnDrag] for specifying a size to the holder at |
|||
/// the two ends of the video trimmer area, while it is being |
|||
/// `dragged`. By default it is set to `8.0`. |
|||
/// |
|||
/// |
|||
/// * [circlePaintColor] for specifying a color to the circle. |
|||
/// By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [borderPaintColor] for specifying a color to the border of |
|||
/// the trim area. By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [scrubberPaintColor] for specifying a color to the video |
|||
/// scrubber inside the trim area. By default it is set to |
|||
/// `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [thumbnailQuality] for specifying the quality of each |
|||
/// generated image thumbnail, to be displayed in the trimmer |
|||
/// area. |
|||
/// |
|||
/// |
|||
/// * [showDuration] for showing the start and the end point of the |
|||
/// video on top of the trimmer area. By default it is set to `true`. |
|||
/// |
|||
/// |
|||
/// * [durationTextStyle] is for providing a `TextStyle` to the |
|||
/// duration text. By default it is set to |
|||
/// `TextStyle(color: Colors.white)` |
|||
/// |
|||
/// |
|||
/// * [onChangeStart] is a callback to the video start position. |
|||
/// |
|||
/// |
|||
/// * [onChangeEnd] is a callback to the video end position. |
|||
/// |
|||
/// |
|||
/// * [onChangePlaybackState] is a callback to the video playback |
|||
/// state to know whether it is currently playing or paused. |
|||
/// |
|||
TrimEditor({ |
|||
@required this.viewerWidth, |
|||
@required this.viewerHeight, |
|||
this.fit = BoxFit.fitHeight, |
|||
this.maxVideoLength = const Duration(milliseconds: 0), |
|||
this.circleSize = 5.0, |
|||
this.circleSizeOnDrag = 8.0, |
|||
this.circlePaintColor = Colors.white, |
|||
this.borderPaintColor = Colors.white, |
|||
this.scrubberPaintColor = Colors.white, |
|||
this.thumbnailQuality = 75, |
|||
this.showDuration = true, |
|||
this.durationTextStyle = const TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
this.onChangeStart, |
|||
this.onChangeEnd, |
|||
this.onChangePlaybackState, |
|||
}) : assert(viewerWidth != null), |
|||
assert(viewerHeight != null), |
|||
assert(fit != null), |
|||
assert(maxVideoLength != null), |
|||
assert(circleSize != null), |
|||
assert(circleSizeOnDrag != null), |
|||
assert(circlePaintColor != null), |
|||
assert(borderPaintColor != null), |
|||
assert(scrubberPaintColor != null), |
|||
assert(thumbnailQuality != null), |
|||
assert(showDuration != null), |
|||
assert(durationTextStyle != null); |
|||
|
|||
@override |
|||
_TrimEditorState createState() => _TrimEditorState(); |
|||
} |
|||
|
|||
class _TrimEditorState extends State<TrimEditor> with TickerProviderStateMixin { |
|||
File _videoFile; |
|||
|
|||
double _videoStartPos = 0.0; |
|||
double _videoEndPos = 0.0; |
|||
|
|||
bool _canUpdateStart = true; |
|||
bool _isLeftDrag = true; |
|||
|
|||
Offset _startPos = Offset(0, 0); |
|||
Offset _endPos = Offset(0, 0); |
|||
|
|||
double _startFraction = 0.0; |
|||
double _endFraction = 1.0; |
|||
|
|||
int _videoDuration = 0; |
|||
int _currentPosition = 0; |
|||
|
|||
double _thumbnailViewerW = 0.0; |
|||
double _thumbnailViewerH = 0.0; |
|||
|
|||
int _numberOfThumbnails = 0; |
|||
|
|||
double _circleSize; |
|||
|
|||
double fraction; |
|||
double maxLengthPixels; |
|||
|
|||
ThumbnailViewer thumbnailWidget; |
|||
|
|||
Animation<double> _scrubberAnimation; |
|||
AnimationController _animationController; |
|||
Tween<double> _linearTween; |
|||
|
|||
Future<void> _initializeVideoController() async { |
|||
if (_videoFile != null) { |
|||
videoPlayerController.addListener(() { |
|||
final bool isPlaying = videoPlayerController.value.isPlaying; |
|||
|
|||
if (isPlaying) { |
|||
widget.onChangePlaybackState(true); |
|||
setState(() { |
|||
_currentPosition = |
|||
videoPlayerController.value.position.inMilliseconds; |
|||
|
|||
if (_currentPosition > _videoEndPos.toInt()) { |
|||
widget.onChangePlaybackState(false); |
|||
videoPlayerController.pause(); |
|||
_animationController.stop(); |
|||
} else { |
|||
if (!_animationController.isAnimating) { |
|||
widget.onChangePlaybackState(true); |
|||
_animationController.forward(); |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
if (videoPlayerController.value.isInitialized) { |
|||
if (_animationController != null) { |
|||
if ((_scrubberAnimation.value).toInt() == (_endPos.dx).toInt()) { |
|||
_animationController.reset(); |
|||
} |
|||
_animationController.stop(); |
|||
widget.onChangePlaybackState(false); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
videoPlayerController.setVolume(1.0); |
|||
_videoDuration = videoPlayerController.value.duration.inMilliseconds; |
|||
print(_videoFile.path); |
|||
|
|||
_videoEndPos = fraction != null |
|||
? _videoDuration.toDouble() * fraction |
|||
: _videoDuration.toDouble(); |
|||
|
|||
widget.onChangeEnd(_videoEndPos); |
|||
|
|||
final ThumbnailViewer _thumbnailWidget = ThumbnailViewer( |
|||
videoFile: _videoFile, |
|||
videoDuration: _videoDuration, |
|||
fit: widget.fit, |
|||
thumbnailHeight: _thumbnailViewerH, |
|||
numberOfThumbnails: _numberOfThumbnails, |
|||
quality: widget.thumbnailQuality, |
|||
); |
|||
thumbnailWidget = _thumbnailWidget; |
|||
} |
|||
} |
|||
|
|||
void _setVideoStartPosition(DragUpdateDetails details) async { |
|||
if (!(_startPos.dx + details.delta.dx < 0) && |
|||
!(_startPos.dx + details.delta.dx > _thumbnailViewerW) && |
|||
!(_startPos.dx + details.delta.dx > _endPos.dx)) { |
|||
if (maxLengthPixels != null) { |
|||
if (!(_endPos.dx - _startPos.dx - details.delta.dx > maxLengthPixels)) { |
|||
setState(() { |
|||
if (!(_startPos.dx + details.delta.dx < 0)) |
|||
_startPos += details.delta; |
|||
|
|||
_startFraction = (_startPos.dx / _thumbnailViewerW); |
|||
|
|||
_videoStartPos = _videoDuration * _startFraction; |
|||
widget.onChangeStart(_videoStartPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoStartPos.toInt())); |
|||
_linearTween.begin = _startPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} else { |
|||
setState(() { |
|||
if (!(_startPos.dx + details.delta.dx < 0)) |
|||
_startPos += details.delta; |
|||
|
|||
_startFraction = (_startPos.dx / _thumbnailViewerW); |
|||
|
|||
_videoStartPos = _videoDuration * _startFraction; |
|||
widget.onChangeStart(_videoStartPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoStartPos.toInt())); |
|||
_linearTween.begin = _startPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void _setVideoEndPosition(DragUpdateDetails details) async { |
|||
if (!(_endPos.dx + details.delta.dx > _thumbnailViewerW) && |
|||
!(_endPos.dx + details.delta.dx < 0) && |
|||
!(_endPos.dx + details.delta.dx < _startPos.dx)) { |
|||
if (maxLengthPixels != null) { |
|||
if (!(_endPos.dx - _startPos.dx + details.delta.dx > maxLengthPixels)) { |
|||
setState(() { |
|||
_endPos += details.delta; |
|||
_endFraction = _endPos.dx / _thumbnailViewerW; |
|||
|
|||
_videoEndPos = _videoDuration * _endFraction; |
|||
widget.onChangeEnd(_videoEndPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoEndPos.toInt())); |
|||
_linearTween.end = _endPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} else { |
|||
setState(() { |
|||
_endPos += details.delta; |
|||
_endFraction = _endPos.dx / _thumbnailViewerW; |
|||
|
|||
_videoEndPos = _videoDuration * _endFraction; |
|||
widget.onChangeEnd(_videoEndPos); |
|||
}); |
|||
await videoPlayerController.pause(); |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: _videoEndPos.toInt())); |
|||
_linearTween.end = _endPos.dx; |
|||
_animationController.duration = |
|||
Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()); |
|||
_animationController.reset(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_circleSize = widget.circleSize; |
|||
|
|||
_videoFile = Trimmer.currentVideoFile; |
|||
_thumbnailViewerH = widget.viewerHeight; |
|||
|
|||
_numberOfThumbnails = widget.viewerWidth ~/ _thumbnailViewerH; |
|||
|
|||
_thumbnailViewerW = _numberOfThumbnails * _thumbnailViewerH; |
|||
|
|||
Duration totalDuration = videoPlayerController.value.duration; |
|||
|
|||
if (widget.maxVideoLength > Duration(milliseconds: 0) && |
|||
widget.maxVideoLength < totalDuration) { |
|||
if (widget.maxVideoLength < totalDuration) { |
|||
fraction = |
|||
widget.maxVideoLength.inMilliseconds / totalDuration.inMilliseconds; |
|||
|
|||
maxLengthPixels = _thumbnailViewerW * fraction; |
|||
} |
|||
} |
|||
|
|||
_initializeVideoController(); |
|||
_endPos = Offset( |
|||
maxLengthPixels != null ? maxLengthPixels : _thumbnailViewerW, |
|||
_thumbnailViewerH, |
|||
); |
|||
|
|||
// Defining the tween points |
|||
_linearTween = Tween(begin: _startPos.dx, end: _endPos.dx); |
|||
|
|||
_animationController = AnimationController( |
|||
vsync: this, |
|||
duration: Duration(milliseconds: (_videoEndPos - _videoStartPos).toInt()), |
|||
); |
|||
|
|||
_scrubberAnimation = _linearTween.animate(_animationController) |
|||
..addListener(() { |
|||
setState(() {}); |
|||
}) |
|||
..addStatusListener((status) { |
|||
if (status == AnimationStatus.completed) { |
|||
_animationController.stop(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
videoPlayerController.pause(); |
|||
widget.onChangePlaybackState(false); |
|||
if (_videoFile != null) { |
|||
videoPlayerController.setVolume(0.0); |
|||
videoPlayerController.pause(); |
|||
videoPlayerController.dispose(); |
|||
widget.onChangePlaybackState(false); |
|||
} |
|||
super.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return GestureDetector( |
|||
onHorizontalDragStart: (DragStartDetails details) { |
|||
print("START"); |
|||
print(details.localPosition); |
|||
print((_startPos.dx - details.localPosition.dx).abs()); |
|||
print((_endPos.dx - details.localPosition.dx).abs()); |
|||
|
|||
if (_endPos.dx >= _startPos.dx) { |
|||
if ((_startPos.dx - details.localPosition.dx).abs() > |
|||
(_endPos.dx - details.localPosition.dx).abs()) { |
|||
setState(() { |
|||
_canUpdateStart = false; |
|||
}); |
|||
} else { |
|||
setState(() { |
|||
_canUpdateStart = true; |
|||
}); |
|||
} |
|||
} else { |
|||
if (_startPos.dx > details.localPosition.dx) { |
|||
_isLeftDrag = true; |
|||
} else { |
|||
_isLeftDrag = false; |
|||
} |
|||
} |
|||
}, |
|||
onHorizontalDragEnd: (DragEndDetails details) { |
|||
setState(() { |
|||
_circleSize = widget.circleSize; |
|||
}); |
|||
}, |
|||
onHorizontalDragUpdate: (DragUpdateDetails details) { |
|||
_circleSize = widget.circleSizeOnDrag; |
|||
|
|||
if (_endPos.dx >= _startPos.dx) { |
|||
_isLeftDrag = false; |
|||
if (_canUpdateStart && _startPos.dx + details.delta.dx > 0) { |
|||
_isLeftDrag = false; // To prevent from scrolling over |
|||
_setVideoStartPosition(details); |
|||
} else if (!_canUpdateStart && |
|||
_endPos.dx + details.delta.dx < _thumbnailViewerW) { |
|||
_isLeftDrag = true; // To prevent from scrolling over |
|||
_setVideoEndPosition(details); |
|||
} |
|||
} else { |
|||
if (_isLeftDrag && _startPos.dx + details.delta.dx > 0) { |
|||
_setVideoStartPosition(details); |
|||
} else if (!_isLeftDrag && |
|||
_endPos.dx + details.delta.dx < _thumbnailViewerW) { |
|||
_setVideoEndPosition(details); |
|||
} |
|||
} |
|||
}, |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: <Widget>[ |
|||
widget.showDuration |
|||
? Container( |
|||
width: _thumbnailViewerW, |
|||
child: Padding( |
|||
padding: const EdgeInsets.only(bottom: 8.0), |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
mainAxisSize: MainAxisSize.max, |
|||
children: <Widget>[ |
|||
Text( |
|||
Duration(milliseconds: _videoStartPos.toInt()) |
|||
.toString() |
|||
.split('.')[0], |
|||
style: widget.durationTextStyle, |
|||
), |
|||
Text( |
|||
Duration(milliseconds: _videoEndPos.toInt()) |
|||
.toString() |
|||
.split('.')[0], |
|||
style: widget.durationTextStyle, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
) |
|||
: Container(), |
|||
CustomPaint( |
|||
foregroundPainter: TrimEditorPainter( |
|||
startPos: _startPos, |
|||
endPos: _endPos, |
|||
scrubberAnimationDx: _scrubberAnimation.value, |
|||
circleSize: _circleSize, |
|||
circlePaintColor: widget.circlePaintColor, |
|||
borderPaintColor: widget.borderPaintColor, |
|||
scrubberPaintColor: widget.scrubberPaintColor, |
|||
), |
|||
child: Container( |
|||
color: Colors.grey[900], |
|||
height: _thumbnailViewerH, |
|||
width: _thumbnailViewerW, |
|||
child: thumbnailWidget == null ? Column() : thumbnailWidget, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,150 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
class TrimEditorPainter extends CustomPainter { |
|||
/// To define the start offset |
|||
final Offset startPos; |
|||
|
|||
/// To define the end offset |
|||
final Offset endPos; |
|||
|
|||
/// To define the horizontal length of the selected video area |
|||
final double scrubberAnimationDx; |
|||
|
|||
/// For specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// By default it is set to `0.5`. |
|||
final double circleSize; |
|||
|
|||
/// For specifying the width of the border around |
|||
/// the trim area. By default it is set to `3`. |
|||
final double borderWidth; |
|||
|
|||
/// For specifying the width of the video scrubber |
|||
final double scrubberWidth; |
|||
|
|||
/// For specifying whether to show the scrubber |
|||
final bool showScrubber; |
|||
|
|||
/// For specifying a color to the border of |
|||
/// the trim area. By default it is set to `Colors.white`. |
|||
final Color borderPaintColor; |
|||
|
|||
/// For specifying a color to the circle. |
|||
/// By default it is set to `Colors.white` |
|||
final Color circlePaintColor; |
|||
|
|||
/// For specifying a color to the video |
|||
/// scrubber inside the trim area. By default it is set to |
|||
/// `Colors.white`. |
|||
final Color scrubberPaintColor; |
|||
|
|||
/// For drawing the trim editor slider |
|||
/// |
|||
/// The required parameters are [startPos], [endPos] |
|||
/// & [scrubberAnimationDx] |
|||
/// |
|||
/// * [startPos] to define the start offset |
|||
/// |
|||
/// |
|||
/// * [endPos] to define the end offset |
|||
/// |
|||
/// |
|||
/// * [scrubberAnimationDx] to define the horizontal length of the |
|||
/// selected video area |
|||
/// |
|||
/// |
|||
/// The optional parameters are: |
|||
/// |
|||
/// * [circleSize] for specifying a size to the holder at the |
|||
/// two ends of the video trimmer area, while it is `idle`. |
|||
/// By default it is set to `0.5`. |
|||
/// |
|||
/// |
|||
/// * [borderWidth] for specifying the width of the border around |
|||
/// the trim area. By default it is set to `3`. |
|||
/// |
|||
/// |
|||
/// * [scrubberWidth] for specifying the width of the video scrubber |
|||
/// |
|||
/// |
|||
/// * [showScrubber] for specifying whether to show the scrubber |
|||
/// |
|||
/// |
|||
/// * [borderPaintColor] for specifying a color to the border of |
|||
/// the trim area. By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [circlePaintColor] for specifying a color to the circle. |
|||
/// By default it is set to `Colors.white`. |
|||
/// |
|||
/// |
|||
/// * [scrubberPaintColor] for specifying a color to the video |
|||
/// scrubber inside the trim area. By default it is set to |
|||
/// `Colors.white`. |
|||
/// |
|||
TrimEditorPainter({ |
|||
@required this.startPos, |
|||
@required this.endPos, |
|||
@required this.scrubberAnimationDx, |
|||
this.circleSize = 0.5, |
|||
this.borderWidth = 3, |
|||
this.scrubberWidth = 1, |
|||
this.showScrubber = true, |
|||
this.borderPaintColor = Colors.white, |
|||
this.circlePaintColor = Colors.white, |
|||
this.scrubberPaintColor = Colors.white, |
|||
}) : assert(startPos != null), |
|||
assert(endPos != null), |
|||
assert(scrubberAnimationDx != null), |
|||
assert(circleSize != null), |
|||
assert(borderWidth != null), |
|||
assert(scrubberWidth != null), |
|||
assert(showScrubber != null), |
|||
assert(borderPaintColor != null), |
|||
assert(circlePaintColor != null), |
|||
assert(scrubberPaintColor != null); |
|||
|
|||
@override |
|||
void paint(Canvas canvas, Size size) { |
|||
var borderPaint = Paint() |
|||
..color = borderPaintColor |
|||
..strokeWidth = borderWidth |
|||
..style = PaintingStyle.stroke |
|||
..strokeCap = StrokeCap.round; |
|||
|
|||
var circlePaint = Paint() |
|||
..color = circlePaintColor |
|||
..strokeWidth = 1 |
|||
..style = PaintingStyle.fill |
|||
..strokeCap = StrokeCap.round; |
|||
|
|||
var scrubberPaint = Paint() |
|||
..color = scrubberPaintColor |
|||
..strokeWidth = scrubberWidth |
|||
..style = PaintingStyle.stroke |
|||
..strokeCap = StrokeCap.round; |
|||
|
|||
final rect = Rect.fromPoints(startPos, endPos); |
|||
|
|||
if (showScrubber) { |
|||
if (scrubberAnimationDx.toInt() > startPos.dx.toInt()) { |
|||
canvas.drawLine( |
|||
Offset(scrubberAnimationDx, 0), |
|||
Offset(scrubberAnimationDx, 0) + Offset(0, endPos.dy), |
|||
scrubberPaint, |
|||
); |
|||
} |
|||
} |
|||
|
|||
canvas.drawRect(rect, borderPaint); |
|||
canvas.drawCircle( |
|||
startPos + Offset(0, endPos.dy / 2), circleSize, circlePaint); |
|||
canvas.drawCircle( |
|||
endPos + Offset(0, -endPos.dy / 2), circleSize, circlePaint); |
|||
} |
|||
|
|||
@override |
|||
bool shouldRepaint(CustomPainter oldDelegate) { |
|||
return true; |
|||
} |
|||
} |
@ -1,300 +0,0 @@ |
|||
import 'dart:io'; |
|||
import 'package:path/path.dart'; |
|||
|
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; |
|||
import 'package:intl/intl.dart'; |
|||
import 'package:path_provider/path_provider.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/Video/Trimmer/file_formats.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/Video/Trimmer/storage_dir.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Campaign/Video/Trimmer/trim_editor.dart'; |
|||
import 'package:video_player/video_player.dart'; |
|||
|
|||
/// Helps in loading video from file, saving trimmed video to a file |
|||
/// and gives video playback controls. Some of the helpful methods |
|||
/// are: |
|||
/// * [loadVideo()] |
|||
/// * [saveTrimmedVideo()] |
|||
/// * [videPlaybackControl()] |
|||
class Trimmer { |
|||
static File currentVideoFile; |
|||
|
|||
final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg(); |
|||
|
|||
/// Loads a video using the path provided. |
|||
/// |
|||
/// Returns the loaded video file. |
|||
Future<void> loadVideo({@required File videoFile}) async { |
|||
currentVideoFile = videoFile; |
|||
if (currentVideoFile != null) { |
|||
videoPlayerController = VideoPlayerController.file(currentVideoFile); |
|||
await videoPlayerController.initialize().then((_) { |
|||
TrimEditor( |
|||
viewerHeight: 50, |
|||
viewerWidth: 50.0 * 8, |
|||
// currentVideoFile: currentVideoFile, |
|||
); |
|||
}); |
|||
// TrimEditor( |
|||
// viewerHeight: 50, |
|||
// viewerWidth: 50.0 * 8, |
|||
// // currentVideoFile: currentVideoFile, |
|||
// ); |
|||
} |
|||
} |
|||
|
|||
Future<String> _createFolderInAppDocDir( |
|||
String folderName, |
|||
StorageDir storageDir, |
|||
) async { |
|||
Directory _directory; |
|||
|
|||
if (storageDir == null) { |
|||
_directory = await getApplicationDocumentsDirectory(); |
|||
} else { |
|||
switch (storageDir.toString()) { |
|||
case 'temporaryDirectory': |
|||
_directory = await getTemporaryDirectory(); |
|||
break; |
|||
|
|||
case 'applicationDocumentsDirectory': |
|||
_directory = await getApplicationDocumentsDirectory(); |
|||
break; |
|||
|
|||
case 'externalStorageDirectory': |
|||
_directory = await getExternalStorageDirectory(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Directory + folder name |
|||
final Directory _directoryFolder = |
|||
Directory('${_directory.path}/$folderName/'); |
|||
|
|||
if (await _directoryFolder.exists()) { |
|||
// If folder already exists return path |
|||
print('Exists'); |
|||
return _directoryFolder.path; |
|||
} else { |
|||
print('Creating'); |
|||
// If folder does not exists create folder and then return its path |
|||
final Directory _directoryNewFolder = |
|||
await _directoryFolder.create(recursive: true); |
|||
return _directoryNewFolder.path; |
|||
} |
|||
} |
|||
|
|||
/// Saves the trimmed video to file system. |
|||
/// |
|||
/// Returns the output video path |
|||
/// |
|||
/// The required parameters are [startValue] & [endValue]. |
|||
/// |
|||
/// The optional parameters are [videoFolderName], [videoFileName], |
|||
/// [outputFormat], [fpsGIF], [scaleGIF], [applyVideoEncoding]. |
|||
/// |
|||
/// The `@required` parameter [startValue] is for providing a starting point |
|||
/// to the trimmed video. To be specified in `milliseconds`. |
|||
/// |
|||
/// The `@required` parameter [endValue] is for providing an ending point |
|||
/// to the trimmed video. To be specified in `milliseconds`. |
|||
/// |
|||
/// The parameter [videoFolderName] is used to |
|||
/// pass a folder name which will be used for creating a new |
|||
/// folder in the selected directory. The default value for |
|||
/// it is `Trimmer`. |
|||
/// |
|||
/// The parameter [videoFileName] is used for giving |
|||
/// a new name to the trimmed video file. By default the |
|||
/// trimmed video is named as `<original_file_name>_trimmed.mp4`. |
|||
/// |
|||
/// The parameter [outputFormat] is used for providing a |
|||
/// file format to the trimmed video. This only accepts value |
|||
/// of [FileFormat] type. By default it is set to `FileFormat.mp4`, |
|||
/// which is for `mp4` files. |
|||
/// |
|||
/// The parameter [storageDir] can be used for providing a storage |
|||
/// location option. It accepts only [StorageDir] values. By default |
|||
/// it is set to [applicationDocumentsDirectory]. Some of the |
|||
/// storage types are: |
|||
/// |
|||
/// * [temporaryDirectory] (Only accessible from inside the app, can be |
|||
/// cleared at anytime) |
|||
/// |
|||
/// * [applicationDocumentsDirectory] (Only accessible from inside the app) |
|||
/// |
|||
/// * [externalStorageDirectory] (Supports only `Android`, accessible externally) |
|||
/// |
|||
/// The parameters [fpsGIF] & [scaleGIF] are used only if the |
|||
/// selected output format is `FileFormat.gif`. |
|||
/// |
|||
/// * [fpsGIF] for providing a FPS value (by default it is set |
|||
/// to `10`) |
|||
/// |
|||
/// |
|||
/// * [scaleGIF] for proving a width to output GIF, the height |
|||
/// is selected by maintaining the aspect ratio automatically (by |
|||
/// default it is set to `480`) |
|||
/// |
|||
/// |
|||
/// * [applyVideoEncoding] for specifying whether to apply video |
|||
/// encoding (by default it is set to `false`). |
|||
/// |
|||
/// |
|||
/// ADVANCED OPTION: |
|||
/// |
|||
/// If you want to give custom `FFmpeg` command, then define |
|||
/// [ffmpegCommand] & [customVideoFormat] strings. The `input path`, |
|||
/// `output path`, `start` and `end` position is already define. |
|||
/// |
|||
/// NOTE: The advanced option does not provide any safety check, so if wrong |
|||
/// video format is passed in [customVideoFormat], then the app may |
|||
/// crash. |
|||
/// |
|||
Future<String> saveTrimmedVideo({ |
|||
@required double startValue, |
|||
@required double endValue, |
|||
bool applyVideoEncoding = false, |
|||
FileFormat outputFormat, |
|||
String ffmpegCommand, |
|||
String customVideoFormat, |
|||
int fpsGIF, |
|||
int scaleGIF, |
|||
String videoFolderName, |
|||
String videoFileName, |
|||
StorageDir storageDir, |
|||
}) async { |
|||
final String _videoPath = currentVideoFile.path; |
|||
final String _videoName = basename(_videoPath).split('.')[0]; |
|||
|
|||
String _command; |
|||
|
|||
// Formatting Date and Time |
|||
String dateTime = DateFormat.yMMMd() |
|||
.addPattern('-') |
|||
.add_Hms() |
|||
.format(DateTime.now()) |
|||
.toString(); |
|||
|
|||
// String _resultString; |
|||
String _outputPath; |
|||
String _outputFormatString; |
|||
String formattedDateTime = dateTime.replaceAll(' ', ''); |
|||
|
|||
print("DateTime: $dateTime"); |
|||
print("Formatted: $formattedDateTime"); |
|||
|
|||
if (videoFolderName == null) { |
|||
videoFolderName = "Trimmer"; |
|||
} |
|||
|
|||
if (videoFileName == null) { |
|||
videoFileName = "${_videoName}_trimmed:$formattedDateTime"; |
|||
} |
|||
|
|||
videoFileName = videoFileName.replaceAll(' ', '_'); |
|||
|
|||
String path = await _createFolderInAppDocDir( |
|||
videoFolderName, |
|||
storageDir, |
|||
).whenComplete( |
|||
() => print("Retrieved Trimmer folder"), |
|||
); |
|||
|
|||
Duration startPoint = Duration(milliseconds: startValue.toInt()); |
|||
Duration endPoint = Duration(milliseconds: endValue.toInt()); |
|||
|
|||
// Checking the start and end point strings |
|||
print("Start: ${startPoint.toString()} & End: ${endPoint.toString()}"); |
|||
|
|||
print(path); |
|||
|
|||
if (outputFormat == null) { |
|||
if (Platform.isIOS) { |
|||
outputFormat = FileFormat.mp4; |
|||
} else { |
|||
outputFormat = FileFormat.mkv; |
|||
} |
|||
_outputFormatString = outputFormat.toString(); |
|||
print('OUTPUT: $_outputFormatString'); |
|||
} else { |
|||
_outputFormatString = outputFormat.toString(); |
|||
} |
|||
|
|||
String _trimLengthCommand = |
|||
' -ss $startPoint -i "$_videoPath" -t ${endPoint - startPoint} -avoid_negative_ts make_zero '; |
|||
|
|||
if (ffmpegCommand == null) { |
|||
_command = '$_trimLengthCommand -c:a copy '; |
|||
|
|||
if (!applyVideoEncoding) { |
|||
_command += '-c:v copy '; |
|||
} |
|||
|
|||
if (outputFormat == FileFormat.gif) { |
|||
if (fpsGIF == null) { |
|||
fpsGIF = 10; |
|||
} |
|||
if (scaleGIF == null) { |
|||
scaleGIF = 480; |
|||
} |
|||
_command = |
|||
'$_trimLengthCommand -vf "fps=$fpsGIF,scale=$scaleGIF:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 '; |
|||
} |
|||
} else { |
|||
_command = '$_trimLengthCommand $ffmpegCommand '; |
|||
_outputFormatString = customVideoFormat; |
|||
} |
|||
|
|||
_outputPath = '$path$videoFileName$_outputFormatString'; |
|||
|
|||
_command += '"$_outputPath"'; |
|||
|
|||
await _flutterFFmpeg.execute(_command).whenComplete(() { |
|||
print('Got value'); |
|||
debugPrint('Video successfuly saved'); |
|||
// _resultString = 'Video successfuly saved'; |
|||
}).catchError((error) { |
|||
print('Error'); |
|||
// _resultString = 'Couldn\'t save the video'; |
|||
debugPrint('Couldn\'t save the video'); |
|||
}); |
|||
|
|||
return _outputPath; |
|||
} |
|||
|
|||
/// For getting the video controller state, to know whether the |
|||
/// video is playing or paused currently. |
|||
/// |
|||
/// The two required parameters are [startValue] & [endValue] |
|||
/// |
|||
/// * [startValue] is the current starting point of the video. |
|||
/// * [endValue] is the current ending point of the video. |
|||
/// |
|||
/// Returns a `Future<bool>`, if `true` then video is playing |
|||
/// otherwise paused. |
|||
Future<bool> videPlaybackControl({ |
|||
@required double startValue, |
|||
@required double endValue, |
|||
}) async { |
|||
if (videoPlayerController.value.isPlaying) { |
|||
await videoPlayerController.pause(); |
|||
return false; |
|||
} else { |
|||
if (videoPlayerController.value.position.inMilliseconds >= |
|||
endValue.toInt()) { |
|||
await videoPlayerController |
|||
.seekTo(Duration(milliseconds: startValue.toInt())); |
|||
await videoPlayerController.play(); |
|||
return true; |
|||
} else { |
|||
await videoPlayerController.play(); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
File getVideoFile() { |
|||
return currentVideoFile; |
|||
} |
|||
} |
@ -1,92 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/Firebase/Posts.dart'; |
|||
import 'package:teso/Classes/Uploading.dart'; |
|||
import 'package:teso/Pages/PageWidgets/Posts/user_posted.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; |
|||
import 'package:teso/Pages/PageWidgets/Uploads/Pending.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/@Generic/Camera/Video/RecordVideo.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/Pages/PageWidgets/Personal/Empty.dart'; |
|||
|
|||
class Posts extends StatefulWidget { |
|||
@override |
|||
_PostsState createState() => _PostsState(); |
|||
} |
|||
|
|||
class _PostsState extends State<Posts> { |
|||
// ScrollController _controller; |
|||
List<FBPosts> trends = <FBPosts>[]; |
|||
List<FBPosts> show = <FBPosts>[]; |
|||
int count; |
|||
Uint8List thumbnail; |
|||
SharedPreferences prefs; |
|||
bool loading = false; |
|||
|
|||
void postContent(context) async { |
|||
await Navigator.of(context).push( |
|||
PageRouteBuilder( |
|||
opaque: false, |
|||
pageBuilder: (_, __, ___) => RecordVideo(), |
|||
), |
|||
); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Consumer<UserProvider>( |
|||
builder: (context, UserProvider value, child) { |
|||
if (value.posts == null || value.posts.isEmpty) { |
|||
return buildEmpty(context, postContent); |
|||
} else { |
|||
return StaggeredGridView.count( |
|||
crossAxisCount: 3, |
|||
children: List.generate(value.posts.length, (int index) { |
|||
// if (index == 0 && provider.isNotEmpty) { |
|||
return buildPosted(context, value.posts.elementAt(index), 0.325); |
|||
// }else{ |
|||
// return buildPosted( |
|||
// context, value.posts.elementAt(index), 0.325); |
|||
// } |
|||
}), |
|||
staggeredTiles: List.generate( |
|||
value.posts.length, |
|||
(int index) { |
|||
return StaggeredTile.fit(1); |
|||
}, |
|||
), |
|||
); |
|||
} |
|||
}, |
|||
); |
|||
// : Center( |
|||
// child: CupertinoActivityIndicator( |
|||
// animating: true, |
|||
// radius: 15, |
|||
// ), |
|||
// ); |
|||
} |
|||
|
|||
Widget getTiles(BuildContext context, value) { |
|||
try { |
|||
List<Uploading> provider = value.getPending(); |
|||
if (value.pending != null) { |
|||
return SizedBox( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: ListView( |
|||
children: provider |
|||
.map<Widget>((item) => uploadTile(context, item)) |
|||
.toList()), |
|||
); |
|||
} else { |
|||
return Container(); |
|||
} |
|||
} catch (e) { |
|||
return Container(); |
|||
} |
|||
} |
|||
} |
@ -1,196 +0,0 @@ |
|||
import 'dart:convert'; |
|||
import 'dart:typed_data'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/Uploading.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:http/http.dart' as http; |
|||
|
|||
class CreatePost extends StatefulWidget { |
|||
final String video; |
|||
final Uint8List thumbnail; |
|||
final String aspectRatio; |
|||
const CreatePost({ |
|||
Key key, |
|||
this.video, |
|||
this.thumbnail, |
|||
this.aspectRatio, |
|||
}) : super(key: key); |
|||
@override |
|||
_CreatePostState createState() => _CreatePostState(); |
|||
} |
|||
|
|||
class _CreatePostState extends State<CreatePost> { |
|||
String aspectRatio; |
|||
TextEditingController controller; |
|||
SharedPreferences prefs; |
|||
bool sending = false; |
|||
|
|||
void postVideo(context) async { |
|||
setState(() { |
|||
sending = true; |
|||
}); |
|||
try { |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
String token = prefs.getString("tokensTeso"); |
|||
Map<String, String> requestHeaders = {'Authorization': token}; |
|||
String urlLocation = tesoStreaming + "api/mobile/upload/authurl"; |
|||
var client = |
|||
await http.get(Uri.parse(urlLocation), headers: requestHeaders); |
|||
if (client.statusCode == 200) { |
|||
var details = jsonDecode(client.body); |
|||
String muxuploadsID = details["data"]["id"]; |
|||
String muxuploadsURL = details["data"]["url"]; |
|||
|
|||
Provider.of<UserProvider>(context, listen: false).uploadPost(Uploading( |
|||
id: DateTime.now().toString() + |
|||
widget.video.replaceAll("file://", ""), |
|||
aspect: widget.aspectRatio, |
|||
path: widget.video.replaceAll("file://", ""), |
|||
thumbnail: |
|||
widget.thumbnail != null ? base64Encode(widget.thumbnail) : null, |
|||
title: controller.text.isNotEmpty ? controller.text : "", |
|||
pending: 0, |
|||
muxuploadID: muxuploadsID, |
|||
muxuploadURL: muxuploadsURL, |
|||
)); |
|||
Navigator.pop(context); |
|||
} |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
setState(() { |
|||
sending = false; |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
controller = new TextEditingController(); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
super.dispose(); |
|||
controller.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: PreferredSize( |
|||
child: AppBar( |
|||
automaticallyImplyLeading: true, |
|||
title: Text("Post"), |
|||
centerTitle: true, |
|||
), |
|||
preferredSize: Size.fromHeight(70.0), |
|||
), |
|||
body: SingleChildScrollView( |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
padding: EdgeInsets.all(MediaQuery.of(context).size.width * 0.025), |
|||
child: Column( |
|||
children: [ |
|||
Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
Container( |
|||
width: MediaQuery.of(context).size.width * 0.25, |
|||
height: MediaQuery.of(context).size.width * 0.35, |
|||
color: Colors.black, |
|||
child: widget.thumbnail != null |
|||
? Image.memory(widget.thumbnail) |
|||
: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
Container( |
|||
width: (MediaQuery.of(context).size.width) - |
|||
(MediaQuery.of(context).size.width * 0.35), |
|||
height: MediaQuery.of(context).size.width * 0.35, |
|||
child: TextField( |
|||
decoration: InputDecoration( |
|||
border: OutlineInputBorder( |
|||
borderSide: BorderSide.none, |
|||
), |
|||
filled: true, |
|||
isDense: true, |
|||
labelText: "Say Something..", |
|||
labelStyle: TextStyle( |
|||
color: Colors.black54, |
|||
), |
|||
fillColor: Colors.white70, |
|||
), |
|||
controller: controller, |
|||
maxLines: null, |
|||
keyboardType: TextInputType.text, |
|||
textInputAction: TextInputAction.done, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Divider(), |
|||
Container( |
|||
margin: EdgeInsets.only( |
|||
top: 10, |
|||
), |
|||
child: Text( |
|||
"Teso businesses and other Teso users can see your post in their feeds and on your profile.", |
|||
textAlign: TextAlign.center, |
|||
style: TextStyle( |
|||
color: Colors.grey, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
floatingActionButton: !sending |
|||
? Container( |
|||
margin: EdgeInsets.all(20), |
|||
width: MediaQuery.of(context).size.width, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(15.0), |
|||
color: tesoBlue, |
|||
), |
|||
child: InkWell( |
|||
onTap: () => postVideo(context), |
|||
child: Center( |
|||
child: Text( |
|||
"NEXT", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
height: 50, |
|||
) |
|||
: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: 50, |
|||
child: Center( |
|||
child: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
), |
|||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, |
|||
); |
|||
} |
|||
} |
@ -1,585 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:cloud_firestore/cloud_firestore.dart'; |
|||
import 'package:flare_flutter/flare_actor.dart'; |
|||
import 'package:flare_flutter/flare_controls.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:flutter_bloc/flutter_bloc.dart'; |
|||
|
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:page_transition/page_transition.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/CouponDetails.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/PostFav.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/comment.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/userProfile3P.dart'; |
|||
import 'package:teso/Services/video_controller_service.dart'; |
|||
import 'package:teso/blocs/video_player/video_player_bloc.dart'; |
|||
import 'package:teso/blocs/video_player/video_player_event.dart'; |
|||
import 'package:teso/blocs/video_player/video_player_state.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/SizeConfig.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:numeral/numeral.dart'; |
|||
import 'package:teso/GeneralWidgets/widgets/video_player_widget.dart'; |
|||
import 'package:http/http.dart' as http; |
|||
import 'dart:convert'; |
|||
|
|||
// ignore: must_be_immutable |
|||
class ViewPost extends StatefulWidget { |
|||
Post postedAd; |
|||
TesoUser user; |
|||
bool friend; |
|||
final bool play; |
|||
|
|||
ViewPost({ |
|||
Key key, |
|||
this.postedAd, |
|||
this.user, |
|||
this.friend, |
|||
@required this.play, |
|||
// this.posts, |
|||
}) : super(key: key); |
|||
@override |
|||
_ViewPostState createState() => _ViewPostState(); |
|||
} |
|||
|
|||
class _ViewPostState extends State<ViewPost> { |
|||
bool favoured = false; |
|||
List<CouponDetails> coupons = <CouponDetails>[]; |
|||
Uint8List imageBitmap; |
|||
final FlareControls flareControls = FlareControls(); |
|||
bool campaignAd = false; |
|||
int likes = 0; |
|||
int comments = 0; |
|||
var userDoc; |
|||
var document; |
|||
|
|||
void sharing(Post ad) async { |
|||
await rootBundle |
|||
.load("assets/images/rawLogoOverlay.png") |
|||
.then((value) => setState(() { |
|||
imageBitmap = value.buffer.asUint8List(); |
|||
})); |
|||
Provider.of<UserProvider>(context, listen: false).downloadVideo( |
|||
ad.postID, ad.playbackID, ad.rendition, imageBitmap, context); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
rootBundle.evict("assets/images/rawLogoOverlay.png"); |
|||
super.dispose(); |
|||
} |
|||
|
|||
void likePost(Post ad) { |
|||
SharedPreferences.getInstance().then((value) { |
|||
String cid = value.getString("id"); |
|||
PostFav liked = new PostFav(); |
|||
liked.admirerId = cid; |
|||
liked.countId = DateTime.now().toString() + "$cid"; |
|||
liked.timestamp = DateTime.now().toIso8601String(); |
|||
liked.postId = ad.postID; |
|||
|
|||
setState(() { |
|||
// ad.likes.add(liked); |
|||
likes++; |
|||
favoured = true; |
|||
}); |
|||
|
|||
flareControls.play("like"); |
|||
Provider.of<UserProvider>(context, listen: false).addLike(liked); |
|||
}); |
|||
} |
|||
|
|||
void dislikePost(Post ad) { |
|||
setState(() { |
|||
favoured = false; |
|||
likes--; |
|||
}); |
|||
Provider.of<UserProvider>(context, listen: false).deleteLike(ad.postID); |
|||
} |
|||
|
|||
void commentsDialog(BuildContext context) { |
|||
if (userDoc == null) { |
|||
FirebaseFirestore.instance |
|||
.collection("users") |
|||
.doc(widget.postedAd.publisherID) |
|||
.get() |
|||
.then((value) { |
|||
setState(() { |
|||
userDoc = value.data(); |
|||
}); |
|||
}); |
|||
} else { |
|||
showModalBottomSheet( |
|||
context: context, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), |
|||
), |
|||
builder: (BuildContext bc) { |
|||
return ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(20.0), |
|||
topRight: Radius.circular(20.0), |
|||
), |
|||
child: CommentSection( |
|||
postedAd: widget.postedAd, |
|||
user: TesoUser( |
|||
username: userDoc["username"], |
|||
userGUID: userDoc["id"], |
|||
)), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
|||
|
|||
Future<void> getCampaignCoupons(String campaign) async { |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
Map<String, String> requestHeaders = { |
|||
'Content-type': 'application/json', |
|||
'Authorization': prefs.getString('tokensTeso') |
|||
}; |
|||
try { |
|||
var register2 = serverLocation + 'coupons/campaign_coupons'; |
|||
var client1 = await http.post( |
|||
Uri.parse(register2), |
|||
headers: requestHeaders, |
|||
body: json.encode(campaign), |
|||
); |
|||
if (client1.statusCode == 200) { |
|||
var details = jsonDecode(client1.body); |
|||
setState(() { |
|||
coupons = List<CouponDetails>.from( |
|||
details.map((model) => CouponDetails.fromJSON(model)).toList()); |
|||
// coupons.removeWhere( |
|||
// (element) => element.expiration.isAfter(DateTime.now())); |
|||
}); |
|||
} |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
campaignAd = false; |
|||
_getDocuments(); |
|||
_likedListen(); |
|||
_commentsListen(); |
|||
FirebaseFirestore.instance |
|||
.collection("users") |
|||
.doc(widget.postedAd.publisherID) |
|||
.get() |
|||
.then((value) { |
|||
if (mounted) |
|||
setState(() { |
|||
userDoc = value.data(); |
|||
}); |
|||
}); |
|||
super.initState(); |
|||
} |
|||
|
|||
_getDocuments() { |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.get() |
|||
.then((value) { |
|||
if (mounted) |
|||
setState(() { |
|||
document = value.data(); |
|||
if (document != null) { |
|||
if (document["campaignId"] != null) { |
|||
campaignAd = true; |
|||
getCampaignCoupons(document["campaignId"]); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
_likedListen() { |
|||
SharedPreferences.getInstance().then((value) { |
|||
String cid = value.getString("id"); |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.collection("likes") |
|||
.snapshots() |
|||
.listen((event) { |
|||
if (mounted) { |
|||
setState(() { |
|||
favoured = |
|||
event.docs.any((element) => element.data()["admirerID"] == cid); |
|||
likes = event.docs.length; |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
_commentsListen() { |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.collection("comments") |
|||
.snapshots() |
|||
.listen((event) { |
|||
if (mounted) { |
|||
setState(() { |
|||
comments = event.docs.length; |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
SizeConfig().init(context); |
|||
return Scaffold( |
|||
body: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
color: Colors.black, |
|||
child: Stack( |
|||
children: [ |
|||
_buildVideoPlayer(widget.postedAd), |
|||
Align( |
|||
alignment: Alignment.bottomRight, |
|||
child: Container( |
|||
margin: EdgeInsets.only( |
|||
right: 10, |
|||
bottom: 30, |
|||
), |
|||
width: 50, |
|||
height: MediaQuery.of(context).size.width * 0.73, |
|||
child: Column( |
|||
children: [ |
|||
Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: InkWell( |
|||
onTap: widget.postedAd.publisherID != null |
|||
? () => Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
child: UserProfileThirdPerson( |
|||
user: new TesoUser( |
|||
username: userDoc["username"], |
|||
userGUID: userDoc["id"], |
|||
firstname: userDoc["firstname"], |
|||
lastname: userDoc["surname"], |
|||
), |
|||
), |
|||
type: PageTransitionType.fade, |
|||
), |
|||
) |
|||
: null, |
|||
child: CachedNetworkImage( |
|||
imageUrl: serverLocation + |
|||
"api/pulldp/" + |
|||
widget.postedAd.publisherID, |
|||
imageBuilder: (context, imageProvider) => |
|||
FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: imageProvider, |
|||
placeholder: |
|||
AssetImage("assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Container( |
|||
height: 50, |
|||
child: InkWell( |
|||
onTap: () { |
|||
if (campaignAd) { |
|||
if (favoured) { |
|||
return null; |
|||
} else { |
|||
likePost(widget.postedAd); |
|||
} |
|||
} else { |
|||
if (favoured) { |
|||
dislikePost(widget.postedAd); |
|||
} else { |
|||
likePost(widget.postedAd); |
|||
} |
|||
} |
|||
}, |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Container( |
|||
width: 50, |
|||
height: 30, |
|||
child: Center( |
|||
child: Icon( |
|||
Icons.favorite, |
|||
size: 30, |
|||
color: favoured ? Colors.red : Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
height: 20, |
|||
width: 50, |
|||
child: Center( |
|||
child: Text( |
|||
Numeral(likes).value().toString(), |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Container( |
|||
height: 50, |
|||
child: InkWell( |
|||
onTap: () => commentsDialog(context), |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Container( |
|||
width: 50, |
|||
height: 30, |
|||
child: Center( |
|||
child: Icon( |
|||
Icons.comment, |
|||
size: 30, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
height: 20, |
|||
width: 50, |
|||
child: Center( |
|||
child: Text( |
|||
Numeral(comments).value().toString(), |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Container( |
|||
height: 30, |
|||
child: InkWell( |
|||
onTap: () => sharing(widget.postedAd), |
|||
child: Icon( |
|||
Icons.share, |
|||
size: 30, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
Align( |
|||
alignment: Alignment.bottomLeft, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: 10, |
|||
vertical: MediaQuery.of(context).size.height * 0.05, |
|||
), |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
new RichText( |
|||
maxLines: 5, |
|||
text: TextSpan( |
|||
text: userDoc != null ? "@" + userDoc["username"] : "", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.3, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
), |
|||
SizedBox(height: 5), |
|||
Container( |
|||
margin: EdgeInsets.only(bottom: 20), |
|||
width: MediaQuery.of(context).size.width * 0.7, |
|||
child: Text( |
|||
document != null |
|||
? document["title"] != null |
|||
? document["title"] |
|||
: "" |
|||
: "", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.3, |
|||
height: 1.5, |
|||
), |
|||
maxLines: 4, |
|||
overflow: TextOverflow.ellipsis, |
|||
textDirection: TextDirection.rtl, |
|||
textAlign: TextAlign.left, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
Align( |
|||
alignment: Alignment.topLeft, |
|||
child: Container( |
|||
margin: EdgeInsets.only(top: 25), |
|||
child: IconButton( |
|||
onPressed: () => Navigator.of(context).pop(), |
|||
icon: new Icon( |
|||
Icons.arrow_back, |
|||
color: Colors.white, |
|||
size: 25.0, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _buildVideoPlayer(Post ad) { |
|||
return BlocProvider<VideoPlayerBloc>( |
|||
create: (context) => VideoPlayerBloc( |
|||
RepositoryProvider.of<VideoControllerService>(context)) |
|||
..add(VideoSelectedEvent(ad)), |
|||
child: BlocBuilder<VideoPlayerBloc, VideoPlayerState>( |
|||
builder: (context, state) { |
|||
return Container(child: _getPlayer(context, state, ad)); |
|||
}, |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _getPlayer(BuildContext context, VideoPlayerState state, Post ad) { |
|||
// final screenWidth = MediaQuery.of(context).size.width; |
|||
// final containerHeight = screenWidth / ASPECT_RATIO; |
|||
final containerHeight = MediaQuery.of(context).size.height; |
|||
if (state is VideoPlayerStateLoaded) { |
|||
return GestureDetector( |
|||
onDoubleTap: () { |
|||
if (campaignAd) { |
|||
if (favoured) { |
|||
return null; |
|||
} else { |
|||
likePost(ad); |
|||
} |
|||
} else { |
|||
if (favoured) { |
|||
dislikePost(ad); |
|||
} else { |
|||
likePost(ad); |
|||
} |
|||
} |
|||
}, |
|||
child: Stack( |
|||
children: [ |
|||
VideoPlayerWidget( |
|||
key: Key(state.video.playbackID), |
|||
controller: state.controller, |
|||
ad: ad, |
|||
play: widget.play, |
|||
details: coupons, |
|||
), |
|||
Container( |
|||
width: double.infinity, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: Center( |
|||
child: SizedBox( |
|||
width: 80, |
|||
height: 80, |
|||
child: FlareActor( |
|||
'assets/like.flr', |
|||
controller: flareControls, |
|||
animation: 'idle', |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
if (state is VideoPlayerStateLoading) { |
|||
return Container( |
|||
height: containerHeight, |
|||
color: Colors.black, |
|||
child: Center( |
|||
child: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
if (state is VideoPlayerStateError) { |
|||
return Container( |
|||
height: containerHeight, |
|||
color: Colors.black, |
|||
child: Center( |
|||
child: Text(state.message), |
|||
), |
|||
); |
|||
} |
|||
|
|||
return Container( |
|||
height: containerHeight, |
|||
color: Colors.black, |
|||
child: Center( |
|||
child: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,515 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:cloud_firestore/cloud_firestore.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:flutter_bloc/flutter_bloc.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/PostFav.dart'; |
|||
import 'package:teso/Classes/Firebase/Posts.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/deletePost.dart'; |
|||
import 'package:teso/Services/uservideo_controller_service.dart'; |
|||
import 'package:teso/blocs/video_player/uservideo_player_bloc.dart'; |
|||
import 'package:teso/blocs/video_player/uservideo_player_event.dart'; |
|||
import 'package:teso/blocs/video_player/uservideo_player_state.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/SizeConfig.dart'; |
|||
import 'package:numeral/numeral.dart'; |
|||
import 'package:teso/GeneralWidgets/widgets/uservideo_player_widget.dart'; |
|||
|
|||
import 'comment.dart'; |
|||
|
|||
// ignore: must_be_immutable |
|||
class UserPosts extends StatefulWidget { |
|||
FBPosts postedAd; |
|||
|
|||
UserPosts({Key key, this.postedAd}) : super(key: key); |
|||
@override |
|||
_UserPostsState createState() => _UserPostsState(); |
|||
} |
|||
|
|||
class _UserPostsState extends State<UserPosts> { |
|||
bool favoured = false; |
|||
Uint8List imageBitmap; |
|||
var document; |
|||
var userDoc; |
|||
bool likeShow = false; |
|||
|
|||
void sharing() async { |
|||
await rootBundle |
|||
.load("assets/images/rawLogoOverlay.png") |
|||
.then((value) => setState(() { |
|||
imageBitmap = value.buffer.asUint8List(); |
|||
})); |
|||
Provider.of<UserProvider>(context, listen: false).downloadVideo( |
|||
widget.postedAd.postID, |
|||
widget.postedAd.playbackID, |
|||
widget.postedAd.rendition, |
|||
imageBitmap, |
|||
context); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
rootBundle.evict("assets/images/rawLogoOverlay.png"); |
|||
super.dispose(); |
|||
} |
|||
|
|||
void commentsDialog(BuildContext context) { |
|||
if (userDoc == null) { |
|||
FirebaseFirestore.instance |
|||
.collection("users") |
|||
.doc(widget.postedAd.publisherID) |
|||
.get() |
|||
.then((value) { |
|||
setState(() { |
|||
userDoc = value.data(); |
|||
}); |
|||
}); |
|||
} else { |
|||
showModalBottomSheet( |
|||
context: context, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), |
|||
), |
|||
builder: (BuildContext bc) { |
|||
return ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(20.0), |
|||
topRight: Radius.circular(20.0), |
|||
), |
|||
child: CommentSection( |
|||
postedAd: new Post( |
|||
aspect: widget.postedAd.aspect, |
|||
assetID: widget.postedAd.assetID, |
|||
playbackID: widget.postedAd.playbackID, |
|||
postID: widget.postedAd.postID, |
|||
publisherID: widget.postedAd.publisherID, |
|||
title: widget.postedAd.title, |
|||
rendition: widget.postedAd.rendition, |
|||
timestamp: widget.postedAd.timestamp, |
|||
), |
|||
user: TesoUser( |
|||
username: userDoc["username"], |
|||
userGUID: userDoc["id"], |
|||
)), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
|||
|
|||
void likePost() { |
|||
setState(() { |
|||
likeShow = true; |
|||
}); |
|||
SharedPreferences.getInstance().then((value) { |
|||
String cid = value.getString("id"); |
|||
PostFav liked = new PostFav(); |
|||
liked.admirerId = cid; |
|||
liked.countId = DateTime.now().toString() + "$cid"; |
|||
liked.timestamp = DateTime.now().toIso8601String(); |
|||
liked.postId = widget.postedAd.postID; |
|||
|
|||
setState(() { |
|||
widget.postedAd.likes++; |
|||
favoured = true; |
|||
}); |
|||
Provider.of<UserProvider>(context, listen: false).addLike(liked); |
|||
}); |
|||
} |
|||
|
|||
void dislikePost() { |
|||
SharedPreferences.getInstance().then((value) { |
|||
// String cid = value.getString("id"); |
|||
setState(() { |
|||
widget.postedAd.likes--; |
|||
favoured = false; |
|||
}); |
|||
Provider.of<UserProvider>(context, listen: false) |
|||
.deleteLike(widget.postedAd.postID); |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_likedListen(); |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.get() |
|||
.then((value) { |
|||
setState(() { |
|||
document = value.data(); |
|||
}); |
|||
}); |
|||
FirebaseFirestore.instance |
|||
.collection("users") |
|||
.doc(widget.postedAd.publisherID) |
|||
.get() |
|||
.then((value) { |
|||
setState(() { |
|||
userDoc = value.data(); |
|||
}); |
|||
}); |
|||
// SharedPreferences.getInstance().then((value) { |
|||
// String cid = value.getString("id"); |
|||
// if (widget.postedAd.likes |
|||
// .map((e) => e.admirerId) |
|||
// .toList() |
|||
// .contains(cid)) { |
|||
// setState(() { |
|||
// favoured = true; |
|||
// }); |
|||
// } |
|||
// }); |
|||
// _future = initializeController(); |
|||
} |
|||
|
|||
_likedListen() { |
|||
SharedPreferences.getInstance().then((value) { |
|||
String cid = value.getString("id"); |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.collection("likes") |
|||
.snapshots() |
|||
.listen((event) { |
|||
setState(() { |
|||
favoured = |
|||
event.docs.any((element) => element.data()["admirerID"] == cid); |
|||
widget.postedAd.likes = event.docs.length; |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: PreferredSize( |
|||
child: AppBar( |
|||
automaticallyImplyLeading: false, |
|||
backgroundColor: Colors.transparent, |
|||
leading: _backWidget(context), |
|||
), |
|||
preferredSize: Size.fromHeight(40)), |
|||
extendBodyBehindAppBar: true, |
|||
body: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
color: Colors.black, |
|||
child: Stack( |
|||
children: [ |
|||
_buildVideoPlayer(), |
|||
Align( |
|||
alignment: Alignment.bottomRight, |
|||
child: Container( |
|||
margin: EdgeInsets.only( |
|||
right: 10, |
|||
), |
|||
width: 50, |
|||
height: MediaQuery.of(context).size.width * 0.7, |
|||
child: Column( |
|||
children: [ |
|||
_favoriteWidget(context), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
_commentWidget(context), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Container( |
|||
height: 30, |
|||
child: InkWell( |
|||
onTap: () => sharing(), |
|||
child: Icon( |
|||
Icons.share, |
|||
size: 30, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
_deleteWidget(context), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
_nameDescription(context), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _buildVideoPlayer() { |
|||
return BlocProvider<VideoPlayerBloc>( |
|||
create: (context) => VideoPlayerBloc( |
|||
RepositoryProvider.of<VideoControllerService>(context)) |
|||
..add(VideoSelectedEvent(widget.postedAd)), |
|||
child: BlocBuilder<VideoPlayerBloc, VideoPlayerState>( |
|||
builder: (context, state) { |
|||
return Container(child: _getPlayer(context, state)); |
|||
}, |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _getPlayer(BuildContext context, VideoPlayerState state) { |
|||
// final screenWidth = MediaQuery.of(context).size.width; |
|||
// final containerHeight = screenWidth / ASPECT_RATIO; |
|||
if (state is VideoPlayerStateLoaded) { |
|||
return GestureDetector( |
|||
onDoubleTap: () { |
|||
if (widget.postedAd.campaignID != null) { |
|||
if (favoured) { |
|||
return null; |
|||
} else { |
|||
likePost(); |
|||
} |
|||
} else { |
|||
if (favoured) { |
|||
dislikePost(); |
|||
} else { |
|||
likePost(); |
|||
} |
|||
} |
|||
}, |
|||
child: Stack( |
|||
children: [ |
|||
VideoPlayerWidget( |
|||
key: Key(state.video.playbackID), |
|||
controller: state.controller, |
|||
ad: widget.postedAd, |
|||
), |
|||
AnimatedOpacity( |
|||
opacity: likeShow ? 1 : 0, |
|||
duration: Duration(seconds: 2), |
|||
onEnd: () { |
|||
setState(() { |
|||
likeShow = false; |
|||
}); |
|||
}, |
|||
child: Container( |
|||
width: double.infinity, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: Center( |
|||
child: Image.asset("assets/lovw.gif"), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
if (state is VideoPlayerStateLoading) { |
|||
return Container(); |
|||
} |
|||
|
|||
if (state is VideoPlayerStateError) { |
|||
return Container(); |
|||
} |
|||
|
|||
return Container(); |
|||
} |
|||
|
|||
Widget _favoriteWidget(BuildContext context) { |
|||
return Container( |
|||
height: 50, |
|||
child: InkWell( |
|||
onTap: () { |
|||
if (widget.postedAd.campaignID != null) { |
|||
if (favoured) { |
|||
return null; |
|||
} else { |
|||
likePost(); |
|||
} |
|||
} else { |
|||
if (favoured) { |
|||
dislikePost(); |
|||
} else { |
|||
likePost(); |
|||
} |
|||
} |
|||
}, |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Container( |
|||
width: 50, |
|||
height: 30, |
|||
child: Center( |
|||
child: Icon( |
|||
Icons.favorite, |
|||
size: 30, |
|||
color: favoured ? Colors.red : Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
height: 20, |
|||
width: 50, |
|||
child: Center( |
|||
child: Text( |
|||
Numeral(widget.postedAd.likes).value().toString(), |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _commentWidget(BuildContext context) { |
|||
return Container( |
|||
height: 50, |
|||
child: InkWell( |
|||
onTap: () => commentsDialog(context), |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Container( |
|||
width: 50, |
|||
height: 30, |
|||
child: Center( |
|||
child: Icon( |
|||
Icons.comment, |
|||
size: 30, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
height: 20, |
|||
width: 50, |
|||
child: Center( |
|||
child: Text( |
|||
Numeral(widget.postedAd.comments).value().toString(), |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _nameDescription(BuildContext context) { |
|||
return Align( |
|||
alignment: Alignment.bottomLeft, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: 10, |
|||
vertical: MediaQuery.of(context).size.height * 0.05, |
|||
), |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
new RichText( |
|||
maxLines: 5, |
|||
text: TextSpan( |
|||
text: userDoc != null |
|||
? userDoc["username"] != null |
|||
? "@" + userDoc["username"] |
|||
: "" |
|||
: "", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.3, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
), |
|||
SizedBox(height: 5), |
|||
Container( |
|||
margin: EdgeInsets.only(bottom: 20), |
|||
width: MediaQuery.of(context).size.width * 0.7, |
|||
child: Text( |
|||
widget.postedAd.title != null ? widget.postedAd.title : "", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.3, |
|||
height: 1.5, |
|||
), |
|||
maxLines: 4, |
|||
overflow: TextOverflow.ellipsis, |
|||
textDirection: TextDirection.rtl, |
|||
textAlign: TextAlign.left, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _deleteWidget(BuildContext context) { |
|||
return Container( |
|||
height: 30, |
|||
child: GestureDetector( |
|||
onTap: () async { |
|||
bool result = await Navigator.push( |
|||
context, |
|||
PageRouteBuilder( |
|||
opaque: false, |
|||
pageBuilder: (_, __, ___) => DeletePost(), |
|||
), |
|||
); |
|||
if (result) { |
|||
Provider.of<UserProvider>(context, listen: false).deletePost(Post( |
|||
aspect: widget.postedAd.aspect, |
|||
playbackID: widget.postedAd.playbackID, |
|||
postID: widget.postedAd.postID, |
|||
publisherID: widget.postedAd.publisherID, |
|||
assetID: widget.postedAd.assetID, |
|||
timestamp: widget.postedAd.timestamp, |
|||
title: widget.postedAd.title, |
|||
)); |
|||
Navigator.pop(context); |
|||
} |
|||
}, |
|||
child: Icon( |
|||
Icons.delete, |
|||
size: 28, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _backWidget(BuildContext context) { |
|||
return GestureDetector( |
|||
onTap: () { |
|||
Navigator.pop(context); |
|||
}, |
|||
child: Container( |
|||
height: 40, |
|||
width: 40, |
|||
decoration: BoxDecoration( |
|||
color: Colors.transparent, |
|||
), |
|||
child: Icon( |
|||
Icons.arrow_back_ios, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,920 +0,0 @@ |
|||
import 'dart:typed_data'; |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:cloud_firestore/cloud_firestore.dart'; |
|||
import 'package:flare_flutter/flare_controls.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:flutter_bloc/flutter_bloc.dart'; |
|||
|
|||
import 'package:page_transition/page_transition.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/CouponDetails.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/PostFav.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/comment.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/userProfile3P.dart'; |
|||
import 'package:teso/Services/video_controller_service.dart'; |
|||
import 'package:teso/blocs/video_player/video_player_bloc.dart'; |
|||
import 'package:teso/blocs/video_player/video_player_event.dart'; |
|||
import 'package:teso/blocs/video_player/video_player_state.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/SizeConfig.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:numeral/numeral.dart'; |
|||
import 'package:teso/GeneralWidgets/widgets/video_player_widget.dart'; |
|||
import 'package:http/http.dart' as http; |
|||
import 'dart:convert'; |
|||
|
|||
// ignore: must_be_immutable |
|||
class ViewPost extends StatefulWidget { |
|||
Post postedAd; |
|||
TesoUser user; |
|||
bool friend; |
|||
final bool play; |
|||
Function report; |
|||
|
|||
ViewPost({ |
|||
Key key, |
|||
this.postedAd, |
|||
this.user, |
|||
this.friend, |
|||
@required this.play, |
|||
@required this.report, |
|||
// this.posts, |
|||
}) : super(key: key); |
|||
@override |
|||
_ViewPostState createState() => _ViewPostState(); |
|||
} |
|||
|
|||
class _ViewPostState extends State<ViewPost> { |
|||
bool favoured = false; |
|||
List<CouponDetails> coupons = <CouponDetails>[]; |
|||
Uint8List imageBitmap; |
|||
final FlareControls flareControls = FlareControls(); |
|||
bool campaignAd = false; |
|||
int likes = 0; |
|||
int comments = 0; |
|||
var userDoc; |
|||
var document; |
|||
bool likeShow = false; |
|||
bool dark = false; |
|||
|
|||
void sharing(Post ad) async { |
|||
await rootBundle |
|||
.load("assets/images/rawLogoOverlay.png") |
|||
.then((value) => setState(() { |
|||
imageBitmap = value.buffer.asUint8List(); |
|||
})); |
|||
Provider.of<UserProvider>(context, listen: false).downloadVideo( |
|||
ad.postID, ad.playbackID, ad.rendition, imageBitmap, context); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
rootBundle.evict("assets/images/rawLogoOverlay.png"); |
|||
super.dispose(); |
|||
} |
|||
|
|||
void likePost(Post ad) { |
|||
setState(() { |
|||
likeShow = true; |
|||
}); |
|||
SharedPreferences.getInstance().then((value) { |
|||
String cid = value.getString("id"); |
|||
PostFav liked = new PostFav(); |
|||
liked.admirerId = cid; |
|||
liked.countId = DateTime.now().toString() + "$cid"; |
|||
liked.timestamp = DateTime.now().toIso8601String(); |
|||
liked.postId = ad.postID; |
|||
|
|||
setState(() { |
|||
// ad.likes.add(liked); |
|||
likes++; |
|||
favoured = true; |
|||
}); |
|||
|
|||
flareControls.play("like"); |
|||
Provider.of<UserProvider>(context, listen: false).addLike(liked); |
|||
}); |
|||
} |
|||
|
|||
void dislikePost(Post ad) { |
|||
setState(() { |
|||
favoured = false; |
|||
likes--; |
|||
}); |
|||
Provider.of<UserProvider>(context, listen: false).deleteLike(ad.postID); |
|||
} |
|||
|
|||
void commentsDialog(BuildContext context) { |
|||
if (userDoc == null) { |
|||
FirebaseFirestore.instance |
|||
.collection("users") |
|||
.doc(widget.postedAd.publisherID) |
|||
.get() |
|||
.then((value) { |
|||
setState(() { |
|||
userDoc = value.data(); |
|||
}); |
|||
}); |
|||
} else { |
|||
showModalBottomSheet( |
|||
context: context, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), |
|||
), |
|||
builder: (BuildContext bc) { |
|||
return ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(20.0), |
|||
topRight: Radius.circular(20.0), |
|||
), |
|||
child: CommentSection( |
|||
postedAd: widget.postedAd, |
|||
user: TesoUser( |
|||
username: userDoc["username"], |
|||
userGUID: userDoc["id"], |
|||
)), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
|||
|
|||
Future<void> getCampaignCoupons(String campaign) async { |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
Map<String, String> requestHeaders = { |
|||
'Content-type': 'application/json', |
|||
'Authorization': prefs.getString('tokensTeso') |
|||
}; |
|||
try { |
|||
var register2 = serverLocation + 'coupons/campaign_coupons'; |
|||
var client1 = await http.post( |
|||
Uri.parse(register2), |
|||
headers: requestHeaders, |
|||
body: json.encode(campaign), |
|||
); |
|||
if (client1.statusCode == 200) { |
|||
var details = jsonDecode(client1.body); |
|||
setState(() { |
|||
coupons = List<CouponDetails>.from( |
|||
details.map((model) => CouponDetails.fromJSON(model)).toList()); |
|||
// coupons.removeWhere( |
|||
// (element) => element.expiration.isAfter(DateTime.now())); |
|||
}); |
|||
} |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
campaignAd = false; |
|||
_getDocuments(); |
|||
_likedListen(); |
|||
_commentsListen(); |
|||
FirebaseFirestore.instance |
|||
.collection("users") |
|||
.doc(widget.postedAd.publisherID) |
|||
.get() |
|||
.then((value) { |
|||
if (mounted) |
|||
setState(() { |
|||
userDoc = value.data(); |
|||
}); |
|||
}); |
|||
SharedPreferences.getInstance().then((value) => |
|||
value.getString("theme") == "light" ? dark = false : dark = true); |
|||
super.initState(); |
|||
} |
|||
|
|||
_getDocuments() { |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.get() |
|||
.then((value) { |
|||
if (mounted) |
|||
setState(() { |
|||
document = value.data(); |
|||
if (document != null) { |
|||
if (document["campaignId"] != null) { |
|||
campaignAd = true; |
|||
getCampaignCoupons(document["campaignId"]); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
_likedListen() { |
|||
SharedPreferences.getInstance().then((value) { |
|||
String cid = value.getString("id"); |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.collection("likes") |
|||
.snapshots() |
|||
.listen((event) { |
|||
if (mounted) { |
|||
setState(() { |
|||
favoured = |
|||
event.docs.any((element) => element.data()["admirerID"] == cid); |
|||
likes = event.docs.length; |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
_commentsListen() { |
|||
FirebaseFirestore.instance |
|||
.collection("posts") |
|||
.doc(widget.postedAd.postID) |
|||
.collection("comments") |
|||
.snapshots() |
|||
.listen((event) { |
|||
if (mounted) { |
|||
setState(() { |
|||
comments = event.docs.length; |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
SizeConfig().init(context); |
|||
return Scaffold( |
|||
body: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: MediaQuery.of(context).size.height, |
|||
color: Colors.black, |
|||
child: Stack( |
|||
children: [ |
|||
_buildVideoPlayer(widget.postedAd), |
|||
Align( |
|||
alignment: Alignment.bottomRight, |
|||
child: Container( |
|||
margin: EdgeInsets.only( |
|||
right: 10, |
|||
bottom: 30, |
|||
), |
|||
width: 50, |
|||
height: MediaQuery.of(context).size.width * 0.73, |
|||
child: Column( |
|||
children: [ |
|||
_publisherWidget(context), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
_favoriteWidget(context), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
_commentWidget(context), |
|||
SizedBox( |
|||
height: 20, |
|||
), |
|||
Container( |
|||
height: 30, |
|||
child: InkWell( |
|||
onTap: () => moreDialog(context, widget.postedAd), |
|||
child: Icon( |
|||
Icons.more_horiz, |
|||
size: 30, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
_nameDescription(context), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _buildVideoPlayer(Post ad) { |
|||
return BlocProvider<VideoPlayerBloc>( |
|||
create: (context) => VideoPlayerBloc( |
|||
RepositoryProvider.of<VideoControllerService>(context)) |
|||
..add(VideoSelectedEvent(ad)), |
|||
child: BlocBuilder<VideoPlayerBloc, VideoPlayerState>( |
|||
builder: (context, state) { |
|||
return Container(child: _getPlayer(context, state, ad)); |
|||
}, |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _getPlayer(BuildContext context, VideoPlayerState state, Post ad) { |
|||
// final screenWidth = MediaQuery.of(context).size.width; |
|||
// final containerHeight = screenWidth / ASPECT_RATIO; |
|||
final containerHeight = MediaQuery.of(context).size.height; |
|||
if (state is VideoPlayerStateLoaded) { |
|||
return GestureDetector( |
|||
onDoubleTap: () { |
|||
if (campaignAd) { |
|||
if (favoured) { |
|||
return null; |
|||
} else { |
|||
likePost(ad); |
|||
} |
|||
} else { |
|||
if (favoured) { |
|||
dislikePost(ad); |
|||
} else { |
|||
likePost(ad); |
|||
} |
|||
} |
|||
}, |
|||
child: Stack( |
|||
children: [ |
|||
VideoPlayerWidget( |
|||
key: Key(state.video.playbackID), |
|||
controller: state.controller, |
|||
ad: ad, |
|||
play: widget.play, |
|||
details: coupons, |
|||
), |
|||
AnimatedOpacity( |
|||
opacity: likeShow ? 1 : 0, |
|||
duration: Duration(seconds: 2), |
|||
onEnd: () { |
|||
setState(() { |
|||
likeShow = false; |
|||
}); |
|||
}, |
|||
child: Container( |
|||
width: double.infinity, |
|||
height: MediaQuery.of(context).size.height, |
|||
child: Center( |
|||
child: Image.asset("assets/lovw.gif"), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
if (state is VideoPlayerStateLoading) { |
|||
return Container(); |
|||
} |
|||
|
|||
if (state is VideoPlayerStateError) { |
|||
return Container( |
|||
height: containerHeight, |
|||
color: Colors.black, |
|||
child: Center( |
|||
child: Text(state.message), |
|||
), |
|||
); |
|||
} |
|||
|
|||
return Container(); |
|||
} |
|||
|
|||
Widget _nameDescription(BuildContext context) { |
|||
return Align( |
|||
alignment: Alignment.bottomLeft, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: 10, |
|||
vertical: MediaQuery.of(context).size.height * 0.05, |
|||
), |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
new RichText( |
|||
maxLines: 5, |
|||
text: TextSpan( |
|||
text: userDoc != null ? "@" + userDoc["username"] : "", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.3, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
), |
|||
SizedBox(height: 5), |
|||
Container( |
|||
margin: EdgeInsets.only(bottom: 20), |
|||
width: MediaQuery.of(context).size.width * 0.7, |
|||
child: Text( |
|||
document != null |
|||
? document["title"] != null |
|||
? document["title"] |
|||
: "" |
|||
: "", |
|||
style: TextStyle( |
|||
color: Colors.white, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.3, |
|||
height: 1.5, |
|||
), |
|||
maxLines: 4, |
|||
overflow: TextOverflow.ellipsis, |
|||
textDirection: TextDirection.rtl, |
|||
textAlign: TextAlign.left, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _publisherWidget(BuildContext context) { |
|||
return Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: InkWell( |
|||
onTap: widget.postedAd.publisherID != null |
|||
? () => Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
child: UserProfileThirdPerson( |
|||
user: new TesoUser( |
|||
username: userDoc["username"], |
|||
userGUID: userDoc["id"], |
|||
firstname: userDoc["firstname"], |
|||
lastname: userDoc["surname"], |
|||
), |
|||
), |
|||
type: PageTransitionType.fade, |
|||
), |
|||
) |
|||
: null, |
|||
child: CachedNetworkImage( |
|||
imageUrl: |
|||
serverLocation + "api/pulldp/" + widget.postedAd.publisherID, |
|||
imageBuilder: (context, imageProvider) => FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: imageProvider, |
|||
placeholder: AssetImage("assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _favoriteWidget(BuildContext context) { |
|||
return Container( |
|||
height: 50, |
|||
child: InkWell( |
|||
onTap: () { |
|||
if (campaignAd) { |
|||
if (favoured) { |
|||
return null; |
|||
} else { |
|||
likePost(widget.postedAd); |
|||
} |
|||
} else { |
|||
if (favoured) { |
|||
dislikePost(widget.postedAd); |
|||
} else { |
|||
likePost(widget.postedAd); |
|||
} |
|||
} |
|||
}, |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Container( |
|||
width: 50, |
|||
height: 30, |
|||
child: Center( |
|||
child: Icon( |
|||
Icons.favorite, |
|||
size: 30, |
|||
color: favoured ? Colors.red : Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
height: 20, |
|||
width: 50, |
|||
child: Center( |
|||
child: Text( |
|||
Numeral(likes).value().toString(), |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _commentWidget(BuildContext context) { |
|||
return Container( |
|||
height: 50, |
|||
child: InkWell( |
|||
onTap: () => commentsDialog(context), |
|||
child: new Wrap( |
|||
direction: Axis.vertical, |
|||
children: [ |
|||
Container( |
|||
width: 50, |
|||
height: 30, |
|||
child: Center( |
|||
child: Icon( |
|||
Icons.comment, |
|||
size: 30, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
height: 20, |
|||
width: 50, |
|||
child: Center( |
|||
child: Text( |
|||
Numeral(comments).value().toString(), |
|||
style: TextStyle( |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
void moreDialog(BuildContext context, Post ad) { |
|||
showModalBottomSheet( |
|||
context: context, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.vertical(top: Radius.circular(30.0)), |
|||
), |
|||
builder: (BuildContext bc) { |
|||
return Container( |
|||
child: SingleChildScrollView( |
|||
scrollDirection: Axis.vertical, |
|||
child: new Wrap( |
|||
children: <Widget>[ |
|||
new Container( |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: 15.0, |
|||
), |
|||
child: Center( |
|||
child: Padding( |
|||
padding: const EdgeInsets.symmetric(horizontal: 20.0), |
|||
child: Container( |
|||
width: 50, |
|||
height: 4, |
|||
color: Colors.grey, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
buildTop3(context, ad), |
|||
new Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: 40, |
|||
margin: EdgeInsets.symmetric(vertical: 20.0, horizontal: 15), |
|||
decoration: BoxDecoration( |
|||
color: !dark ? Colors.grey[200] : Colors.white12, |
|||
borderRadius: BorderRadius.circular(5)), |
|||
child: new Center( |
|||
child: Text( |
|||
"Why you're seeing this post", |
|||
textAlign: TextAlign.center, |
|||
style: TextStyle( |
|||
color: !dark ? Colors.black : Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
// bottomButtons(context, friends), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
|
|||
buildTop3(BuildContext context, Post ad) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceAround, |
|||
children: [ |
|||
GestureDetector( |
|||
onTap: () => sharing(ad), |
|||
child: Container( |
|||
width: SizeConfig.blockSizeHorizontal * 30, |
|||
height: SizeConfig.blockSizeHorizontal * 20, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular( |
|||
10, |
|||
), |
|||
), |
|||
color: !dark ? Colors.grey[200] : Colors.white12, |
|||
), |
|||
alignment: Alignment.center, |
|||
child: Center( |
|||
child: Column( |
|||
mainAxisAlignment: MainAxisAlignment.spaceAround, |
|||
children: [ |
|||
Icon(Icons.share), |
|||
Text( |
|||
"Share", |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
), |
|||
GestureDetector( |
|||
onTap: () => reportDialog(context), |
|||
child: Container( |
|||
width: SizeConfig.blockSizeHorizontal * 30, |
|||
height: SizeConfig.blockSizeHorizontal * 20, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular( |
|||
10, |
|||
), |
|||
), |
|||
color: !dark ? Colors.grey[200] : Colors.white12, |
|||
), |
|||
alignment: Alignment.center, |
|||
child: Center( |
|||
child: Column( |
|||
mainAxisAlignment: MainAxisAlignment.spaceAround, |
|||
children: [ |
|||
Icon( |
|||
Icons.report_problem, |
|||
color: Colors.red[900], |
|||
), |
|||
Text( |
|||
"Report", |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
); |
|||
} |
|||
|
|||
flagContent(level) { |
|||
Provider.of<UserProvider>(context, listen: false) |
|||
.flagPost(widget.postedAd, level); |
|||
Navigator.pop(context); |
|||
widget.report(widget.postedAd); |
|||
this.dispose(); |
|||
} |
|||
|
|||
bottomButtons(BuildContext context, friends) { |
|||
return new Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
margin: EdgeInsets.symmetric(vertical: 5.0, horizontal: 15), |
|||
child: Column( |
|||
children: [ |
|||
new Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: new Center( |
|||
child: ElevatedButton( |
|||
style: ElevatedButton.styleFrom( |
|||
primary: Colors.grey[200], |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular(5.0), |
|||
), |
|||
), |
|||
), |
|||
onPressed: () => Navigator.pop(context), |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Text( |
|||
"Hide", |
|||
textAlign: TextAlign.center, |
|||
)), |
|||
), |
|||
), |
|||
), |
|||
friends |
|||
? new Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: new Center( |
|||
child: ElevatedButton( |
|||
style: ElevatedButton.styleFrom( |
|||
primary: Colors.grey[300], |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular(5.0), |
|||
), |
|||
), |
|||
), |
|||
onPressed: () => Navigator.pop(context), |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Text( |
|||
"Unfrend", |
|||
textAlign: TextAlign.center, |
|||
)), |
|||
), |
|||
), |
|||
) |
|||
: new Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: new Center( |
|||
child: ElevatedButton( |
|||
style: ElevatedButton.styleFrom( |
|||
primary: Colors.grey[300], |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular(5.0), |
|||
), |
|||
), |
|||
), |
|||
onPressed: () => Navigator.pop(context), |
|||
child: Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Text( |
|||
"Add Friend", |
|||
textAlign: TextAlign.center, |
|||
)), |
|||
), |
|||
), |
|||
) |
|||
], |
|||
)); |
|||
} |
|||
|
|||
void reportDialog(BuildContext context) { |
|||
showModalBottomSheet( |
|||
context: context, |
|||
isScrollControlled: true, |
|||
enableDrag: true, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.vertical(top: Radius.circular(30.0)), |
|||
), |
|||
builder: (BuildContext bc) { |
|||
return Container( |
|||
height: MediaQuery.of(context).size.height * 0.95, |
|||
child: Column( |
|||
children: [ |
|||
new Container( |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: 15.0, |
|||
), |
|||
child: Center( |
|||
child: Padding( |
|||
padding: const EdgeInsets.symmetric(horizontal: 20.0), |
|||
child: Container( |
|||
width: 50, |
|||
height: 4, |
|||
color: Colors.grey, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
child: Center( |
|||
child: Text( |
|||
"Report", |
|||
style: TextStyle( |
|||
fontSize: SizeConfig.blockSizeHorizontal * 3.5, |
|||
fontWeight: FontWeight.w800, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
Divider(), |
|||
Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 10), |
|||
child: Text( |
|||
"Why are you reporting this post?", |
|||
textAlign: TextAlign.left, |
|||
style: TextStyle( |
|||
fontSize: SizeConfig.blockSizeHorizontal * 3.5, |
|||
fontWeight: FontWeight.w800, |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 5, |
|||
), |
|||
Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 15), |
|||
child: Center( |
|||
child: Text( |
|||
"Your report would be handled as soon as possible. However if someone is in immediate danger, call the local emergency services - don't wait.", |
|||
style: TextStyle( |
|||
fontSize: SizeConfig.blockSizeHorizontal * 3.5, |
|||
fontWeight: FontWeight.w400, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
Divider(), |
|||
Container( |
|||
height: MediaQuery.of(context).size.height * 0.7, |
|||
child: new ListView( |
|||
scrollDirection: Axis.vertical, |
|||
children: <Widget>[ |
|||
ListTile( |
|||
title: Text("It's a spam"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(1), |
|||
), |
|||
ListTile( |
|||
title: Text("Nudity or sexual activity"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(2), |
|||
), |
|||
ListTile( |
|||
title: Text("I just don't like it"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(3), |
|||
), |
|||
ListTile( |
|||
title: Text("Hate speech or symbols"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(4), |
|||
), |
|||
ListTile( |
|||
title: Text("False information"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(5), |
|||
), |
|||
ListTile( |
|||
title: Text("Bullying harassment"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(6), |
|||
), |
|||
ListTile( |
|||
title: Text("Violence or dangerous organisations"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(7), |
|||
), |
|||
ListTile( |
|||
title: Text("Scam or fraud"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(8), |
|||
), |
|||
ListTile( |
|||
title: Text("Intellectual property violation"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(9), |
|||
), |
|||
ListTile( |
|||
title: Text("Sale of illegal or regulated goods"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(10), |
|||
), |
|||
ListTile( |
|||
title: Text("Suicide or self-injury"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(11), |
|||
), |
|||
ListTile( |
|||
title: Text("Eating disorders"), |
|||
trailing: Icon(Icons.arrow_forward_ios), |
|||
onTap: () => flagContent(12), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
@ -1,332 +0,0 @@ |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Classes/Firebase/Comments.dart'; |
|||
import 'package:teso/Classes/TesoUser.dart'; |
|||
import 'package:teso/Pages/PageWidgets/Posts/user3P_commentTitle.dart'; |
|||
import 'package:cloud_firestore/cloud_firestore.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/CommentsPost.dart'; |
|||
import 'package:teso/Pages/PageWidgets/ChatScreen/bottomBar.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:intl/intl.dart'; |
|||
import 'package:time_elapsed/time_elapsed.dart'; |
|||
|
|||
class CommentSection extends StatefulWidget { |
|||
final Post postedAd; |
|||
final TesoUser user; |
|||
CommentSection({ |
|||
Key key, |
|||
@required this.postedAd, |
|||
@required this.user, |
|||
}) : assert(postedAd != null), |
|||
assert(user != null), |
|||
super(key: key); |
|||
@override |
|||
_CommentSectionState createState() => _CommentSectionState(); |
|||
} |
|||
|
|||
class _CommentSectionState extends State<CommentSection> { |
|||
TextEditingController controller = new TextEditingController(); |
|||
final ScrollController listScrollController = ScrollController(); |
|||
List<FBComments> listMessage = <FBComments>[]; |
|||
int _limit = 20; |
|||
final int _limitIncrement = 20; |
|||
int total = 0; |
|||
var userDocs; |
|||
List<FBComments> latest = <FBComments>[]; |
|||
|
|||
_scrollListener() { |
|||
if (listScrollController.offset >= |
|||
listScrollController.position.maxScrollExtent && |
|||
!listScrollController.position.outOfRange) { |
|||
print("reach the bottom"); |
|||
setState(() { |
|||
print("reach the bottom"); |
|||
_limit += _limitIncrement; |
|||
}); |
|||
} |
|||
if (listScrollController.offset <= |
|||
listScrollController.position.minScrollExtent && |
|||
!listScrollController.position.outOfRange) { |
|||
print("reach the top"); |
|||
setState(() { |
|||
print("reach the top"); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
listScrollController.addListener(_scrollListener); |
|||
_listenComments(); |
|||
FirebaseFirestore.instance |
|||
.collection("users") |
|||
.doc(widget.postedAd.publisherID) |
|||
.get() |
|||
.then((value) { |
|||
setState(() { |
|||
userDocs = value.data(); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
controller.dispose(); |
|||
listScrollController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
void sendComment(String text, int position) async { |
|||
TesoUser user = |
|||
Provider.of<UserProvider>(context, listen: false).currentUser; |
|||
if (controller.text.isNotEmpty) { |
|||
CommentsPost comment = new CommentsPost(); |
|||
SharedPreferences.getInstance().then((value) async { |
|||
comment.postId = widget.postedAd.postID; |
|||
comment.comment = text.trim(); |
|||
comment.timestamp = DateTime.now().toIso8601String(); |
|||
comment.commenterId = value.getString("id"); |
|||
comment.commentId = |
|||
"TESCPCM" + DateTime.now().toString() + value.getString("id"); |
|||
|
|||
setState(() { |
|||
latest.add(FBComments( |
|||
comment: comment.comment, |
|||
commenterID: comment.commenterId, |
|||
commentID: comment.commentId, |
|||
commenter: user.username, |
|||
postID: comment.postId, |
|||
thumbnail: user.thumbnail_dp, |
|||
timestamp: DateTime.now(), |
|||
)); |
|||
total++; |
|||
}); |
|||
}); |
|||
Provider.of<UserProvider>(context, listen: false).commentPost(comment); |
|||
controller.clear(); |
|||
} |
|||
} |
|||
|
|||
_listenComments() { |
|||
FirebaseFirestore.instance |
|||
.collection('posts') |
|||
.doc(widget.postedAd.postID) |
|||
.collection("comments") |
|||
.orderBy('timestamp') |
|||
.snapshots() |
|||
.listen((event) { |
|||
if (mounted) { |
|||
setState(() { |
|||
total = event.docs.length; |
|||
latest.clear(); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: AppBar( |
|||
automaticallyImplyLeading: false, |
|||
centerTitle: true, |
|||
title: Text( |
|||
"$total comments", |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
), |
|||
), |
|||
actions: [ |
|||
IconButton( |
|||
onPressed: () => Navigator.pop(context), |
|||
icon: Icon( |
|||
Icons.close, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
body: Container( |
|||
height: MediaQuery.of(context).size.height, |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Stack( |
|||
children: [ |
|||
Container( |
|||
height: MediaQuery.of(context).size.height, |
|||
width: MediaQuery.of(context).size.width, |
|||
margin: EdgeInsets.only(bottom: 60), |
|||
child: StreamBuilder( |
|||
stream: FirebaseFirestore.instance |
|||
.collection('posts') |
|||
.doc(widget.postedAd.postID) |
|||
.collection("comments") |
|||
.orderBy('timestamp') |
|||
.limit(_limit) |
|||
.snapshots(), |
|||
builder: (context, snapshot) { |
|||
if (snapshot.data == null && |
|||
snapshot.connectionState == ConnectionState.waiting) { |
|||
return Center( |
|||
child: CircularProgressIndicator( |
|||
valueColor: AlwaysStoppedAnimation<Color>( |
|||
Theme.of(context).primaryColor))); |
|||
} else if (snapshot.data.docs.length == 0) { |
|||
if (widget.postedAd.title != null) { |
|||
return Align( |
|||
alignment: Alignment.topCenter, |
|||
child: buildPostTile3P( |
|||
context, widget.user, widget.postedAd), |
|||
); |
|||
} else { |
|||
return Container(); |
|||
} |
|||
} else { |
|||
QuerySnapshot results = snapshot.data; |
|||
listMessage = results.docs |
|||
.map((e) => FBComments.fromJSON(e.data())) |
|||
.toList(); |
|||
if (latest.length != 0) { |
|||
listMessage.addAll(latest); |
|||
} |
|||
|
|||
return ListView.builder( |
|||
padding: EdgeInsets.all(10.0), |
|||
itemCount: listMessage.length, |
|||
itemBuilder: (context, index) { |
|||
var formattedDate = DateFormat("yyyy-MM-dd HH:mm:ss") |
|||
.format(listMessage[index].timestamp); |
|||
if (index == 0 && widget.postedAd.title != null) { |
|||
return Column(children: [ |
|||
buildPostTile3P( |
|||
context, widget.user, widget.postedAd), |
|||
Divider(), |
|||
ListTile( |
|||
leading: Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
)), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage(serverLocation + |
|||
"api/pulldp/" + |
|||
snapshot.data.docs[index] |
|||
.data()['commenterID']), |
|||
placeholder: AssetImage( |
|||
"assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
title: new RichText( |
|||
text: new TextSpan( |
|||
style: new TextStyle( |
|||
fontSize: 14.0, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
children: <TextSpan>[ |
|||
new TextSpan( |
|||
text: snapshot.data.docs[index] |
|||
.data()['commenter'] + |
|||
" ", |
|||
style: new TextStyle( |
|||
fontWeight: FontWeight.bold)), |
|||
new TextSpan( |
|||
text: snapshot.data.docs[index] |
|||
.data()['comment']), |
|||
], |
|||
), |
|||
), |
|||
subtitle: Text( |
|||
TimeElapsed.fromDateTime( |
|||
DateTime.parse(formattedDate)), |
|||
), |
|||
), |
|||
]); |
|||
} |
|||
return ListTile( |
|||
leading: Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
)), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage(serverLocation + |
|||
"api/pulldp/" + |
|||
listMessage[index].commenterID), |
|||
placeholder: |
|||
AssetImage("assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
title: new RichText( |
|||
text: new TextSpan( |
|||
style: new TextStyle( |
|||
fontSize: 14.0, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
children: <TextSpan>[ |
|||
new TextSpan( |
|||
text: listMessage[index].commenter + " ", |
|||
style: new TextStyle( |
|||
fontWeight: FontWeight.bold)), |
|||
new TextSpan(text: listMessage[index].comment), |
|||
], |
|||
), |
|||
), |
|||
subtitle: Text( |
|||
TimeElapsed.fromDateTime( |
|||
DateTime.parse(formattedDate)), |
|||
), |
|||
); |
|||
}, |
|||
// reverse: true, |
|||
controller: listScrollController, |
|||
); |
|||
} |
|||
}, |
|||
), |
|||
), |
|||
Align( |
|||
alignment: Alignment.bottomCenter, |
|||
child: buildBottom( |
|||
context, |
|||
controller, |
|||
sendComment, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,155 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/widgets.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/util/SizeConfig.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
class DeletePost extends StatefulWidget { |
|||
final Post post; |
|||
const DeletePost({Key key, this.post}) : super(key: key); |
|||
|
|||
@override |
|||
_DeletePostState createState() => _DeletePostState(); |
|||
} |
|||
|
|||
class _DeletePostState extends State<DeletePost> { |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
SizeConfig().init(context); |
|||
return Scaffold( |
|||
backgroundColor: Color.fromRGBO(0, 0, 0, 0.5), |
|||
body: Stack( |
|||
children: [ |
|||
Align( |
|||
alignment: Alignment.bottomCenter, |
|||
child: Container( |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: SizeConfig.blockSizeHorizontal * 5, |
|||
), |
|||
width: MediaQuery.of(context).size.width, |
|||
height: SizeConfig.blockSizeHorizontal * 52.6, |
|||
child: Column( |
|||
children: [ |
|||
_descriptionBox(context), |
|||
SizedBox( |
|||
height: 5, |
|||
), |
|||
_cancelDelete(context), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _descriptionBox(BuildContext context) { |
|||
return Container( |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(30.0), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(15), |
|||
topRight: Radius.circular(15), |
|||
bottomLeft: Radius.circular(15), |
|||
bottomRight: Radius.circular(15), |
|||
), |
|||
child: Material( |
|||
child: Container( |
|||
padding: EdgeInsets.all(10), |
|||
child: Column( |
|||
children: [ |
|||
SizedBox( |
|||
height: 5, |
|||
), |
|||
Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: SizeConfig.safeBlockHorizontal * 2), |
|||
padding: EdgeInsets.symmetric( |
|||
horizontal: SizeConfig.safeBlockHorizontal * 6), |
|||
alignment: Alignment.center, |
|||
child: Text( |
|||
"Once you proceed you cannot undo your actions . " + |
|||
"Are you sure you would like to delete this post ? ", |
|||
textAlign: TextAlign.center, |
|||
style: TextStyle( |
|||
color: Theme.of(context).colorScheme.secondary == |
|||
Color(0xFFfd0a35) |
|||
? Colors.white24 |
|||
: tesoBlue), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 10, |
|||
), |
|||
Divider(), |
|||
SizedBox( |
|||
height: 10, |
|||
), |
|||
InkWell( |
|||
onTap: () => Navigator.pop(context, true), |
|||
child: Container( |
|||
alignment: Alignment.center, |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Text( |
|||
"Delete", |
|||
style: TextStyle( |
|||
color: Colors.red, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.0, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
SizedBox( |
|||
height: 10, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _cancelDelete(BuildContext context) { |
|||
return Container( |
|||
width: MediaQuery.of(context).size.width, |
|||
height: SizeConfig.safeBlockVertical * 6.5, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(30.0), |
|||
), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(15), |
|||
topRight: Radius.circular(15), |
|||
bottomLeft: Radius.circular(15), |
|||
bottomRight: Radius.circular(15), |
|||
), |
|||
child: ColorFiltered( |
|||
colorFilter: ColorFilter.mode( |
|||
Colors.white.withOpacity(0.1), BlendMode.lighten), |
|||
child: Material( |
|||
child: Container( |
|||
padding: EdgeInsets.all(10), |
|||
child: InkWell( |
|||
onTap: () => Navigator.pop(context, false), |
|||
child: Text( |
|||
"Cancel", |
|||
textAlign: TextAlign.center, |
|||
style: TextStyle( |
|||
color: Colors.blueAccent, |
|||
fontSize: SizeConfig.safeBlockHorizontal * 4.5, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,103 +0,0 @@ |
|||
// import 'dart:typed_data'; |
|||
|
|||
// import 'package:teso/Classes/API%20Clasess/PostedAd.dart'; |
|||
// import 'package:teso/Pages/PageWidgets/Posts/comment.dart'; |
|||
// import 'package:teso/Pages/PageWidgets/Posts/expandedPost.dart'; |
|||
// import 'package:flutter/material.dart'; |
|||
// |
|||
// import 'package:teso/util/consts.dart'; |
|||
// import 'package:video_player/video_player.dart'; |
|||
|
|||
// class PostDetails extends StatefulWidget { |
|||
// final PostedAd type; |
|||
// const PostDetails({Key key, this.type}) : super(key: key); |
|||
// @override |
|||
// _PostDetailsState createState() => _PostDetailsState(post: type); |
|||
// } |
|||
|
|||
// class _PostDetailsState extends State<PostDetails> { |
|||
// PostedAd post; |
|||
// _PostDetailsState({this.post}); |
|||
// bool available = false; |
|||
// Uint8List bytes; |
|||
// TextEditingController controller; |
|||
// VideoPlayerController _videoPlayerController; |
|||
|
|||
// void deleteDialog(context) { |
|||
// showModalBottomSheet( |
|||
// context: context, |
|||
// shape: RoundedRectangleBorder( |
|||
// borderRadius: BorderRadius.vertical(top: Radius.circular(30.0)), |
|||
// ), |
|||
// builder: (BuildContext bc) { |
|||
// return Container( |
|||
// child: new Wrap( |
|||
// children: <Widget>[ |
|||
// new Container( |
|||
// width: double.infinity, |
|||
// margin: EdgeInsets.only( |
|||
// top: 20.0, |
|||
// bottom: 12.0, |
|||
// ), |
|||
// child: Center( |
|||
// child: Text( |
|||
// "Delete post", |
|||
// style: TextStyle( |
|||
// fontSize: 12.0, |
|||
// ), |
|||
// ), |
|||
// )), |
|||
// Padding( |
|||
// padding: const EdgeInsets.symmetric(horizontal: 20.0), |
|||
// child: Divider(), |
|||
// ), |
|||
// new ListTile( |
|||
// leading: new Icon(MaterialCommunityIcons.trash_can), |
|||
// title: new Text('Delete'), |
|||
// onTap: () => print("Delete")), |
|||
// ], |
|||
// ), |
|||
// ); |
|||
// }, |
|||
// ); |
|||
// } |
|||
|
|||
// @override |
|||
// void initState() { |
|||
// super.initState(); |
|||
// _videoPlayerController = |
|||
// VideoPlayerController.network(tesoStreaming + post.post.path) |
|||
// ..initialize().then((_) { |
|||
// _videoPlayerController.play(); |
|||
// _videoPlayerController.setLooping(true); |
|||
// setState(() {}); |
|||
// }); |
|||
// } |
|||
|
|||
// @override |
|||
// Widget build(BuildContext context) { |
|||
// return Scaffold( |
|||
// appBar: null, |
|||
// body: Container( |
|||
// margin: EdgeInsets.only( |
|||
// top: 50, |
|||
// left: 10, |
|||
// right: 10, |
|||
// ), |
|||
// height: MediaQuery.of(context).size.height, |
|||
// width: MediaQuery.of(context).size.width, |
|||
// child: SingleChildScrollView( |
|||
// scrollDirection: Axis.vertical, |
|||
// child: Column( |
|||
// children: [ |
|||
// buildPostDetails( |
|||
// context, post, deleteDialog, _videoPlayerController), |
|||
// Padding(padding: EdgeInsets.all(05)), |
|||
// buildCommentTile(context, available, bytes, controller), |
|||
// ], |
|||
// ), |
|||
// ), |
|||
// ), |
|||
// ); |
|||
// } |
|||
// } |
@ -1,289 +0,0 @@ |
|||
import 'package:cloud_firestore/cloud_firestore.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
import 'package:page_transition/page_transition.dart'; |
|||
import 'package:provider/provider.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/CommentsPost.dart'; |
|||
import 'package:teso/Classes/Firebase/Posts.dart'; |
|||
import 'package:teso/Pages/PageWidgets/ChatScreen/bottomBar.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/UserPosts.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
import 'package:intl/intl.dart'; |
|||
import 'package:time_elapsed/time_elapsed.dart'; |
|||
|
|||
// ignore: must_be_immutable |
|||
class CommentSection extends StatefulWidget { |
|||
FBPosts postedAd; |
|||
CommentSection({ |
|||
Key key, |
|||
@required this.postedAd, |
|||
}) : assert(postedAd != null), |
|||
super(key: key); |
|||
@override |
|||
_CommentSectionState createState() => _CommentSectionState(); |
|||
} |
|||
|
|||
class _CommentSectionState extends State<CommentSection> { |
|||
TextEditingController controller = new TextEditingController(); |
|||
final ScrollController listScrollController = ScrollController(); |
|||
List<QueryDocumentSnapshot> listMessage = new List.from([]); |
|||
int _limit = 20; |
|||
final int _limitIncrement = 20; |
|||
|
|||
_scrollListener() { |
|||
if (listScrollController.offset >= |
|||
listScrollController.position.maxScrollExtent && |
|||
!listScrollController.position.outOfRange) { |
|||
print("reach the bottom"); |
|||
setState(() { |
|||
print("reach the bottom"); |
|||
_limit += _limitIncrement; |
|||
}); |
|||
} |
|||
if (listScrollController.offset <= |
|||
listScrollController.position.minScrollExtent && |
|||
!listScrollController.position.outOfRange) { |
|||
print("reach the top"); |
|||
setState(() { |
|||
print("reach the top"); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
listScrollController.addListener(_scrollListener); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
controller.dispose(); |
|||
listScrollController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
void sendComment(String text, int position) async { |
|||
if (controller.text.isNotEmpty) { |
|||
CommentsPost comment = new CommentsPost(); |
|||
SharedPreferences.getInstance().then((value) async { |
|||
comment.postId = widget.postedAd.postID; |
|||
comment.comment = text; |
|||
comment.timestamp = DateTime.now().toIso8601String(); |
|||
comment.commenterId = value.getString("id"); |
|||
comment.commentId = |
|||
"TESCPCM" + DateTime.now().toString() + value.getString("id"); |
|||
}); |
|||
// setState(() { |
|||
// widget.postedAd.comments.add(comment); |
|||
// }); |
|||
|
|||
Provider.of<UserProvider>(context, listen: false).commentPost(comment); |
|||
controller.clear(); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: AppBar( |
|||
title: Text("Comments"), |
|||
leading: IconButton( |
|||
icon: Icon( |
|||
Icons.arrow_back_ios, |
|||
), |
|||
onPressed: () => Navigator.pushReplacement( |
|||
context, |
|||
PageTransition( |
|||
child: UserPosts( |
|||
postedAd: widget.postedAd, |
|||
), |
|||
type: PageTransitionType.rightToLeft, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
body: Container( |
|||
height: MediaQuery.of(context).size.height, |
|||
width: MediaQuery.of(context).size.width, |
|||
child: Stack( |
|||
children: [ |
|||
Container( |
|||
height: MediaQuery.of(context).size.height, |
|||
width: MediaQuery.of(context).size.width, |
|||
margin: EdgeInsets.only(bottom: 60), |
|||
child: StreamBuilder( |
|||
stream: FirebaseFirestore.instance |
|||
.collection('posts') |
|||
.doc('comments') |
|||
.collection(widget.postedAd.postID) |
|||
.orderBy('timestamp') |
|||
.limit(_limit) |
|||
.snapshots(), |
|||
builder: (context, snapshot) { |
|||
if (snapshot.data == null && |
|||
snapshot.connectionState == ConnectionState.waiting) { |
|||
return Center( |
|||
child: CircularProgressIndicator( |
|||
valueColor: AlwaysStoppedAnimation<Color>( |
|||
Theme.of(context).primaryColor))); |
|||
} else if (snapshot.data.docs.length == 0) { |
|||
// if (widget.postedAd.title != null) { |
|||
// return Align( |
|||
// alignment: Alignment.topCenter, |
|||
// child: buildPostTile(context, widget.postedAd), |
|||
// ); |
|||
// } else { |
|||
return Container(); |
|||
// } |
|||
} else { |
|||
listMessage = snapshot.data.docs; |
|||
return ListView.builder( |
|||
padding: EdgeInsets.all(10.0), |
|||
itemCount: listMessage.length, |
|||
itemBuilder: (context, index) { |
|||
int timeInMillis = int.parse(snapshot.data.docs[index] |
|||
.data()['timestamp'] |
|||
.toString()); |
|||
var date = |
|||
DateTime.fromMillisecondsSinceEpoch(timeInMillis); |
|||
var formattedDate = |
|||
DateFormat("yyyy-MM-dd HH:mm:ss").format(date); |
|||
if (index == 0 && widget.postedAd.title != null) { |
|||
return Column(children: [ |
|||
// buildPostTile(context, widget.postedAd), |
|||
Divider(), |
|||
ListTile( |
|||
leading: Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
)), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage(serverLocation + |
|||
"api/pulldp/" + |
|||
snapshot.data.docs[index] |
|||
.data()['commenterID']), |
|||
placeholder: AssetImage( |
|||
"assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
title: new RichText( |
|||
text: new TextSpan( |
|||
style: new TextStyle( |
|||
fontSize: 14.0, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
children: <TextSpan>[ |
|||
new TextSpan( |
|||
text: snapshot.data.docs[index] |
|||
.data()['commenter'] + |
|||
" ", |
|||
style: new TextStyle( |
|||
fontWeight: FontWeight.bold)), |
|||
new TextSpan( |
|||
text: snapshot.data.docs[index] |
|||
.data()['comment']), |
|||
], |
|||
), |
|||
), |
|||
subtitle: Text( |
|||
TimeElapsed.fromDateTime( |
|||
DateTime.parse(formattedDate)), |
|||
), |
|||
), |
|||
]); |
|||
} |
|||
return ListTile( |
|||
leading: Container( |
|||
width: 40, |
|||
height: 40, |
|||
decoration: BoxDecoration( |
|||
shape: BoxShape.circle, |
|||
border: Border.all( |
|||
color: Colors.black, |
|||
width: 1, |
|||
)), |
|||
child: ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(90.0), |
|||
topRight: Radius.circular(90.0), |
|||
bottomLeft: Radius.circular(90), |
|||
bottomRight: Radius.circular(90), |
|||
), |
|||
child: FadeInImage( |
|||
height: 90, |
|||
width: 90, |
|||
fit: BoxFit.fill, |
|||
image: NetworkImage(serverLocation + |
|||
"api/pulldp/" + |
|||
snapshot.data.docs[index] |
|||
.data()['commenterID']), |
|||
placeholder: |
|||
AssetImage("assets/images/tesoDP/dp1.png"), |
|||
), |
|||
), |
|||
), |
|||
title: new RichText( |
|||
text: new TextSpan( |
|||
style: new TextStyle( |
|||
fontSize: 14.0, |
|||
color: Theme.of(context).primaryColorLight, |
|||
), |
|||
children: <TextSpan>[ |
|||
new TextSpan( |
|||
text: snapshot.data.docs[index] |
|||
.data()['commenter'] + |
|||
" ", |
|||
style: new TextStyle( |
|||
fontWeight: FontWeight.bold)), |
|||
new TextSpan( |
|||
text: snapshot.data.docs[index] |
|||
.data()['comment']), |
|||
], |
|||
), |
|||
), |
|||
subtitle: Text( |
|||
TimeElapsed.fromDateTime( |
|||
DateTime.parse(formattedDate)), |
|||
), |
|||
); |
|||
}, |
|||
// reverse: true, |
|||
controller: listScrollController, |
|||
); |
|||
} |
|||
}, |
|||
), |
|||
), |
|||
Align( |
|||
alignment: Alignment.bottomCenter, |
|||
child: buildBottom( |
|||
context, |
|||
controller, |
|||
sendComment, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,204 +0,0 @@ |
|||
import 'dart:convert'; |
|||
import 'dart:math'; |
|||
import 'package:http/http.dart' as http; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:pull_to_refresh/pull_to_refresh.dart'; |
|||
import 'package:shared_preferences/shared_preferences.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Pages/PageWidgets/Home/homeTile.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/homeSub/VideoList.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
class HomeFeed extends StatefulWidget { |
|||
@override |
|||
_HomeFeedState createState() => _HomeFeedState(); |
|||
} |
|||
|
|||
class _HomeFeedState extends State<HomeFeed> { |
|||
ScrollController _controller; |
|||
// List<PostedAd> trends; |
|||
List<Post> show; |
|||
int count; |
|||
var _future; |
|||
List<Post> deadFeed; |
|||
RefreshController _refreshController = |
|||
RefreshController(initialRefresh: false); |
|||
|
|||
Future<List<Post>> pullAds() async { |
|||
try { |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
String token = prefs.getString("tokensTeso"); |
|||
Map<String, String> requestHeaders = { |
|||
'Content-type': 'application/json', |
|||
'Authorization': token |
|||
}; |
|||
var register = serverLocation + 'posts/homefeed'; |
|||
var client = |
|||
await http.post(Uri.parse(register), headers: requestHeaders); |
|||
if (client.statusCode == 200) { |
|||
var posts = jsonDecode(client.body); |
|||
setState(() { |
|||
this.show = List<Post>.from( |
|||
posts.map((model) => Post.fromJSON(model)).toList()); |
|||
// this.show.sort((b, a) => a.timestamp.compareTo(b.timestamp)); |
|||
}); |
|||
|
|||
await prefs.setString("homefeeds", client.body); |
|||
// await fetchImages(); |
|||
// |
|||
return show; |
|||
} else { |
|||
return null; |
|||
} |
|||
} catch (e) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
void _scrollListener() { |
|||
if (_controller.offset >= _controller.position.maxScrollExtent && |
|||
!_controller.position.outOfRange) { |
|||
pullAds(); |
|||
} |
|||
} |
|||
|
|||
void _onRefresh() async { |
|||
try { |
|||
await pullAds(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
_refreshController.refreshCompleted(); |
|||
} |
|||
|
|||
Future<void> watch(advert) async { |
|||
List<Post> towatch = <Post>[]; |
|||
towatch.add(advert); |
|||
if (show.length != 0) { |
|||
for (int i = 0; (i < show.length) && (i < 10); i++) { |
|||
final _random = new Random(); |
|||
towatch.add(show[_random.nextInt(show.length)]); |
|||
} |
|||
} else if (show.length == 0) { |
|||
for (int i = 0; (i < deadFeed.length) && (i < 10); i++) { |
|||
final _random = new Random(); |
|||
towatch.add(deadFeed[_random.nextInt(show.length)]); |
|||
} |
|||
} |
|||
Post reported = await Navigator.of(context).push( |
|||
new PageRouteBuilder( |
|||
pageBuilder: (_, __, ___) => new VideoList( |
|||
postedAd: towatch, |
|||
// posts: towatch, |
|||
// user: user, |
|||
//friend: addable, |
|||
), |
|||
), |
|||
); |
|||
if (reported != null) { |
|||
show.remove(reported); |
|||
SharedPreferences prefs = await SharedPreferences.getInstance(); |
|||
await prefs.setString("homefeeds", jsonEncode(show)); |
|||
|
|||
print(prefs.getString("homefeed")); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
// WidgetsBinding.instance.addPostFrameCallback((_) => widget.toggle(1)); |
|||
_controller = ScrollController(); |
|||
_controller.addListener(_scrollListener); |
|||
count = 0; |
|||
// pullAds(); |
|||
SharedPreferences.getInstance().then((value) { |
|||
if (value.getString("homefeeds") != null) { |
|||
var posts = jsonDecode(value.getString("homefeeds")); |
|||
setState(() { |
|||
deadFeed = List<Post>.from( |
|||
posts.map((model) => Post.fromJSON(model)).toList()); |
|||
}); |
|||
} |
|||
}); |
|||
_future = pullAds(); |
|||
super.initState(); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
_controller.dispose(); |
|||
_refreshController.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
body: FutureBuilder( |
|||
initialData: deadFeed, |
|||
future: _future, |
|||
builder: (context, snapshot) { |
|||
if (show == null && |
|||
deadFeed == null && |
|||
(snapshot.connectionState == ConnectionState.none || |
|||
snapshot.connectionState == ConnectionState.waiting)) { |
|||
return Center( |
|||
child: CupertinoActivityIndicator( |
|||
animating: true, |
|||
radius: 15, |
|||
), |
|||
); |
|||
} |
|||
if (show == null && |
|||
deadFeed == null && |
|||
snapshot.connectionState == ConnectionState.done) { |
|||
return SmartRefresher( |
|||
enablePullDown: true, |
|||
enablePullUp: false, |
|||
header: ClassicHeader(), |
|||
controller: _refreshController, |
|||
onRefresh: _onRefresh, |
|||
child: Container( |
|||
child: Center( |
|||
child: Text("An error occurred"), |
|||
), |
|||
), |
|||
); |
|||
} else { |
|||
show = show != null ? show : deadFeed; |
|||
return SmartRefresher( |
|||
enablePullDown: true, |
|||
enablePullUp: false, |
|||
header: ClassicHeader(), |
|||
controller: _refreshController, |
|||
onRefresh: _onRefresh, |
|||
child: StaggeredGridView.count( |
|||
controller: _controller, |
|||
crossAxisCount: 2, |
|||
children: List.generate(show.length, (int index) { |
|||
return show.elementAt(index).aspect == null || |
|||
show.elementAt(index).aspect == "null" |
|||
? Container() |
|||
: double.parse(show.elementAt(index).aspect.toString()) < |
|||
1 |
|||
? buildTile( |
|||
context, show.elementAt(index), 0.65, watch) |
|||
: buildTile( |
|||
context, show.elementAt(index), 0.35, watch); |
|||
}), |
|||
staggeredTiles: List.generate( |
|||
show.length, |
|||
(int index) { |
|||
return StaggeredTile.fit(1); |
|||
}, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
}, |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1,66 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/rendering.dart'; |
|||
|
|||
import 'package:provider/provider.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/Pages/Sub_Pages/Posts/ViewPost.dart'; |
|||
import 'package:teso/providers/user_provider.dart'; |
|||
|
|||
class VideoList extends StatefulWidget { |
|||
final List<Post> postedAd; |
|||
|
|||
VideoList({Key key, this.postedAd}) : super(key: key); |
|||
|
|||
@override |
|||
_VideoListState createState() => _VideoListState(); |
|||
} |
|||
|
|||
class _VideoListState extends State<VideoList> { |
|||
@override |
|||
void initState() { |
|||
Provider.of<UserProvider>(context, listen: false) |
|||
.predownloadAds(widget.postedAd.map((e) => e.playbackID).toList()); |
|||
super.initState(); |
|||
} |
|||
|
|||
report(Post post) { |
|||
Navigator.pop(context, post); |
|||
Navigator.pop(context, post); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: AppBar( |
|||
automaticallyImplyLeading: true, |
|||
backgroundColor: Colors.transparent, |
|||
leading: InkWell( |
|||
onTap: () => Navigator.pop(context), |
|||
child: Container( |
|||
height: 50, |
|||
width: 40, |
|||
margin: EdgeInsets.symmetric( |
|||
vertical: 30.0, |
|||
horizontal: 10, |
|||
), |
|||
child: Icon( |
|||
Icons.arrow_back_ios, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
extendBodyBehindAppBar: true, |
|||
body: PageView( |
|||
scrollDirection: Axis.vertical, |
|||
children: List<ViewPost>.generate( |
|||
widget.postedAd.length > 10 ? 10 : widget.postedAd.length, |
|||
(index) => new ViewPost( |
|||
play: true, |
|||
postedAd: widget.postedAd[index], |
|||
report: report, |
|||
)), |
|||
), |
|||
); |
|||
} |
|||
} |
@ -1 +0,0 @@ |
|||
export 'uservideo_controller_service.dart'; |
@ -1 +0,0 @@ |
|||
export 'video_controller_service.dart'; |
@ -1,67 +0,0 @@ |
|||
import 'package:better_player/better_player.dart'; |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; |
|||
import 'package:teso/Classes/Firebase/Posts.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
abstract class VideoControllerService { |
|||
Future<BetterPlayerController> getControllerForVideo(FBPosts video); |
|||
} |
|||
|
|||
class CachedVideoControllerService extends VideoControllerService { |
|||
// ignore: unused_field |
|||
final BaseCacheManager _cacheManager; |
|||
|
|||
CachedVideoControllerService(this._cacheManager) |
|||
: assert(_cacheManager != null); |
|||
|
|||
@override |
|||
Future<BetterPlayerController> getControllerForVideo(FBPosts video) async { |
|||
try { |
|||
BetterPlayerDataSource betterPlayerDataSource = BetterPlayerDataSource( |
|||
BetterPlayerDataSourceType.network, |
|||
"https://stream.mux.com/${video.playbackID}.m3u8", |
|||
videoFormat: BetterPlayerVideoFormat.hls, |
|||
cacheConfiguration: BetterPlayerCacheConfiguration( |
|||
useCache: true, |
|||
), |
|||
); |
|||
|
|||
return BetterPlayerController( |
|||
BetterPlayerConfiguration( |
|||
autoPlay: true, |
|||
aspectRatio: double.tryParse(video.aspect), |
|||
looping: true, |
|||
fit: double.parse(video.aspect) < 1 |
|||
? BoxFit.fitHeight |
|||
: BoxFit.fitWidth, |
|||
showPlaceholderUntilPlay: true, |
|||
placeholder: Container( |
|||
child: Center( |
|||
child: CachedNetworkImage( |
|||
imageUrl: tesoPostThumb(video.playbackID), |
|||
imageBuilder: (context, imageProvider) => FadeInImage( |
|||
width: double.infinity, |
|||
fit: BoxFit.fill, |
|||
image: imageProvider, |
|||
placeholder: AssetImage("assets/images/blank.jpg"), |
|||
), |
|||
errorWidget: (context, url, error) => |
|||
Image.asset("assets/images/blank.jpg"), |
|||
), |
|||
), |
|||
), |
|||
controlsConfiguration: BetterPlayerControlsConfiguration( |
|||
showControls: false, |
|||
), |
|||
autoDispose: true, |
|||
), |
|||
betterPlayerDataSource: betterPlayerDataSource, |
|||
); |
|||
} catch (e) { |
|||
// return BetterPlayerController.network(tesoStreaming + "pd/" + video.path); |
|||
return null; |
|||
} |
|||
} |
|||
} |
@ -1,66 +0,0 @@ |
|||
import 'package:better_player/better_player.dart'; |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; |
|||
import 'package:teso/Classes/API%20Clasess/Post.dart'; |
|||
import 'package:teso/util/consts.dart'; |
|||
|
|||
abstract class VideoControllerService { |
|||
Future<BetterPlayerController> getControllerForVideo(Post video); |
|||
} |
|||
|
|||
class CachedVideoControllerService extends VideoControllerService { |
|||
// ignore: unused_field |
|||
final BaseCacheManager _cacheManager; |
|||
|
|||
CachedVideoControllerService(this._cacheManager) |
|||
: assert(_cacheManager != null); |
|||
|
|||
@override |
|||
Future<BetterPlayerController> getControllerForVideo(Post video) async { |
|||
try { |
|||
BetterPlayerDataSource betterPlayerDataSource = BetterPlayerDataSource( |
|||
BetterPlayerDataSourceType.network, |
|||
"https://stream.mux.com/${video.playbackID}.m3u8", |
|||
videoFormat: BetterPlayerVideoFormat.hls, |
|||
cacheConfiguration: BetterPlayerCacheConfiguration( |
|||
useCache: true, |
|||
), |
|||
); |
|||
|
|||
return BetterPlayerController( |
|||
BetterPlayerConfiguration( |
|||
autoPlay: true, |
|||
aspectRatio: double.tryParse(video.aspect), |
|||
looping: true, |
|||
fit: BoxFit.fill, |
|||
showPlaceholderUntilPlay: true, |
|||
placeholder: Container( |
|||
child: Center( |
|||
child: CachedNetworkImage( |
|||
imageUrl: tesoPostThumb(video.playbackID), |
|||
imageBuilder: (context, imageProvider) => FadeInImage( |
|||
width: double.infinity, |
|||
fit: BoxFit.fill, |
|||
image: imageProvider, |
|||
placeholder: AssetImage("assets/images/blank.jpg"), |
|||
), |
|||
errorWidget: (context, url, error) => |
|||
Image.asset("assets/images/blank.jpg"), |
|||
), |
|||
), |
|||
), |
|||
controlsConfiguration: BetterPlayerControlsConfiguration( |
|||
showControls: false, |
|||
), |
|||
autoDispose: true, |
|||
), |
|||
betterPlayerDataSource: betterPlayerDataSource, |
|||
); |
|||
} catch (e) { |
|||
// return BetterPlayerController.network(tesoStreaming + "pd/" + video.path); |
|||
return null; |
|||
} |
|||
} |
|||
} |
@ -1 +0,0 @@ |
|||
export 'video_player/video_player.dart'; |
@ -1,3 +0,0 @@ |
|||
export 'video_player_bloc.dart'; |
|||
export 'uservideo_player_event.dart'; |
|||
export 'uservideo_player_state.dart'; |
@ -1,28 +0,0 @@ |
|||
import 'package:bloc/bloc.dart'; |
|||
import 'package:teso/Services/services.dart'; |
|||
|
|||
import 'uservideo_player.dart'; |
|||
|
|||
class VideoPlayerBloc extends Bloc<VideoPlayerEvent, VideoPlayerState> { |
|||
final VideoControllerService _videoControllerService; |
|||
|
|||
VideoPlayerBloc(this._videoControllerService) |
|||
: assert(_videoControllerService != null), |
|||
super(null); |
|||
|
|||
VideoPlayerState get initialState => VideoPlayerStateInitial(); |
|||
|
|||
@override |
|||
Stream<VideoPlayerState> mapEventToState(VideoPlayerEvent event) async* { |
|||
if (event is VideoSelectedEvent) { |
|||
yield VideoPlayerStateLoading(); |
|||
try { |
|||
final videoController = |
|||
await _videoControllerService.getControllerForVideo(event.video); |
|||
yield VideoPlayerStateLoaded(event.video, videoController); |
|||
} catch (e) { |
|||
yield VideoPlayerStateError(e.message ?? 'An unknown error occurred'); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,16 +0,0 @@ |
|||
import 'package:equatable/equatable.dart'; |
|||
import 'package:teso/Classes/Firebase/Posts.dart'; |
|||
|
|||
abstract class VideoPlayerEvent extends Equatable { |
|||
@override |
|||
List<Object> get props => const []; |
|||
} |
|||
|
|||
class VideoSelectedEvent extends VideoPlayerEvent { |
|||
final FBPosts video; |
|||
|
|||
VideoSelectedEvent(this.video) : assert(video != null); |
|||
|
|||
@override |
|||
List<Object> get props => [video]; |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue