commit 8ec33980c6285c2bd7716139d525084f5befec98 Author: Agboola Onasanya Date: Wed Jan 1 00:35:25 2025 +0000 Messaging App Initial Commit Pages Already Implemented Login and Registeration Page diff --git a/README.md b/README.md new file mode 100644 index 0000000..a00d3cc --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# messaging_app + +A chat app built using Flutter for Both iOS and Android. + +Backend is being covered by Appwrite. It is opensource and can be selfhosted, which is the main reason for using is a backend. + + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# 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.dev/lints. + # + # 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 diff --git a/lib/components/constants.dart b/lib/components/constants.dart new file mode 100644 index 0000000..106f808 --- /dev/null +++ b/lib/components/constants.dart @@ -0,0 +1,11 @@ + + +//const endPoint = 'http://10.15.45.11/v1'; +const endPoint = 'https://api.theonasanyas.com/v1'; +const projectId = 'chatapp'; +const selfSigned = false; +const databaseId = '676d7bc5000ffc9ca2a4'; +const userCollectionId = '676d7d05000253c91d27'; +const chatsCollectionId = '67734e8b002936f64abd'; +const convCollectionId = '67734e7f00205b8f40a6'; +const imageStorageBucket = '6771b9000011f84c14fa'; \ No newline at end of file diff --git a/lib/components/messaging_provider.dart b/lib/components/messaging_provider.dart new file mode 100644 index 0000000..e5e59b7 --- /dev/null +++ b/lib/components/messaging_provider.dart @@ -0,0 +1,158 @@ +import 'dart:io'; + +import 'package:appwrite/appwrite.dart'; +import 'package:appwrite/models.dart' as models; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:messaging_app/components/constants.dart'; +import 'package:messaging_app/service/navigation_service.dart'; +import 'package:messaging_app/service/snackbar_service.dart'; + +enum AuthStatus { + notAuthenticated, + authenticated, + authenticating, + userNotFound, + error, +} + +class MessagingProviders extends ChangeNotifier { + // + Client client = Client(); + late final Account account; + late final Databases database; + late final Storage storage; + + // Auth Status + AuthStatus? status; + + //User Details + models.User? user; + models.Session? session; + + // var _email; + // var _name; + // var _imageURL; + // var _timeStamp; + + static MessagingProviders instance = MessagingProviders(); + + MessagingProviders() { + init(); + loadUser(); + } + + init() { + client.setEndpoint(endPoint).setProject(projectId).setSelfSigned(); + account = Account(client); + database = Databases(client); + storage = Storage(client); + } + + loadUser() async { + try { + user = await account.get(); + status = AuthStatus.authenticated; + } catch (e) { + status = AuthStatus.notAuthenticated; + } finally { + notifyListeners(); + } + } + + void loginWithEmailAndPassword(String _email, String _password) async { + // + status = AuthStatus.authenticating; + notifyListeners(); + try { + session = await account.createEmailPasswordSession( + email: _email, password: _password); + +// user = await account.get(); + status = AuthStatus.authenticated; + SnackBarService.instance.showSnackBarSuccess("Welcome! ${user!.name}"); + NavigationService.instance.navigateToReplacement("home"); + // account.deleteSession(sessionId: 'current'); + } on AppwriteException catch (e) { + SnackBarService.instance.showSnackBarError(e.message!); + status = AuthStatus.error; + user = null; + // + } on Exception catch (e) { + SnackBarService.instance.showSnackBarError(e.toString()); + // + } + notifyListeners(); + } + + Future createUser(String _email, String _password, + Future onSuccess(String _uid)) async { + try { + models.User _user = await account.create( + userId: ID.unique(), email: _email, password: _password); + + status = AuthStatus.authenticated; + await onSuccess(_user.$id); + SnackBarService.instance.showSnackBarSuccess("Welcome! ${_user.email}"); + NavigationService.instance.goBack(); + NavigationService.instance.navigateToReplacement("home"); + } on AppwriteException catch (e) { + status = AuthStatus.error; + user = null; + SnackBarService.instance.showSnackBarError(e.message!); + } on Exception catch (e) { + SnackBarService.instance.showSnackBarError(e.toString()); + // + } + notifyListeners(); + } + + Future createUserInDB( + String _documentId, String _email, String _name, String _imageURL) async { + try { + await database.createDocument( + databaseId: databaseId, + collectionId: userCollectionId, + documentId: _documentId, + data: { + "name": _name, + "email": _email, + "timeStamp": DateTime.now().toUtc().toIso8601String(), + "image": _imageURL + }); + + status = AuthStatus.authenticated; + } on AppwriteException catch (e) { + SnackBarService.instance.showSnackBarError(e.message!); + // + } on Exception catch (e) { + SnackBarService.instance.showSnackBarError(e.toString()); + // + } + notifyListeners(); + } + + uploadUserImage(XFile? file, String _uid) async { + try { + File _file = File(file!.path); + var _test = await storage.createFile( + bucketId: imageStorageBucket, + fileId: ID.unique(), + file: InputFile.fromPath(path: _file.path, filename: file.name), + permissions: [ + Permission.read(Role.any()), + ], + ); + + return "$endPoint/storage/buckets/${_test.bucketId}/files/${_test.$id}/view?project=$projectId"; + } on AppwriteException catch (e) { + SnackBarService.instance.showSnackBarError(e.message!); + print(e.message); + // + } on Exception catch (e) { + SnackBarService.instance.showSnackBarError(e.toString()); + // + } + notifyListeners(); + } +} diff --git a/lib/components/messaging_routes.dart b/lib/components/messaging_routes.dart new file mode 100644 index 0000000..fdf7661 --- /dev/null +++ b/lib/components/messaging_routes.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:messaging_app/pages/home_page.dart'; +import 'package:messaging_app/pages/login_page.dart'; +import 'package:messaging_app/pages/registeration_page.dart'; + +class MessagingRoutes { + // + static MessagingRoutes instance = MessagingRoutes(); + + Map get messagingRoutes { + return { + "login": (BuildContext _context) => LoginPage(), + "register": (BuildContext _context) => RegisterationPage(), + "home": (BuildContext _context) => HomePage(), + }; + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..434f0c1 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:messaging_app/components/messaging_routes.dart'; +import 'package:messaging_app/service/navigation_service.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + runApp(const MessagingApp()); +} + +class MessagingApp extends StatelessWidget { + const MessagingApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + + return MaterialApp( + title: 'Messaging App', + navigatorKey: NavigationService.instance.navigatorKey, + theme: ThemeData( + brightness: Brightness.dark, + scaffoldBackgroundColor: Colors.black, + primaryColor: Color.fromRGBO(42, 117, 188, 1.0), + colorScheme: ColorScheme.dark(), + useMaterial3: true, + ), + routes: MessagingRoutes.instance.messagingRoutes, + initialRoute: "login", + // home: RegisterationPage(), + ); + } + + +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart new file mode 100644 index 0000000..fcab073 --- /dev/null +++ b/lib/pages/home_page.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() { + // + return _HomePageState(); + } +} + +class _HomePageState extends State { + @override + Widget build(BuildContext context) { + // + return Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: AppBar( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + title: Text("Messaging App"), + titleTextStyle: TextStyle(fontSize: 15), + ), + ); + } +} diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart new file mode 100644 index 0000000..799d1eb --- /dev/null +++ b/lib/pages/login_page.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:messaging_app/components/messaging_provider.dart'; +import 'package:messaging_app/service/navigation_service.dart'; +import 'package:messaging_app/service/snackbar_service.dart'; +import 'package:provider/provider.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + State createState() { + return _LoginPageState(); + } +} + +class _LoginPageState extends State { + // + var _deviceHeight; + var _deviceWidth; + + String? _email; + String? _password; + + late GlobalKey _formKey; + late MessagingProviders _auth; + + _LoginPageState() { + _formKey = GlobalKey(); + } + + @override + Widget build(BuildContext context) { + // + _deviceHeight = MediaQuery.of(context).size.height; + _deviceWidth = MediaQuery.of(context).size.width; + + return Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + body: Align( + child: ChangeNotifierProvider.value( + value: MessagingProviders.instance, + child: _loginPageUI(), + ), + //_loginPageUI(), + ), + ); + } + + Widget _loginPageUI() { + return Builder(builder: (BuildContext _context) { + SnackBarService.instance.buildContext = _context; + _auth = Provider.of(_context); + return Container( + height: _deviceHeight * 0.60, + // color: Colors.red, + padding: EdgeInsets.symmetric(horizontal: _deviceWidth * 0.10), + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _headingWidget(), + _inputForm(), + _loginButton(), + _registerButton(), + ], + ), + ); + }); + } + + Widget _headingWidget() { + return SizedBox( + height: _deviceHeight * 0.12, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Welcome Back!', + style: TextStyle(fontSize: 35, fontWeight: FontWeight.w700), + ), + Text( + 'Please Login to your Account', + style: TextStyle(fontSize: 25, fontWeight: FontWeight.w200), + ) + ], + ), + ); + } + + Widget _inputForm() { + return SizedBox( + height: _deviceHeight * 0.16, + child: Form( + key: _formKey, + onChanged: () { + _formKey.currentState?.save(); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _emailTextField(), + _passwordTextField(), + ], + ), + ), + ); + } + + Widget _emailTextField() { + return TextFormField( + autocorrect: false, + style: TextStyle( + color: Colors.white, + ), + validator: (_input) { + return _input!.isNotEmpty && _input.contains("@") + ? null + : "Please enter a valid Email"; + }, + onSaved: (_input) { + setState(() { + _email = _input!; + }); + }, + cursorColor: Colors.white, + decoration: InputDecoration( + hintText: "Email Address", + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white))), + ); + } + + Widget _passwordTextField() { + return TextFormField( + autocorrect: false, + style: TextStyle( + color: Colors.white, + ), + validator: (_input) { + return _input!.isNotEmpty ? null : "Please enter a Password"; + }, + onSaved: (_input) { + setState(() { + _password = _input!; + }); + }, + obscureText: true, + cursorColor: Colors.white, + decoration: InputDecoration( + hintText: "Password", + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white))), + ); + } + + Widget _loginButton() { + return _auth.status == AuthStatus.authenticating + ? Align( + alignment: Alignment.center, + child: CircularProgressIndicator(), + ) + : SizedBox( + height: _deviceHeight * 0.06, + width: _deviceWidth, + child: MaterialButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + //Login User + _auth.loginWithEmailAndPassword(_email!, _password!); + } + }, + color: Colors.blue, + child: Text( + 'Login', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } + + Widget _registerButton() { + return GestureDetector( + onTap: () { + NavigationService.instance.navigateTo("register"); + }, + child: SizedBox( + height: _deviceHeight * 0.06, + width: _deviceWidth, + child: Text( + 'REGISTER', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } +} diff --git a/lib/pages/registeration_page.dart b/lib/pages/registeration_page.dart new file mode 100644 index 0000000..1d50035 --- /dev/null +++ b/lib/pages/registeration_page.dart @@ -0,0 +1,269 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:messaging_app/components/messaging_provider.dart'; +import 'package:messaging_app/service/media_service.dart'; +import 'package:messaging_app/service/navigation_service.dart'; +import 'package:messaging_app/service/snackbar_service.dart'; +import 'package:provider/provider.dart'; + +class RegisterationPage extends StatefulWidget { + const RegisterationPage({super.key}); + + @override + State createState() { + return _RegisterationPageState(); + } +} + +class _RegisterationPageState extends State { +// + var _deviceHeight; + var _deviceWidth; + XFile? _image; + String? _name; + String? _email; + String? _password; + + late GlobalKey _formKey; + late MessagingProviders _auth; + + _RegisterationPageState() { + _formKey = GlobalKey(); + } + + @override + Widget build(BuildContext context) { +// + _deviceHeight = MediaQuery.of(context).size.height; + _deviceWidth = MediaQuery.of(context).size.width; + + return Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + body: Container( + // color: Colors.blue, + alignment: Alignment.center, + child: ChangeNotifierProvider.value( + value: MessagingProviders.instance, + child: _registerationPageUI(), + ), + ), + ); + } + + Widget _registerationPageUI() { + return Builder(builder: (BuildContext _context) { + SnackBarService.instance.buildContext = _context; + _auth = Provider.of(_context); + return Container( + // color: Colors.deepOrange, + height: _deviceHeight * 0.75, + padding: EdgeInsets.symmetric(horizontal: _deviceWidth * 0.10), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _headingWidget(), + _inputForm(), + _registerButton(), + _backToLoginPage(), + ], + ), + ); + }); + } + + Widget _headingWidget() { + return SizedBox( + height: _deviceHeight * 0.12, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'SignUp to Enjoy!', + style: TextStyle(fontSize: 35, fontWeight: FontWeight.w700), + ), + Text( + 'Please enter your details', + style: TextStyle(fontSize: 25, fontWeight: FontWeight.w200), + ) + ], + ), + ); + } + + Widget _inputForm() { + return Container( + height: _deviceHeight * 0.35, + child: Form( + key: _formKey, + onChanged: () { + _formKey.currentState?.save(); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _imageSelectorWidgeet(), + _nameTextField(), + _emailTextField(), + _passwordTextField(), + ], + ), + ), + ); + } + + Widget _imageSelectorWidgeet() { + return Align( + child: GestureDetector( + onTap: () async { + XFile? _imageFile = await MediaService.instance.getImageFromLibrary(); + _imageFile!.name; + setState(() { + _image = _imageFile; + }); + }, + child: Container( + height: _deviceHeight * 0.10, + width: _deviceHeight * 0.10, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(500), + image: DecorationImage( + fit: BoxFit.cover, + image: _image != null + ? Image.file(File(_image!.path)).image + : NetworkImage('https://i.pravatar.cc/1000'), + // image: NetworkImage('https://cdn0.iconfinder.com/data/icons/occupation-002/64/programmer-programming-occupation-avatar-512.png'), + )), + ), + ), + ); + } + + Widget _nameTextField() { + return TextFormField( + autocorrect: false, + style: TextStyle( + color: Colors.white, + ), + validator: (_input) { + return _input!.isNotEmpty ? null : "Please enter a valid Name"; + }, + onSaved: (_input) { + setState(() { + _name = _input!; + }); + }, + cursorColor: Colors.white, + decoration: InputDecoration( + hintText: "Name", + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white))), + ); + } + + Widget _emailTextField() { + return TextFormField( + autocorrect: false, + style: TextStyle( + color: Colors.white, + ), + validator: (_input) { + return _input!.isNotEmpty && _input.contains("@") + ? null + : "Please enter a valid Email"; + }, + onSaved: (_input) { + setState(() { + _email = _input!; + }); + }, + cursorColor: Colors.white, + decoration: InputDecoration( + hintText: "Email", + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white))), + ); + } + + Widget _passwordTextField() { + return TextFormField( + autocorrect: false, + style: TextStyle( + color: Colors.white, + ), + validator: (_input) { + return _input!.isNotEmpty ? null : "Please enter a Password"; + }, + onSaved: (_input) { + setState(() { + _password = _input!; + }); + }, + obscureText: true, + cursorColor: Colors.white, + decoration: InputDecoration( + hintText: "Password", + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white))), + ); + } + + Widget _registerButton() { + return _auth.status == AuthStatus.authenticating + ? Align( + child: CircularProgressIndicator(), + ) + : SizedBox( + height: _deviceHeight * 0.06, + width: _deviceWidth, + child: MaterialButton( + onPressed: () { + if (_formKey.currentState!.validate() && _image != null) { + // + _auth.createUser(_email!, _password!, (String _uid) async { + var _url = await _auth.uploadUserImage(_image, _uid); + _auth.createUserInDB(_uid, _email!, _name!, _url); + if (_auth.status == AuthStatus.authenticated) { + _auth.loginWithEmailAndPassword(_email!, _password!); + } + // + }); + //Login User + // _auth.loginWithEmailAndPassword(_email!, _password!); + } + }, + color: Colors.blue, + child: Text( + 'Register', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } + + Widget _backToLoginPage() { + return GestureDetector( + onTap: () { + NavigationService.instance.goBack(); + }, + child: Container( + height: _deviceHeight * 0.06, + width: _deviceWidth, + child: Icon( + Icons.arrow_back, + size: 40, + ), + ), + ); + } +} diff --git a/lib/service/media_service.dart b/lib/service/media_service.dart new file mode 100644 index 0000000..29c7799 --- /dev/null +++ b/lib/service/media_service.dart @@ -0,0 +1,11 @@ +import 'package:image_picker/image_picker.dart'; + +class MediaService { + // + static MediaService instance = MediaService(); + + Future getImageFromLibrary() async { + ImagePicker _picker = ImagePicker(); + return await _picker.pickImage(source: ImageSource.gallery); + } +} diff --git a/lib/service/navigation_service.dart b/lib/service/navigation_service.dart new file mode 100644 index 0000000..113be00 --- /dev/null +++ b/lib/service/navigation_service.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + // + static NavigationService instance = NavigationService(); + + GlobalKey? navigatorKey; + + NavigationService() { + navigatorKey = GlobalKey(); + } + + Future? navigateToReplacement(String _routeName) { + return navigatorKey!.currentState!.pushReplacementNamed(_routeName); + } + + Future? navigateTo(String _routeName) { + return navigatorKey!.currentState!.pushNamed(_routeName); + } + + Future? navigateToRoute(MaterialPageRoute _route) { + return navigatorKey!.currentState!.push(_route); + } + + // Manually Call Function + goBack() { + return navigatorKey!.currentState!.pop(); + } +} diff --git a/lib/service/snackbar_service.dart b/lib/service/snackbar_service.dart new file mode 100644 index 0000000..e7cfc18 --- /dev/null +++ b/lib/service/snackbar_service.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +class SnackBarService { + // + + BuildContext? _buildContext; + static SnackBarService instance = SnackBarService(); + + SnackBarService() {} + + set buildContext(BuildContext _context) { + _buildContext = _context; + } + + void showSnackBarError(String _message) { + ScaffoldMessenger.of(_buildContext!).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + content: Text( + _message, + style: TextStyle( + color: Colors.white, + fontSize: 15, + ), + ), + backgroundColor: Colors.red, + ), + ); + } + + void showSnackBarSuccess(String _message) { + ScaffoldMessenger.of(_buildContext!).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + content: Text( + _message, + style: TextStyle( + color: Colors.white, + fontSize: 15, + ), + ), + backgroundColor: Colors.green, + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..01fec15 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,94 @@ +name: messaging_app +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.6.0 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + appwrite: 13.0.0 + timeago: ^3.7.0 + flutter_spinkit: ^5.2.1 + provider: ^6.1.2 + image_picker: ^1.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..7dd52b5 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:messaging_app/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}