diff --git a/assets/icons/ic_face_id.png b/assets/icons/ic_face_id.png new file mode 100644 index 0000000..913e850 Binary files /dev/null and b/assets/icons/ic_face_id.png differ diff --git a/assets/icons/ic_fingerprint.png b/assets/icons/ic_fingerprint.png new file mode 100644 index 0000000..bf73197 Binary files /dev/null and b/assets/icons/ic_fingerprint.png differ diff --git a/assets/icons/ic_sms.png b/assets/icons/ic_sms.png new file mode 100644 index 0000000..aa72e0e Binary files /dev/null and b/assets/icons/ic_sms.png differ diff --git a/assets/icons/ic_whatsapp.png b/assets/icons/ic_whatsapp.png new file mode 100644 index 0000000..3f36403 Binary files /dev/null and b/assets/icons/ic_whatsapp.png differ diff --git a/assets/images/bn_map.png b/assets/images/bn_map.png new file mode 100644 index 0000000..b04b5aa Binary files /dev/null and b/assets/images/bn_map.png differ diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/lib/config/app_provider.dart b/lib/config/app_provider.dart new file mode 100644 index 0000000..6f11d24 --- /dev/null +++ b/lib/config/app_provider.dart @@ -0,0 +1,19 @@ +import 'package:mohem_flutter_app/provider/counter.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class AppProvider extends StatelessWidget { + final Widget child; + + AppProvider({required this.child}); + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => Counter()), + ], + child: child, + ); + } +} diff --git a/lib/config/background_loader.dart b/lib/config/background_loader.dart new file mode 100644 index 0000000..0683157 --- /dev/null +++ b/lib/config/background_loader.dart @@ -0,0 +1,34 @@ +//class which loads components "in the background", i.e. ui does not depend on it + +import 'package:mohem_flutter_app/services/shared_preferences.dart'; +import 'package:injector/injector.dart'; +//import 'package:revocheckapp/services/firebase_service.dart'; + + +class BackgroundLoader { + Future loadBackgroundData() async { + //init notification setting + try { + /* + final isPromotionNotificationEnabled = await Injector.appInstance + .getDependency() + .promotionNotificationsEnabled; + if (isPromotionNotificationEnabled == null) { + await Injector.appInstance + .getDependency() + .setPromotionNotificationEnabled(true); + Injector.appInstance + .getDependency() + .subscribeForPromotions(); + } */ + } catch (_) { + //something wend wrong, set it to true + await Injector.appInstance + .getDependency() + .setPromotionNotificationEnabled(true); + /*Injector.appInstance + .getDependency() + .subscribeForPromotions();*/ + } + } +} diff --git a/lib/config/constants.dart b/lib/config/constants.dart new file mode 100644 index 0000000..87c2c46 --- /dev/null +++ b/lib/config/constants.dart @@ -0,0 +1,8 @@ +enum YesOrNo { + no, + yes, +} + +const String icons = "assets/icons/"; +const String categorySvgIcons = "assets/category/svg/"; +const String svgIcons = "assets/svg/"; diff --git a/lib/config/dependencies.dart b/lib/config/dependencies.dart new file mode 100644 index 0000000..c9b7074 --- /dev/null +++ b/lib/config/dependencies.dart @@ -0,0 +1,38 @@ +// import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +// import 'package:flutter/material.dart'; + +import 'package:mohem_flutter_app/repo/account_repository.dart'; +import 'package:injector/injector.dart'; + +import 'background_loader.dart'; + + +class AppDependencies { + static void addDependencies() { + Injector injector = Injector.appInstance; + + //add dependencies as needed + injector.registerSingleton(() => AcRepository()); + + // injector.registerSingleton((injector) => AcRepository()); + + _addCrashlytics(); + _loadBackgroundTasksNonBlocking(); + } + + static void _addCrashlytics() { + // Set `enableInDevMode` to true to see reports while in debug mode + // This is only to be used for confirming that reports are being + // submitted as expected. It is not intended to be used for everyday + // development. + //Crashlytics.instance.enableInDevMode = true; + + // Pass all uncaught errors from the framework to Crashlytics. + // FlutterError.onError = Crashlytics.instance.recordFlutterError; + } + + static void _loadBackgroundTasksNonBlocking() { + final backgroundLoader = BackgroundLoader(); + backgroundLoader.loadBackgroundData(); + } +} diff --git a/lib/config/localization.dart b/lib/config/localization.dart new file mode 100644 index 0000000..5d7acdf --- /dev/null +++ b/lib/config/localization.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; + +class AppLocalizations { + static const Iterable> localizationsDelegates = + [ + // ... app-specific localization delegate[s] here + // S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate + ]; + + static const List supportedLocales = [ + const Locale("en", "US") + ]; +} diff --git a/lib/config/routes.dart b/lib/config/routes.dart new file mode 100644 index 0000000..33c8eb0 --- /dev/null +++ b/lib/config/routes.dart @@ -0,0 +1,46 @@ +import 'package:mohem_flutter_app/pages/dashboard/dashboard_page.dart'; +import 'package:mohem_flutter_app/pages/user/complete_profile_page.dart'; +import 'package:mohem_flutter_app/pages/user/forget_password_page.dart'; +import 'package:mohem_flutter_app/pages/user/login_verification_page.dart'; +import 'package:mohem_flutter_app/pages/user/login_verify_account_page.dart'; +import 'package:mohem_flutter_app/pages/user/profile/profile_1_page.dart'; +import 'package:mohem_flutter_app/pages/user/profile/profile_2_page.dart'; +import 'package:mohem_flutter_app/pages/user/profile/profile_3_page.dart'; +import 'package:mohem_flutter_app/pages/user/register_page.dart'; +import 'package:mohem_flutter_app/pages/user/register_selection_page.dart'; +import 'package:mohem_flutter_app/pages/user/splash_page.dart'; +import 'package:flutter/material.dart'; + +class AppRoutes { + //User + static final String splash = "/splash"; + static final String registerSelection = "/registerSelection"; + static final String loginVerifyAccount = "/loginVerifyAccount"; + static final String register = "/register"; + static final String forgetPassword = "/forgetPassword"; + static final String loginVerification = "/loginVerification"; + static final String dashboard = "/dashboard"; + static final String completeProfile = "/completeProfile"; + static final String profile1 = "/profile1"; + static final String profile2 = "/profile2"; + static final String profile3 = "/profile3"; + + + + static final String initialRoute = splash; + + static final Map routes = { + //User + splash: (context) => SplashPage(), + registerSelection: (context) => RegisterSelectionPage(), + loginVerifyAccount: (context) => LoginVerifyAccountPage(), + register: (context) => RegisterPage(), + forgetPassword: (context) => ForgetPasswordPage(), + loginVerification: (context) => LoginVerificationPage(), + dashboard: (context) => DashboardPage(), + completeProfile: (context) => CompleteProfilePage(), + profile1: (context) => Profile1Page(), + profile2: (context) => Profile2Page(), + profile3: (context) => Profile3Page(), + }; +} diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart new file mode 100644 index 0000000..1157a89 --- /dev/null +++ b/lib/generated/codegen_loader.g.dart @@ -0,0 +1,94 @@ +// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart + +// ignore_for_file: prefer_single_quotes + +import 'dart:ui'; + +import 'package:easy_localization/easy_localization.dart' show AssetLoader; + +class CodegenLoader extends AssetLoader{ + const CodegenLoader(); + + @override + Future> load(String fullPath, Locale locale ) { + return Future.value(mapLocales[locale.toString()]); + } + + static const Map en = { + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +}; +static const Map en_US = { + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +}; +static const Map> mapLocales = {"en": en, "en_US": en_US}; +} diff --git a/lib/main.dart b/lib/main.dart index 202509b..df63e0d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,115 +1,50 @@ +import 'package:mohem_flutter_app/config/app_provider.dart'; +import 'package:mohem_flutter_app/config/dependencies.dart'; +import 'package:mohem_flutter_app/theme/app_theme.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); +import 'package:sizer/sizer.dart'; + +import 'config/localization.dart'; +import 'config/routes.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await EasyLocalization.ensureInitialized(); + runApp( + EasyLocalization( + supportedLocales: [ + Locale('en', 'US'), + ], + path: 'resources/langs', + child: MyApp(), + ), + ); } class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); + MyApp() { + AppDependencies.addDependencies(); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), + return AppProvider( + child: Sizer( + builder: (context, orientation, deviceType) { + return MaterialApp( + theme: AppTheme.getTheme(), + debugShowCheckedModeBanner: false, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + initialRoute: AppRoutes.initialRoute, + routes: AppRoutes.routes, + ); + }, ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } diff --git a/lib/models/account.dart b/lib/models/account.dart new file mode 100644 index 0000000..830b62f --- /dev/null +++ b/lib/models/account.dart @@ -0,0 +1,43 @@ +// To parse this JSON data, do +// +// final account = accountFromJson(jsonString); + +import 'dart:convert'; + +import 'package:mohem_flutter_app/models/parent_list.dart'; + + + +Account accountFromJson(String str) => Account.fromJson(json.decode(str)); + +String accountToJson(Account data) => json.encode(data.toJson()); + +class Account { + Account({ + required this.parentList, + required this.selectedItem, + }); + + List? parentList; + int selectedItem; + + factory Account.fromJson(Map json) => Account( + parentList: json["parentList"] == null + ? null + : List.from( + json["parentList"].map((x) => ParentList.fromJson(x))), + selectedItem: + json["selectedItem"] == null ? null : json["selectedItem"], + ); + + Map toJson() => { + "parentList": parentList == null + ? null + : List.from(parentList!.map((x) => x.toJson())), + "selectedItem": selectedItem == null ? null : selectedItem, + }; + + Map toJsonData() => { + "selectedItem": selectedItem == null ? null : selectedItem, + }; +} diff --git a/lib/models/config_model.dart b/lib/models/config_model.dart new file mode 100644 index 0000000..0245ede --- /dev/null +++ b/lib/models/config_model.dart @@ -0,0 +1,12 @@ +class ConfigModel { + ConfigModel(this.endpoint, this.organizationName); + + String endpoint; + + String organizationName; + + factory ConfigModel.fromJson(Map json) => + ConfigModel("", ""); + +// Map toJson() => _$ConfigModelToJson(this); +} diff --git a/lib/models/parent_list.dart b/lib/models/parent_list.dart new file mode 100644 index 0000000..3f2286f --- /dev/null +++ b/lib/models/parent_list.dart @@ -0,0 +1,26 @@ +class ParentList { + ParentList({ + required this.dbId, + required this.text, + required this.path, + required this.isSelected, + }); + + int dbId; + String text; + String path; + bool isSelected; + + factory ParentList.fromJson(Map json) => ParentList( + dbId: json["dbId"] == null ? null : json["dbId"], + text: json["text"] == null ? null : json["text"], + path: json["path"] == null ? null : json["path"], + isSelected: false, + ); + + Map toJson() => { + "dbId": dbId == null ? null : dbId, + "text": text == null ? null : text, + "path": path == null ? null : path, + }; +} diff --git a/lib/models/response_models.dart b/lib/models/response_models.dart new file mode 100644 index 0000000..872893b --- /dev/null +++ b/lib/models/response_models.dart @@ -0,0 +1,34 @@ +/// +/// This example was taken from +/// https://flutter.dev/docs/development/data-and-backend/json +/// + +/// This allows the `User` class to access private members in +/// the generated file. The value for this is *.g.dart, where +/// the star denotes the source file name. + +/// An annotation for the code generator to know that this class needs the +/// JSON serialization logic to be generated. + +class BackendResponse { + BackendResponse({required this.id, required this.isOk, required this.result}); + + int id; + bool isOk; + dynamic result; + + /// A necessary factory constructor for creating a new User instance + /// from a map. Pass the map to the generated `_$UserFromJson()` constructor. + /// The constructor is named after the source class, in this case, User. + factory BackendResponse.fromJson(Map json) => + BackendResponse( + id: 1, + isOk: false, + result: null, + ); +// +// /// `toJson` is the convention for a class to declare support for serialization +// /// to JSON. The implementation simply calls the private, generated +// /// helper method `_$UserToJson`. +// Map toJson() => _$BackendResponseToJson(this); +} diff --git a/lib/models/user.dart b/lib/models/user.dart new file mode 100644 index 0000000..e09c282 --- /dev/null +++ b/lib/models/user.dart @@ -0,0 +1,9 @@ +class User { + int id; + + User(this.id, this.userName, this.userImage, this.createdDate); + + String userName; + String userImage; + String createdDate; +} diff --git a/lib/pages/a.dart b/lib/pages/a.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/pages/user/splash_page.dart b/lib/pages/user/splash_page.dart new file mode 100644 index 0000000..c593f56 --- /dev/null +++ b/lib/pages/user/splash_page.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/utils/navigator.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class SplashPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + mFlex(5), + Txt( + "Logo", + fontSize: 45, + bold: true, + ), + mFlex(3), + Txt( + "First Time Log In", + txtType: TxtType.heading1, + isFlatButton: true, + onTap: () { + navigateWithName(context, AppRoutes.registerSelection); + }, + ), + mFlex(1), + Txt( + "Already Signed Up and Logged In", + txtType: TxtType.heading1, + isFlatButton: true, + onTap: () { + navigateWithName(context, AppRoutes.loginVerification); + }, + ), + mFlex(5), + ], + ), + ), + ); + } +} diff --git a/lib/provider/counter.dart b/lib/provider/counter.dart new file mode 100644 index 0000000..0272327 --- /dev/null +++ b/lib/provider/counter.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class Counter with ChangeNotifier, DiagnosticableTreeMixin { + int _count = 0; + + int get count => _count; + + void increment() { + _count++; + notifyListeners(); + } + + /// Makes `Counter` readable inside the devtools by listing all of its properties + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IntProperty('count', count)); + } +} \ No newline at end of file diff --git a/lib/repo/account_repository.dart b/lib/repo/account_repository.dart new file mode 100644 index 0000000..770cbb4 --- /dev/null +++ b/lib/repo/account_repository.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:mohem_flutter_app/models/account.dart'; +import 'package:mohem_flutter_app/models/response_models.dart'; +import 'package:mohem_flutter_app/services/backend_service.dart'; +import 'package:injector/injector.dart'; + +abstract class IAcRepository { + Future getAccountList(); + + Future updateAccount(String dataAsJson); +} + +class AcRepository implements IAcRepository { + static const String ACCOUNT_API_CONTROLLER_MOBILE = + "AccountApiControllerMobile/"; + + static const String ACCOUNT_LIST = ACCOUNT_API_CONTROLLER_MOBILE + "list"; + static const String UPDATE_LIST = + ACCOUNT_API_CONTROLLER_MOBILE + "saveaccountselected"; + + @override + Future getAccountList() async { + BackendResponse response = await Injector.appInstance + .getDependency() + .getAuthenticatedAPI(ACCOUNT_LIST); + + if (response != null && response.isOk) { + return Account.fromJson(response.result); + } else { + throw Exception(); + } + } + + @override + Future updateAccount(String dataAsJson) async { + BackendResponse response = await Injector.appInstance + .getDependency() + .postAuthenticatedAPI(UPDATE_LIST, dataAsJson); + + if (response != null && response.isOk) { + //if parsing failed, throw exception + return response; + } else { + throw Exception(); + } + } +} diff --git a/lib/services/backend_service.dart b/lib/services/backend_service.dart new file mode 100644 index 0000000..b2e4de0 --- /dev/null +++ b/lib/services/backend_service.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:mohem_flutter_app/models/response_models.dart'; +import 'package:mohem_flutter_app/services/secure_storage.dart'; +import 'package:http/http.dart'; +import 'package:injector/injector.dart'; + +import 'http_service.dart'; +import 'network_service.dart'; + +abstract class IBackendApiService { + Future getUnauthenticatedAPI(String route); + + Future getAuthenticatedAPI(String route); + + Future postUnauthenticatedAPI( + String route, String dataAsJson); + + Future postAuthenticatedAPI(String route, String dataAsJson); + + Future deleteAuthenticatedAPI(String route, String id); +} + +class BackendApiService implements IBackendApiService { + static String _homeUrl = "https://check.revotec.eu/check2/"; + static String _serverApiBaseUrl = _homeUrl + "mapi/v1/"; + + static String get homeUrl => _homeUrl; + + final ISecureStorage _secureStorage = + Injector.appInstance.getDependency(); + final IHttpService _httpService = + Injector.appInstance.getDependency(); + + ///internal helper functions which executes the given api call + ///and wraps the response + Future _callApi(Future callback) async { + Response response; + try { + //execute future + response = await callback; + //check response code, and if not ok return isOk = false + //200 for Get + //201 for Post + + // print("res121: " + + // response.statusCode.toString() + + // " Body:" + + // response.body.toString()); + //if delete request sent so server is returning 204 in case of success. + if (response.statusCode == 204) + return BackendResponse(id: 1, isOk: true, result: null); + + if (response.statusCode != 200 && response.statusCode != 201) + return BackendResponse(id: -1, isOk: false, result: null); + //if response code is good then parse message and return parsed response + return BackendResponse.fromJson(json.decode(response.body)); + //return BackendResponse.fromJson(dioResponse.body); + } catch (e) { + return BackendResponse(id: -1, isOk: false, result: null); + // try { + // return BackendResponse.fromJson(json.decode(response.body)); + // } catch (e) { + // return BackendResponse(id:-1, isOk:false,result: null); + // } + } + } + + @override + Future getAuthenticatedAPI(String route) async { + await checkConnection(); + final token = await _secureStorage.readBearerToken(); + return _callApi(_httpService.get(_serverApiBaseUrl + route, headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*', + HttpHeaders.authorizationHeader: "Bearer $token" + })); + } + + @override + Future getUnauthenticatedAPI(String route) async { + await checkConnection(); + return _callApi(_httpService.get(_serverApiBaseUrl + route, + headers: {'Content-Type': 'application/json', 'Accept': '*/*'})); + } + + @override + Future postAuthenticatedAPI( + String route, String dataAsJson) async { + await checkConnection(); + final token = await _secureStorage.readBearerToken(); + // print("res121: " + _serverApiBaseUrl + route); + return _callApi(_httpService + .post(_serverApiBaseUrl + route, body: dataAsJson, headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*', + HttpHeaders.authorizationHeader: "Bearer $token" + })); + } + + @override + Future postUnauthenticatedAPI( + String route, String dataAsJson) async { + await checkConnection(); + return _callApi(_httpService.post(_serverApiBaseUrl + route, + body: dataAsJson, headers: {'Content-Type': 'application/json'})); + } + + Future checkConnection() async { + if (!(await Injector.appInstance + .getDependency() + .isHostAvailable(_homeUrl))) throw NetworkException(); + } + + @override + Future deleteAuthenticatedAPI( + String route, String id) async { + await checkConnection(); + final token = await _secureStorage.readBearerToken(); + return _callApi( + _httpService.delete(_serverApiBaseUrl + route + "/" + id, headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*', + HttpHeaders.authorizationHeader: "Bearer $token" + })); + } +} diff --git a/lib/services/firebase_service.dart b/lib/services/firebase_service.dart new file mode 100644 index 0000000..ad76959 --- /dev/null +++ b/lib/services/firebase_service.dart @@ -0,0 +1,180 @@ +/* +import 'dart:io' show Platform; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +abstract class IFirebaseService { + Future get token; + + Future backgroundMessageHandler(Map message); + + Future messageHandler(Map message); + + Future onLaunch(Map message); + + Future onResume(Map message); + + void subscribeForPromotions(); + + void unsubscribeFromPromotions(); +} + +//https://medium.com/@SebastianEngel/easy-push-notifications-with-flutter-and-firebase-cloud-messaging-d96084f5954f +class FirebaseService implements IFirebaseService { + FirebaseMessaging _firebaseMessaging; + + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; + + FirebaseService() {. + _firebaseMessaging = FirebaseMessaging(); + + //https://github.com/FirebaseExtended/flutterfire/issues/1695 + _firebaseMessaging.configure( + onMessage: messageHandler, + onBackgroundMessage: + Platform.isAndroid ? myBackgroundMessageHandler : null, + onLaunch: onLaunch, + onResume: onResume, + ); + + //monitor firebase token changes + //https://firebase.google.com/docs/cloud-messaging/android/client#sample-register + ///The registration token may change when: + // + //The app deletes Instance ID + //The app is restored on a new device + //The user uninstalls/reinstall the app + //The user clears app data. + /// + + //for the first release we don't care about token refreshes + /*Stream fcmStream = _firebaseMessaging.onTokenRefresh; + fcmStream.listen((token) { + + });*/ + + //ios specific settings + //taken from https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_messaging/example/lib/main.dart + _firebaseMessaging.requestNotificationPermissions( + const IosNotificationSettings( + sound: true, badge: true, alert: true, provisional: true)); + _firebaseMessaging.onIosSettingsRegistered + .listen((IosNotificationSettings settings) { + print("Settings registered: $settings"); + }); + + var initializationSettingsAndroid = + AndroidInitializationSettings('app_icon'); + var initializationSettingsIOS = + IOSInitializationSettings(onDidReceiveLocalNotification: onDidReceiveLocalNotification); + var initializationSettings = InitializationSettings( + initializationSettingsAndroid, initializationSettingsIOS); + flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + flutterLocalNotificationsPlugin.initialize(initializationSettings, + onSelectNotification: selectNotification); + } + + Future onDidReceiveLocalNotification(int id, String title, String body, String payload) async{ + var androidPlatformChannelSpecifics = AndroidNotificationDetails( + 'new_message_channel_id', + 'Neue Nachricht', + 'Channel für neue Nachrichten', + importance: Importance.Max, + priority: Priority.High, + ticker: 'ticker'); + var iOSPlatformChannelSpecifics = IOSNotificationDetails(); + var platformChannelSpecifics = NotificationDetails( + androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show( + 0, + title, + body, + platformChannelSpecifics); + } + + @override + Future backgroundMessageHandler(Map message) async { + await myBackgroundMessageHandler(message); + } + + @override + Future messageHandler(Map message) async { + print("onMessage: $message"); + +// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + + var androidPlatformChannelSpecifics = AndroidNotificationDetails( + 'new_message_channel_id', + 'Neue Nachricht', + 'Channel für neue Nachrichten', + importance: Importance.Max, + priority: Priority.High, + ticker: 'ticker'); + var iOSPlatformChannelSpecifics = IOSNotificationDetails(); + var platformChannelSpecifics = NotificationDetails( + androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); + + if(Platform.isAndroid) { + await flutterLocalNotificationsPlugin.show( + 0, + message["notification"]["title"], + message["notification"]["body"], + platformChannelSpecifics); + }else if(Platform.isIOS){ + await flutterLocalNotificationsPlugin.show( + 0, + message["aps"]["alert"]["title"], + message["aps"]["alert"]["body"], + platformChannelSpecifics); + } + } + + Future selectNotification(String payload) async { + if (payload != null) { + debugPrint('notification payload: ' + payload); + } + } + + @override + Future onLaunch(Map message) async { + print("onLaunch: $message"); + } + + @override + Future onResume(Map message) async { + print("onResume: $message"); + } + + @override + Future get token => _firebaseMessaging.getToken(); + + @override + void subscribeForPromotions() { + _firebaseMessaging.subscribeToTopic("promotions"); + } + + @override + void unsubscribeFromPromotions() { + _firebaseMessaging.unsubscribeFromTopic("promotions"); + } +} + +Future myBackgroundMessageHandler(Map message) { + debugPrint("BACKGROUND MESSAGE RECEIVED"); + print("BACKGROUND MESSAGE RECEIVED"); + return Future.value(() => true); + + /*if (message.containsKey('data')) { + // Handle data message + final dynamic data = message['data']; + } + + if (message.containsKey('notification')) { + // Handle notification message + final dynamic notification = message['notification']; + }*/ + + // Or do other work. +} +*/ \ No newline at end of file diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart new file mode 100644 index 0000000..ff99ed0 --- /dev/null +++ b/lib/services/http_service.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:async'; + + +import 'package:http/http.dart' as http; +import 'package:http/http.dart'; +import 'package:path/path.dart'; +import 'package:injector/injector.dart'; + +abstract class IHttpService { + Future post(url, + {Map headers, body, Encoding encoding}); + + Future get(url, {Map headers}); + + Future delete(url, {Map headers}); +} + +class HttpService implements IHttpService { + @override + Future delete(url, {Map? headers}) { + return http.delete(url, headers: headers); + } + + @override + Future get(url, {Map? headers}) { + return http.get(url, headers: headers); + } + + @override + Future post(url, + {Map? headers, body, Encoding? encoding}) { + return http.post(url, headers: headers, body: body, encoding: encoding); + } +} diff --git a/lib/services/media_service.dart b/lib/services/media_service.dart new file mode 100644 index 0000000..0762d7f --- /dev/null +++ b/lib/services/media_service.dart @@ -0,0 +1,26 @@ +// import 'dart:io'; +// +// import 'package:image_picker/image_picker.dart'; +// +// abstract class IMediaService { +// Future takePicture(); +// +// Future openImageFromGallery(); +// } +// +// class MediaService implements IMediaService { +// @override +// Future openImageFromGallery() async { +// final pickedFile = +// await ImagePicker().getImage(source: ImageSource.gallery); +// if (pickedFile == null) return null; +// return File(pickedFile.path); +// } +// +// @override +// Future takePicture() async { +// final pickedFile = await ImagePicker().getImage(source: ImageSource.camera); +// if (pickedFile == null) return null; +// return File(pickedFile.path); +// } +// } diff --git a/lib/services/network_service.dart b/lib/services/network_service.dart new file mode 100644 index 0000000..50a861d --- /dev/null +++ b/lib/services/network_service.dart @@ -0,0 +1,25 @@ + +import 'dart:io'; + +abstract class INetworkService { + Future isHostAvailable(String endpoint); +} + +class NetworkService implements INetworkService{ + @override + Future isHostAvailable(String endpoint) async { + try { + final result = await InternetAddress.lookup(endpoint.substring(endpoint.indexOf('//')+2).substring(0,endpoint.substring(endpoint.indexOf('//')+2).indexOf('/'))); + if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { + return true; + } else{ + return false; + } + } on SocketException catch (_) { + return false; + } + } +} + +class NetworkException implements Exception { +} diff --git a/lib/services/secure_storage.dart b/lib/services/secure_storage.dart new file mode 100644 index 0000000..7622f7f --- /dev/null +++ b/lib/services/secure_storage.dart @@ -0,0 +1,33 @@ +abstract class ISecureStorage { + Future readBearerToken(); + + Future clearUserCredentials(); +} + +class SecureStorage implements ISecureStorage { + ///return bearer token if present, or null if not + @override + Future readBearerToken() async { + try { + return ""; + } catch (_) { + //an error occured returning null + return ""; + } + } + + ///returns true if write was successful, false otherwise + @override + Future writeBearerToken(String token) async { + try { + await ""; + return true; + } catch (_) { + //an error occured returning false + return false; + } + } + + @override + Future clearUserCredentials() async {} +} diff --git a/lib/services/shared_preferences.dart b/lib/services/shared_preferences.dart new file mode 100644 index 0000000..890a616 --- /dev/null +++ b/lib/services/shared_preferences.dart @@ -0,0 +1,119 @@ +import 'dart:convert'; + +import 'package:mohem_flutter_app/models/config_model.dart'; + +import 'package:shared_preferences/shared_preferences.dart' +as SharedPrefsPlugin; + +/// +/// Taken from AlarmGuide Project +/// + +abstract class ISharedPreferences { + Future get authState; + + Future setAuthState(int authState); + + Future get configState; + + Future setConfigState(int confState); + + Future get config; + + Future setConfig(ConfigModel config); + + Future get promotionNotificationsEnabled; + + Future setPromotionNotificationEnabled(bool newSetting); + + Future get helpAlreadyShown; + + Future setHelpAlreadyShown(); + + Future get useS3; + + Future setUseS3(int value); +} + +class SharedPreferences implements ISharedPreferences { + static const String _AUTH_STATE_KEY = "auth_key"; + static const String _CONFIG_KEY = "config"; + static const String _CONFIG_STATE_KEY = "config_key"; + static const String _PROMOTION_NOTIFICATION_KEY = "promotion"; + static const String _HELP_ALREADY_SHOWN = "help_shown"; + static const String _USE_S3 = "s3"; + + @override + Future get authState async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getInt(_AUTH_STATE_KEY); + } + + @override + Future setAuthState(int authState) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setInt(_AUTH_STATE_KEY, authState); + } + + @override + Future get config async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + final configAsJson = sharedPrefs.getString(_CONFIG_KEY); + return ConfigModel.fromJson(jsonDecode(configAsJson!)); + } + + @override + Future setConfig(ConfigModel config) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setString(_CONFIG_KEY, jsonEncode(config)); + setConfigState(1); + } + + @override + Future get promotionNotificationsEnabled async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getBool(_PROMOTION_NOTIFICATION_KEY); + } + + @override + Future setPromotionNotificationEnabled(bool newSetting) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setBool(_PROMOTION_NOTIFICATION_KEY, newSetting); + } + + @override + Future get helpAlreadyShown async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getBool(_HELP_ALREADY_SHOWN); + } + + @override + Future setHelpAlreadyShown() async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setBool(_HELP_ALREADY_SHOWN, true); + } + + @override + Future get configState async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getInt(_CONFIG_STATE_KEY); + } + + @override + Future setConfigState(int confState) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setInt(_CONFIG_STATE_KEY, confState); + } + + @override + Future setUseS3(int value) async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + sharedPrefs.setInt(_USE_S3, value); + } + + @override + Future get useS3 async { + final sharedPrefs = await SharedPrefsPlugin.SharedPreferences.getInstance(); + return sharedPrefs.getInt(_USE_S3); + } +} diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart new file mode 100644 index 0000000..5202825 --- /dev/null +++ b/lib/theme/app_theme.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 'colors.dart'; + +class AppTheme { + static getTheme() => ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primaryColor: primaryColor, + primarySwatch: Colors.blue, + backgroundColor: Colors.white, + primaryTextTheme: TextTheme( + headline6: TextStyle(color: Colors.white), + ), + ); +} + +extension ExtendedRevoCheckTheme on TextTheme { + //add custom styles and colors here + //taken from https://medium.com/@crizantlai/flutter-how-to-extend-themedata-b5b987a95bb5 + TextStyle get price => const TextStyle(color: Colors.redAccent); +} diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart new file mode 100644 index 0000000..8510500 --- /dev/null +++ b/lib/theme/colors.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +const Color primaryColor = Colors.white; +const Color accentColor = Colors.blue; +const Color appBackgroundColor = Colors.white; +Color? accentColorDark = Colors.green[800]; +const Color borderColor = Colors.blueGrey; +Color? borderLightColor = Colors.blueGrey[50]; +Color backgroudColor = + Colors.blueGrey[50]!.withOpacity(0.5) ?? Colors.transparent; +const Color iconColor = Colors.blueGrey; +Color? headingColor = Colors.blueGrey[800]; +Color? txtColor = Colors.blueGrey[500]; diff --git a/lib/utils/AppPermissionHandler.dart b/lib/utils/AppPermissionHandler.dart new file mode 100644 index 0000000..3e3877e --- /dev/null +++ b/lib/utils/AppPermissionHandler.dart @@ -0,0 +1,36 @@ +import 'package:permission_handler/permission_handler.dart'; + +import 'dialogs.dart'; + +enum ConfirmAction { CANCEL, ACCEPT } + +Future requestPermissionGranted( + context, Permission requestPermissions) async { + var result = await requestPermissions.request(); + + switch (result) { + case PermissionStatus.granted: + // Application has been given permission to use the feature. + return true; + case PermissionStatus.denied: + // Application has been denied permission to use the feature. + return false; + case PermissionStatus.permanentlyDenied: + ConfirmAction? res = await showConfirmDialogs( + context, + 'You was denied Permission. You have give manual permission from app setting. ', + 'Open App Setting', + 'Cancel'); + if (res == ConfirmAction.ACCEPT) { + return false; + } else if (res == ConfirmAction.CANCEL) { + return false; + } + return false; + case PermissionStatus.restricted: + // iOS has restricted access to a specific feature. + return false; + default: + return false; + } +} diff --git a/lib/utils/dialogs.dart b/lib/utils/dialogs.dart new file mode 100644 index 0000000..a6bff7e --- /dev/null +++ b/lib/utils/dialogs.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import 'AppPermissionHandler.dart'; + +Future showConfirmDialogs( + context, msg, positiveText, negativeText) async { + return showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + backgroundColor: Colors.white, + title: Text(msg, style: TextStyle(fontSize: 16)), + actions: [ + TextButton( + child: Text( + negativeText, + ), + onPressed: () { + Navigator.of(context).pop(ConfirmAction.CANCEL); + }, + ), + TextButton( + child: Text( + positiveText, + ), + onPressed: () { + Navigator.of(context).pop(ConfirmAction.ACCEPT); + }, + ) + ], + ); + }, + ); +} diff --git a/lib/utils/navigator.dart b/lib/utils/navigator.dart new file mode 100644 index 0000000..f9855a9 --- /dev/null +++ b/lib/utils/navigator.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +navigateWithName(BuildContext context, String routeName, {Object? arguments}) { + Navigator.pushNamed(context, routeName, arguments: arguments); +} + +pop(BuildContext context) { + Navigator.of(context).pop(); +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart new file mode 100644 index 0000000..e0b0f03 --- /dev/null +++ b/lib/utils/utils.dart @@ -0,0 +1,311 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:mohem_flutter_app/config/constants.dart'; +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import 'package:sizer/sizer.dart'; + +Color getColorFromHex(String hexColor) { + hexColor = hexColor.toUpperCase().replaceAll('#', ''); + + if (hexColor.length == 6) { + hexColor = 'FF' + hexColor; + } + + return Color(int.parse(hexColor, radix: 16)); +} + +Widget spacerVertical(double v) { + return Container( + height: v, + width: double.infinity, + ); +} +Future delay(int millis) async { + return await Future.delayed(Duration(milliseconds: millis)); +} + +inkWellCorner({double? r}) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular(r ?? 4), + ); +} + +Widget spacerHorizontal(double v) { + return Container( + height: v, + width: v, + ); +} + +Widget mHeight(double f) { + return Container( + width: f, + height: f, + ); +} + +Widget mDivider(Color color, {double? h}) { + return Container( + width: double.infinity, + height: h ?? 1, + color: color, + ); +} + +Widget mDivider3({double? h}) { + return Container( + width: double.infinity, + height: h ?? 1, + color: borderLightColor!.withOpacity(0.7) ?? Colors.transparent, + ); +} + +Widget mDivider2(Color color, double w) { + return Container( + width: w, + height: 1, + color: color, + ); +} + +InputDecoration txtField(String label) { + return new InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + hintText: label, + hintStyle: TextStyle(color: Colors.grey), + disabledBorder: InputBorder.none, + isDense: false, + contentPadding: EdgeInsets.only(left: 15, right: 15), + ); +} + +Widget mWidth(double f) { + return Container( + width: f, + height: f, + ); +} + +Widget mFlex(int f) { + return Flexible( + flex: f, + child: Container( + width: double.infinity, + height: double.infinity, + ), + ); +} +Widget mExp(int f) { + return Expanded( + flex: f, + child: Container( + width: double.infinity, + ), + ); +} + +spacer() { + return SizedBox( + height: 8, + ); +} + +Widget floatButton(String icon, {Color? color, required Function onClick, String? title}) { + return Padding( + padding: const EdgeInsets.only(top: 12, bottom: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FloatingActionButton( + onPressed: () { + onClick(); + }, + heroTag: icon, + backgroundColor: accentColor, + elevation: 4, + child: Container( + child: SvgPicture.asset( + categorySvgIcons + icon, + color: color, + ), + width: double.infinity, + height: double.infinity, + decoration: containerRadius(Colors.white, 200), + clipBehavior: Clip.antiAlias, + padding: EdgeInsets.all(15), + margin: EdgeInsets.all(1), + ), + ), + if (title != null) mHeight(2.w), + if (title != null) + Txt( + title, + fontSize: 12.sp, + bold: true, + color: headingColor, + ) + ], + ), + ); +} + +navigateTo(context, page) { + Navigator.push(context, MaterialPageRoute(builder: (context) => page)); +} + +circularImage(String im, double width, double height) { + return new Container(width: 190.0, height: 190.0, decoration: new BoxDecoration(shape: BoxShape.circle, image: new DecorationImage(fit: BoxFit.fill, image: new AssetImage(im)))); +} + +circularImage2(String im, double width, double height) { + return new Container(width: width, height: height, decoration: new BoxDecoration(shape: BoxShape.circle, image: new DecorationImage(fit: BoxFit.fill, image: new AssetImage(im)))); +} + +cardRadius(double radius) { + return RoundedRectangleBorder( + side: BorderSide(color: Colors.transparent, width: 1), + borderRadius: BorderRadius.circular(radius), + ); +} + +cardRadiusWithoutBorder(double radius) { + return RoundedRectangleBorder( + side: BorderSide(color: Colors.transparent, width: 1), + borderRadius: BorderRadius.circular(radius), + ); +} + +Image imageFromBase64String(String base64String) { + return Image.memory(base64Decode(base64String)); +} + +Uint8List dataFromBase64String(String base64String) { + return base64Decode(base64String); +} + +String base64String(Uint8List data) { + return base64Encode(data); +} + +Widget overLayWidget({double? width, double? height,List? color}) { + return Container( + width: width ?? double.infinity, + height: height ?? 60, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: color!=null?color:[ + Colors.black.withOpacity(0.2), + Colors.black.withOpacity(0.1), + Colors.black.withOpacity(0.004), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + tileMode: TileMode.clamp, + ), + ), + ); +} + +Decoration containerRadius(Color color, double r) { + return BoxDecoration( + color: color, + borderRadius: BorderRadius.all(Radius.circular(r)), + ); +} + +Decoration containerRadiusTop({Color? color, double? r}) { + return BoxDecoration( + color: color ?? Colors.white, + borderRadius: BorderRadius.only(topRight: Radius.circular(r ?? 12), topLeft: Radius.circular(r ?? 12)), + ); +} + +Decoration containerRadiusBorder(Color color, double r) { + return BoxDecoration( + color: Colors.transparent, + border: Border.all(color: color, width: 1), + borderRadius: BorderRadius.all(Radius.circular(r)), + ); +} + +Decoration containerRadiusBottom(Color color, double r) { + return BoxDecoration( + color: color, + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(r), bottomRight: Radius.circular(r)), + ); +} + +ShapeBorder cardRadiusTop(double radius) { + return RoundedRectangleBorder( + side: BorderSide(color: Colors.transparent, width: 0), + borderRadius: BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)), + ); +} + +Decoration containerColorRadiusBorderWidth(Color background, double radius, Color color, double w) { + return BoxDecoration( + color: background, + border: Border.all( + width: w, // + color: color // <--- border width here + ), + borderRadius: BorderRadius.circular(radius), + ); +} + +ShapeBorder cardRadiusTop2(double radius) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)), + ); +} + +ShapeBorder cardRadiusBottom(double radius) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(radius), bottomRight: Radius.circular(radius)), + ); +} + +Decoration containerColorRadiusBorder(Color background, double radius, Color color) { + return BoxDecoration( + color: background, + border: Border.all( + width: 1, // + color: color // <--- border width here + ), + borderRadius: BorderRadius.circular(radius), + ); +} + + + + + + +//Decoration appGradient = BoxDecoration( +// gradient: LinearGradient( +// colors: [ +// Colors.green[200], +// Colors.green, +// ], +// begin: Alignment.topCenter, +// end: Alignment.bottomCenter, +// ), +//); +// launchURL(String url) async { +// if (await canLaunch(url)) { +// await launch(url); +// } else { +// throw 'Could not launch $url'; +// } +// } diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart new file mode 100644 index 0000000..3ad0b80 --- /dev/null +++ b/lib/widgets/app_bar.dart @@ -0,0 +1,28 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; + +AppBar appBar({ + Color? backgroundColor, + double? elevation, + String? title, + Color? titleColor, + bool? isTitleCenter, + Color? backIconColor, + List? actions, +}) { + return AppBar( + backgroundColor: backgroundColor ?? appBackgroundColor, + elevation: elevation ?? 0, + centerTitle: isTitleCenter ?? true, + iconTheme: IconThemeData( + color: backIconColor ?? Colors.black, //change your color here + ), + actions: actions, + title: Txt( + title ?? "", + txtType: TxtType.appBar, + ), + ); +} diff --git a/lib/widgets/blurry_container.dart b/lib/widgets/blurry_container.dart new file mode 100644 index 0000000..8bebc4a --- /dev/null +++ b/lib/widgets/blurry_container.dart @@ -0,0 +1,62 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +const kDemoText = Center( + child: Text( + '', + style: TextStyle( + fontSize: 25, + color: Colors.white, + letterSpacing: 2, + ), + textAlign: TextAlign.center, + ), +); +const double kBlur = 1.0; +const EdgeInsetsGeometry kDefaultPadding = EdgeInsets.all(16); +const Color kDefaultColor = Colors.transparent; +const BorderRadius kBorderRadius = BorderRadius.all(Radius.circular(20)); +const double kColorOpacity = 0.0; + +class BlurryContainer extends StatelessWidget { + final Widget child; + final double blur; + final double? height, width; + final EdgeInsetsGeometry padding; + final Color bgColor; + + final BorderRadius borderRadius; + + //final double colorOpacity; + + BlurryContainer({ + this.child = kDemoText, + this.blur = 5, + required this.height, + required this.width, + this.padding = kDefaultPadding, + this.bgColor = kDefaultColor, + this.borderRadius = kBorderRadius, + //this.colorOpacity = kColorOpacity, + }); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: borderRadius, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), + child: Container( + height: height!, + width: width!, + padding: padding, + color: bgColor == Colors.transparent + ? bgColor + : bgColor.withOpacity(0.5), + child: child, + ), + ), + ); + } +} diff --git a/lib/widgets/button/show_circular_button.dart b/lib/widgets/button/show_circular_button.dart new file mode 100644 index 0000000..c2bd90c --- /dev/null +++ b/lib/widgets/button/show_circular_button.dart @@ -0,0 +1,23 @@ +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class ShowCircularButton extends StatelessWidget { + VoidCallback onPressed; + IconData? iconData; + ShowCircularButton({this.iconData,required this.onPressed}); + @override + Widget build(BuildContext context) { + return Card( + shape: cardRadius(1000), + color: Colors.black.withOpacity(0.2), + margin: EdgeInsets.all(12), + child: IconButton( + onPressed: onPressed, + icon: Icon( + iconData?? Icons.amp_stories_outlined, + color: Colors.white, + ), + ), + ); + } +} diff --git a/lib/widgets/button/show_image_button.dart b/lib/widgets/button/show_image_button.dart new file mode 100644 index 0000000..8e60d11 --- /dev/null +++ b/lib/widgets/button/show_image_button.dart @@ -0,0 +1,43 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +import '../txt.dart'; + +class ShowImageButton extends StatelessWidget { + String icon, title; + VoidCallback onClick; + + ShowImageButton( + {required this.icon, required this.title, required this.onClick}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onClick, + child: Container( + width: double.infinity, + child: Column( + children: [ + Container( + width: double.infinity, + height: 120, + color: accentColor, + padding: EdgeInsets.all(30), + child: Image.asset( + icon, + color: Colors.white, + ), + ), + mHeight(12), + Txt( + title, + txtType: TxtType.heading2, + color: Colors.blue, + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/dialog/dialogs.dart b/lib/widgets/dialog/dialogs.dart new file mode 100644 index 0000000..9a5a822 --- /dev/null +++ b/lib/widgets/dialog/dialogs.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +void showMDialog( + context, { + Widget? child, +}) async { + return showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + return Dialog( + child: child, + ); + }, + ); +} diff --git a/lib/widgets/dialog/message_dialog.dart b/lib/widgets/dialog/message_dialog.dart new file mode 100644 index 0000000..4181370 --- /dev/null +++ b/lib/widgets/dialog/message_dialog.dart @@ -0,0 +1,38 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/navigator.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:mohem_flutter_app/widgets/show_fill_button.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class MessageDialog extends StatelessWidget { + String? title, buttonTitle; + VoidCallback? onClick; + MessageDialog({this.title, this.buttonTitle,this.onClick}); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + padding: EdgeInsets.all(30), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Txt( + title ?? "message", + txtType: TxtType.heading3, + ), + mHeight(40), + ShowFillButton( + title: buttonTitle ?? "Continue", + width: double.infinity, + onPressed: () { + onClick!(); + }, + ) + ], + ), + ); + } +} diff --git a/lib/widgets/dialog/otp_dialog.dart b/lib/widgets/dialog/otp_dialog.dart new file mode 100644 index 0000000..3790c44 --- /dev/null +++ b/lib/widgets/dialog/otp_dialog.dart @@ -0,0 +1,74 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/navigator.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:mohem_flutter_app/widgets/show_fill_button.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class OtpDialog extends StatelessWidget { + VoidCallback onClick; + + OtpDialog({required this.onClick}); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + padding: EdgeInsets.all(30), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Txt( + "Please insert OTP Code", + txtType: TxtType.heading3, + ), + mHeight(20), + Row( + children: [ + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + mWidth(12), + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + mWidth(12), + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + mWidth(12), + Expanded( + child: Container( + width: double.infinity, + height: 60, + color: accentColor.withOpacity(0.3), + ), + ), + ], + ), + mHeight(40), + ShowFillButton( + title: "Check Code", + width: double.infinity, + onPressed: () { + onClick(); + }, + ) + ], + ), + ); + } +} diff --git a/lib/widgets/dragable_sheet.dart b/lib/widgets/dragable_sheet.dart new file mode 100644 index 0000000..4d91118 --- /dev/null +++ b/lib/widgets/dragable_sheet.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +showDraggableDialog(BuildContext context, Widget child) { + showGeneralDialog( + barrierLabel: "Label", + barrierDismissible: false, + barrierColor: Colors.black.withOpacity(0.2), + transitionDuration: Duration(milliseconds: 200), + context: context, + pageBuilder: (context, anim1, anim2) { + return Dismissible( + direction: DismissDirection.vertical, + key: const Key('key'), + onDismissed: (_) => Navigator.of(context).pop(), + child: child, + ); + }, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1), + child: child, + ); + }, + ); +} diff --git a/lib/widgets/dropdown/dropdow_field.dart b/lib/widgets/dropdown/dropdow_field.dart new file mode 100644 index 0000000..091b6f6 --- /dev/null +++ b/lib/widgets/dropdown/dropdow_field.dart @@ -0,0 +1,78 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; + +import '../txt.dart'; + +class DropdownField extends StatefulWidget { + String? hint; + List? list; + + DropdownField({this.hint, this.list}); + + @override + State createState() => _DropdownFieldState(); +} + +class _DropdownFieldState extends State { + String? dropdownValue; + + @override + Widget build(BuildContext context) { + return Container( + decoration: containerColorRadiusBorderWidth( + Colors.transparent, + 4, + borderColor, + 0.5, + ), + margin: EdgeInsets.all(2), + padding: EdgeInsets.only(left: 8, right: 8), + child: DropdownButton( + value: dropdownValue, + icon: const Icon(Icons.keyboard_arrow_down_sharp), + elevation: 16, + iconSize: 16, + iconEnabledColor: borderColor, + iconDisabledColor: borderColor, + isExpanded: true, + style: const TextStyle(color: Colors.black), + hint: Txt( + widget.hint ?? "", + txtType: TxtType.heading1, + bold: false, + color: borderColor, + ), + underline: Container( + height: 0, + ), + onChanged: (String? newValue) { + setState(() { + dropdownValue = newValue!; + }); + }, + items: (widget.list ?? + [ + 'One', + 'Two', + 'Free', + 'Four', + ]) + .map>( + (String value) { + return DropdownMenuItem( + value: value, + child: Txt( + value, + txtType: TxtType.heading1, + bold: false, + ), + ); + }, + ).toList(), + ), + ); + } +} diff --git a/lib/widgets/dropdown/dropdown_text.dart b/lib/widgets/dropdown/dropdown_text.dart new file mode 100644 index 0000000..4031a98 --- /dev/null +++ b/lib/widgets/dropdown/dropdown_text.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mohem_flutter_app/config/constants.dart'; +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; + +class DropDownText extends StatelessWidget { + String title; + + DropDownText(this.title); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Txt( + title, + txtType: TxtType.heading3, + color: accentColor, + ), + mWidth(16), + SvgPicture.asset( + svgIcons + "ic_arrow_down.svg", + width: 10, + height: 10, + ), + ], + ); + } +} diff --git a/lib/widgets/extensions/extensions_widget.dart b/lib/widgets/extensions/extensions_widget.dart new file mode 100644 index 0000000..caaaba9 --- /dev/null +++ b/lib/widgets/extensions/extensions_widget.dart @@ -0,0 +1,235 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +extension ExtendedText on Widget { + showOverlay({double? width, double? height}){ + return Container( + width: width ?? double.infinity, + height: height ?? 60, + decoration: BoxDecoration( + gradient: LinearGradient(colors: [ + Colors.black.withOpacity(0.2), + Colors.black.withOpacity(0.1), + Colors.black.withOpacity(0.004), + ], begin: Alignment.topCenter, end: Alignment.bottomCenter, tileMode: TileMode.clamp), + ), + child: this, + ); + } +} + + + + + +extension ImageExt on Image { + Widget circular() { + return ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(100), + child: this, + ); + } +} + +extension WidgetExt on Widget { + Widget toWidget() { + return this as Widget; + } + + Widget sized({double? width, double? height}) { + return SizedBox( + width: width, + height: height, + child: this, + ); + } + Widget inkWell({required VoidCallback onTap,double radius=0}){ + return InkWell( + child: Material(child: this,color: Colors.transparent,), + onTap: onTap, + ); + } + Widget sizeSq(double size) { + return SizedBox( + width: size, + height: size, + child: this, + ); + } + + Widget fillParent({double hFactor = 1, double? vFactor}) { + return FractionallySizedBox( + heightFactor: null, + widthFactor: hFactor, + child: this, + ); + } + + Widget margin( + {double top = 0, double bottom = 0, double left = 0, double right = 0}) { + var pad = + EdgeInsets.only(top: top, left: left, bottom: bottom, right: right); + try { + (this as dynamic).margin = pad; + } catch (err) { + return Padding( + padding: pad, + child: this, + ); + } + return this; + } + + Widget toClip({double r = 40}) { + return ClipRRect( + borderRadius: BorderRadius.all( + Radius.circular(r), + ), + clipBehavior: Clip.antiAlias, + child: this, + ); + } + + // Widget scrollable({Axis? direction, bool fadingEdge = true}) { + // var scrollview = SingleChildScrollView( + // child: this, + // controller: ScrollController(), + // scrollDirection: direction ?? Axis.vertical, + // ); + // return fadingEdge ? scrollview.fadingEdge() : scrollview; + // } + + Widget expand() { + return Expanded( + child: this, + ); + } + + Widget align(Alignment alignment) { + return Align( + alignment: alignment, + child: this, + ); + } + + Widget center() { + return Center( + child: this, + ); + } + + Widget padding(EdgeInsets padding) { + return Padding( + padding: padding, + child: this, + ); + } + + Widget paddingAll(double padding) { + return Padding( + padding: EdgeInsets.all(padding), + child: this, + ); + } + + // Widget onTap(VoidCallback onTap, {double corners = 0}) { + // return Clickable.widget(child: this, corners: corners, onTap: onTap); + // } + + Widget ignoreInteraction() { + return IgnorePointer( + child: this, + ); + } +} + +// extension xScrollView on ScrollView { +// Widget fadingEdge() => FadingEdgeScrollView.fromScrollView( +// child: this, +// gradientFractionOnEnd: 0.08, +// gradientFractionOnStart: 0.08, +// shouldDisposeScrollController: true, +// ); +// } +// +// extension x2ScrollView on SingleChildScrollView { +// Widget fadingEdge() => FadingEdgeScrollView.fromSingleChildScrollView( +// child: this, +// gradientFractionOnEnd: 0.08, +// gradientFractionOnStart: 0.08, +// shouldDisposeScrollController: true, +// ); +// } + +class Position { + final double x, y, w, h; + + Position(this.x, this.y, this.w, this.h); +} + +extension KeyExt on GlobalKey { + Position globalPosition() { + RenderBox box = currentContext!.findRenderObject() as RenderBox; + Offset xy = box.localToGlobal(Offset.zero); + Size wh = box.size; + return Position(xy.dx, xy.dy, wh.width, wh.height); + } +} + +extension StateExt on State { + reload({VoidCallback? b}) { + setState(b ?? () {}); + } +} + +// extension LocatiionExt on Location { +// LatLng toLatLng() { +// return LatLng(lat!, lng!); +// } +// } + +// extension xList on List { +// T randomItem() { +// final random = new Random(); +// var i = random.nextInt(this.length); +// return this[i]; +// } +// +// List append(List items) { +// this.addAll(items); +// return this; +// } +// +// bool isFirst(T item) => first == item; +// +// bool isLast(T item) => last == item; +// +// getOneByOne( +// {required int delayMillis, +// required Function(T) callback, VoidCallback? complete}) async { +// for (var i in this) { +// await delay(delayMillis); +// callback(i); +// } +// complete!(); +// } +// } + +extension xFuture on Future { + thenWithDelay(int millis, Function(T) thenBlock) { + then((value) { + Future.delayed(Duration(milliseconds: millis)) + .then((value) => thenBlock(value)); + }); + } +} + +extension xDouble on int { + Duration durationMillis() => Duration(milliseconds: this); + + Duration durationSec() => Duration(seconds: this); + + Duration durationHour() => Duration(hours: this); +} diff --git a/lib/widgets/gradient_app_bar.dart b/lib/widgets/gradient_app_bar.dart new file mode 100644 index 0000000..c3ded30 --- /dev/null +++ b/lib/widgets/gradient_app_bar.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +class GradientAppBar extends StatelessWidget { + final String title; + final double barHeight = 50.0; + IconData? iconData; + + GradientAppBar(this.title, {this.iconData}); + + @override + Widget build(BuildContext context) { + final double statusbarHeight = MediaQuery.of(context).padding.top; + + return new Container( + padding: EdgeInsets.only(top: statusbarHeight), + height: statusbarHeight + barHeight, + child: Row( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), + ), + Expanded( + child: Center( + child: Text( + title, + style: TextStyle( + fontSize: 20.0, + color: Colors.white, + fontWeight: FontWeight.bold), + ), + ), + ), + if (iconData != null) + IconButton( + onPressed: () {}, + icon: Icon( + iconData, + color: Colors.white, + ), + ), + ], + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withOpacity(0.4), + Colors.black.withOpacity(0.2), + Colors.black.withOpacity(0.0001), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + tileMode: TileMode.clamp, + ), + ), + ); + } +} diff --git a/lib/widgets/images/circular_image.dart b/lib/widgets/images/circular_image.dart new file mode 100644 index 0000000..dd64efa --- /dev/null +++ b/lib/widgets/images/circular_image.dart @@ -0,0 +1,30 @@ +import 'package:mohem_flutter_app/config/constants.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class CircularImage extends StatelessWidget { + double? w, h, padding; + String? image; + + CircularImage({this.w, this.h, this.image, this.padding}); + + @override + Widget build(BuildContext context) { + return Container( + width: w ?? 120, + height: h ?? 120, + child: Card( + shape: cardRadius(2000), + clipBehavior: Clip.antiAlias, + elevation: 4, + child: Card( + shape: cardRadius(2000), + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.all(padding ?? 0), + child: Image.asset(image ?? icons + "green.jpg"), + ), + ), + ); + } +} diff --git a/lib/widgets/show_card_buttton.dart b/lib/widgets/show_card_buttton.dart new file mode 100644 index 0000000..7b9ac38 --- /dev/null +++ b/lib/widgets/show_card_buttton.dart @@ -0,0 +1,32 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/widgets/show_fill_button.dart'; +import 'package:flutter/material.dart'; + +class ShowCardButton extends StatelessWidget { + String title; + VoidCallback onPressed; + Color txtColor; + + ShowCardButton({ + required this.title, + required this.onPressed, + this.txtColor = Colors.white, + }); + + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.zero, + elevation: 20, + child: Container( + width: double.infinity, + padding: EdgeInsets.all(12), + child: ShowFillButton( + title: title, + onPressed: onPressed, + txtColor: txtColor, + ), + ), + ); + } +} diff --git a/lib/widgets/show_fill_button.dart b/lib/widgets/show_fill_button.dart new file mode 100644 index 0000000..168521e --- /dev/null +++ b/lib/widgets/show_fill_button.dart @@ -0,0 +1,41 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; + +class ShowFillButton extends StatelessWidget { + String title; + VoidCallback onPressed; + Color txtColor; + double elevation, radius,width; + + ShowFillButton({ + required this.title, + required this.onPressed, + this.txtColor = Colors.white, + this.elevation = 4, + this.radius = 6, + this.width=88, + }); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + onPrimary: Colors.black87, + primary: accentColor, + minimumSize: Size(width, 45), + padding: EdgeInsets.symmetric(horizontal: 16), + elevation: elevation, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(radius)), + ), + ), + onPressed: onPressed, + child: Txt( + title.toUpperCase(), + color: txtColor, + txtType: TxtType.heading1, + ), + ); + } +} diff --git a/lib/widgets/txt.dart b/lib/widgets/txt.dart new file mode 100644 index 0000000..8591277 --- /dev/null +++ b/lib/widgets/txt.dart @@ -0,0 +1,170 @@ +// import 'package:auto_size_text/auto_size_text.dart'; +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; + +enum TxtType { + small, + normal, + heading1, + heading2, + heading3, + appBar, +} + +class Txt extends StatelessWidget { + String text; + int? maxLines; + double? fontSize; + Color? color; + bool? bold; + bool? isUnderline; + bool? isFlatButton; + double? pedding; + TextAlign? textAlign; + FontWeight? fontWeight; + Function? onTap; + TxtType txtType; + + Txt(this.text, {this.maxLines, this.color, this.bold, this.fontSize, this.isUnderline, this.isFlatButton, this.pedding, this.textAlign, this.fontWeight, this.onTap, this.txtType = TxtType.normal}); + + @override + Widget build(BuildContext context) { + if (isFlatButton != null) + return Padding( + padding: EdgeInsets.only(right: pedding ?? 0, left: pedding ?? 0), + child: InkWell( + onTap: () { + onTap!(); + }, + customBorder: inkWellCorner(r: 4), + child: Padding( + padding: const EdgeInsets.only( + left: 14, + right: 14, + top: 6, + bottom: 6, + ), + child: getText(), + ), + ), + ); + else + return getText(); + } + + Widget getText() { + return Material( + type: MaterialType.transparency, + child: Text( + text, + maxLines: maxLines, + textAlign: textAlign, + overflow: maxLines != null ? TextOverflow.ellipsis : null, + style: TextStyle( + fontSize: fontSize ?? + (txtType == TxtType.small + ? 8.sp + : txtType == TxtType.normal + ? 10.sp + : txtType == TxtType.heading1 + ? 11.sp + : txtType == TxtType.heading2 + ? 12.sp + : txtType == TxtType.heading3 + ? 13.sp + : txtType == TxtType.appBar + ? 14.sp + : 8.sp), + color: color ?? + (txtType == TxtType.appBar + ? Colors.black + : txtType == TxtType.heading1 + ? headingColor + : txtType == TxtType.heading2 + ? headingColor + : txtType == TxtType.heading3 + ? headingColor + : null), + fontWeight: (fontWeight != null) + ? fontWeight + : ((bold != null) + ? FontWeight.bold + : (txtType == TxtType.appBar + ? FontWeight.bold + : txtType == TxtType.heading1 + ? FontWeight.bold + : txtType == TxtType.heading2 + ? FontWeight.bold + : txtType == TxtType.heading3 + ? FontWeight.bold + : null)), + decoration: (isUnderline != null) ? TextDecoration.underline : null, + ), + ), + ); + } +} + +// class TxtAuto extends StatelessWidget { +// String text; +// int? maxLines; +// double? fontSize; +// Color? color; +// bool? bold; +// bool? isUnderline; +// bool? isFlatButton; +// double? pedding; +// TextAlign? textAlign; +// +// TxtAuto( +// this.text, { +// this.maxLines, +// this.color, +// this.bold, +// this.fontSize, +// this.isUnderline, +// this.isFlatButton, +// this.pedding, +// this.textAlign, +// }); +// +// @override +// Widget build(BuildContext context) { +// if (isFlatButton != null) +// return Padding( +// padding: EdgeInsets.only(right: pedding ?? 0, left: pedding ?? 0), +// child: InkWell( +// onTap: () {}, +// customBorder: inkWellCorner(r: 4), +// child: Padding( +// padding: const EdgeInsets.only( +// left: 14, +// right: 14, +// top: 6, +// bottom: 6, +// ), +// child: getText(), +// ), +// ), +// ); +// else +// return getText(); +// } +// +// Widget getText() { +// return AutoSizeText( +// text, +// maxLines: maxLines, +// textAlign: textAlign, +// overflow: maxLines != null ? TextOverflow.ellipsis : null, +// style: TextStyle( +// fontSize: fontSize, +// color: color, +// fontWeight: (bold != null) ? FontWeight.bold : null, +// decoration: (isUnderline != null) ? TextDecoration.underline : null, +// ), +// ); +// } +// } diff --git a/lib/widgets/txt_field.dart b/lib/widgets/txt_field.dart new file mode 100644 index 0000000..c081229 --- /dev/null +++ b/lib/widgets/txt_field.dart @@ -0,0 +1,155 @@ +import 'package:mohem_flutter_app/theme/colors.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:mohem_flutter_app/widgets/txt.dart'; +import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; + +class TxtField extends StatelessWidget { + TextEditingController controller = new TextEditingController(); + String? title; + String? hint; + String? lable; + IconData? prefixData; + IconData? postfixData; + bool isNeedFilterButton; + bool isNeedClickAll; + bool isButtonEnable; + double? elevation; + Function? onTap; + String? buttonTitle; + int? maxLines; + bool isSidePaddingZero; + bool isNeedBorder; + + TxtField({ + this.title, + this.lable, + this.hint, + this.prefixData, + this.postfixData, + this.isNeedClickAll = false, + this.isNeedFilterButton = false, + this.elevation, + this.onTap, + this.isButtonEnable = false, + this.buttonTitle, + this.maxLines, + this.isSidePaddingZero = false, + this.isNeedBorder = true, + }); + + @override + Widget build(BuildContext context) { + controller.text = title ?? ""; + return InkWell( + onTap: isNeedClickAll == false + ? null + : () { + onTap!(); + }, + customBorder: inkWellCorner(), + child: Row( + children: [ + Expanded( + child: Card( + elevation: elevation, + margin: isSidePaddingZero ? EdgeInsets.zero : null, + child: TextField( + autofocus: false, + controller: controller, + enabled: isNeedClickAll == true ? false : true, + maxLines: maxLines, + onTap: () {}, + decoration: InputDecoration( + labelText: lable, + alignLabelWithHint: true, + fillColor: Colors.white, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: accentColor, width: isNeedBorder ? 1.0 : 0), + borderRadius: BorderRadius.circular(4.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: borderColor, width: isNeedBorder ? 1.0 : 0), + borderRadius: BorderRadius.circular(4.0), + ), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: borderColor, width: isNeedBorder ? 1.0 : 0), + borderRadius: BorderRadius.circular(4.0), + ), + prefixIcon: prefixData != null + ? Icon( + Icons.search, + color: borderColor, + ) + : null, + labelStyle: TextStyle(color: borderColor, fontSize: 13.sp), + hintStyle: TextStyle(color: borderColor, fontSize: 9.sp), + hintText: hint ?? "", + contentPadding: prefixData == null + ? EdgeInsets.only( + left: 12, + right: 12, + top: maxLines != null ? 12 : 0, + bottom: maxLines != null ? 12 : 0, + ) + : EdgeInsets.zero, + ), + ), + ), + ), + if (isNeedFilterButton) mWidth(8), + if (isNeedFilterButton) + InkWell( + onTap: isNeedClickAll + ? null + : () { + controller.clear(); + }, + child: Container( + width: 55, + height: 55, + child: Card( + color: accentColor, + // margin: EdgeInsets.all(4), + // shape: cardRadius(0), + child: Icon( + postfixData ?? Icons.filter_alt, + color: Colors.white, + ), + ), + ), + ), + if (isButtonEnable) + Material( + child: InkWell( + onTap: () {}, + customBorder: inkWellCorner(), + child: Container( + height: 55, + child: Card( + color: accentColor, + // margin: EdgeInsets.all(4), + // shape: cardRadius(0), + child: Center( + child: Padding( + padding: const EdgeInsets.only(left: 12, right: 12), + child: Txt( + buttonTitle ?? "Search", + color: Colors.white, + fontSize: 18, + bold: true, + ), + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/user_image.dart b/lib/widgets/user_image.dart new file mode 100644 index 0000000..9ff8102 --- /dev/null +++ b/lib/widgets/user_image.dart @@ -0,0 +1,26 @@ +import 'package:mohem_flutter_app/config/constants.dart'; +import 'package:mohem_flutter_app/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class UserImage extends StatelessWidget { + double? size; + String? url; + + UserImage({this.size, this.url}); + + @override + Widget build(BuildContext context) { + return Container( + height: size ?? 60, + width: size ?? 60, + decoration: containerRadius(Colors.transparent, 1000), + clipBehavior: Clip.antiAlias, + child: Image.asset( + url ?? icons + "Blue Masked.jpg", + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 18fcc0f..1910eb0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" async: dependency: transitive description: @@ -43,6 +50,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" cupertino_icons: dependency: "direct main" description: @@ -50,6 +64,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + easy_logger: + dependency: transitive + description: + name: easy_logger + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2" fake_async: dependency: transitive description: @@ -57,6 +85,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" flutter: dependency: "direct main" description: flutter @@ -69,11 +111,63 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "0.22.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + injector: + dependency: "direct main" + description: + name: injector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" lints: dependency: transitive description: @@ -95,6 +189,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -102,6 +203,181 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1+1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.0" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.1" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + sizer: + dependency: "direct main" + description: + name: sizer + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" sky_engine: dependency: transitive description: flutter @@ -156,6 +432,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" vector_math: dependency: transitive description: @@ -163,5 +446,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.14.0 <3.0.0" + flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5346e91..c383e65 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,15 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + path_provider: ^2.0.4 + injector: ^2.0.0 + provider: ^6.0.0 + easy_localization: ^3.0.0 + http: ^0.13.3 + permission_handler: 7.1.0 + flutter_svg: ^0.22.0 + sizer: ^2.0.15 + dev_dependencies: flutter_test: @@ -61,6 +70,11 @@ flutter: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg + assets: + - resources/langs/ + - assets/ + - assets/icons/ + - assets/images/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/resources/langs/en-US.json b/resources/langs/en-US.json new file mode 100644 index 0000000..c9f87fc --- /dev/null +++ b/resources/langs/en-US.json @@ -0,0 +1,38 @@ +{ + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +} \ No newline at end of file diff --git a/resources/langs/en.json b/resources/langs/en.json new file mode 100644 index 0000000..c9f87fc --- /dev/null +++ b/resources/langs/en.json @@ -0,0 +1,38 @@ +{ + "title": "Hello", + "msg": "Hello {} in the {} world ", + "msg_named": "{} are written in the {lang} language", + "clickMe": "Click me", + "profile": { + "reset_password": { + "label": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "two": "You clicked {} times!", + "few": "You clicked {} times!", + "many": "You clicked {} times!", + "other": "You clicked {} times!" + }, + "amount": { + "zero": "Your amount : {} ", + "one": "Your amount : {} ", + "two": "Your amount : {} ", + "few": "Your amount : {} ", + "many": "Your amount : {} ", + "other": "Your amount : {} " + }, + "gender": { + "male": "Hi man ;) ", + "female": "Hello girl :)", + "with_arg": { + "male": "Hi man ;) {}", + "female": "Hello girl :) {}" + } + }, + "reset_locale": "Reset Language" +} \ No newline at end of file diff --git a/resources/s.dart b/resources/s.dart new file mode 100644 index 0000000..e69de29 diff --git a/test/widget_test.dart b/test/widget_test.dart index 42d9b33..a360c7e 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:mohem_flutter_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()); + await tester.pumpWidget( MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);