From 8697af9cf0587906303ea6516194cb2e846e14ef Mon Sep 17 00:00:00 2001 From: netharu methmitha Date: Tue, 19 Apr 2022 22:31:12 +0530 Subject: [PATCH] added comments to the code just to help devs and also changed appName to Whatskit Flutter Demo i didnt even notice the change --- lib/main.dart | 24 ++++----------- lib/pages/settings_page.dart | 5 ++++ lib/pages/status_preview_page.dart | 16 +++++++--- lib/pages/statuses_page.dart | 25 ++++++++++++++-- lib/pages/trimmed_video_page.dart | 27 +++++++++++++++++ lib/pages/video_timmer.dart | 47 +++++++++++++++++++++++++----- lib/provider/theme_provider.dart | 23 ++++++++++----- lib/widgets/status_card.dart | 3 +- pubspec.yaml | 4 +-- 9 files changed, 130 insertions(+), 44 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ee5c55d..48f09cc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,11 +6,12 @@ import 'package:whatskit/provider/theme_provider.dart'; import 'package:provider/provider.dart'; void main() { - runApp(const MyApp()); + runApp(const Application()); } -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); +// main app +class Application extends StatelessWidget { + const Application({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -19,7 +20,7 @@ class MyApp extends StatelessWidget { builder: (context, _) { final ThemeProvider themeProvider = Provider.of(context); return MaterialApp( - title: 'Flutter Demo', + title: 'Whatskit', themeMode: themeProvider.getThemeMode, theme: AppThemes.lightTheme, darkTheme: AppThemes.darkTheme, @@ -89,18 +90,3 @@ class _AppState extends State { ); } } - -MaterialColor generateMaterialColorFromColor(Color color) { - return MaterialColor(color.value, { - 50: Color.fromRGBO(color.red, color.green, color.blue, 0.1), - 100: Color.fromRGBO(color.red, color.green, color.blue, 0.2), - 200: Color.fromRGBO(color.red, color.green, color.blue, 0.3), - 300: Color.fromRGBO(color.red, color.green, color.blue, 0.4), - 400: Color.fromRGBO(color.red, color.green, color.blue, 0.5), - 500: Color.fromRGBO(color.red, color.green, color.blue, 0.6), - 600: Color.fromRGBO(color.red, color.green, color.blue, 0.7), - 700: Color.fromRGBO(color.red, color.green, color.blue, 0.8), - 800: Color.fromRGBO(color.red, color.green, color.blue, 0.9), - 900: Color.fromRGBO(color.red, color.green, color.blue, 1.0), - }); -} diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 8c82bca..b9c23e5 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:whatskit/provider/theme_provider.dart'; +/// settings page of the app class SettingsPage extends StatefulWidget { const SettingsPage({Key? key}) : super(key: key); @@ -64,6 +65,7 @@ class _SettingsPageState extends State { iconColor: Colors.pink, icon: Icons.cleaning_services_rounded, onTap: () async { + // clearing the rendered cache if the user forgot to clear the saved cache var renderedCacheDir = Directory( '/storage/emulated/0/Android/data/com.netharuM.whatskit/files/Trimmer'); if (await renderedCacheDir.exists()) { @@ -114,6 +116,7 @@ class _SettingsPageState extends State { } } +/// the widget that shows a settings option class SettingsOption extends StatelessWidget { final String title; final String subtitle; @@ -177,6 +180,7 @@ class SettingsOption extends StatelessWidget { } } +/// about the app page class AboutPage extends StatefulWidget { const AboutPage({Key? key}) : super(key: key); @@ -333,6 +337,7 @@ class _AboutPageState extends State { } } +/// showing an option about the app class AboutCard extends StatelessWidget { final String? title; final String? subtitle; diff --git a/lib/pages/status_preview_page.dart b/lib/pages/status_preview_page.dart index 4ac2eb0..79c80e9 100644 --- a/lib/pages/status_preview_page.dart +++ b/lib/pages/status_preview_page.dart @@ -4,15 +4,16 @@ import 'package:video_player/video_player.dart'; import 'package:share_plus/share_plus.dart'; import 'package:filesystem_picker/filesystem_picker.dart'; -class PreviewPage extends StatefulWidget { +/// the page that shows the preview of the status +class StatusPreviewPage extends StatefulWidget { final File file; - const PreviewPage({Key? key, required this.file}) : super(key: key); + const StatusPreviewPage({Key? key, required this.file}) : super(key: key); @override - State createState() => _PreviewPageState(); + State createState() => _StatusPreviewPageState(); } -class _PreviewPageState extends State { +class _StatusPreviewPageState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -21,6 +22,7 @@ class _PreviewPageState extends State { child: Builder( builder: (context) { if (widget.file.path.endsWith('.mp4')) { + // if the media file is a video return Column( children: [ StatusPlayer( @@ -67,6 +69,7 @@ class _PreviewPageState extends State { ], ); } else if (widget.file.path.endsWith('.jpg')) { + // or if its a picture return Stack( children: [ Center( @@ -87,6 +90,7 @@ class _PreviewPageState extends State { children: [ TextButton( onPressed: () { + // sharing the status Share.shareFiles( [widget.file.path], ); @@ -95,6 +99,7 @@ class _PreviewPageState extends State { ), TextButton( onPressed: () async { + // copying the status to another directory String? path = await FilesystemPicker.open( title: 'Save to folder', context: context, @@ -135,6 +140,7 @@ class _PreviewPageState extends State { } } +/// this plays the status class StatusPlayer extends StatefulWidget { final File file; const StatusPlayer({Key? key, required this.file}) : super(key: key); @@ -216,6 +222,8 @@ class _StatusPlayerState extends State { } } +/// this shows the position of the video +/// and also you can seek to a specific position class PlayerPosIndicator extends StatefulWidget { final VideoPlayerController controller; const PlayerPosIndicator({Key? key, required this.controller}) diff --git a/lib/pages/statuses_page.dart b/lib/pages/statuses_page.dart index ed3f2f7..879d262 100644 --- a/lib/pages/statuses_page.dart +++ b/lib/pages/statuses_page.dart @@ -4,14 +4,18 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:whatskit/widgets/status_card.dart'; import 'package:device_info_plus/device_info_plus.dart'; -class Permissions { +/// this is like a middleware +/// different android versions use different type of permissions for the same thing +/// in this case android 11 needs the manageExternalStorage permission to work +class _Permissions { late AndroidDeviceInfo _androidDeviceInfo; int? androidVersion; - Permissions() { + _Permissions() { _init(); } + /// returns ```true``` if the permission is granted Future get isGranted async { if (androidVersion == null) { _androidDeviceInfo = await DeviceInfoPlugin().androidInfo; @@ -24,6 +28,7 @@ class Permissions { } } + /// requests permissions if they have not been granted Future requestIfNotFound() async { if (await isGranted == false) { return await requestPermission(); @@ -32,10 +37,12 @@ class Permissions { } } + /// requests permissions Future requestPermission() async { return await Permission.manageExternalStorage.request(); } + /// async init function of the class Future _init() async { _androidDeviceInfo = await DeviceInfoPlugin().androidInfo; androidVersion = int.parse(_androidDeviceInfo.version.release ?? '0'); @@ -43,6 +50,7 @@ class Permissions { } } +/// page that shows the statuses class StatusesPage extends StatefulWidget { const StatusesPage({Key? key}) : super(key: key); @@ -53,13 +61,18 @@ class StatusesPage extends StatefulWidget { class _StatusesPageState extends State { bool? isWhatsappInstalled; bool? isPermissionGranted; + + /// list of statuses List _statusFiles = []; - final Permissions _permissions = Permissions(); + final _Permissions _permissions = _Permissions(); + // returns a list of Status files from the statuses dir Future> getFileList() async { + /// dir that has all the statuses Directory dir = Directory( '/storage/emulated/0/Android/media/com.whatsapp/Whatsapp/Media/.Statuses/'); List fileList = await dir.list(recursive: false).toList(); + // we only wants the media files return fileList.where((element) { return element.path.endsWith('.mp4') || element.path.endsWith('.jpg'); }).toList(); @@ -95,10 +108,13 @@ class _StatusesPageState extends State { @override Widget build(BuildContext context) { if (isWhatsappInstalled == null || isPermissionGranted == null) { + // showing a loading circle while everything is verifying return const Center( child: CircularProgressIndicator(), ); } else if (!isPermissionGranted!) { + // if we dont have the permission + // we display a screen so the user can grant it return GestureDetector( onTap: () async { PermissionStatus permissionGranted = @@ -134,6 +150,7 @@ class _StatusesPageState extends State { ), ); } else if (isWhatsappInstalled == false) { + // the screen that we show if the whatsapp isn't installed return Scaffold( body: Center( child: Column( @@ -154,12 +171,14 @@ class _StatusesPageState extends State { ), ); } else { + // if every thing is oky we show the page return Scaffold( body: RefreshIndicator( backgroundColor: Theme.of(context).scaffoldBackgroundColor, onRefresh: _init, child: _statusFiles.isEmpty ? Column( + // when there are no statuses mainAxisAlignment: MainAxisAlignment.center, children: const [ Icon( diff --git a/lib/pages/trimmed_video_page.dart b/lib/pages/trimmed_video_page.dart index b6b761c..92534c5 100644 --- a/lib/pages/trimmed_video_page.dart +++ b/lib/pages/trimmed_video_page.dart @@ -3,9 +3,16 @@ import 'package:flutter/material.dart'; import 'package:share_plus/share_plus.dart'; import 'package:video_trimmer/video_trimmer.dart'; +/// shows the trimmed version of a video +/// also users can manually trim the video and share it individually class TrimmedVideoPage extends StatefulWidget { + /// the position which the trim starts final Duration start; + + /// the position which the trim ends final Duration end; + + /// video file we are trimming final File video; const TrimmedVideoPage({ Key? key, @@ -19,9 +26,16 @@ class TrimmedVideoPage extends StatefulWidget { } class _TrimmedVideoPageState extends State { + /// trimmer controller Trimmer? _trimmer; + + /// whether the video is being played or not bool? _isPlaying; + + /// the starting position of the trim in milliseconds double? _trimStart; + + /// the ending position of the trim in milliseconds double? _trimEnd; @override @@ -30,6 +44,7 @@ class _TrimmedVideoPageState extends State { super.initState(); } + /// async init function of the class Future _init() async { _trimmer = Trimmer(); await _trimmer!.loadVideo(videoFile: widget.video); @@ -162,9 +177,15 @@ class _TrimmedVideoPageState extends State { } } +/// indicating the position of the video class TrimmerPosIndicator extends StatefulWidget { + /// the trimmer controller final Trimmer trimmer; + + /// the starting position of the trim in milliseconds final double trimStart; + + /// the ending position of the trim in milliseconds final double trimEnd; const TrimmerPosIndicator({ Key? key, @@ -178,8 +199,14 @@ class TrimmerPosIndicator extends StatefulWidget { } class _TrimmerPosIndicatorState extends State { + /// minimum value of the slider double min = 0; + + /// maximum value of the slider double max = 100; + + /// the current value of the slider + /// position of the video double position = 0; void _update() { diff --git a/lib/pages/video_timmer.dart b/lib/pages/video_timmer.dart index 26a6174..e5379f9 100644 --- a/lib/pages/video_timmer.dart +++ b/lib/pages/video_timmer.dart @@ -6,6 +6,7 @@ import 'package:video_player/video_player.dart'; import 'package:whatskit/pages/trimmed_video_page.dart'; import 'package:video_trimmer/video_trimmer.dart'; +/// the page where you can see the parts of trimmed videos class VideoTrimmerPage extends StatefulWidget { const VideoTrimmerPage({Key? key}) : super(key: key); @@ -14,7 +15,10 @@ class VideoTrimmerPage extends StatefulWidget { } class _VideoTrimmerPageState extends State { + /// to pick the video file final ImagePicker _picker = ImagePicker(); + + /// video file we are trimming XFile? _video; @override @@ -62,31 +66,48 @@ class _VideoTrimmerPageState extends State { } return SafeArea( - child: TrimPreviews( + child: _TrimPreviews( video: File(_video!.path), ), ); } } -class TrimPreviews extends StatefulWidget { +/// previews of the autoMatically trimmed trims +class _TrimPreviews extends StatefulWidget { final File video; - const TrimPreviews({Key? key, required this.video}) : super(key: key); + const _TrimPreviews({Key? key, required this.video}) : super(key: key); @override - State createState() => _TrimPreviewsState(); + State<_TrimPreviews> createState() => _TrimPreviewsState(); } +/// sharingState of trims +/// - none : nothing is going on +/// - rendering : rendering the clips +/// - sharing : sharing the clips enum SharingState { + /// nothing is happening none, + + /// sharing state is rendering rendering, + + /// sharing the rendered clips sharing, } -class _TrimPreviewsState extends State { +class _TrimPreviewsState extends State<_TrimPreviews> { + /// full duration of the video Duration? _fullVidDuration; + + /// trimmed videos clips of the full video List>? _trimmedVids; + + /// list of paths of exported clips List? _exportedVids; + + /// sharing state of the clips SharingState _sharingState = SharingState.none; @override @@ -100,6 +121,7 @@ class _TrimPreviewsState extends State { } Future _init() async { + // getting the data of the video VideoPlayerController _vidPlayController = VideoPlayerController.file(widget.video); await _vidPlayController.initialize(); @@ -107,12 +129,17 @@ class _TrimPreviewsState extends State { await _vidPlayController.dispose(); } + /// get the list of starting and ending positions of trimmed videos + /// this will use 30 seconds as clips List> _getTrimmedVideos() { + /// list of trimmed video clips to be returned List> _trimmedVideos = []; for (int i = 0; i < _fullVidDuration!.inSeconds; i += 30) { _trimmedVideos.add({ 'start': Duration(seconds: i), 'end': Duration( + // if the last clip ending point is longer than the video duration we use the videoDuration as the ending position + // ex : last trim -- 30 seconds to 60 seconds but the video is only 58 seconds long seconds: i + 30 > _fullVidDuration!.inSeconds ? _fullVidDuration!.inSeconds : i + 30, @@ -125,6 +152,7 @@ class _TrimPreviewsState extends State { @override Widget build(BuildContext context) { if (_trimmedVids == null) { + // if videos havent trimmed yet return const Scaffold( body: Center( child: CircularProgressIndicator(), @@ -225,7 +253,6 @@ class _TrimPreviewsState extends State { TrimmedVidCard( start: _trimmedVids![i]['start']!, end: _trimmedVids![i]['end']!, - duration: _fullVidDuration!, video: widget.video, ), ], @@ -257,16 +284,20 @@ class _TrimPreviewsState extends State { } } +/// video card that shows the starting and ending position of the video class TrimmedVidCard extends StatelessWidget { + /// starting position of the clip final Duration start; + + /// ending position of the clip final Duration end; - final Duration duration; + + /// video file final File video; const TrimmedVidCard({ Key? key, required this.start, required this.end, - required this.duration, required this.video, }) : super(key: key); diff --git a/lib/provider/theme_provider.dart b/lib/provider/theme_provider.dart index 0111c15..3471f78 100644 --- a/lib/provider/theme_provider.dart +++ b/lib/provider/theme_provider.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; +/// providing the theme mode class ThemeProvider extends ChangeNotifier { ThemeProvider() { _init(); } - ThemeMode themeMode = ThemeMode.system; + /// the theme mode + ThemeMode _themeMode = ThemeMode.system; // theme by default late final SharedPreferences _prefs; void _init() async { @@ -19,6 +21,7 @@ class ThemeProvider extends ChangeNotifier { } } + /// sets the theme mode from a string void fromString(String themeModeStr) { assert( themeModeStr == 'system' || @@ -28,22 +31,24 @@ class ThemeProvider extends ChangeNotifier { ); switch (themeModeStr) { case 'system': - themeMode = ThemeMode.system; + _themeMode = ThemeMode.system; break; case 'light': - themeMode = ThemeMode.light; + _themeMode = ThemeMode.light; break; case 'dark': - themeMode = ThemeMode.dark; + _themeMode = ThemeMode.dark; break; } _prefs.setString('themeMode', themeModeStr); notifyListeners(); } + /// converts the theme mode to a string + /// usefull when saving the theme @override String toString() { - switch (themeMode) { + switch (_themeMode) { case ThemeMode.system: return 'system'; case ThemeMode.light: @@ -53,9 +58,12 @@ class ThemeProvider extends ChangeNotifier { } } - ThemeMode get getThemeMode => themeMode; + /// returns the current theme mode + ThemeMode get getThemeMode => _themeMode; + + /// sets the theme mode void changeThemeMode(ThemeMode theme) { - themeMode = theme; + _themeMode = theme; _prefs.setString('themeMode', toString()); notifyListeners(); } @@ -120,6 +128,7 @@ class AppThemes { ); } +/// generates a [MaterialColor] from a [Color] MaterialColor generateMaterialColorFromColor(Color color) { return MaterialColor(color.value, { 50: Color.fromRGBO(color.red, color.green, color.blue, 0.1), diff --git a/lib/widgets/status_card.dart b/lib/widgets/status_card.dart index 6024dd4..6d9917a 100644 --- a/lib/widgets/status_card.dart +++ b/lib/widgets/status_card.dart @@ -4,6 +4,7 @@ import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:flutter/material.dart'; import 'package:whatskit/pages/status_preview_page.dart'; +/// shows the Status as card that user can click on class WhatsappStatusCard extends StatelessWidget { final FileSystemEntity file; final double elevation; @@ -35,7 +36,7 @@ class WhatsappStatusCard extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (builder) => PreviewPage( + builder: (builder) => StatusPreviewPage( file: File(file.path), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index abe28c1..a1b7b6a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: whatskit -description: toolkit for whatsapp. +description: a toolkit for whatsapp. repository: https://github.com/netharuM/whatskit # The following line prevents the package from being accidentally published to @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.3+4 +version: 1.0.3+5 environment: sdk: ">=2.16.2 <3.0.0"