├── conf.xml ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist └── .gitignore ├── lib ├── data │ ├── providers │ │ ├── database │ │ │ └── firebase_database_provider.dart │ │ └── network │ │ │ ├── api_endpoint.dart │ │ │ ├── api_request_representable.dart │ │ │ ├── apis │ │ │ ├── medium_api.dart │ │ │ └── images_api.dart │ │ │ └── api_provider.dart │ ├── models │ │ ├── unsplash_search_response_model.dart │ │ ├── unsplash_image_urls_model.dart │ │ └── unsplash_image_model.dart │ └── repositories │ │ ├── images_repository_impl.dart │ │ └── medium_posts_repository_impl.dart ├── app │ ├── constants │ │ ├── timeline_status.dart │ │ ├── work_places.dart │ │ ├── job_type.dart │ │ └── themes.dart │ ├── core │ │ └── usecases │ │ │ ├── no_param_usecase.dart │ │ │ └── pram_usecase.dart │ └── util │ │ ├── styles.dart │ │ ├── web_scroll_behavior.dart │ │ ├── dependency.dart │ │ ├── util.dart │ │ ├── url_helper.dart │ │ └── video_player_data_manager.dart ├── domain │ ├── entities │ │ ├── unsplash_search_response.dart │ │ ├── paging.dart │ │ ├── portfolio.dart │ │ ├── unsplash_image_urls.dart │ │ ├── unsplash_image.dart │ │ ├── experience.dart │ │ ├── medium_feed.dart │ │ ├── medium_posts_response.dart │ │ └── medium_post_item.dart │ ├── repositories │ │ ├── images_repository.dart │ │ └── medium_posts_repository.dart │ └── usecases │ │ ├── fetch_person_images_use_case.dart │ │ └── fetch_user_posts_use_case.dart ├── main.dart └── presentation │ ├── projects │ ├── projets_binding.dart │ ├── projects_controller.dart │ └── projects_page.dart │ ├── information │ ├── information_binding.dart │ ├── information_controller.dart │ └── information_page.dart │ ├── experiences │ ├── experiences_binding.dart │ ├── experiences_controller.dart │ └── experiences_page.dart │ ├── home │ ├── home_binding.dart │ ├── home_page.dart │ └── home_controller.dart │ ├── blog │ ├── blog_binding.dart │ ├── blog_controller.dart │ └── blog_page.dart │ ├── about │ ├── about_binding.dart │ ├── about_controller.dart │ └── about_page.dart │ ├── widgets │ ├── common │ │ ├── post_item_widget.dart │ │ ├── hero_images.dart │ │ ├── home_background_painter.dart │ │ ├── loading_widget.dart │ │ ├── no_post_widget.dart │ │ ├── blog_background_painter.dart │ │ ├── curve_line_painter.dart │ │ ├── rounded_button_with_icon.dart │ │ ├── experience_background_painter.dart │ │ ├── rounded_button.dart │ │ ├── experience_card_painter.dart │ │ ├── typewriter.dart │ │ ├── blog_post_item.dart │ │ ├── social_media_bar.dart │ │ ├── web_video_control.dart │ │ ├── nav_bar.dart │ │ └── experience_time_line_widget.dart │ ├── mobile │ │ ├── home_background_painter_for_mobile.dart │ │ ├── blog_background_painter_for_mobile.dart │ │ ├── experience_background_painter_for_mobile.dart │ │ ├── drawer.dart │ │ └── social_media_bar_for_mobile.dart │ └── custom_snackbar.dart │ └── app.dart ├── snapshot.jpg ├── web ├── favicon.ico ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── manifest.json └── index.html ├── assets ├── images │ ├── me.png │ ├── man.png │ ├── irisa.jpg │ ├── nicode.jpg │ ├── uplance.jpg │ ├── kohmart_1.png │ ├── kohmart_2.png │ ├── kohmart_3.png │ ├── kohmart_4.png │ ├── ic_no_image.jpg │ ├── laptop_frame.png │ ├── mobile_frame.png │ ├── no_posts_yet.gif │ ├── programmer.gif │ ├── orthobiomed_1.png │ ├── orthobiomed_2.png │ ├── orthobiomed_3.png │ ├── kargozare_man_1.png │ ├── kargozare_man_2.png │ ├── kargozare_man_3.png │ ├── kargozare_man_4.png │ ├── experience_card_top.png │ ├── experience_card_bottom.png │ ├── ic_arrow_top_to_left.png │ └── ic_arrow_top_to_right.png ├── miladjalali.pdf └── fonts │ ├── MySocialIcons.ttf │ ├── Ubuntu-Bold.ttf │ ├── Ubuntu-Medium.ttf │ └── Ubuntu-Regular.ttf ├── .metadata ├── README.md ├── .gitignore ├── test ├── widget_test.dart └── data │ └── repositories │ └── images_repository_impl_test.dart └── pubspec.yaml /conf.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /lib/data/providers/database/firebase_database_provider.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/app/constants/timeline_status.dart: -------------------------------------------------------------------------------- 1 | enum TimelineStatus{ 2 | done, 3 | inProgress, 4 | todo 5 | } -------------------------------------------------------------------------------- /snapshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/snapshot.jpg -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/web/favicon.ico -------------------------------------------------------------------------------- /assets/images/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/me.png -------------------------------------------------------------------------------- /lib/app/core/usecases/no_param_usecase.dart: -------------------------------------------------------------------------------- 1 | abstract class NoParamUseCase { 2 | Future execute(); 3 | } 4 | -------------------------------------------------------------------------------- /assets/images/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/man.png -------------------------------------------------------------------------------- /assets/miladjalali.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/miladjalali.pdf -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/images/irisa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/irisa.jpg -------------------------------------------------------------------------------- /assets/images/nicode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/nicode.jpg -------------------------------------------------------------------------------- /assets/images/uplance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/uplance.jpg -------------------------------------------------------------------------------- /assets/images/kohmart_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kohmart_1.png -------------------------------------------------------------------------------- /assets/images/kohmart_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kohmart_2.png -------------------------------------------------------------------------------- /assets/images/kohmart_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kohmart_3.png -------------------------------------------------------------------------------- /assets/images/kohmart_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kohmart_4.png -------------------------------------------------------------------------------- /lib/app/core/usecases/pram_usecase.dart: -------------------------------------------------------------------------------- 1 | abstract class ParamUseCase { 2 | Future execute(Params params); 3 | } 4 | -------------------------------------------------------------------------------- /assets/fonts/MySocialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/fonts/MySocialIcons.ttf -------------------------------------------------------------------------------- /assets/fonts/Ubuntu-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/fonts/Ubuntu-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Ubuntu-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/fonts/Ubuntu-Medium.ttf -------------------------------------------------------------------------------- /assets/images/ic_no_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/ic_no_image.jpg -------------------------------------------------------------------------------- /assets/images/laptop_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/laptop_frame.png -------------------------------------------------------------------------------- /assets/images/mobile_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/mobile_frame.png -------------------------------------------------------------------------------- /assets/images/no_posts_yet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/no_posts_yet.gif -------------------------------------------------------------------------------- /assets/images/programmer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/programmer.gif -------------------------------------------------------------------------------- /assets/fonts/Ubuntu-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/fonts/Ubuntu-Regular.ttf -------------------------------------------------------------------------------- /assets/images/orthobiomed_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/orthobiomed_1.png -------------------------------------------------------------------------------- /assets/images/orthobiomed_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/orthobiomed_2.png -------------------------------------------------------------------------------- /assets/images/orthobiomed_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/orthobiomed_3.png -------------------------------------------------------------------------------- /assets/images/kargozare_man_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kargozare_man_1.png -------------------------------------------------------------------------------- /assets/images/kargozare_man_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kargozare_man_2.png -------------------------------------------------------------------------------- /assets/images/kargozare_man_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kargozare_man_3.png -------------------------------------------------------------------------------- /assets/images/kargozare_man_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/kargozare_man_4.png -------------------------------------------------------------------------------- /assets/images/experience_card_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/experience_card_top.png -------------------------------------------------------------------------------- /assets/images/experience_card_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/experience_card_bottom.png -------------------------------------------------------------------------------- /assets/images/ic_arrow_top_to_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/ic_arrow_top_to_left.png -------------------------------------------------------------------------------- /assets/images/ic_arrow_top_to_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/assets/images/ic_arrow_top_to_right.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/data/providers/network/api_endpoint.dart: -------------------------------------------------------------------------------- 1 | class APIEndpoint { 2 | 3 | static String get unsplashApi => "https://api.unsplash.com"; 4 | static String get mediumUserPostsFromRss => "https://api.rss2json.com"; 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/util/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Styles{ 4 | static TextStyle navBarTextStyle = TextStyle( 5 | color: Colors.white, 6 | fontSize: 17, 7 | fontWeight: FontWeight.w700, 8 | ); 9 | } -------------------------------------------------------------------------------- /lib/domain/entities/unsplash_search_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:miladjalali_ir/domain/entities/unsplash_image.dart'; 2 | 3 | class UnsplashSearchResponse { 4 | int? total; 5 | int? totalPages; 6 | List? results; 7 | } 8 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:miladjalali_ir/app/util/dependency.dart'; 3 | import 'package:miladjalali_ir/presentation/app.dart'; 4 | 5 | void main() { 6 | DenpendencyCreator.init(); 7 | runApp(App()); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /lib/presentation/projects/projets_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'projects_controller.dart'; 3 | 4 | class ProjectsBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | Get.put(ProjectsController()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/presentation/information/information_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'information_controller.dart'; 3 | 4 | class InformationBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | Get.put(InformationController()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/presentation/experiences/experiences_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'experiences_controller.dart'; 4 | 5 | class ExperiencesBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.put(ExperiencesController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/domain/entities/paging.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 3 | 4 | class Paging { 5 | Paging({ 6 | required this.total, 7 | required this.response, 8 | }); 9 | 10 | int total; 11 | UnsplashSearchResponse response; 12 | } 13 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/domain/repositories/images_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:either_dart/either.dart'; 2 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 3 | 4 | abstract class ImagesRepository { 5 | Future> fetchPersonImages(int page, int pageSize); 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/domain/entities/portfolio.dart: -------------------------------------------------------------------------------- 1 | class Portfolio{ 2 | late int index; 3 | late String title; 4 | late String descriptionHeader; 5 | late String description; 6 | late List images; 7 | late String link; 8 | 9 | Portfolio(this.index, this.title, this.descriptionHeader, this.description, this.images, this.link); 10 | } -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Milad Jalali Personal Website 2 | 3 | 4 | # Flutter Responsive Personal Website With GetX 5 | 6 | My portfolio Website (https://pwa.miladjalali.ir) 7 | 8 | 9 | ![App Screenshot](https://raw.githubusercontent.com/miladjalalli/Flutter-Personal-Website-With-GetX/main/snapshot.jpg) 10 | 11 | #getx #flutter_web #pwa #flutter #flutter_responsive 12 | -------------------------------------------------------------------------------- /lib/domain/repositories/medium_posts_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:either_dart/either.dart'; 2 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 3 | 4 | import '../entities/medium_posts_response.dart'; 5 | 6 | abstract class MediumPostsRepository { 7 | Future> fetchUserPosts(String tagName); 8 | } 9 | -------------------------------------------------------------------------------- /lib/presentation/home/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:miladjalali_ir/presentation/home/home_controller.dart'; 3 | 4 | class HomeBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | // Get.lazyPut(() => SignUpUseCase(Get.find())); 8 | Get.put(HomeController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/util/web_scroll_behavior.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class WebScrollBehavior extends MaterialScrollBehavior { 5 | // Override behavior methods and getters like dragDevices 6 | @override 7 | Set get dragDevices => { 8 | PointerDeviceKind.touch, 9 | PointerDeviceKind.mouse, 10 | }; 11 | } -------------------------------------------------------------------------------- /lib/domain/entities/unsplash_image_urls.dart: -------------------------------------------------------------------------------- 1 | 2 | class UnsplashImageUrls { 3 | String? raw; 4 | String? full; 5 | String? regular; 6 | String? small; 7 | String? thumb; 8 | String? smallS3; 9 | 10 | UnsplashImageUrls( 11 | {this.raw, 12 | this.full, 13 | this.regular, 14 | this.small, 15 | this.thumb, 16 | this.smallS3}); 17 | } 18 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/app/constants/work_places.dart: -------------------------------------------------------------------------------- 1 | enum Workplaces { 2 | onSite, 3 | remote, 4 | hybrid, 5 | } 6 | 7 | extension WorkplacesExtension on Workplaces{ 8 | 9 | String get getName { 10 | if (this == Workplaces.onSite) 11 | return "On Site"; 12 | else if (this == Workplaces.remote) 13 | return "Remote"; 14 | else if (this == Workplaces.hybrid) 15 | return "Hybrid"; 16 | else 17 | return "On Site"; 18 | } 19 | } -------------------------------------------------------------------------------- /lib/app/util/dependency.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:miladjalali_ir/data/repositories/images_repository_impl.dart'; 3 | 4 | import '../../data/repositories/images_repository_impl.dart'; 5 | import '../../data/repositories/medium_posts_repository_impl.dart'; 6 | class DenpendencyCreator { 7 | static init() { 8 | Get.lazyPut(() => ImagesRepositoryIml()); 9 | Get.lazyPut(() => MediumPostsRepositoryIml()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/blog/blog_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:miladjalali_ir/domain/usecases/fetch_user_posts_use_case.dart'; 3 | 4 | import '../../data/repositories/medium_posts_repository_impl.dart'; 5 | import 'blog_controller.dart'; 6 | 7 | class BlogBinding extends Bindings { 8 | @override 9 | void dependencies() { 10 | Get.lazyPut(() => FetchUserPostsUseCase(Get.find())); 11 | Get.put(BlogController(Get.find())); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/util/util.dart: -------------------------------------------------------------------------------- 1 | class Utils { 2 | static String getImagePath(String name, {String format: 'png'}) { 3 | return 'assets/images/$name.$format'; 4 | } 5 | 6 | 7 | static String paramsToUrl({ required Map params, required String url}) { 8 | String newUrl = url + "?"; 9 | List paramsList = params.entries.toList(); 10 | params.forEach((key, value) { 11 | if (value != null) { 12 | 13 | newUrl += (key.toString() + "=" + value.toString() + "&"); 14 | 15 | } 16 | }); 17 | return newUrl; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /lib/presentation/information/information_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class InformationController extends GetxController with GetTickerProviderStateMixin { 5 | 6 | RxBool helloSeen = false.obs; 7 | RxBool nameSeen = false.obs; 8 | RxBool positionSeen = false.obs; 9 | RxBool abstractSeen = false.obs; 10 | RxBool showName = false.obs; 11 | RxBool showPosition = false.obs; 12 | RxBool showAbstract = false.obs; 13 | RxBool showHireMe = false.obs; 14 | RxBool hovered = false.obs; 15 | 16 | } -------------------------------------------------------------------------------- /lib/presentation/about/about_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:miladjalali_ir/data/repositories/images_repository_impl.dart'; 3 | import 'package:miladjalali_ir/domain/usecases/fetch_person_images_use_case.dart'; 4 | import 'package:miladjalali_ir/presentation/about/about_controller.dart'; 5 | import 'package:miladjalali_ir/presentation/home/home_controller.dart'; 6 | 7 | class AboutBinding extends Bindings { 8 | @override 9 | void dependencies() { 10 | Get.lazyPut(() => FetchPersonImagesUseCase(Get.find())); 11 | Get.put(AboutController(Get.find())); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/post_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:html'; 3 | import 'dart:ui' as ui; 4 | 5 | 6 | class PostItemWidget extends StatefulWidget { 7 | const PostItemWidget({Key? key}) : super(key: key); 8 | 9 | @override 10 | State createState() => _PostItemWidgetState(); 11 | } 12 | 13 | class _PostItemWidgetState extends State { 14 | 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Container(); 19 | } 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/app/constants/job_type.dart: -------------------------------------------------------------------------------- 1 | enum JobType{ 2 | fullTime, 3 | partTime, 4 | contract, 5 | internship, 6 | temporary, 7 | } 8 | 9 | extension JobTypeExtension on JobType{ 10 | 11 | String get getName { 12 | if (this == JobType.fullTime) 13 | return "Full Time"; 14 | else if (this == JobType.partTime) 15 | return "Part Time"; 16 | else if (this == JobType.contract) 17 | return "Contract"; 18 | else if (this == JobType.internship) 19 | return "Internship"; 20 | else if (this == JobType.temporary) 21 | return "Temporary"; 22 | else 23 | return "Full Time"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /lib/domain/entities/unsplash_image.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:miladjalali_ir/domain/entities/unsplash_image_urls.dart'; 3 | 4 | class UnsplashImage { 5 | String? id; 6 | String? createdAt; 7 | String? updatedAt; 8 | String? promotedAt; 9 | int? width; 10 | int? height; 11 | String? color; 12 | String? blurHash; 13 | String? description; 14 | String? altDescription; 15 | UnsplashImageUrls? urls; 16 | 17 | UnsplashImage( 18 | {this.id, 19 | this.createdAt, 20 | this.updatedAt, 21 | this.promotedAt, 22 | this.width, 23 | this.height, 24 | this.color, 25 | this.blurHash, 26 | this.description, 27 | this.altDescription, 28 | this.urls}); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/domain/entities/experience.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import '../../app/constants/job_type.dart'; 4 | import '../../app/constants/work_places.dart'; 5 | 6 | class Experience { 7 | late String companyTitle; 8 | late ImageProvider companyLogo; 9 | late String startDate; 10 | late String endDate; 11 | late String jobPosition; 12 | late JobType jobType; 13 | late Workplaces workplaces; 14 | late String description; 15 | 16 | Experience( 17 | {required this.companyTitle, 18 | required this.companyLogo, 19 | required this.startDate, 20 | required this.endDate, 21 | required this.jobPosition, 22 | required this.jobType, 23 | required this.workplaces, 24 | required this.description}); 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/hero_images.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HeroImage extends StatelessWidget { 4 | final Color borderColor; 5 | final Color backgroundColor; 6 | 7 | const HeroImage({ 8 | this.borderColor = Colors.white, 9 | this.backgroundColor = Colors.transparent, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | width: 230, 16 | height: 230, 17 | decoration: BoxDecoration( 18 | shape: BoxShape.circle, 19 | color: Colors.white, 20 | border: Border.all(color: borderColor, width: 2), 21 | image: DecorationImage( 22 | image: AssetImage('assets/images/me.png'), 23 | ), 24 | ), 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/data/models/unsplash_search_response_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:miladjalali_ir/data/models/unsplash_image_model.dart'; 2 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 3 | 4 | class UnsplashSearchResponseModel extends UnsplashSearchResponse { 5 | final int? total; 6 | final int? totalPages; 7 | final List? results; 8 | 9 | UnsplashSearchResponseModel({this.total, this.totalPages, this.results}); 10 | 11 | @override 12 | factory UnsplashSearchResponseModel.fromJson(Map json) => UnsplashSearchResponseModel( 13 | total: json['total'], 14 | totalPages: json['total_pages'], 15 | results: json['results'] != null ? List.from(json["results"].map((x) => UnsplashImageModel.fromJson(x))) : null); 16 | } 17 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/home_background_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomeBackgroundPainter extends CustomPainter { 4 | Color color; 5 | 6 | HomeBackgroundPainter(this.color); 7 | 8 | 9 | @override 10 | void paint(Canvas canvas, Size size) { 11 | final path = Path(); 12 | 13 | path.moveTo(size.width * 0.4, 0); 14 | path.lineTo(size.width, 0); 15 | path.lineTo(size.width, size.height); 16 | path.lineTo(size.width * 0.6, size.height); 17 | path.close(); 18 | 19 | final paint = Paint() 20 | ..color = color 21 | ..style = PaintingStyle.fill; 22 | 23 | canvas.drawPath(path, paint); 24 | } 25 | 26 | @override 27 | bool shouldRepaint(covariant HomeBackgroundPainter oldDelegate) => false; 28 | } 29 | -------------------------------------------------------------------------------- /lib/presentation/widgets/mobile/home_background_painter_for_mobile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomeBackgroundPainterForMobile extends CustomPainter { 4 | Color color; 5 | 6 | 7 | HomeBackgroundPainterForMobile(this.color); 8 | 9 | @override 10 | void paint(Canvas canvas, Size size) { 11 | final path = Path(); 12 | 13 | path.moveTo(0, 0); 14 | path.lineTo(0, size.height * 0.4); 15 | path.lineTo(size.width, 0.5); 16 | path.lineTo(size.width, 0); 17 | path.close(); 18 | 19 | final paint = Paint() 20 | ..color = color 21 | ..style = PaintingStyle.fill; 22 | 23 | canvas.drawPath(path, paint); 24 | } 25 | 26 | @override 27 | bool shouldRepaint(covariant HomeBackgroundPainterForMobile oldDelegate) => false; 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/util/url_helper.dart: -------------------------------------------------------------------------------- 1 | // ignore: avoid_web_libraries_in_flutter 2 | import 'dart:js' as js; 3 | // ignore: avoid_web_libraries_in_flutter 4 | import 'dart:html' as html; 5 | 6 | import 'package:flutter/services.dart'; 7 | import 'package:url_launcher/url_launcher.dart'; 8 | 9 | class UrlHelper { 10 | static Future launchUrl(String url) async { 11 | if (await canLaunch(url)) { 12 | await launch(url, enableJavaScript: true); 13 | } 14 | } 15 | 16 | static Future downloadResume() async { 17 | final readValue = await rootBundle.load('assets/miladjalali.pdf'); 18 | 19 | // Call the "saveAs" method from the FileSaver.js libray 20 | js.context.callMethod("saveAs", [ 21 | html.Blob([readValue]), 22 | "miladjalali.pdf", 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/domain/usecases/fetch_person_images_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:either_dart/either.dart'; 2 | import 'package:miladjalali_ir/app/core/usecases/pram_usecase.dart'; 3 | import 'package:miladjalali_ir/domain/entities/paging.dart'; 4 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 5 | import 'package:miladjalali_ir/domain/repositories/images_repository.dart'; 6 | import 'package:tuple/tuple.dart'; 7 | 8 | class FetchPersonImagesUseCase extends ParamUseCase, Tuple2> { 9 | final ImagesRepository _repo; 10 | FetchPersonImagesUseCase(this._repo); 11 | 12 | @override 13 | Future> execute(Tuple2 param) { 14 | return _repo.fetchPersonImages(param.item1, param.item2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/constants/themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Themes { 4 | static ThemeData themeBlueOrange = ThemeData( 5 | primaryColor: Colors.lightBlue, 6 | primaryColorDark: Colors.blue, 7 | accentColor: Colors.orange); 8 | 9 | static ThemeData themeBlackOrange = ThemeData( 10 | primaryColor: Colors.black, 11 | primaryColorDark: Colors.black, 12 | accentColor: Colors.orange); 13 | 14 | static ThemeData themePurpleOrange = ThemeData( 15 | primaryColor: Color(0XFF003140), 16 | primaryColorDark: Color(0XFF003140), 17 | accentColor: Colors.orange); 18 | static ThemeData themeOrangeOrange = ThemeData( 19 | primaryColor: Colors.orange, 20 | primaryColorDark: Colors.deepOrangeAccent, 21 | accentColor: Colors.orange); 22 | } -------------------------------------------------------------------------------- /lib/data/providers/network/api_request_representable.dart: -------------------------------------------------------------------------------- 1 | enum HTTPMethod { get, post, delete, put, patch } 2 | 3 | extension HTTPMethodString on HTTPMethod { 4 | String get string { 5 | switch (this) { 6 | case HTTPMethod.get: 7 | return "get"; 8 | case HTTPMethod.post: 9 | return "post"; 10 | case HTTPMethod.delete: 11 | return "delete"; 12 | case HTTPMethod.patch: 13 | return "patch"; 14 | case HTTPMethod.put: 15 | return "put"; 16 | } 17 | } 18 | } 19 | 20 | abstract class APIRequestRepresentable { 21 | String get url; 22 | String get endpoint; 23 | String get path; 24 | HTTPMethod get method; 25 | Map? get headers; 26 | Map? get query; 27 | dynamic get body; 28 | Future request(); 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /lib/presentation/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get_navigation/get_navigation.dart'; 3 | import 'package:miladjalali_ir/app/constants/themes.dart'; 4 | import 'package:miladjalali_ir/presentation/home/home_binding.dart'; 5 | import 'package:miladjalali_ir/presentation/home/home_page.dart'; 6 | 7 | import '../app/util/web_scroll_behavior.dart'; 8 | 9 | class App extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return GetMaterialApp( 13 | initialRoute: "/", 14 | initialBinding: HomeBinding(), 15 | debugShowCheckedModeBanner: false, 16 | theme: Themes.themeBlueOrange, 17 | scrollBehavior: WebScrollBehavior(), 18 | title: "Milad Jalali | Developer, Designer and Teacher", 19 | home: HomePage(), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/widgets/custom_snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CustomSnackBar { 5 | static show(BuildContext context,String message, ContentType contentType) { 6 | SnackBar snackBar = SnackBar( 7 | elevation: 0, 8 | behavior: SnackBarBehavior.floating, 9 | backgroundColor: Colors.transparent, 10 | content: AwesomeSnackbarContent( 11 | title: contentType == ContentType.success 12 | ? 'Well !' 13 | : contentType == ContentType.failure 14 | ? "Failure" 15 | : "Warning", 16 | message: message, 17 | contentType: contentType, 18 | ), 19 | ); 20 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/data/models/unsplash_image_urls_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:miladjalali_ir/domain/entities/unsplash_image_urls.dart'; 2 | 3 | class UnsplashImageUrlsModel extends UnsplashImageUrls { 4 | String? raw; 5 | String? full; 6 | String? regular; 7 | String? small; 8 | String? thumb; 9 | String? smallS3; 10 | 11 | UnsplashImageUrlsModel({required this.raw,required this.full,required this.regular,required this.small,required this.thumb,required this.smallS3}) 12 | : super(full: full, raw: raw, regular: regular, small: small, smallS3: smallS3, thumb: thumb); 13 | 14 | @override 15 | factory UnsplashImageUrlsModel.fromJson(Map json) => UnsplashImageUrlsModel( 16 | raw : json['raw'], 17 | full : json['full'], 18 | regular : json['regular'], 19 | small : json['small'], 20 | thumb : json['thumb'], 21 | smallS3 : json['small_s3'], 22 | ); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /lib/data/repositories/images_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:either_dart/either.dart'; 4 | import 'package:miladjalali_ir/data/providers/network/apis/images_api.dart'; 5 | import 'package:miladjalali_ir/domain/entities/paging.dart'; 6 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 7 | import 'package:miladjalali_ir/domain/repositories/images_repository.dart'; 8 | import '../models/unsplash_search_response_model.dart'; 9 | 10 | class ImagesRepositoryIml extends ImagesRepository { 11 | @override 12 | Future> fetchPersonImages(int page, int pageSize) async { 13 | Either response = await ImagesAPI.getPersonalImages(page, pageSize).request(); 14 | if (response.isLeft) 15 | return Left(response.left); 16 | else 17 | return Right(UnsplashSearchResponseModel.fromJson(response.right)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/domain/entities/medium_feed.dart: -------------------------------------------------------------------------------- 1 | 2 | class MediumFeed { 3 | String? url; 4 | String? title; 5 | String? link; 6 | String? author; 7 | String? description; 8 | String? image; 9 | 10 | MediumFeed({this.url, this.title, this.link, this.author, this.description, this.image}); 11 | 12 | MediumFeed.fromJson(Map json) { 13 | url = json['url']; 14 | title = json['title']; 15 | link = json['link']; 16 | author = json['author']; 17 | description = json['description']; 18 | image = json['image']; 19 | } 20 | 21 | Map toJson() { 22 | final Map data = new Map(); 23 | data['url'] = this.url; 24 | data['title'] = this.title; 25 | data['link'] = this.link; 26 | data['author'] = this.author; 27 | data['description'] = this.description; 28 | data['image'] = this.image; 29 | return data; 30 | } 31 | } -------------------------------------------------------------------------------- /lib/domain/usecases/fetch_user_posts_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:either_dart/either.dart'; 2 | import 'package:miladjalali_ir/app/core/usecases/pram_usecase.dart'; 3 | import 'package:miladjalali_ir/domain/entities/medium_posts_response.dart'; 4 | import 'package:miladjalali_ir/domain/entities/paging.dart'; 5 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 6 | import 'package:miladjalali_ir/domain/repositories/images_repository.dart'; 7 | import 'package:miladjalali_ir/domain/repositories/medium_posts_repository.dart'; 8 | import 'package:tuple/tuple.dart'; 9 | 10 | class FetchUserPostsUseCase extends ParamUseCase, String> { 11 | final MediumPostsRepository _repo; 12 | 13 | FetchUserPostsUseCase(this._repo); 14 | 15 | @override 16 | Future> execute(String param) { 17 | return _repo.fetchUserPosts(param); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/loading_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class LoadingWidget extends StatelessWidget { 6 | const LoadingWidget({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | width: double.infinity, 12 | child: Column( 13 | crossAxisAlignment: CrossAxisAlignment.center, 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | SpinKitDualRing( 17 | color: Get.theme.accentColor, 18 | size: 48, 19 | ), 20 | SizedBox( 21 | height: 16, 22 | ), 23 | Text( 24 | "Loading", 25 | style: TextStyle(fontSize: 24), 26 | ) 27 | ], 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/no_post_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class NoPostWidget extends StatelessWidget { 6 | const NoPostWidget({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | width: double.infinity, 12 | child: Column( 13 | crossAxisAlignment: CrossAxisAlignment.center, 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | Image.asset( 17 | "assets/images/no_posts_yet.gif", 18 | fit: BoxFit.cover, 19 | width: 300, 20 | height: 300, 21 | ), 22 | SizedBox( 23 | height: 16, 24 | ), 25 | Text( 26 | "No Post Yet!", 27 | style: TextStyle(fontSize: 24), 28 | ) 29 | ], 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/domain/entities/medium_posts_response.dart: -------------------------------------------------------------------------------- 1 | import 'medium_feed.dart'; 2 | import 'medium_post_item.dart'; 3 | 4 | class MediumPostsResponse { 5 | String? status; 6 | MediumFeed? feed; 7 | List? items; 8 | 9 | MediumPostsResponse({this.status, this.feed, this.items}); 10 | 11 | MediumPostsResponse.fromJson(Map json) { 12 | status = json['status']; 13 | feed = json['feed'] != null ? new MediumFeed.fromJson(json['feed']) : null; 14 | if (json['items'] != null) { 15 | items = []; 16 | json['items'].forEach((v) { items!.add(new MediumPostItem.fromJson(v)); }); 17 | } 18 | } 19 | 20 | Map toJson() { 21 | final Map data = new Map(); 22 | data['status'] = this.status; 23 | if (this.feed != null) { 24 | data['feed'] = this.feed!.toJson(); 25 | } 26 | if (this.items != null) { 27 | data['items'] = this.items!.map((v) => v.toJson()).toList(); 28 | } 29 | return data; 30 | } 31 | } -------------------------------------------------------------------------------- /lib/data/repositories/medium_posts_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:either_dart/either.dart'; 2 | import 'package:miladjalali_ir/data/providers/network/apis/images_api.dart'; 3 | import 'package:miladjalali_ir/domain/entities/medium_posts_response.dart'; 4 | import 'package:miladjalali_ir/domain/entities/paging.dart'; 5 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 6 | import 'package:miladjalali_ir/domain/repositories/images_repository.dart'; 7 | 8 | import '../../domain/repositories/medium_posts_repository.dart'; 9 | import '../models/unsplash_search_response_model.dart'; 10 | import '../providers/network/apis/medium_api.dart'; 11 | 12 | class MediumPostsRepositoryIml extends MediumPostsRepository { 13 | 14 | @override 15 | Future> fetchUserPosts(String username) async { 16 | Either response = await MediumAPI.fetchUserPosts(username).request(); 17 | if(response.isLeft) 18 | return Left(response.left); 19 | else 20 | return Right(MediumPostsResponse.fromJson(response.right)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:miladjalali_ir/presentation/app.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(App()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/data/providers/network/apis/medium_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:either_dart/either.dart'; 2 | import 'package:miladjalali_ir/app/util/util.dart'; 3 | import 'package:miladjalali_ir/data/providers/network/api_endpoint.dart'; 4 | import 'package:miladjalali_ir/data/providers/network/api_provider.dart'; 5 | import 'package:miladjalali_ir/data/providers/network/api_request_representable.dart'; 6 | 7 | class MediumAPI implements APIRequestRepresentable { 8 | String tagName; 9 | 10 | MediumAPI._({required this.tagName}); 11 | 12 | MediumAPI.fetchUserPosts(username) : this._(tagName:username); 13 | 14 | @override 15 | String get endpoint => APIEndpoint.mediumUserPostsFromRss; 16 | 17 | String get path { 18 | return "/v1/api.json"; 19 | } 20 | 21 | @override 22 | HTTPMethod get method { 23 | return HTTPMethod.get; 24 | } 25 | 26 | Map get headers => {}; 27 | 28 | Map get query { 29 | return { 30 | "rss_url": "https://medium.com/feed/tag/$tagName", 31 | }; 32 | } 33 | 34 | @override 35 | get body => null; 36 | 37 | Future request() { 38 | return APIProvider.instance.request(this); 39 | } 40 | 41 | @override 42 | String get url { 43 | return endpoint + path; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/presentation/blog/blog_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:either_dart/either.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:miladjalali_ir/domain/entities/medium_posts_response.dart'; 5 | import 'package:miladjalali_ir/domain/usecases/fetch_user_posts_use_case.dart'; 6 | 7 | import '../widgets/custom_snackbar.dart'; 8 | 9 | class BlogController extends GetxController with GetTickerProviderStateMixin { 10 | BlogController(this._fetchUserPostsUseCase); 11 | 12 | String _tagName = "flutter"; 13 | 14 | late FetchUserPostsUseCase _fetchUserPostsUseCase; 15 | Rx? mediumPostsResponse = Rx(null); 16 | 17 | @override 18 | void onInit() { 19 | super.onInit(); 20 | } 21 | 22 | @override 23 | void onReady() { 24 | fetchUserPosts(); 25 | super.onReady(); 26 | } 27 | 28 | @override 29 | void dispose() { 30 | super.dispose(); 31 | } 32 | 33 | fetchUserPosts() async { 34 | Either response = await _fetchUserPostsUseCase.execute(_tagName); 35 | response.fold((left) { 36 | CustomSnackBar.show(Get.context!, left.message, ContentType.failure); 37 | }, (right) { 38 | mediumPostsResponse!.value = right; 39 | update(); 40 | }); 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/domain/entities/medium_post_item.dart: -------------------------------------------------------------------------------- 1 | 2 | class MediumPostItem { 3 | String? title; 4 | String? pubDate; 5 | String? link; 6 | String? guid; 7 | String? author; 8 | String? thumbnail; 9 | String? description; 10 | String? content; 11 | List? categories; 12 | 13 | MediumPostItem({this.title, this.pubDate, this.link, this.guid, this.author, this.thumbnail, this.description, this.content, this.categories}); 14 | 15 | MediumPostItem.fromJson(Map json) { 16 | title = json['title']; 17 | pubDate = json['pubDate']; 18 | link = json['link']; 19 | guid = json['guid']; 20 | author = json['author']; 21 | thumbnail = json['thumbnail']; 22 | description = json['description']; 23 | content = json['content']; 24 | categories = json['categories'].cast(); 25 | } 26 | 27 | Map toJson() { 28 | final Map data = new Map(); 29 | data['title'] = this.title; 30 | data['pubDate'] = this.pubDate; 31 | data['link'] = this.link; 32 | data['guid'] = this.guid; 33 | data['author'] = this.author; 34 | data['thumbnail'] = this.thumbnail; 35 | data['description'] = this.description; 36 | data['content'] = this.content; 37 | data['categories'] = this.categories; 38 | return data; 39 | } 40 | } -------------------------------------------------------------------------------- /test/data/repositories/images_repository_impl_test.dart: -------------------------------------------------------------------------------- 1 | // import 'package:flutter_test/flutter_test.dart'; 2 | // import 'package:miladjalali_ir/data/repositories/images_repository_impl.dart'; 3 | // import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 4 | // import 'package:mockito/annotations.dart'; 5 | // import 'package:mockito/mockito.dart'; 6 | // 7 | // class ImagesRepositoryImlMock extends Mock implements ImagesRepositoryIml{} 8 | // class UnsplashSearchResponseMock extends Mock implements UnsplashSearchResponse{} 9 | // 10 | // void main() { 11 | // 12 | // late ImagesRepositoryImlMock imagesRepositoryImlMock; 13 | // setUp(() { 14 | // imagesRepositoryImlMock = new ImagesRepositoryImlMock(); 15 | // }); 16 | // 17 | // int page = 0; 18 | // int pageSize = 0; 19 | // 20 | // group("data wa get successfully", () { 21 | // 22 | // test('should return a NoConnectionFailure if the device is offline', () async { 23 | // //arrange 24 | // when(imagesRepositoryImlMock.fetchPersonImages(page, pageSize)).thenAnswer((_) => Future.value(UnsplashSearchResponseMock())); 25 | // 26 | // //act 27 | // final result = await imagesRepositoryImlMock.fetchPersonImages(page, pageSize); 28 | // 29 | // //assert 30 | // expect(result, UnsplashSearchResponse()); 31 | // }); 32 | // }); 33 | // } -------------------------------------------------------------------------------- /lib/presentation/widgets/common/blog_background_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BlogBackgroundPainter extends CustomPainter { 4 | 5 | BlogBackgroundPainter(); 6 | 7 | 8 | @override 9 | void paint(Canvas canvas, Size size) { 10 | final path = Path(); 11 | 12 | // path.moveTo(0 , 0); 13 | // path.lineTo(size.width, 0); 14 | // path.lineTo(size.width, 64); 15 | // path.lineTo(0, 128); 16 | // path.moveTo(size.width, size.height); 17 | // path.lineTo(0, size.height); 18 | // path.lineTo(0, size.height - 64); 19 | // path.lineTo(size.width, size.height -128); 20 | // path.close(); 21 | 22 | 23 | path.moveTo(0 , 0); 24 | path.lineTo(size.width, 0); 25 | path.lineTo(size.width, 80); 26 | path.lineTo(0, 80); 27 | path.close(); 28 | 29 | 30 | final paint = Paint() 31 | ..style = PaintingStyle.fill; 32 | var rect = Offset.zero & size; 33 | paint.shader = LinearGradient( 34 | begin: Alignment.topRight, 35 | end: Alignment.bottomLeft, 36 | colors: [ 37 | Colors.black54, 38 | Colors.black54, 39 | Colors.black54, 40 | Colors.black54, 41 | ], 42 | ).createShader(rect); 43 | 44 | canvas.drawPath(path, paint); 45 | } 46 | 47 | @override 48 | bool shouldRepaint(covariant BlogBackgroundPainter oldDelegate) => false; 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/util/video_player_data_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flick_video_player/flick_video_player.dart'; 4 | import 'package:video_player/video_player.dart'; 5 | 6 | class VideoPlayerDataManager { 7 | VideoPlayerDataManager({required this.flickManager, required this.urls}); 8 | 9 | int currentPlaying = 0; 10 | final FlickManager flickManager; 11 | final List urls; 12 | late Timer videoChangeTimer; 13 | 14 | String getNextVideo() { 15 | currentPlaying++; 16 | return urls[currentPlaying]; 17 | } 18 | 19 | bool hasNextVideo() { 20 | return currentPlaying != urls.length - 1; 21 | } 22 | 23 | bool hasPreviousVideo() { 24 | return currentPlaying != 0; 25 | } 26 | 27 | skipToNextVideo([Duration? duration]) { 28 | if (hasNextVideo()) { 29 | flickManager.handleChangeVideo(VideoPlayerController.network(urls[currentPlaying + 1]), videoChangeDuration: duration); 30 | ++currentPlaying; 31 | } 32 | } 33 | 34 | skipToPreviousVideo() { 35 | if (hasPreviousVideo()) { 36 | currentPlaying--; 37 | flickManager.handleChangeVideo(VideoPlayerController.network(urls[currentPlaying])); 38 | } 39 | } 40 | 41 | cancelVideoAutoPlayTimer({required bool playNext}) { 42 | if (playNext != true) { 43 | currentPlaying--; 44 | } 45 | flickManager.flickVideoManager?.cancelVideoAutoPlayTimer(playNext: playNext); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/data/providers/network/apis/images_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:either_dart/either.dart'; 2 | import 'package:miladjalali_ir/app/util/util.dart'; 3 | import 'package:miladjalali_ir/data/providers/network/api_endpoint.dart'; 4 | import 'package:miladjalali_ir/data/providers/network/api_provider.dart'; 5 | import 'package:miladjalali_ir/data/providers/network/api_request_representable.dart'; 6 | 7 | class ImagesAPI implements APIRequestRepresentable { 8 | int? page; 9 | int? pageSize; 10 | 11 | ImagesAPI._({this.page, this.pageSize}); 12 | 13 | ImagesAPI.getPersonalImages(page, pageSize) : this._(page: page, pageSize: pageSize); 14 | 15 | @override 16 | String get endpoint => APIEndpoint.unsplashApi; 17 | 18 | String get path { 19 | return "/search/photos"; 20 | } 21 | 22 | @override 23 | HTTPMethod get method { 24 | return HTTPMethod.get; 25 | } 26 | 27 | Map get headers => {}; 28 | 29 | Map get query { 30 | return { 31 | "client_id": "jQJvABfDdHT7-S56KViqMSzeyd_f5CnWxVaIaeiweqc", 32 | "page": "$page", 33 | "per_page": "$pageSize", 34 | "query": "man face", 35 | "color":"black_and_white" 36 | }; 37 | } 38 | 39 | @override 40 | get body => null; 41 | 42 | Future request() { 43 | return APIProvider.instance.request(this); 44 | } 45 | 46 | @override 47 | String get url { 48 | return endpoint + path; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/curve_line_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:path_drawing/path_drawing.dart'; 4 | 5 | enum DrawPosition{ 6 | top, 7 | bottom 8 | } 9 | 10 | class CurveLinePainter extends CustomPainter { 11 | DrawPosition drawPosition; 12 | 13 | 14 | CurveLinePainter(this.drawPosition); 15 | 16 | @override 17 | void paint(Canvas canvas, Size size) { 18 | var paint = Paint(); 19 | 20 | paint.color = Color(0XFF003140); 21 | paint.style = PaintingStyle.stroke; 22 | paint.strokeWidth = 3; 23 | 24 | var startPoint = Offset(0, size.height / 2 - 12 + (drawPosition.index * 24)); 25 | var controlPoint1 = Offset(size.width / 4, size.height / 3 * (drawPosition.index+1)); 26 | var controlPoint2 = Offset(3 * size.width / 4, size.height / 3 * (drawPosition.index+1)); 27 | var endPoint = Offset(size.width, size.height / 2-12+ (drawPosition.index * 24)); 28 | 29 | var path = Path(); 30 | path.moveTo(startPoint.dx, startPoint.dy); 31 | path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx, 32 | controlPoint2.dy, endPoint.dx, endPoint.dy); 33 | 34 | canvas.drawPath( 35 | dashPath( 36 | path, 37 | dashArray: CircularIntervalList([15.0, 10]), 38 | ), 39 | paint, 40 | ); 41 | } 42 | 43 | @override 44 | bool shouldRepaint(CustomPainter oldDelegate) { 45 | return false; 46 | } 47 | } -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miladjalali.ir", 3 | "short_name": "miladjalali.ir", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Milad Jalali | Developer, Designer and Teacher", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "\/icons/android-icon-36x36.png", 14 | "sizes": "36x36", 15 | "type": "image\/png", 16 | "density": "0.75" 17 | }, 18 | { 19 | "src": "\/icons/android-icon-48x48.png", 20 | "sizes": "48x48", 21 | "type": "image\/png", 22 | "density": "1.0" 23 | }, 24 | { 25 | "src": "\/icons/android-icon-72x72.png", 26 | "sizes": "72x72", 27 | "type": "image\/png", 28 | "density": "1.5" 29 | }, 30 | { 31 | "src": "\/icons/android-icon-96x96.png", 32 | "sizes": "96x96", 33 | "type": "image\/png", 34 | "density": "2.0" 35 | }, 36 | { 37 | "src": "\/icons/android-icon-144x144.png", 38 | "sizes": "144x144", 39 | "type": "image\/png", 40 | "density": "3.0" 41 | }, 42 | { 43 | "src": "\/icons/android-icon-192x192.png", 44 | "sizes": "192x192", 45 | "type": "image\/png", 46 | "density": "4.0" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/rounded_button_with_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class RoundedButtonByIcon extends StatelessWidget { 5 | final Color color; 6 | final IconData icon; 7 | final Function onTap; 8 | 9 | RoundedButtonByIcon(this.color, this.icon, this.onTap); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Padding( 14 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), 15 | child: InkWell( 16 | onTap: () async { 17 | onTap(); 18 | }, 19 | child: TextButton( 20 | child: Padding( 21 | padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), 22 | child: Row( 23 | mainAxisSize: MainAxisSize.min, 24 | children: [ 25 | Text( 26 | "View Post", 27 | style: TextStyle(color: Colors.white), 28 | ), 29 | const SizedBox(width: 8.0), 30 | Icon(icon, color: Colors.white), 31 | ], 32 | ), 33 | ), 34 | style: ButtonStyle( 35 | shape: MaterialStateProperty.all(RoundedRectangleBorder( 36 | borderRadius: new BorderRadius.circular(100.0), side: BorderSide(color: Colors.black))), 37 | backgroundColor: MaterialStateProperty.all(Colors.black), 38 | ), 39 | onPressed: () async { 40 | onTap(); 41 | }, 42 | ), 43 | )); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | miladjalali_ir 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/presentation/widgets/mobile/blog_background_painter_for_mobile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ExperienceBackgroundPainterForMobile extends CustomPainter { 4 | 5 | @override 6 | void paint(Canvas canvas, Size size) { 7 | final path = Path(); 8 | 9 | // path.moveTo(0 , 0); 10 | // path.lineTo(size.width, 0); 11 | // path.lineTo(size.width, 64); 12 | // path.lineTo(0, 128); 13 | // path.moveTo(size.width, size.height); 14 | // path.lineTo(0, size.height); 15 | // path.lineTo(0, size.height - 64); 16 | // path.lineTo(size.width, size.height -128); 17 | // path.close(); 18 | 19 | 20 | path.moveTo(0 , 0); 21 | path.lineTo(size.width, 0); 22 | path.lineTo(size.width, 56); 23 | path.lineTo(0, 56); 24 | path.moveTo(size.width, size.height); 25 | path.lineTo(0, size.height); 26 | path.quadraticBezierTo( 27 | size.width / 4, size.height - 96, size.width / 2, size.height - 56); 28 | path.quadraticBezierTo( 29 | 3 / 4 * size.width, size.height - 60, size.width, size.height - 130); 30 | path.lineTo(size.width, size.height -64); 31 | path.close(); 32 | 33 | 34 | final paint = Paint() 35 | ..style = PaintingStyle.fill; 36 | var rect = Offset.zero & size; 37 | paint.shader = LinearGradient( 38 | begin: Alignment.topRight, 39 | end: Alignment.bottomLeft, 40 | colors: [ 41 | Colors.black54, 42 | Colors.black54, 43 | Colors.black54, 44 | Colors.black54, 45 | ], 46 | ).createShader(rect); 47 | 48 | canvas.drawPath(path, paint); 49 | } 50 | 51 | @override 52 | bool shouldRepaint(covariant ExperienceBackgroundPainterForMobile oldDelegate) => false; 53 | } 54 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/experience_background_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ExperienceBackgroundPainter extends CustomPainter { 4 | Color color; 5 | 6 | ExperienceBackgroundPainter(this.color); 7 | 8 | 9 | @override 10 | void paint(Canvas canvas, Size size) { 11 | final path = Path(); 12 | 13 | // path.moveTo(0 , 0); 14 | // path.lineTo(size.width, 0); 15 | // path.lineTo(size.width, 64); 16 | // path.lineTo(0, 128); 17 | // path.moveTo(size.width, size.height); 18 | // path.lineTo(0, size.height); 19 | // path.lineTo(0, size.height - 64); 20 | // path.lineTo(size.width, size.height -128); 21 | // path.close(); 22 | 23 | 24 | path.moveTo(0 , 0); 25 | path.lineTo(size.width, 0); 26 | path.lineTo(size.width, 80); 27 | path.lineTo(0, 80); 28 | path.moveTo(size.width, size.height); 29 | path.lineTo(0, size.height); 30 | path.quadraticBezierTo( 31 | size.width / 4, size.height - 96, size.width / 2, size.height - 60); 32 | path.quadraticBezierTo( 33 | 3 / 4 * size.width, size.height, size.width, size.height - 130); 34 | path.lineTo(size.width, size.height -64); 35 | path.close(); 36 | 37 | 38 | final paint = Paint() 39 | ..style = PaintingStyle.fill; 40 | var rect = Offset.zero & size; 41 | paint.shader = LinearGradient( 42 | begin: Alignment.topRight, 43 | end: Alignment.bottomLeft, 44 | colors: [ 45 | Color(0XFF003140), 46 | Color(0XFF003140), 47 | Color(0XFF003140), 48 | Color(0XFF003140), 49 | ], 50 | ).createShader(rect); 51 | 52 | canvas.drawPath(path, paint); 53 | } 54 | 55 | @override 56 | bool shouldRepaint(covariant ExperienceBackgroundPainter oldDelegate) => false; 57 | } 58 | -------------------------------------------------------------------------------- /lib/presentation/widgets/mobile/experience_background_painter_for_mobile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ExperienceBackgroundPainterForMobile extends CustomPainter { 4 | Color color; 5 | 6 | ExperienceBackgroundPainterForMobile(this.color); 7 | 8 | 9 | @override 10 | void paint(Canvas canvas, Size size) { 11 | final path = Path(); 12 | 13 | // path.moveTo(0 , 0); 14 | // path.lineTo(size.width, 0); 15 | // path.lineTo(size.width, 64); 16 | // path.lineTo(0, 128); 17 | // path.moveTo(size.width, size.height); 18 | // path.lineTo(0, size.height); 19 | // path.lineTo(0, size.height - 64); 20 | // path.lineTo(size.width, size.height -128); 21 | // path.close(); 22 | 23 | 24 | path.moveTo(0 , 0); 25 | path.lineTo(size.width, 0); 26 | path.lineTo(size.width, 56); 27 | path.lineTo(0, 56); 28 | path.moveTo(size.width, size.height); 29 | path.lineTo(0, size.height); 30 | path.quadraticBezierTo( 31 | size.width / 4, size.height - 96, size.width / 2, size.height - 56); 32 | path.quadraticBezierTo( 33 | 3 / 4 * size.width, size.height - 60, size.width, size.height - 130); 34 | path.lineTo(size.width, size.height -64); 35 | path.close(); 36 | 37 | 38 | final paint = Paint() 39 | ..style = PaintingStyle.fill; 40 | var rect = Offset.zero & size; 41 | paint.shader = LinearGradient( 42 | begin: Alignment.topRight, 43 | end: Alignment.bottomLeft, 44 | colors: [ 45 | Color(0XFF003140), 46 | Color(0XFF003140), 47 | Color(0XFF003140), 48 | Color(0XFF003140), 49 | ], 50 | ).createShader(rect); 51 | 52 | canvas.drawPath(path, paint); 53 | } 54 | 55 | @override 56 | bool shouldRepaint(covariant ExperienceBackgroundPainterForMobile oldDelegate) => false; 57 | } 58 | -------------------------------------------------------------------------------- /lib/presentation/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/presentation/home/home_controller.dart'; 4 | import 'dart:html' as html; 5 | import '../widgets/common/nav_bar.dart'; 6 | import '../widgets/mobile/drawer.dart'; 7 | 8 | class HomePage extends GetResponsiveView { 9 | 10 | 11 | 12 | @override 13 | Widget phone() { 14 | 15 | // Add the File Saver js script 16 | final script = html.document.createElement('script') as html.ScriptElement; 17 | script.src = "https://cdn.jsdelivr.net/g/filesaver.js"; 18 | html.document.body?.nodes.add(script); 19 | 20 | return Scaffold( 21 | key: controller.scaffoldKey, 22 | endDrawer: AppDrawer(), 23 | body: Stack( 24 | children: [ 25 | Obx(() => Container( 26 | width: Get.width, 27 | height: Get.height, 28 | child: controller.pages[controller.selectedIndex.value], 29 | )), 30 | Align( 31 | alignment: Alignment.topCenter, 32 | child: Navbar(), 33 | ), 34 | ], 35 | ), 36 | ); 37 | } 38 | 39 | @override 40 | Widget desktop() { 41 | 42 | // Add the File Saver js script 43 | final script = html.document.createElement('script') as html.ScriptElement; 44 | script.src = "https://cdn.jsdelivr.net/g/filesaver.js"; 45 | html.document.body?.nodes.add(script); 46 | 47 | return Scaffold( 48 | body: Stack( 49 | children: [ 50 | Obx(() => Container( 51 | width: Get.width, 52 | height: Get.height, 53 | child: controller.pages[controller.selectedIndex.value], 54 | )), 55 | Align( 56 | alignment: Alignment.topCenter, 57 | child: Navbar(), 58 | ), 59 | ], 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/rounded_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RoundedButton extends StatefulWidget { 4 | final String text; 5 | final Color color; 6 | final Color hoveredColor; 7 | final Color hoveredTextColor; 8 | final Function onTap; 9 | 10 | RoundedButton(this.text, this.color, this.hoveredColor, this.hoveredTextColor, this.onTap); 11 | 12 | @override 13 | _ResumeButtonState createState() => _ResumeButtonState(); 14 | } 15 | 16 | class _ResumeButtonState extends State { 17 | bool hovered = false; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return InkWell( 22 | hoverColor: Colors.transparent, 23 | splashColor: Colors.transparent, 24 | onTap: () { 25 | widget.onTap(); 26 | }, 27 | onHover: (value) { 28 | if (mounted) { 29 | setState(() { 30 | hovered = value; 31 | }); 32 | } 33 | }, 34 | child: AnimatedContainer( 35 | height: 40, 36 | duration: kThemeAnimationDuration, 37 | alignment: Alignment.center, 38 | padding: const EdgeInsets.symmetric( 39 | vertical: 6, 40 | horizontal: 10, 41 | ), 42 | decoration: BoxDecoration( 43 | border: Border.all(color: !hovered ? widget.color : widget.hoveredColor, width: 2), 44 | borderRadius: BorderRadius.all(Radius.circular(30)), 45 | color: !hovered ? Colors.transparent : widget.hoveredColor), 46 | child: AnimatedDefaultTextStyle( 47 | duration: kThemeAnimationDuration, 48 | style: TextStyle( 49 | color: !hovered ? widget.color : widget.hoveredTextColor, 50 | fontSize: 17, 51 | fontWeight: FontWeight.w500, 52 | fontFamily: 'Ubuntu', 53 | ), 54 | child: Text( 55 | widget.text, 56 | ), 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/data/models/unsplash_image_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:miladjalali_ir/data/models/unsplash_image_urls_model.dart'; 2 | import 'package:miladjalali_ir/domain/entities/unsplash_image.dart'; 3 | 4 | class UnsplashImageModel extends UnsplashImage { 5 | final String? id; 6 | final String? createdAt; 7 | final String? updatedAt; 8 | final String? promotedAt; 9 | final int? width; 10 | final int? height; 11 | final String? color; 12 | final String? blurHash; 13 | final String? description; 14 | final String? altDescription; 15 | final UnsplashImageUrlsModel? urls; 16 | 17 | UnsplashImageModel( 18 | {required this.id, 19 | required this.createdAt, 20 | required this.updatedAt, 21 | required this.promotedAt, 22 | required this.width, 23 | required this.height, 24 | required this.color, 25 | required this.blurHash, 26 | required this.description, 27 | required this.altDescription, 28 | required this.urls}) 29 | : super( 30 | id: id, 31 | color: color, 32 | width: width, 33 | description: description, 34 | createdAt: createdAt, 35 | height: height, 36 | altDescription: altDescription, 37 | blurHash: blurHash, 38 | promotedAt: promotedAt, 39 | updatedAt: updatedAt, 40 | urls: urls); 41 | 42 | @override 43 | factory UnsplashImageModel.fromJson(Map json) => UnsplashImageModel( 44 | id : json['id'], 45 | createdAt : json['created_at'], 46 | updatedAt : json['updated_at'], 47 | promotedAt : json['promoted_at'], 48 | width : json['width'], 49 | height : json['height'], 50 | color : json['color'], 51 | blurHash : json['blur_hash'], 52 | description : json['description'], 53 | altDescription : json['alt_description'], 54 | urls : json['urls'] != null ? new UnsplashImageUrlsModel.fromJson(json['urls']) : null, 55 | ); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/experience_card_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/presentation/widgets/common/curve_line_painter.dart'; 4 | import 'package:path_drawing/path_drawing.dart'; 5 | 6 | class ExperienceCardPainter extends CustomPainter { 7 | DrawPosition drawPosition; 8 | 9 | 10 | ExperienceCardPainter(this.drawPosition); 11 | 12 | @override 13 | void paint(Canvas canvas, Size size) { 14 | var paint = Paint(); 15 | 16 | paint.color = Colors.black87; 17 | paint.style = PaintingStyle.stroke; 18 | paint.strokeWidth = 3; 19 | 20 | var point1; 21 | var point2; 22 | var point3; 23 | var point4; 24 | var point5; 25 | var point6; 26 | if(drawPosition==DrawPosition.top){ 27 | point1 = Offset(0, 3); 28 | point2 = Offset(size.width ,3); 29 | point3 = Offset(size.width, size.height); 30 | point4 = Offset(size.width/ 5 , size.height); 31 | point5 = Offset(0, size.height * 8 / 10); 32 | point6 = Offset(0, 1.5); 33 | }else{ 34 | point1 = Offset(size.width, 0); 35 | point2 = Offset(size.width ,size.height -3); 36 | point3 = Offset(0, size.height -3); 37 | point4 = Offset(0, size.height / 5); 38 | point5 = Offset(size.width / 5, 3); 39 | point6 = Offset(size.width, 1.5); 40 | } 41 | 42 | var path = Path(); 43 | path.moveTo(point1.dx, point1.dy); 44 | path.lineTo(point1.dx, point1.dy); 45 | path.lineTo(point2.dx, point2.dy); 46 | path.lineTo(point3.dx, point3.dy); 47 | path.lineTo(point4.dx, point4.dy); 48 | path.arcToPoint(point5,radius: Radius.circular(48),clockwise: false); 49 | path.lineTo(point6.dx, point6.dy); 50 | 51 | canvas.drawPath( 52 | dashPath( 53 | path, 54 | dashArray: CircularIntervalList([10.0, 1]), 55 | ), 56 | paint, 57 | ); 58 | } 59 | 60 | @override 61 | bool shouldRepaint(CustomPainter oldDelegate) { 62 | return false; 63 | } 64 | } -------------------------------------------------------------------------------- /lib/presentation/home/home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/presentation/blog/blog_page.dart'; 4 | import 'package:miladjalali_ir/presentation/experiences/experiences_binding.dart'; 5 | import 'package:miladjalali_ir/presentation/information/information_binding.dart'; 6 | import 'package:miladjalali_ir/presentation/information/information_page.dart'; 7 | import '../../app/constants/themes.dart'; 8 | import '../about/about_binding.dart'; 9 | import '../about/about_page.dart'; 10 | import '../blog/blog_binding.dart'; 11 | import '../experiences/experiences_page.dart'; 12 | import '../information/information_controller.dart'; 13 | import '../projects/projects_page.dart'; 14 | import '../projects/projets_binding.dart'; 15 | 16 | class HomeController extends GetxController { 17 | 18 | 19 | RxInt selectedIndex = 0.obs; 20 | RxInt navBarSelectedIndex = 0.obs; 21 | RxList pages = [InformationPage(),AboutPage(),ExperiencesPage(),ProjectsPage(),BlogPage()].obs; 22 | 23 | var scaffoldKey = GlobalKey(); 24 | 25 | 26 | final items = [ 27 | 'Home', 28 | 'About', 29 | 'Experience', 30 | 'Projects', 31 | 'Blog', 32 | 'Resume', 33 | ]; 34 | 35 | void onIndexChanged(){ 36 | switch(selectedIndex.value){ 37 | case 0: 38 | InformationBinding().dependencies(); 39 | Get.changeTheme(Themes.themeBlueOrange); 40 | break; 41 | case 1: 42 | AboutBinding().dependencies(); 43 | Get.changeTheme(Themes.themeBlackOrange); 44 | break; 45 | case 2: 46 | ExperiencesBinding().dependencies(); 47 | Get.changeTheme(Themes.themePurpleOrange); 48 | break; 49 | case 3: 50 | ProjectsBinding().dependencies(); 51 | Get.changeTheme(Themes.themeOrangeOrange); 52 | break; 53 | case 4: 54 | BlogBinding().dependencies(); 55 | Get.changeTheme(Themes.themeBlackOrange); 56 | break; 57 | } 58 | } 59 | 60 | @override 61 | void onInit() { 62 | Get.put(InformationController()); 63 | super.onInit(); 64 | } 65 | 66 | onNavbarItemSelected(int v){ 67 | selectedIndex.value = v; 68 | onIndexChanged(); 69 | update(); 70 | } 71 | } -------------------------------------------------------------------------------- /lib/presentation/widgets/common/typewriter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Typewriter extends StatefulWidget { 4 | final String text; 5 | final Curve curve; 6 | final TextStyle textStyle; 7 | final VoidCallback onEnd; 8 | final Duration duration; 9 | final bool animate; 10 | 11 | Typewriter( 12 | this.text, { 13 | this.curve = Curves.easeInOut, 14 | required this.textStyle, 15 | required this.onEnd, 16 | this.animate = true, 17 | this.duration = const Duration(seconds: 2), 18 | }); 19 | 20 | @override 21 | _TypewriterState createState() => _TypewriterState(); 22 | } 23 | 24 | class _TypewriterState extends State with TickerProviderStateMixin { 25 | late Animation _characterCount; 26 | late AnimationController controller; 27 | 28 | int _stringIndex = 0; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | controller = AnimationController( 34 | duration: widget.duration, 35 | vsync: this, 36 | ); 37 | 38 | _stringIndex = _stringIndex == null ? 0 : _stringIndex + 1; 39 | 40 | _characterCount = StepTween(begin: 0, end: widget.text.length).animate( 41 | CurvedAnimation( 42 | parent: controller, 43 | curve: widget.curve, 44 | ), 45 | )..addListener(() { 46 | setState(() {}); 47 | }); 48 | 49 | if (widget.animate) controller.forward(); 50 | 51 | controller.addListener(() { 52 | if (widget.onEnd != null && 53 | controller.status == AnimationStatus.completed) { 54 | widget.onEnd(); 55 | } 56 | }); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | controller.dispose(); 62 | super.dispose(); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return Container( 68 | child: _characterCount == null 69 | ? null 70 | : AnimatedBuilder( 71 | animation: _characterCount, 72 | builder: (BuildContext context, Widget? child) { 73 | String text = !widget.animate 74 | ? widget.text 75 | : widget.text.substring(0, _characterCount.value); 76 | 77 | return Text(text, style: widget.textStyle); 78 | }, 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/presentation/experiences/experiences_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/domain/entities/experience.dart'; 4 | 5 | import '../../app/constants/job_type.dart'; 6 | import '../../app/constants/work_places.dart'; 7 | 8 | class ExperiencesController extends GetxController with GetTickerProviderStateMixin { 9 | RxList experiences = [].obs; 10 | 11 | @override 12 | void onInit() { 13 | addExperiences(); 14 | super.onInit(); 15 | } 16 | 17 | @override 18 | void onReady() { 19 | super.onReady(); 20 | } 21 | 22 | @override 23 | void dispose() { 24 | super.dispose(); 25 | } 26 | 27 | void addExperiences() { 28 | experiences.add(Experience( 29 | companyTitle: "Irisa", 30 | companyLogo: AssetImage("assets/images/irisa.jpg"), 31 | startDate: "08/06/2016", 32 | endDate: "08/08/2016", 33 | jobPosition: "Android Intern", 34 | jobType: JobType.internship, 35 | workplaces: Workplaces.hybrid, 36 | description: "")); 37 | 38 | experiences.add(Experience( 39 | companyTitle: "Nicode", 40 | companyLogo: AssetImage("assets/images/nicode.jpg"), 41 | startDate: "22/05/2018", 42 | endDate: "23/10/2019", 43 | jobPosition: "Android Developer", 44 | jobType: JobType.fullTime, 45 | workplaces: Workplaces.onSite, 46 | description: "")); 47 | 48 | experiences.add(Experience( 49 | companyTitle: "Nicode", 50 | companyLogo: AssetImage("assets/images/nicode.jpg"), 51 | startDate: "23/10/2019", 52 | endDate: "21/03/2022", 53 | jobPosition: "Flutter Developer", 54 | jobType: JobType.fullTime, 55 | workplaces: Workplaces.onSite, 56 | description: "")); 57 | 58 | 59 | experiences.add(Experience( 60 | companyTitle: "Nicode", 61 | companyLogo: AssetImage("assets/images/nicode.jpg"), 62 | startDate: "21/03/2022", 63 | endDate: "Present", 64 | jobPosition: "Flutter Developer", 65 | jobType: JobType.partTime, 66 | workplaces: Workplaces.hybrid, 67 | description: "")); 68 | 69 | experiences.add(Experience( 70 | companyTitle: "Uplance", 71 | companyLogo: AssetImage("assets/images/uplance.jpg"), 72 | startDate: "21/03/2021", 73 | endDate: "Present", 74 | jobPosition: "Flutter Developer", 75 | jobType: JobType.contract, 76 | workplaces: Workplaces.remote, 77 | description: "")); 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/presentation/projects/projects_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:miladjalali_ir/domain/entities/portfolio.dart'; 3 | 4 | import '../widgets/common/portfolio_widget.dart'; 5 | 6 | 7 | class ProjectsController extends GetxController with GetTickerProviderStateMixin { 8 | late List portfolios; 9 | RxInt selectedIndex = 0.obs; 10 | 11 | @override 12 | void onInit() async{ 13 | portfolios = [].obs; 14 | 15 | addPortfolio(); 16 | 17 | super.onInit(); 18 | } 19 | 20 | @override 21 | void onReady() { 22 | super.onReady(); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | super.dispose(); 28 | } 29 | 30 | void addPortfolio() async{ 31 | 32 | portfolios.add(MobilePortfolio(Portfolio( 33 | 0, 34 | "Koohmart", 35 | "Koohmart Services", 36 | "Koohmart is a specialized software for buying and selling mountaineering equipment.\n\nAlso, in this software there is information about different peaks and you can find the way to reach the peak and reach the peak with the help of the software.", 37 | ["assets/images/kohmart_1.png", "assets/images/kohmart_2.png", "assets/images/kohmart_3.png", "assets/images/kohmart_4.png"], 38 | "https://play.google.com/store/apps/details?id=ir.itamin.koohposh&hl=en&gl=US", 39 | ))); 40 | 41 | 42 | portfolios.add(MobilePortfolio(Portfolio( 43 | 0, 44 | "Kargozare Man", 45 | "Kargozare Man Services", 46 | "Kargozareman is an online insurance service system that is available in mobile and web versions, and users can get the services they need online.\n\nThis system can be used by employers, retirees and employees", 47 | ["assets/images/kargozare_man_1.png", "assets/images/kargozare_man_2.png", "assets/images/kargozare_man_3.png", "assets/images/kargozare_man_4.png"], 48 | "https://kargozareman.com", 49 | ))); 50 | 51 | portfolios.add(WebPortfolio(Portfolio( 52 | 0, 53 | "Orthobiomed Inc", 54 | "Orthobiomed Services", 55 | "Ortho BioMed Inc. is a multi-scope company working in the medical landscape by innovating on and distributing cutting-edge medical technologies available to medical experts and patients, and providing a broad range of expertise-based clinical research services to other medical device manufacturers.\n\nOrtho BioMed Inc. operates primarily in North America, with headquarters located in Toronto, ON, Canada and is known in the medical industry for its timely and reliable services in a variety of areas and its flexibility in meeting clients’ needs.", 56 | ["assets/images/orthobiomed_1.png","assets/images/orthobiomed_2.png","assets/images/orthobiomed_3.png"], 57 | "https://orthobiomed.ca", 58 | ))); 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/presentation/experiences/experiences_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/presentation/widgets/mobile/experience_background_painter_for_mobile.dart'; 4 | 5 | import '../widgets/common/experience_background_painter.dart'; 6 | import '../widgets/common/experience_time_line_widget.dart'; 7 | import 'experiences_controller.dart'; 8 | 9 | class ExperiencesPage extends GetResponsiveView { 10 | ExperiencesPage() 11 | : super(settings: ResponsiveScreenSettings(desktopChangePoint: 1000, tabletChangePoint: 768, watchChangePoint: 300)); 12 | 13 | @override 14 | Widget desktop() { 15 | return Scaffold( 16 | body: Container( 17 | color: Colors.white, 18 | width: Get.width, 19 | height: Get.height, 20 | child: Stack( 21 | children: [ 22 | Align( 23 | alignment: Alignment.bottomRight, 24 | child: Container( 25 | margin: EdgeInsets.only(bottom: 48), 26 | child: Image( 27 | image: AssetImage("assets/images/programmer.gif"), 28 | height: Get.height / 4, 29 | width: Get.height / 3, 30 | ), 31 | ), 32 | ), 33 | CustomPaint(painter: ExperienceBackgroundPainter(Colors.white), size: Size.infinite), 34 | Align( 35 | alignment: Alignment.center, 36 | child: Container( 37 | margin: const EdgeInsets.only(bottom: 128), 38 | child: ExprienceTimeLineWidget(controller.experiences.value), 39 | )), 40 | ], 41 | ), 42 | ), 43 | ); 44 | } 45 | 46 | @override 47 | Widget phone() { 48 | return Scaffold( 49 | body: Container( 50 | color: Colors.white, 51 | width: Get.width, 52 | height: Get.height, 53 | child: Stack( 54 | children: [ 55 | Align( 56 | alignment: Alignment.bottomRight, 57 | child: Container( 58 | margin: EdgeInsets.only(bottom: 48), 59 | child: Image( 60 | image: AssetImage("assets/images/programmer.gif"), 61 | height: Get.height / 4, 62 | width: Get.height / 3, 63 | ), 64 | ), 65 | ), 66 | CustomPaint(painter: ExperienceBackgroundPainterForMobile(Colors.white), size: Size.infinite), 67 | Align( 68 | alignment: Alignment.center, 69 | child: Container( 70 | margin: const EdgeInsets.only(bottom: 128), 71 | child: ExprienceTimeLineWidget(controller.experiences.value), 72 | )), 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: miladjalali_ir 2 | description: Milad Jalali Personal Website 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.14.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | get: ^4.1.4 27 | tuple: ^2.0.0 28 | either_dart: ^0.2.0 29 | json_annotation: 30 | jiffy: ^4.1.0 31 | shared_preferences: ^2.0.6 32 | visibility_detector: ^0.2.0 33 | awesome_snackbar_content: ^0.0.8 34 | animated_background: ^2.0.0 35 | animate_gradient: 36 | url_launcher: ^6.1.4 37 | video_player: ^2.4.5 38 | flutter_blurhash: ^0.7.0 39 | cached_network_image: ^3.2.1 40 | flick_video_player: ^0.5.0 41 | ripple_animation: ^0.0.1+2 42 | fading_images_slider: ^0.0.4 43 | timelines: ^0.1.0 44 | palette_generator: ^0.3.3+1 45 | mj_image_slider: ^0.0.3 46 | path_drawing: ^1.0.1 47 | font_awesome_flutter: ^10.1.0 48 | flutter_spinkit: ^5.1.0 49 | slider_button: ^2.0.0 50 | mockito: ^5.3.0 51 | 52 | # The following adds the Cupertino Icons font to your application. 53 | # Use with the CupertinoIcons class for iOS style icons. 54 | cupertino_icons: ^1.0.2 55 | 56 | dev_dependencies: 57 | flutter_test: 58 | sdk: flutter 59 | flutter_test_ui: ^2.0.0 60 | 61 | # For information on the generic Dart part of this file, see the 62 | # following page: https://dart.dev/tools/pub/pubspec 63 | 64 | # The following section is specific to Flutter. 65 | flutter: 66 | 67 | # The following line ensures that the Material Icons font is 68 | # included with your application, so that you can use the icons in 69 | # the material Icons class. 70 | uses-material-design: true 71 | 72 | 73 | assets: 74 | - assets/miladjalali.pdf 75 | - assets/images/ 76 | 77 | fonts: 78 | - family: MySocialIcons 79 | fonts: 80 | - asset: assets/fonts/MySocialIcons.ttf 81 | - family: Ubuntu 82 | fonts: 83 | - asset: assets/fonts/Ubuntu-Regular.ttf 84 | - asset: assets/fonts/Ubuntu-Medium.ttf 85 | weight: 500 86 | - asset: assets/fonts/Ubuntu-Bold.ttf 87 | weight: 700 -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/blog_post_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:miladjalali_ir/domain/entities/medium_post_item.dart'; 6 | import 'package:miladjalali_ir/presentation/widgets/common/rounded_button_with_icon.dart'; 7 | 8 | import '../../../app/util/url_helper.dart'; 9 | 10 | @immutable 11 | class BlogPostItem extends StatelessWidget { 12 | late final MediumPostItem postItem; 13 | 14 | BlogPostItem(this.postItem); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Card( 19 | elevation: 5, 20 | surfaceTintColor: Colors.red, 21 | shape: RoundedRectangleBorder( 22 | side: BorderSide(color: Colors.transparent, width: 1), 23 | borderRadius: BorderRadius.circular(15), 24 | ), 25 | child: Column( 26 | crossAxisAlignment: CrossAxisAlignment.start, 27 | mainAxisSize: MainAxisSize.min, 28 | children: [ 29 | ClipRRect( 30 | borderRadius: BorderRadius.only(topRight: Radius.circular(15), topLeft: Radius.circular(15)), 31 | child: CachedNetworkImage( 32 | imageUrl: postItem.thumbnail!, 33 | fit: BoxFit.cover, 34 | progressIndicatorBuilder: (context, url, downloadProgress) => SpinKitDualRing( 35 | color: Get.theme.accentColor, 36 | size: 36, 37 | ), 38 | errorWidget: (context, url, error) => Image.asset("assets/images/ic_no_image.jpg",fit: BoxFit.contain,), 39 | height: 200, 40 | width: double.infinity, 41 | ), 42 | ), 43 | Padding( 44 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), 45 | child: Text( 46 | postItem.title!, 47 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), 48 | maxLines: 1, 49 | overflow: TextOverflow.ellipsis, 50 | ), 51 | ), 52 | Row( 53 | children: [ 54 | Expanded( 55 | child: Column( 56 | crossAxisAlignment: CrossAxisAlignment.start, 57 | children: [ 58 | Padding( 59 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 4), 60 | child: Text( 61 | postItem.author!, 62 | maxLines: 1, 63 | ), 64 | ), 65 | Padding( 66 | padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), 67 | child: Text( 68 | postItem.pubDate.toString(), 69 | maxLines: 1, 70 | ), 71 | ), 72 | ], 73 | )), 74 | RoundedButtonByIcon(Colors.black, Icons.arrow_forward, () { 75 | UrlHelper.launchUrl(postItem.link!); 76 | }) 77 | ], 78 | ) 79 | ], 80 | ), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/presentation/widgets/mobile/drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/app/util/url_helper.dart'; 4 | import 'package:miladjalali_ir/presentation/home/home_controller.dart'; 5 | import 'package:miladjalali_ir/presentation/widgets/common/rounded_button.dart'; 6 | 7 | import 'package:miladjalali_ir/app/util/styles.dart'; 8 | 9 | 10 | class AppDrawer extends GetView{ 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Drawer( 15 | child: Obx(()=>Container( 16 | color: controller.navBarSelectedIndex.value==1?Colors.black.withOpacity(0.75):Colors.lightBlue, 17 | child: Stack( 18 | alignment: Alignment.center, 19 | children: [ 20 | Positioned( 21 | left: 8, 22 | top: 8, 23 | child: IconButton( 24 | color: Colors.white, 25 | icon: Icon(Icons.close), 26 | onPressed: () { 27 | Navigator.pop(context); 28 | }, 29 | ), 30 | ), 31 | Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | mainAxisSize: MainAxisSize.min, 34 | children: controller.items.map((item) { 35 | return InkWell( 36 | onTap: () { 37 | switch (item) { 38 | case 'Resume': 39 | break; 40 | default: 41 | controller.navBarSelectedIndex.value = controller.items.indexOf(item); 42 | controller.onNavbarItemSelected(controller.navBarSelectedIndex.toInt()); 43 | break; 44 | } 45 | 46 | Navigator.pop(context); 47 | }, 48 | child: item == 'Resume' 49 | ? SizedBox(width: 160, child: RoundedButton("Resume",Colors.white,Colors.white,Colors.lightBlue, (){ 50 | UrlHelper.downloadResume(); 51 | })) 52 | : Column( 53 | mainAxisAlignment: MainAxisAlignment.center, 54 | children: [ 55 | Text( 56 | item, 57 | style: Styles.navBarTextStyle.copyWith( 58 | fontWeight: FontWeight.w500, 59 | color: Styles.navBarTextStyle.color?.withOpacity( 60 | controller.navBarSelectedIndex.value == controller.items.indexOf(item) 61 | ? 1.0 62 | : 0.75, 63 | ), 64 | ), 65 | ), 66 | SizedBox(height: 4), 67 | if (item != 'Resume') 68 | AnimatedContainer( 69 | duration: Duration(milliseconds: 300), 70 | height: 2, 71 | width: 20, 72 | color: controller.selectedIndex.value == controller.items.indexOf(item) 73 | ? Colors.white 74 | : Colors.transparent, 75 | ), 76 | SizedBox(height: 20), 77 | ], 78 | ), 79 | ); 80 | }).toList(), 81 | ), 82 | ], 83 | ), 84 | )), 85 | ); 86 | } 87 | } -------------------------------------------------------------------------------- /lib/presentation/blog/blog_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/presentation/blog/blog_controller.dart'; 4 | import 'package:miladjalali_ir/presentation/widgets/common/loading_widget.dart'; 5 | import 'package:miladjalali_ir/presentation/widgets/common/no_post_widget.dart'; 6 | 7 | import '../widgets/common/blog_background_painter.dart'; 8 | import '../widgets/common/blog_post_item.dart'; 9 | import '../widgets/mobile/blog_background_painter_for_mobile.dart'; 10 | 11 | class BlogPage extends GetResponsiveView { 12 | BlogPage() 13 | : super(settings: ResponsiveScreenSettings(desktopChangePoint: 1000, tabletChangePoint: 768, watchChangePoint: 300)); 14 | 15 | @override 16 | Widget desktop() { 17 | return Scaffold( 18 | backgroundColor: Colors.white, 19 | body: Container( 20 | width: Get.width, 21 | height: Get.height, 22 | child: Obx(() => Stack( 23 | children: [ 24 | CustomPaint(painter: BlogBackgroundPainter(), size: Size.infinite), 25 | controller.mediumPostsResponse?.value == null 26 | ? LoadingWidget() 27 | : controller.mediumPostsResponse!.value!.items!.isEmpty 28 | ? NoPostWidget() 29 | : Container( 30 | padding: EdgeInsets.fromLTRB(96,96,96,32), 31 | child: GridView.builder( 32 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 33 | crossAxisCount: 3, 34 | crossAxisSpacing: 8.0, 35 | mainAxisSpacing: 8.0, 36 | childAspectRatio:((Get.width- 192)/3)/330 37 | ), 38 | itemCount: controller.mediumPostsResponse!.value!.items!.length, 39 | physics: ScrollPhysics(), 40 | itemBuilder: (BuildContext ctx, index) { 41 | return BlogPostItem(controller.mediumPostsResponse!.value!.items![index]); 42 | }), 43 | ), 44 | ], 45 | )), 46 | ), 47 | ); 48 | } 49 | 50 | @override 51 | Widget phone() { 52 | return Scaffold( 53 | body: Obx(() => Stack( 54 | children: [ 55 | controller.mediumPostsResponse?.value == null 56 | ? LoadingWidget() 57 | : controller.mediumPostsResponse!.value!.items!.isEmpty 58 | ? NoPostWidget() 59 | : Container( 60 | padding: EdgeInsets.fromLTRB(16,96,16,16), 61 | child: GridView.builder( 62 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 63 | crossAxisCount: 2, 64 | crossAxisSpacing: 8.0, 65 | mainAxisSpacing: 8.0, 66 | childAspectRatio:((Get.width- 32)/2)/330 67 | ), 68 | itemCount: controller.mediumPostsResponse!.value!.items!.length, 69 | physics: AlwaysScrollableScrollPhysics(), 70 | itemBuilder: (BuildContext ctx, index) { 71 | return BlogPostItem(controller.mediumPostsResponse!.value!.items![index]); 72 | }), 73 | ), 74 | CustomPaint(painter: ExperienceBackgroundPainterForMobile(), size: Size.infinite), 75 | ], 76 | )), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/data/providers/network/api_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:either_dart/either.dart'; 4 | import 'package:get/get_connect/connect.dart'; 5 | import 'package:miladjalali_ir/data/providers/network/api_request_representable.dart'; 6 | 7 | import '../../../domain/entities/unsplash_search_response.dart'; 8 | 9 | class APIProvider { 10 | static const requestTimeOut = Duration(seconds: 25); 11 | final _client = GetConnect(timeout: requestTimeOut); 12 | 13 | static final _singleton = APIProvider(); 14 | 15 | static APIProvider get instance => _singleton; 16 | 17 | Future request(APIRequestRepresentable request) async { 18 | try { 19 | final response = await _client.request( 20 | request.url, 21 | request.method.string, 22 | headers: request.headers, 23 | query: request.query, 24 | body: request.body, 25 | ); 26 | return _returnResponse(response); 27 | } on TimeoutException catch (_) { 28 | return Left(TimeOutException(null)); 29 | } on SocketException { 30 | return Left(FetchDataException('No Internet connection')); 31 | } 32 | } 33 | 34 | dynamic _returnResponse(Response response) { 35 | switch (response.statusCode) { 36 | case 200: 37 | return Right(response.body); 38 | case 400: 39 | return Left(BadRequestException(response.body.toString())); 40 | case 401: 41 | case 403: 42 | return Left(UnauthorisedException(response.body.toString())); 43 | case 404: 44 | return Left(BadRequestException('Not found')); 45 | case 500: 46 | return Left( FetchDataException('Internal Server Error')); 47 | default: 48 | return Left(FetchDataException('Error occurred while Communication with Server with StatusCode : ${response.statusCode}')); 49 | } 50 | } 51 | } 52 | 53 | class AppException implements Exception { 54 | final code; 55 | final message; 56 | final details; 57 | 58 | AppException({this.code, this.message, this.details}); 59 | 60 | String toString() { 61 | return "[$code]: $message \n $details"; 62 | } 63 | } 64 | 65 | class FetchDataException extends AppException { 66 | FetchDataException(String? details) 67 | : super( 68 | code: "fetch-data", 69 | message: "Error During Communication", 70 | details: details, 71 | ); 72 | } 73 | 74 | class BadRequestException extends AppException { 75 | BadRequestException(String? details) 76 | : super( 77 | code: "invalid-request", 78 | message: "Invalid Request", 79 | details: details, 80 | ); 81 | } 82 | 83 | class UnauthorisedException extends AppException { 84 | UnauthorisedException(String? details) 85 | : super( 86 | code: "unauthorised", 87 | message: "Unauthorised", 88 | details: details, 89 | ); 90 | } 91 | 92 | class InvalidInputException extends AppException { 93 | InvalidInputException(String? details) 94 | : super( 95 | code: "invalid-input", 96 | message: "Invalid Input", 97 | details: details, 98 | ); 99 | } 100 | 101 | class AuthenticationException extends AppException { 102 | AuthenticationException(String? details) 103 | : super( 104 | code: "authentication-failed", 105 | message: "Authentication Failed", 106 | details: details, 107 | ); 108 | } 109 | 110 | class TimeOutException extends AppException { 111 | TimeOutException(String? details) 112 | : super( 113 | code: "request-timeout", 114 | message: "Request TimeOut", 115 | details: details, 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/social_media_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | import 'package:miladjalali_ir/app/util/url_helper.dart'; 4 | 5 | class SocialMediaBar extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container( 9 | padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), 10 | margin: const EdgeInsets.only(bottom: 32), 11 | decoration: BoxDecoration( 12 | border: Border.all(color: Colors.lightBlue.withOpacity(0.3), width: 1.4), 13 | color: Colors.black.withOpacity(0.75), 14 | borderRadius: BorderRadius.all(Radius.circular(35)), 15 | ), 16 | child: Row( 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | InkWell( 20 | hoverColor: Colors.transparent, 21 | splashColor: Colors.transparent, 22 | onTap: () { 23 | UrlHelper.launchUrl('https://github.com/miladjalalli'); 24 | }, 25 | child: Icon( 26 | FontAwesomeIcons.github, 27 | color: Colors.lightBlue.withOpacity(0.75), 28 | ), 29 | ), 30 | SizedBox(width: 30), 31 | InkWell( 32 | hoverColor: Colors.transparent, 33 | splashColor: Colors.transparent, 34 | onTap: () { 35 | UrlHelper.launchUrl( 36 | 'https://www.linkedin.com/in/miladjalali1994', 37 | ); 38 | }, 39 | child: Icon( 40 | FontAwesomeIcons.linkedin, 41 | color: Colors.lightBlue.withOpacity(0.75), 42 | ), 43 | ), 44 | SizedBox(width: 30), 45 | InkWell( 46 | hoverColor: Colors.transparent, 47 | splashColor: Colors.transparent, 48 | onTap: () { 49 | UrlHelper.launchUrl( 50 | "mailto:miladjalali.dev@gmail.com?subject=Hello%20Milad", 51 | ); 52 | }, 53 | child: Icon( 54 | Icons.email_outlined, 55 | color: Colors.lightBlue.withOpacity(0.75), 56 | ), 57 | ), 58 | SizedBox(width: 30), 59 | InkWell( 60 | hoverColor: Colors.transparent, 61 | splashColor: Colors.transparent, 62 | onTap: () { 63 | UrlHelper.launchUrl('https://stackoverflow.com/users/8349963'); 64 | }, 65 | child: Icon( 66 | FontAwesomeIcons.stackOverflow, 67 | color: Colors.lightBlue.withOpacity(0.75), 68 | ), 69 | ), 70 | SizedBox(width: 30), 71 | InkWell( 72 | hoverColor: Colors.transparent, 73 | splashColor: Colors.transparent, 74 | onTap: () { 75 | UrlHelper.launchUrl('https://www.instagram.com/miladjalali.ir/'); 76 | }, 77 | child: Icon( 78 | FontAwesomeIcons.instagram, 79 | color: Colors.lightBlue.withOpacity(0.75), 80 | ), 81 | ), 82 | SizedBox(width: 30), 83 | InkWell( 84 | hoverColor: Colors.transparent, 85 | splashColor: Colors.transparent, 86 | onTap: () { 87 | UrlHelper.launchUrl('https://medium.com/@miladjalali'); 88 | }, 89 | child: Icon( 90 | FontAwesomeIcons.medium, 91 | color: Colors.lightBlue.withOpacity(0.75), 92 | ), 93 | ), 94 | SizedBox(width: 30), 95 | InkWell( 96 | hoverColor: Colors.transparent, 97 | splashColor: Colors.transparent, 98 | onTap: () { 99 | UrlHelper.launchUrl('https://wa.link/ggxmai'); 100 | }, 101 | child: Icon( 102 | FontAwesomeIcons.whatsapp, 103 | color: Colors.lightBlue.withOpacity(0.75), 104 | ), 105 | ), 106 | ], 107 | ), 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/presentation/projects/projects_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'projects_controller.dart'; 4 | 5 | 6 | class ProjectsPage extends GetResponsiveView { 7 | ProjectsPage() : super(settings: ResponsiveScreenSettings(desktopChangePoint: 1000, tabletChangePoint: 768, watchChangePoint: 300)); 8 | 9 | @override 10 | Widget desktop() { 11 | return Obx(() => Container( 12 | child: Stack( 13 | children: [ 14 | AnimatedSwitcher(duration: Duration(seconds: 3),child: controller.portfolios[controller.selectedIndex.value].desktop(),), 15 | Align( 16 | alignment: Alignment.bottomCenter, 17 | child: Container( 18 | height: 50, 19 | margin: EdgeInsets.all(32), 20 | child: ListView.builder( 21 | itemBuilder: (context,i){ 22 | return InkWell( 23 | onTap: (){ 24 | controller.selectedIndex.value = i; 25 | controller.update(); 26 | controller.selectedIndex.refresh(); 27 | }, 28 | child: Container( 29 | width: 42, 30 | height: 42, 31 | margin: EdgeInsets.all(4), 32 | decoration: BoxDecoration( 33 | color:Colors.black.withOpacity(0.65), 34 | borderRadius: BorderRadius.circular(100), 35 | border: Border.all(color: controller.selectedIndex.value==i?Colors.orange:Colors.transparent,width: 2) 36 | ), 37 | child: Center(child: Text((i+1).toString(),style: TextStyle( 38 | color: controller.selectedIndex.value==i?Colors.orange:Colors.white, 39 | fontSize: controller.selectedIndex.value==i?14:12, 40 | fontWeight: controller.selectedIndex.value==i?FontWeight.w600:FontWeight.normal 41 | ),)), 42 | ), 43 | ); 44 | }, 45 | itemCount: controller.portfolios.length, 46 | scrollDirection: Axis.horizontal, 47 | shrinkWrap: true,), 48 | ), 49 | ) 50 | ], 51 | ), 52 | )); 53 | } 54 | 55 | @override 56 | Widget phone() { 57 | return Obx(() => Stack( 58 | children: [ 59 | AnimatedSwitcher(duration: Duration(seconds: 3),child: controller.portfolios[controller.selectedIndex.value].phone(),), 60 | Align( 61 | alignment: Alignment.centerRight, 62 | child: Container( 63 | width: 44, 64 | margin: EdgeInsets.all(32), 65 | child: ListView.builder( 66 | itemBuilder: (context,i){ 67 | return InkWell( 68 | onTap: (){ 69 | controller.selectedIndex.value = i; 70 | controller.update(); 71 | controller.selectedIndex.refresh(); 72 | }, 73 | child: Container( 74 | width: 36, 75 | height: 36, 76 | margin: EdgeInsets.all(4), 77 | decoration: BoxDecoration( 78 | color:Colors.black.withOpacity(0.65), 79 | borderRadius: BorderRadius.circular(100), 80 | border: Border.all(color: controller.selectedIndex.value==i?Colors.orange:Colors.transparent,width: 2) 81 | ), 82 | child: Center(child: Text((i+1).toString(),style: TextStyle( 83 | color: controller.selectedIndex.value==i?Colors.orange:Colors.white, 84 | fontSize: controller.selectedIndex.value==i?12:10, 85 | fontWeight: controller.selectedIndex.value==i?FontWeight.w600:FontWeight.normal 86 | ),)), 87 | ), 88 | ); 89 | }, 90 | itemCount: controller.portfolios.length, 91 | scrollDirection: Axis.vertical, 92 | shrinkWrap: true,), 93 | ), 94 | ) 95 | ], 96 | )); 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/presentation/about/about_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 4 | import 'package:either_dart/either.dart'; 5 | import 'package:flick_video_player/flick_video_player.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | import 'package:get/get.dart'; 9 | import 'package:miladjalali_ir/presentation/widgets/custom_snackbar.dart'; 10 | import 'package:miladjalali_ir/app/util/video_player_data_manager.dart'; 11 | import 'package:miladjalali_ir/domain/entities/unsplash_image.dart'; 12 | import 'package:miladjalali_ir/domain/entities/unsplash_image_urls.dart'; 13 | import 'package:miladjalali_ir/domain/entities/unsplash_search_response.dart'; 14 | import 'package:miladjalali_ir/domain/usecases/fetch_person_images_use_case.dart'; 15 | import 'package:tuple/tuple.dart'; 16 | import 'package:video_player/video_player.dart'; 17 | 18 | class AboutController extends GetxController with GetTickerProviderStateMixin { 19 | AboutController(this._fetchPersonImagesUseCase); 20 | 21 | late int itemSize; 22 | late int itemsInEachRow; 23 | late int visibleRowsCount; 24 | 25 | late FetchPersonImagesUseCase _fetchPersonImagesUseCase; 26 | int _currentPage = Random().nextInt(5); 27 | int _pageSize = 100; 28 | var unsplashSearchResponse = Rx(null); 29 | var images = RxList([]); 30 | var _isLoadMore = false; 31 | ScrollController scrollController = ScrollController(); 32 | 33 | late FlickManager flickManager; 34 | late VideoPlayerDataManager? dataManager; 35 | 36 | @override 37 | void onInit() { 38 | super.onInit(); 39 | flickManager = FlickManager( 40 | videoPlayerController: VideoPlayerController.network("https://miladjalali.ir/about_me.mp4"), autoPlay: false); 41 | 42 | dataManager = VideoPlayerDataManager(flickManager: flickManager, urls: ["https://miladjalali.ir/about_me.mp4"]); 43 | } 44 | 45 | @override 46 | void onReady() { 47 | if (Get.width < 768) 48 | itemSize = 64; 49 | else 50 | itemSize = 200; 51 | itemsInEachRow = (Get.width / itemSize).floor(); 52 | visibleRowsCount = (Get.height / itemSize).floor(); 53 | 54 | fetchPersonImages(); 55 | scrollController.addListener(_scrollListener); 56 | 57 | super.onReady(); 58 | } 59 | 60 | @override 61 | void dispose() { 62 | scrollController.removeListener(_scrollListener); 63 | flickManager.dispose(); 64 | super.dispose(); 65 | } 66 | 67 | fetchPersonImages() async { 68 | Either response = await _fetchPersonImagesUseCase.execute(Tuple2(_currentPage, _pageSize)); 69 | response.fold((left) { 70 | CustomSnackBar.show(Get.context!, left.message, ContentType.failure); 71 | }, (right) { 72 | UnsplashSearchResponse newResponse = right; 73 | List? temp = newResponse.results; 74 | images.assignAll(temp!); 75 | unsplashSearchResponse.value = newResponse; 76 | addMyPhoto(); 77 | checkScreenFilledWithImages(); 78 | }); 79 | } 80 | 81 | checkScreenFilledWithImages() { 82 | if (images.length < visibleRowsCount * itemsInEachRow) { 83 | loadMore(); 84 | } 85 | } 86 | 87 | loadMore() async { 88 | final totalResults = unsplashSearchResponse.value?.total ?? 0; 89 | if (totalResults / _pageSize <= _currentPage) return; 90 | if (_isLoadMore) return; 91 | _isLoadMore = true; 92 | 93 | Either newResponse = await _fetchPersonImagesUseCase.execute(Tuple2(++_currentPage, _pageSize)); 94 | newResponse.fold((left) { 95 | CustomSnackBar.show(Get.context!, left.message, ContentType.failure); 96 | }, (right) { 97 | images.addAll(right.results!); 98 | unsplashSearchResponse.value?.total = right.total; 99 | _isLoadMore = false; 100 | checkScreenFilledWithImages(); 101 | }); 102 | } 103 | 104 | void _scrollListener() { 105 | if (scrollController.hasClients) { 106 | if (scrollController.position.maxScrollExtent == scrollController.offset) { 107 | print("load more"); 108 | loadMore(); 109 | } 110 | } 111 | } 112 | 113 | void addMyPhoto() { 114 | int position = ((visibleRowsCount / 2).floor() * itemsInEachRow) + 115 | Random().nextInt((itemsInEachRow ~/ 2)) + 116 | (itemsInEachRow / 2).ceil(); 117 | 118 | if (position > images.length) position = Random().nextInt(itemsInEachRow) + itemsInEachRow; 119 | 120 | images.insert( 121 | position, 122 | UnsplashImage( 123 | urls: UnsplashImageUrls(small: "https://pwa.miladjalali.ir/miladjalali.gif"), 124 | blurHash: "LPGcGlkWK+w[_Noes8NG.TRjRQt7")); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/web_video_control.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flick_video_player/flick_video_player.dart'; 3 | import 'package:miladjalali_ir/app/util/video_player_data_manager.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | /// Default portrait controls. 7 | class WebVideoControl extends StatelessWidget { 8 | const WebVideoControl( 9 | {Key? key, 10 | this.iconSize = 20, 11 | this.fontSize = 12, 12 | this.progressBarSettings, 13 | this.dataManager}) 14 | : super(key: key); 15 | 16 | /// Icon size. 17 | /// 18 | /// This size is used for all the player icons. 19 | final double iconSize; 20 | 21 | /// [dataManager] is used to handle video controls. 22 | final VideoPlayerDataManager? dataManager; 23 | 24 | /// Font size. 25 | /// 26 | /// This size is used for all the text. 27 | final double fontSize; 28 | 29 | /// [FlickProgressBarSettings] settings. 30 | final FlickProgressBarSettings? progressBarSettings; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | FlickVideoManager flickVideoManager = 35 | Provider.of(context); 36 | return FlickShowControlsActionWeb( 37 | child: Stack( 38 | children: [ 39 | Positioned( 40 | left: 0, 41 | right: 0, 42 | top: 0, 43 | child: Container( 44 | alignment: Alignment.center, 45 | padding: EdgeInsets.only( 46 | top: 20, 47 | ), 48 | child: FlickAnimatedVolumeLevel( 49 | decoration: BoxDecoration( 50 | color: Colors.black26, 51 | ), 52 | textStyle: TextStyle(color: Colors.white, fontSize: 20), 53 | padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), 54 | ), 55 | ), 56 | ), 57 | Positioned.fill( 58 | child: FlickSeekVideoAction( 59 | child: Center( 60 | child: FlickVideoBuffer( 61 | child: FlickAutoHideChild( 62 | child: Row( 63 | mainAxisAlignment: MainAxisAlignment.center, 64 | children: [ 65 | Padding( 66 | padding: const EdgeInsets.all(8.0), 67 | child: GestureDetector( 68 | onTap: () { 69 | dataManager!.skipToPreviousVideo(); 70 | }, 71 | child: Icon( 72 | Icons.skip_previous, 73 | color: dataManager!.hasPreviousVideo() 74 | ? Colors.white 75 | : Colors.white38, 76 | size: 35, 77 | ), 78 | ), 79 | ), 80 | Padding( 81 | padding: const EdgeInsets.all(8.0), 82 | child: FlickPlayToggle(size: 50), 83 | ), 84 | Padding( 85 | padding: const EdgeInsets.all(8.0), 86 | child: GestureDetector( 87 | onTap: () { 88 | dataManager!.skipToNextVideo(); 89 | }, 90 | child: Icon( 91 | Icons.skip_next, 92 | color: dataManager!.hasNextVideo() 93 | ? Colors.white 94 | : Colors.white38, 95 | size: 35, 96 | ), 97 | ), 98 | ) 99 | ], 100 | ), 101 | ), 102 | ), 103 | ), 104 | ), 105 | ), 106 | Positioned.fill( 107 | child: FlickAutoHideChild( 108 | child: Padding( 109 | padding: const EdgeInsets.all(10.0), 110 | child: Column( 111 | mainAxisAlignment: MainAxisAlignment.end, 112 | children: [ 113 | FlickVideoProgressBar( 114 | flickProgressBarSettings: progressBarSettings, 115 | ), 116 | Padding( 117 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 118 | child: Row( 119 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 120 | children: [ 121 | FlickPlayToggle( 122 | size: iconSize, 123 | ), 124 | SizedBox( 125 | width: iconSize / 2, 126 | ), 127 | FlickSoundToggle( 128 | size: iconSize, 129 | ), 130 | SizedBox( 131 | width: iconSize / 2, 132 | ), 133 | Row( 134 | children: [ 135 | FlickCurrentPosition( 136 | fontSize: fontSize, 137 | ), 138 | FlickAutoHideChild( 139 | child: Text( 140 | ' / ', 141 | style: TextStyle( 142 | color: Colors.white, fontSize: fontSize), 143 | ), 144 | ), 145 | FlickTotalDuration( 146 | fontSize: fontSize, 147 | ), 148 | ], 149 | ), 150 | Expanded( 151 | child: Container(), 152 | ), 153 | FlickFullScreenToggle( 154 | size: iconSize, 155 | ), 156 | ], 157 | ), 158 | ), 159 | ], 160 | ), 161 | ), 162 | ), 163 | ), 164 | ], 165 | ), 166 | ); 167 | } 168 | } -------------------------------------------------------------------------------- /lib/presentation/widgets/mobile/social_media_bar_for_mobile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | import 'package:miladjalali_ir/app/util/url_helper.dart'; 4 | 5 | class SocialMediaBarForMobile extends StatefulWidget { 6 | const SocialMediaBarForMobile({Key? key}) : super(key: key); 7 | 8 | @override 9 | State createState() => _SocialMediaBarForMobileState(); 10 | } 11 | 12 | class _SocialMediaBarForMobileState extends State { 13 | bool showMenu = false; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | padding: EdgeInsets.symmetric(vertical: showMenu ? 24 : 12, horizontal: 12), 19 | margin: const EdgeInsets.only(bottom: 16, right: 16), 20 | decoration: BoxDecoration( 21 | border: Border.all(color: Colors.lightBlue.withOpacity(0.3), width: 1.4), 22 | color: Colors.black.withOpacity(0.75), 23 | borderRadius: BorderRadius.all(Radius.circular(35)), 24 | ), 25 | child: AnimatedSwitcher( 26 | duration: Duration(milliseconds: 200), 27 | transitionBuilder: (Widget child, Animation animation) { 28 | var tween = Tween(begin: Offset(0, 1), end: Offset(0, 0)); 29 | return SlideTransition( 30 | child: child, 31 | position: tween.animate(animation), 32 | ); 33 | }, 34 | child: !showMenu 35 | ? InkWell( 36 | hoverColor: Colors.transparent, 37 | splashColor: Colors.transparent, 38 | onTap: () { 39 | setState(() { 40 | showMenu = !showMenu; 41 | }); 42 | }, 43 | child: Icon( 44 | Icons.connect_without_contact, 45 | color: Colors.lightBlue.withOpacity(0.75), 46 | ), 47 | ) 48 | : Column( 49 | mainAxisSize: MainAxisSize.min, 50 | children: [ 51 | InkWell( 52 | hoverColor: Colors.transparent, 53 | splashColor: Colors.transparent, 54 | onTap: () { 55 | UrlHelper.launchUrl('https://github.com/miladjalalli'); 56 | }, 57 | child: Icon( 58 | FontAwesomeIcons.github, 59 | color: Colors.lightBlue.withOpacity(0.75), 60 | ), 61 | ), 62 | SizedBox(height: 30), 63 | InkWell( 64 | hoverColor: Colors.transparent, 65 | splashColor: Colors.transparent, 66 | onTap: () { 67 | UrlHelper.launchUrl( 68 | 'https://www.linkedin.com/in/miladjalali1994', 69 | ); 70 | }, 71 | child: Icon( 72 | FontAwesomeIcons.linkedin, 73 | color: Colors.lightBlue.withOpacity(0.75), 74 | ), 75 | ), 76 | SizedBox(height: 30), 77 | InkWell( 78 | hoverColor: Colors.transparent, 79 | splashColor: Colors.transparent, 80 | onTap: () { 81 | UrlHelper.launchUrl( 82 | "mailto:miladjalali.dev@gmail.com?subject=Hello%20Milad", 83 | ); 84 | }, 85 | child: Icon( 86 | Icons.mail_outline, 87 | color: Colors.lightBlue.withOpacity(0.75), 88 | ), 89 | ), 90 | SizedBox(height: 30), 91 | InkWell( 92 | hoverColor: Colors.transparent, 93 | splashColor: Colors.transparent, 94 | onTap: () { 95 | UrlHelper.launchUrl('https://stackoverflow.com/users/8349963'); 96 | }, 97 | child: Icon( 98 | FontAwesomeIcons.stackOverflow, 99 | color: Colors.lightBlue.withOpacity(0.75), 100 | ), 101 | ), 102 | SizedBox(height: 30), 103 | InkWell( 104 | hoverColor: Colors.transparent, 105 | splashColor: Colors.transparent, 106 | onTap: () { 107 | UrlHelper.launchUrl('https://www.instagram.com/miladjalali.ir/'); 108 | }, 109 | child: Icon( 110 | FontAwesomeIcons.instagram, 111 | color: Colors.lightBlue.withOpacity(0.75), 112 | ), 113 | ), 114 | SizedBox(height: 30), 115 | InkWell( 116 | hoverColor: Colors.transparent, 117 | splashColor: Colors.transparent, 118 | onTap: () { 119 | UrlHelper.launchUrl('https://medium.com/@miladjalali'); 120 | }, 121 | child: Icon( 122 | FontAwesomeIcons.medium, 123 | color: Colors.lightBlue.withOpacity(0.75), 124 | ), 125 | ), 126 | SizedBox(height: 30), 127 | InkWell( 128 | hoverColor: Colors.transparent, 129 | splashColor: Colors.transparent, 130 | onTap: () { 131 | UrlHelper.launchUrl('https://wa.link/ggxmai'); 132 | }, 133 | child: Icon( 134 | FontAwesomeIcons.whatsapp, 135 | color: Colors.lightBlue.withOpacity(0.75), 136 | ), 137 | ), 138 | SizedBox(height: 30), 139 | InkWell( 140 | hoverColor: Colors.transparent, 141 | splashColor: Colors.transparent, 142 | onTap: () { 143 | setState(() { 144 | showMenu = !showMenu; 145 | }); 146 | }, 147 | child: Icon( 148 | Icons.menu, 149 | color: Colors.lightBlue.withOpacity(0.75), 150 | ), 151 | ), 152 | ], 153 | )), 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Milad Jalali | Developer, Designer and Teacher 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 96 | 97 | 98 | 99 | 102 | 167 | 168 | 171 | 172 |
173 |
174 |
175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/nav_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/app/util/url_helper.dart'; 4 | import 'package:miladjalali_ir/presentation/home/home_controller.dart'; 5 | import 'package:miladjalali_ir/presentation/widgets/common/rounded_button.dart'; 6 | 7 | import 'package:miladjalali_ir/app/util/styles.dart'; 8 | 9 | class Navbar extends GetResponsiveView { 10 | 11 | Navbar():super(settings: ResponsiveScreenSettings(desktopChangePoint: 1000, tabletChangePoint: 768, watchChangePoint: 300)); 12 | 13 | @override 14 | Widget desktop() { 15 | return Obx(()=>Container( 16 | height: 72, 17 | width: double.infinity, 18 | color: controller.navBarSelectedIndex.value==1?Colors.black.withOpacity(0.75):Colors.transparent, 19 | child: Row( 20 | children: [ 21 | Expanded( 22 | child: Padding( 23 | padding: EdgeInsets.fromLTRB(Get.width * 0.1,0,Get.width * 0.1,0), 24 | child: Row( 25 | mainAxisAlignment: MainAxisAlignment.start, 26 | mainAxisSize: MainAxisSize.min, 27 | children: [ 28 | CircleAvatar( 29 | backgroundColor: Get.theme.accentColor, 30 | radius: 15, 31 | child: Text( 32 | 'M', 33 | style: TextStyle( 34 | color: Colors.white, 35 | fontWeight: FontWeight.bold, 36 | ), 37 | ), 38 | ), 39 | SizedBox(width: 8), 40 | RichText( 41 | textAlign: TextAlign.center, 42 | text: TextSpan( 43 | text: 'Milad', 44 | style: Styles.navBarTextStyle.copyWith( 45 | color: (controller.navBarSelectedIndex.value==1 || controller.navBarSelectedIndex.value==2)?Colors.white:Colors.black.withOpacity(0.75), 46 | fontSize: 18, 47 | fontFamily: 'Ubuntu', 48 | ), 49 | children: [ 50 | TextSpan( 51 | text: ' Jalali', 52 | style: Styles.navBarTextStyle.copyWith( 53 | color: Get.theme.accentColor, 54 | fontSize: 18, 55 | fontFamily: 'Ubuntu', 56 | ), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ], 62 | ), 63 | ), 64 | ), 65 | Expanded( 66 | child: Row( 67 | mainAxisAlignment: MainAxisAlignment.spaceAround, 68 | children: controller.items.map((item) { 69 | return InkWell( 70 | onTap: () { 71 | switch (item) { 72 | case 'Resume': 73 | break; 74 | default: 75 | controller.navBarSelectedIndex.value = controller.items.indexOf(item); 76 | controller.onNavbarItemSelected(controller.navBarSelectedIndex.toInt()); 77 | controller.update(); 78 | break; 79 | } 80 | }, 81 | child: item == 'Resume' 82 | ? RoundedButton("Resume",Colors.white,Colors.white,Colors.lightBlue, (){ 83 | UrlHelper.downloadResume(); 84 | }) 85 | : Column( 86 | mainAxisAlignment: MainAxisAlignment.center, 87 | children: [ 88 | Text( 89 | item, 90 | style: Styles.navBarTextStyle.copyWith( 91 | fontWeight: FontWeight.w500, 92 | color: Styles.navBarTextStyle.color?.withOpacity( 93 | controller.navBarSelectedIndex.value == controller.items.indexOf(item) 94 | ? 1.0 95 | : 0.75, 96 | ), 97 | ), 98 | ), 99 | SizedBox(height: 4), 100 | if (item != 'Resume') 101 | AnimatedContainer( 102 | duration: Duration(milliseconds: 300), 103 | height: 2, 104 | width: 20, 105 | color: controller.navBarSelectedIndex == controller.items.indexOf(item) 106 | ? Colors.white 107 | : Colors.transparent, 108 | ), 109 | ], 110 | ), 111 | ); 112 | }).toList(), 113 | ), 114 | ), 115 | ], 116 | ), 117 | )); 118 | } 119 | 120 | @override 121 | Widget phone() { 122 | return Obx(()=>Container( 123 | height: 56, 124 | width: Get.width, 125 | color: controller.navBarSelectedIndex.value==1?Colors.black.withOpacity(0.75):Colors.transparent, 126 | padding: const EdgeInsets.fromLTRB(16,0,16,0), 127 | child: Row( 128 | mainAxisAlignment: MainAxisAlignment.center, 129 | crossAxisAlignment: CrossAxisAlignment.center, 130 | children: [ 131 | IconButton( 132 | color: Colors.white, 133 | highlightColor: Colors.transparent, 134 | hoverColor: Colors.transparent, 135 | splashColor: Colors.transparent, 136 | icon: Icon(Icons.menu), 137 | onPressed: () { 138 | controller.scaffoldKey.currentState?.openEndDrawer(); 139 | }, 140 | ), 141 | Padding( 142 | padding: EdgeInsets.fromLTRB(8,0,16,0), 143 | child: Row( 144 | mainAxisAlignment: MainAxisAlignment.start, 145 | mainAxisSize: MainAxisSize.min, 146 | children: [ 147 | RichText( 148 | textAlign: TextAlign.center, 149 | text: TextSpan( 150 | text: 'Milad', 151 | style: Styles.navBarTextStyle.copyWith( 152 | color: (controller.navBarSelectedIndex.value==1 || controller.navBarSelectedIndex.value==2)?Colors.white:Colors.black.withOpacity(0.75), 153 | fontSize: 18, 154 | fontFamily: 'Ubuntu', 155 | ), 156 | children: [ 157 | TextSpan( 158 | text: ' Jalali', 159 | style: Styles.navBarTextStyle.copyWith( 160 | color: Get.theme.accentColor, 161 | fontSize: 18, 162 | fontFamily: 'Ubuntu', 163 | ), 164 | ), 165 | ], 166 | ), 167 | ), 168 | ], 169 | ), 170 | ), 171 | Expanded(child: Text("")), 172 | ], 173 | ), 174 | )) 175 | ; 176 | } 177 | 178 | } 179 | 180 | -------------------------------------------------------------------------------- /lib/presentation/about/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flick_video_player/flick_video_player.dart'; 2 | 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_blurhash/flutter_blurhash.dart'; 7 | import 'package:get/get.dart'; 8 | import 'package:miladjalali_ir/presentation/about/about_controller.dart'; 9 | import 'package:miladjalali_ir/presentation/widgets/common/web_video_control.dart'; 10 | import 'package:video_player/video_player.dart'; 11 | 12 | class AboutPage extends GetResponsiveView { 13 | AboutPage() : super(settings: ResponsiveScreenSettings(desktopChangePoint: 1000, tabletChangePoint: 768, watchChangePoint: 300)); 14 | 15 | @override 16 | Widget desktop() { 17 | return Scaffold( 18 | body: Obx(() => Container( 19 | width: Get.width, 20 | height: Get.height, 21 | child: Stack( 22 | children: [ 23 | GridView.builder( 24 | controller: controller.scrollController, 25 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 26 | crossAxisCount: Get.width ~/ 200, childAspectRatio: 1, crossAxisSpacing: 1, mainAxisSpacing: 1), 27 | itemCount: controller.images.length, 28 | itemBuilder: (BuildContext ctx, index) { 29 | return CachedNetworkImage( 30 | imageUrl: controller.images.value[index].urls?.small ?? "", 31 | fit: BoxFit.cover, 32 | imageRenderMethodForWeb: ImageRenderMethodForWeb.HtmlImage, 33 | progressIndicatorBuilder: (context, url, downloadProgress) => 34 | BlurHash(hash: controller.images.value[index].blurHash ?? ""), 35 | errorWidget: (context, url, error) => Icon(Icons.error), 36 | ); 37 | }), 38 | Align( 39 | alignment: Alignment.centerLeft, 40 | child: Container( 41 | margin: EdgeInsets.fromLTRB(64, Get.height / 4, Get.width / 2 - 64, Get.height / 4), 42 | child: Center( 43 | child: SizedBox( 44 | height: Get.width/3 * .5625, 45 | width: Get.width/3, 46 | child: Container( 47 | padding: EdgeInsets.all(1), 48 | decoration: BoxDecoration(color: Colors.orange, 49 | boxShadow: [ 50 | BoxShadow( 51 | color: Colors.grey.withOpacity(0.5), 52 | spreadRadius: 5, 53 | blurRadius: 7, 54 | offset: Offset(0, 3), // changes position of shadow 55 | ), 56 | ], 57 | borderRadius: BorderRadius.circular(10)), 58 | child: ClipRRect( 59 | borderRadius: BorderRadius.circular(10), 60 | child: FlickVideoPlayer( 61 | flickManager: controller.flickManager, 62 | flickVideoWithControls: FlickVideoWithControls( 63 | controls: WebVideoControl( 64 | dataManager: controller.dataManager!, 65 | iconSize: 30, 66 | fontSize: 14, 67 | progressBarSettings: FlickProgressBarSettings( 68 | height: 5, 69 | handleRadius: 5.5, 70 | ), 71 | ), 72 | videoFit: BoxFit.contain, 73 | // aspectRatioWhenLoading: 4 / 3, 74 | ), 75 | ), 76 | ), 77 | ), 78 | ), 79 | ), 80 | ), 81 | ) 82 | ], 83 | ), 84 | )), 85 | ); 86 | } 87 | 88 | @override 89 | Widget phone() { 90 | return Scaffold( 91 | body: Obx(() => Container( 92 | width: Get.width, 93 | height: Get.height, 94 | child: Stack( 95 | children: [ 96 | GridView.builder( 97 | controller: controller.scrollController, 98 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 99 | crossAxisCount: Get.width ~/ 64, childAspectRatio: 1, crossAxisSpacing: 1, mainAxisSpacing: 1), 100 | itemCount: controller.images.length, 101 | itemBuilder: (BuildContext ctx, index) { 102 | return CachedNetworkImage( 103 | imageUrl: controller.images.value[index].urls?.small ?? "", 104 | fit: BoxFit.cover, 105 | imageRenderMethodForWeb: ImageRenderMethodForWeb.HtmlImage, 106 | progressIndicatorBuilder: (context, url, downloadProgress) => 107 | BlurHash(hash: controller.images.value[index].blurHash ?? ""), 108 | errorWidget: (context, url, error) => Icon(Icons.error), 109 | ); 110 | }), 111 | Align( 112 | alignment: Alignment.centerLeft, 113 | child: Container( 114 | margin: EdgeInsets.fromLTRB(32,32,32,32), 115 | child: Center( 116 | child: SizedBox( 117 | height: Get.width * 0.5625, 118 | width: Get.width, 119 | child: Container( 120 | padding: EdgeInsets.all(1), 121 | decoration: BoxDecoration(color: Colors.orange, 122 | boxShadow: [ 123 | BoxShadow( 124 | color: Colors.grey.withOpacity(0.5), 125 | spreadRadius: 5, 126 | blurRadius: 7, 127 | offset: Offset(0, 3), // changes position of shadow 128 | ), 129 | ], 130 | borderRadius: BorderRadius.circular(10)), 131 | child: ClipRRect( 132 | borderRadius: BorderRadius.circular(10), 133 | child: FlickVideoPlayer( 134 | flickManager: controller.flickManager, 135 | flickVideoWithControls: FlickVideoWithControls( 136 | controls: WebVideoControl( 137 | dataManager: controller.dataManager!, 138 | iconSize: 30, 139 | fontSize: 14, 140 | progressBarSettings: FlickProgressBarSettings( 141 | height: 5, 142 | handleRadius: 5.5, 143 | ), 144 | ), 145 | videoFit: BoxFit.contain, 146 | // aspectRatioWhenLoading: 4 / 3, 147 | ), 148 | ), 149 | ), 150 | ), 151 | ), 152 | ), 153 | ), 154 | ) 155 | ], 156 | ), 157 | )), 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/presentation/widgets/common/experience_time_line_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/app/constants/job_type.dart'; 4 | import 'package:miladjalali_ir/app/constants/work_places.dart'; 5 | 6 | import 'package:miladjalali_ir/domain/entities/experience.dart'; 7 | import 'curve_line_painter.dart'; 8 | import 'experience_card_painter.dart'; 9 | 10 | class ExprienceTimeLineWidget extends GetResponsiveView { 11 | ExprienceTimeLineWidget(this.experiences); 12 | 13 | List experiences; 14 | 15 | @override 16 | Widget desktop() { 17 | return Container( 18 | width: Get.width, 19 | height: 400, 20 | child: ListView( 21 | scrollDirection: Axis.horizontal, 22 | children: [ 23 | Container( 24 | width: 436, 25 | child: CustomPaint( 26 | painter: CurveLinePainter(DrawPosition.bottom), 27 | child: Container(), 28 | ), 29 | ), 30 | ListView.builder( 31 | itemBuilder: (context, i) { 32 | return Container( 33 | width: 500, 34 | child: Stack( 35 | children: [ 36 | Align( 37 | alignment: Alignment.center, 38 | child: Row( 39 | children: [ 40 | Container( 41 | width: 64, 42 | height: 64, 43 | decoration: BoxDecoration( 44 | border: Border.all(color: Color(0XFF003140), width: 3), 45 | borderRadius: BorderRadius.circular(1000)), 46 | child: ClipRRect( 47 | borderRadius: BorderRadius.circular(1000), 48 | child: Image( 49 | image: experiences[i].companyLogo, 50 | height: 48, 51 | width: 48, 52 | ), 53 | ), 54 | ), 55 | Container( 56 | width: 436, 57 | height: 250, 58 | child: CustomPaint( 59 | painter: CurveLinePainter(i % 2 == 0 ? DrawPosition.top : DrawPosition.bottom), 60 | ), 61 | ), 62 | ], 63 | )), 64 | Align( 65 | alignment: i % 2 != 0 ?Alignment.topLeft:Alignment.bottomLeft, 66 | child: Container( 67 | width: 200, 68 | height: 200, 69 | decoration: BoxDecoration( 70 | image: DecorationImage( 71 | image: AssetImage(i % 2 != 0 ?"assets/images/experience_card_top.png":"assets/images/experience_card_bottom.png"), 72 | fit: BoxFit.fill, 73 | ) 74 | ), 75 | margin: i % 2 != 0 ?EdgeInsets.fromLTRB(36, 0, 0, 84):EdgeInsets.fromLTRB(36, 84, 0, 0 ), 76 | child: Container( 77 | child: Padding( 78 | padding: const EdgeInsets.all(24.0), 79 | child: Stack( 80 | children: [ 81 | Column( 82 | crossAxisAlignment: CrossAxisAlignment.start, 83 | children: [ 84 | 85 | Container( 86 | margin: EdgeInsets.only(top: i % 2 != 0?0:28,bottom: 8), 87 | child: Text(experiences[i].jobPosition, 88 | style: TextStyle( 89 | color: Colors.white 90 | ), 91 | ), 92 | ), 93 | SizedBox(height: 4,), 94 | Text(experiences[i].startDate, 95 | style: TextStyle( 96 | color: Colors.white 97 | ), 98 | ), 99 | SizedBox(height: 4,), 100 | Text(experiences[i].endDate, 101 | style: TextStyle( 102 | color: Colors.white 103 | ), 104 | ), 105 | SizedBox(height: 4,), 106 | Text(experiences[i].workplaces.getName, 107 | style: TextStyle( 108 | color: Colors.white 109 | ), 110 | ), 111 | SizedBox(height: 4,), 112 | Text(experiences[i].jobType.getName, 113 | style: TextStyle( 114 | color: Colors.white 115 | ), 116 | ), 117 | ], 118 | ), 119 | Align( 120 | alignment: Alignment.centerRight, 121 | child: Container( 122 | margin: EdgeInsets.only(right: 4), 123 | child: RotatedBox( 124 | quarterTurns: 1, 125 | child:Text(experiences[i].companyTitle, 126 | style: TextStyle( 127 | fontWeight: FontWeight.w600, 128 | color: Colors.white, 129 | fontSize: 16 130 | ), 131 | ), 132 | ), 133 | ), 134 | ) 135 | ], 136 | ), 137 | ), 138 | ), 139 | )) 140 | ], 141 | ), 142 | ); 143 | }, 144 | itemCount: experiences.length, 145 | shrinkWrap: true, 146 | scrollDirection: Axis.horizontal, 147 | ), 148 | ], 149 | ), 150 | ); 151 | } 152 | 153 | @override 154 | Widget phone() { 155 | return Container( 156 | width: Get.width, 157 | height: 400, 158 | child: ListView( 159 | scrollDirection: Axis.horizontal, 160 | children: [ 161 | Container( 162 | width: 236, 163 | child: CustomPaint( 164 | painter: CurveLinePainter(DrawPosition.bottom), 165 | child: Container(), 166 | ), 167 | ), 168 | ListView.builder( 169 | itemBuilder: (context, i) { 170 | return Container( 171 | width: 400, 172 | child: Stack( 173 | children: [ 174 | Align( 175 | alignment: Alignment.center, 176 | child: Row( 177 | children: [ 178 | Container( 179 | width: 48, 180 | height: 48, 181 | decoration: BoxDecoration( 182 | border: Border.all(color: Color(0XFF003140), width: 3), 183 | borderRadius: BorderRadius.circular(1000)), 184 | child: ClipRRect( 185 | borderRadius: BorderRadius.circular(1000), 186 | child: Image( 187 | image: experiences[i].companyLogo, 188 | height: 36, 189 | width: 36, 190 | ), 191 | ), 192 | ), 193 | Container( 194 | width: 336, 195 | height: 250, 196 | child: CustomPaint( 197 | painter: CurveLinePainter(i % 2 == 0 ? DrawPosition.top : DrawPosition.bottom), 198 | ), 199 | ), 200 | ], 201 | )), 202 | Align( 203 | alignment: i % 2 != 0 ?Alignment.topLeft:Alignment.bottomLeft, 204 | child: Container( 205 | width: 160, 206 | height: 160, 207 | decoration: BoxDecoration( 208 | image: DecorationImage( 209 | image: AssetImage(i % 2 != 0 ?"assets/images/experience_card_top.png":"assets/images/experience_card_bottom.png"), 210 | fit: BoxFit.fill, 211 | ) 212 | ), 213 | margin: i % 2 != 0 ?EdgeInsets.fromLTRB(36, 0, 0, 84):EdgeInsets.fromLTRB(36, 84, 0, 0 ), 214 | child: Container( 215 | child: Padding( 216 | padding: const EdgeInsets.all(24.0), 217 | child: Stack( 218 | children: [ 219 | Column( 220 | crossAxisAlignment: CrossAxisAlignment.start, 221 | children: [ 222 | 223 | Container( 224 | margin: EdgeInsets.only(top: i % 2 != 0?0:12,bottom: 8), 225 | child: Text(experiences[i].jobPosition, 226 | style: TextStyle( 227 | color: Colors.white, 228 | fontSize: 12 229 | ), 230 | ), 231 | ), 232 | SizedBox(height: 2,), 233 | Text(experiences[i].startDate, 234 | style: TextStyle( 235 | color: Colors.white, 236 | fontSize: 12 237 | ), 238 | ), 239 | SizedBox(height: 2,), 240 | Text(experiences[i].endDate, 241 | style: TextStyle( 242 | color: Colors.white, 243 | fontSize: 12 244 | ), 245 | ), 246 | SizedBox(height: 2,), 247 | Text(experiences[i].workplaces.getName, 248 | style: TextStyle( 249 | color: Colors.white, 250 | fontSize: 12 251 | ), 252 | ), 253 | SizedBox(height: 2,), 254 | Text(experiences[i].jobType.getName, 255 | style: TextStyle( 256 | color: Colors.white, 257 | fontSize: 12 258 | ), 259 | ), 260 | ], 261 | ), 262 | Align( 263 | alignment: Alignment.centerRight, 264 | child: Container( 265 | margin: EdgeInsets.only(right: 4,top: 8), 266 | child: RotatedBox( 267 | quarterTurns: 1, 268 | child:Text(experiences[i].companyTitle, 269 | style: TextStyle( 270 | fontWeight: FontWeight.w600, 271 | color: Colors.white, 272 | fontSize: 16 273 | ), 274 | ), 275 | ), 276 | ), 277 | ) 278 | ], 279 | ), 280 | ), 281 | ), 282 | )) 283 | ], 284 | ), 285 | ); 286 | }, 287 | itemCount: experiences.length, 288 | shrinkWrap: true, 289 | scrollDirection: Axis.horizontal, 290 | ), 291 | ], 292 | ), 293 | ); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /lib/presentation/information/information_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:miladjalali_ir/app/util/url_helper.dart'; 4 | import 'package:miladjalali_ir/presentation/widgets/common/rounded_button.dart'; 5 | import '../widgets/common/hero_images.dart'; 6 | import '../widgets/common/home_background_painter.dart'; 7 | import '../widgets/mobile/home_background_painter_for_mobile.dart'; 8 | import '../widgets/mobile/social_media_bar_for_mobile.dart'; 9 | import 'information_controller.dart'; 10 | import '../widgets/common/social_media_bar.dart'; 11 | import '../widgets/common/typewriter.dart'; 12 | 13 | class InformationPage extends GetResponsiveView { 14 | InformationPage() : super(settings: ResponsiveScreenSettings(desktopChangePoint: 1000, tabletChangePoint: 768, watchChangePoint: 300)); 15 | 16 | @override 17 | Widget desktop() { 18 | return Scaffold( 19 | body: Obx(() => Container( 20 | width: Get.width, 21 | height: Get.height, 22 | child: Stack( 23 | children: [ 24 | CustomPaint(painter: HomeBackgroundPainter(Colors.lightBlue), size: Size.infinite), 25 | Row( 26 | children: [ 27 | Expanded( 28 | child: Padding( 29 | padding: EdgeInsets.fromLTRB(Get.width * 0.1,0,Get.width * 0.1,0), 30 | child: AnimatedSize( 31 | vsync: controller, 32 | duration: kThemeAnimationDuration, 33 | alignment: Alignment.topCenter, 34 | child: Column( 35 | crossAxisAlignment: CrossAxisAlignment.start, 36 | mainAxisAlignment: MainAxisAlignment.center, 37 | mainAxisSize: MainAxisSize.min, 38 | children: [ 39 | Typewriter( 40 | 'Hello ... My name is', 41 | animate: !controller.helloSeen.value, 42 | textStyle: TextStyle( 43 | color: Colors.lightBlue, 44 | fontSize: 24, 45 | fontWeight: FontWeight.w700, 46 | letterSpacing: 1.4, 47 | ), 48 | onEnd: () { 49 | controller.showName.value = true; 50 | controller.helloSeen.value = true; 51 | controller.update(); 52 | }, 53 | ), 54 | if (controller.showName.value) ...[ 55 | SizedBox(height: 16), 56 | Typewriter( 57 | 'Milad Jalali', 58 | animate: !controller.nameSeen.value, 59 | textStyle: TextStyle( 60 | color: Colors.blueGrey[900], 61 | fontSize: 40, 62 | fontWeight: FontWeight.w700, 63 | ), 64 | onEnd: () { 65 | controller.showPosition.value = true; 66 | controller.nameSeen.value = true; 67 | 68 | controller.update(); 69 | }, 70 | ), 71 | ], 72 | if (controller.showPosition.value) ...[ 73 | SizedBox(height: 16), 74 | Typewriter( 75 | 'Mobile App Developer & UI/UX Designer', 76 | animate: !controller.positionSeen.value, 77 | textStyle: TextStyle( 78 | color: Colors.blueGrey[900], 79 | fontSize: 20, 80 | fontWeight: FontWeight.w500, 81 | ), 82 | onEnd: () { 83 | controller.showAbstract.value = true; 84 | controller.positionSeen.value = true; 85 | controller.update(); 86 | }, 87 | ), 88 | ], 89 | if (controller.showAbstract.value) ...[ 90 | SizedBox(height: 24), 91 | Typewriter( 92 | "I build neat, cool and scalable mobile apps with Flutter and I'm an aspiring UI-UX Designer.\n" 93 | 'I love to learn and build new stuff that are beneficial to the community and cool to work on.\n' 94 | 'I also have great interest in the open source community.', 95 | animate: !controller.abstractSeen.value, 96 | textStyle: TextStyle( 97 | color: Colors.grey, 98 | fontSize: 16, 99 | letterSpacing: 1.2, 100 | height: 1.3, 101 | ), 102 | onEnd: () { 103 | Future.delayed(Duration(milliseconds: 500), () { 104 | controller.showHireMe.value = true; 105 | controller.abstractSeen.value = true; 106 | controller.update(); 107 | }); 108 | }, 109 | ), 110 | ], 111 | if (controller.showHireMe.value) ...[ 112 | SizedBox(height: 30), 113 | SizedBox( 114 | width: 160, 115 | child: RoundedButton("Hire Me",Colors.orange,Colors.orange,Colors.white,(){ 116 | UrlHelper.launchUrl("mailto:miladjalali.dev@gmail.com"); 117 | }), 118 | ), 119 | ], 120 | ], 121 | ), 122 | ), 123 | ), 124 | ), 125 | Expanded( 126 | child: Padding( 127 | padding: EdgeInsets.fromLTRB(Get.width * 0.1,0,Get.width * 0.1,0), 128 | child:Center(child: HeroImage())), 129 | ), 130 | ], 131 | ), 132 | Align(alignment: Alignment.bottomCenter, child: SocialMediaBar()), 133 | ], 134 | ), 135 | )), 136 | ); 137 | } 138 | 139 | @override 140 | Widget phone() { 141 | return Scaffold( 142 | body: Obx(() => Container( 143 | width: Get.width, 144 | height: Get.height, 145 | child: Stack( 146 | children: [ 147 | CustomPaint(painter: HomeBackgroundPainterForMobile(Colors.lightBlue), size: Size.infinite), 148 | Center( 149 | child: Column( 150 | children: [ 151 | SizedBox(height: 32,), 152 | Expanded( 153 | flex: 4, 154 | child: HeroImage(), 155 | ), 156 | Expanded( 157 | flex: 6, 158 | child: Padding( 159 | padding: const EdgeInsets.all(16.0), 160 | child: SingleChildScrollView( 161 | child: AnimatedSize( 162 | vsync: controller, 163 | duration: kThemeAnimationDuration, 164 | alignment: Alignment.topCenter, 165 | child: Column( 166 | crossAxisAlignment: CrossAxisAlignment.start, 167 | mainAxisAlignment: MainAxisAlignment.center, 168 | mainAxisSize: MainAxisSize.min, 169 | children: [ 170 | SizedBox( 171 | height: Get.height / 3 / 10, 172 | ), 173 | Typewriter( 174 | 'Hello ... My name is', 175 | animate: !controller.helloSeen.value, 176 | textStyle: TextStyle( 177 | color: Colors.lightBlue, 178 | fontSize: 24, 179 | fontWeight: FontWeight.w700, 180 | letterSpacing: 1.4, 181 | ), 182 | onEnd: () { 183 | controller.showName.value = true; 184 | controller.helloSeen.value = true; 185 | controller.update(); 186 | }, 187 | ), 188 | if (controller.showName.value) ...[ 189 | SizedBox(height: 16), 190 | Typewriter( 191 | 'Milad Jalali', 192 | animate: !controller.nameSeen.value, 193 | textStyle: TextStyle( 194 | color: Colors.blueGrey[900], 195 | fontSize: 40, 196 | fontWeight: FontWeight.w700, 197 | ), 198 | onEnd: () { 199 | controller.showPosition.value = true; 200 | controller.nameSeen.value = true; 201 | 202 | controller.update(); 203 | }, 204 | ), 205 | ], 206 | if (controller.showPosition.value) ...[ 207 | SizedBox(height: 16), 208 | Typewriter( 209 | 'Mobile App Developer & UI/UX Designer', 210 | animate: !controller.positionSeen.value, 211 | textStyle: TextStyle( 212 | color: Colors.blueGrey[900], 213 | fontSize: 20, 214 | fontWeight: FontWeight.w500, 215 | ), 216 | onEnd: () { 217 | controller.showAbstract.value = true; 218 | controller.positionSeen.value = true; 219 | controller.update(); 220 | }, 221 | ), 222 | ], 223 | if (controller.showAbstract.value) ...[ 224 | SizedBox(height: 24), 225 | Typewriter( 226 | "I build neat, cool and scalable mobile apps with Flutter and I'm an aspiring UI-UX Designer.\n" 227 | 'I love to learn and build new stuff that are beneficial to the community and cool to work on.\n' 228 | 'I also have great interest in the open source community.', 229 | animate: !controller.abstractSeen.value, 230 | textStyle: TextStyle( 231 | color: Colors.grey, 232 | fontSize: 16, 233 | letterSpacing: 1.2, 234 | height: 1.3, 235 | ), 236 | onEnd: () { 237 | Future.delayed(Duration(milliseconds: 500), () { 238 | controller.showHireMe.value = true; 239 | controller.abstractSeen.value = true; 240 | controller.update(); 241 | }); 242 | }, 243 | ), 244 | ], 245 | if (controller.showHireMe.value) ...[ 246 | SizedBox(height: 30), 247 | InkWell( 248 | hoverColor: Colors.transparent, 249 | splashColor: Colors.transparent, 250 | onTap: () { 251 | UrlHelper.launchUrl("mailto:miladjalali.dev@gmail.com"); 252 | }, 253 | onHover: (value) { 254 | controller.hovered.value = value; 255 | controller.update(); 256 | }, 257 | child: AnimatedContainer( 258 | height: 50, 259 | width: 160, 260 | duration: kThemeAnimationDuration, 261 | alignment: Alignment.center, 262 | decoration: BoxDecoration( 263 | border: Border.all(width: 1.4, color: Colors.teal), 264 | borderRadius: BorderRadius.all(Radius.circular(50)), 265 | color: controller.hovered.value ? Colors.teal.withOpacity(1.0) : Colors.transparent, 266 | ), 267 | child: AnimatedDefaultTextStyle( 268 | duration: kThemeAnimationDuration, 269 | style: TextStyle( 270 | color: controller.hovered.value ? Colors.white : Colors.teal, 271 | fontSize: 17, 272 | fontWeight: FontWeight.w500, 273 | fontFamily: 'Ubuntu', 274 | ), 275 | child: Text( 276 | 'Hire Me', 277 | ), 278 | ), 279 | ), 280 | ), 281 | ], 282 | ], 283 | ), 284 | ), 285 | ), 286 | ), 287 | ), 288 | ], 289 | ), 290 | ), 291 | Align(alignment: Alignment.bottomRight, child: SocialMediaBarForMobile()), 292 | ], 293 | ), 294 | )), 295 | ); 296 | } 297 | 298 | } 299 | --------------------------------------------------------------------------------