├── .docs └── cover.png ├── web ├── favicon.png ├── icons │ └── Icon-192.png ├── manifest.json └── index.html ├── assets ├── images │ └── transparent.png └── fonts │ └── Ubuntu │ ├── Ubuntu-Bold.ttf │ ├── Ubuntu-Light.ttf │ ├── Ubuntu-Medium.ttf │ └── Ubuntu-Regular.ttf ├── lib ├── utilities │ ├── url_launcher.dart │ ├── extensions.dart │ ├── diagonal_path_clipper.dart │ ├── app_constants.dart │ └── showcase_app_model.dart ├── main.dart ├── landing │ ├── widgets │ │ ├── source_aware_image.dart │ │ ├── social_media_button.dart │ │ ├── animated_opacity_when_hovered.dart │ │ ├── external_link_button.dart │ │ ├── animated_image_overlay.dart │ │ ├── animated_background_image.dart │ │ ├── social_media_buttons.dart │ │ ├── interactive_image_viewer.dart │ │ ├── delayed_widget.dart │ │ ├── scroll_up_indicator.dart │ │ └── showcase_app_item.dart │ ├── landing_footer.dart │ ├── landing_screen.dart │ ├── landing_body.dart │ └── landing_header.dart └── app.dart ├── pubspec.yaml ├── .vscode └── launch.json ├── .gitignore ├── .metadata ├── LICENSE ├── analysis_options.yaml ├── README.md └── pubspec.lock /.docs/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/.docs/cover.png -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /assets/images/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/assets/images/transparent.png -------------------------------------------------------------------------------- /assets/fonts/Ubuntu/Ubuntu-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/assets/fonts/Ubuntu/Ubuntu-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Ubuntu/Ubuntu-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/assets/fonts/Ubuntu/Ubuntu-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Ubuntu/Ubuntu-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/assets/fonts/Ubuntu/Ubuntu-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Ubuntu/Ubuntu-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranbekirovyz/flutter-web-portfolio/HEAD/assets/fonts/Ubuntu/Ubuntu-Regular.ttf -------------------------------------------------------------------------------- /lib/utilities/url_launcher.dart: -------------------------------------------------------------------------------- 1 | // OK, just trying something new :) 2 | import 'dart:js' as js; 3 | 4 | void launchUrl(String url, {bool newTab = true}) { 5 | js.context.callMethod( 6 | 'open', 7 | [url, newTab ? '_blank' : '_self'], 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:app/app.dart'; 3 | import 'package:flutter_web_plugins/url_strategy.dart'; 4 | 5 | void main() { 6 | // Removes "#" from URL, for more check out link below: 7 | // https://docs.flutter.dev/development/ui/navigation/url-strategies 8 | usePathUrlStrategy(); 9 | 10 | runApp(const App()); 11 | } 12 | -------------------------------------------------------------------------------- /lib/utilities/extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:responsive_framework/responsive_framework.dart'; 3 | 4 | // Some utility extensions on responsive_framework package. 5 | extension BuildContextX on BuildContext { 6 | ResponsiveWrapperData get responsiveness { 7 | return ResponsiveWrapper.of(this); 8 | } 9 | 10 | bool get isMobile => responsiveness.isPhone; 11 | bool get isTablet => responsiveness.isTablet; 12 | bool get isDesktop => responsiveness.isDesktop; 13 | } 14 | -------------------------------------------------------------------------------- /lib/utilities/diagonal_path_clipper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | 3 | // Extracted from "flutter_custom_clippers" package. 4 | class DiagonalPathClipper extends CustomClipper { 5 | @override 6 | Path getClip(Size size) { 7 | final path = Path() 8 | ..lineTo(0.0, size.height) 9 | ..lineTo(size.width, size.height - 112.0) 10 | ..lineTo(size.width, 0.0) 11 | ..close(); 12 | return path; 13 | } 14 | 15 | @override 16 | bool shouldReclip(CustomClipper oldClipper) { 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/landing/widgets/source_aware_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Widget displays Image.network or Image.asset on the basis of source. 4 | class SourceAwareImage extends StatelessWidget { 5 | final String image; 6 | final bool isNetworkImage; 7 | 8 | const SourceAwareImage({ 9 | required this.image, 10 | required this.isNetworkImage, 11 | super.key, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return isNetworkImage 17 | ? Image.network( 18 | image, 19 | ) 20 | : Image.asset( 21 | image, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: app 2 | description: A new Flutter project. 3 | 4 | publish_to: 'none' 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: '>=2.18.2 <3.0.0' 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | flutter_web_plugins: 15 | sdk: flutter 16 | font_awesome_flutter: ^10.2.1 17 | responsive_framework: ^0.2.0 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | flutter_lints: ^2.0.0 23 | 24 | flutter: 25 | uses-material-design: true 26 | 27 | assets: 28 | - assets/images/ 29 | 30 | fonts: 31 | - family: Ubuntu 32 | fonts: 33 | - asset: assets/fonts/Ubuntu/Ubuntu-Bold.ttf 34 | - asset: assets/fonts/Ubuntu/Ubuntu-Medium.ttf 35 | - asset: assets/fonts/Ubuntu/Ubuntu-Regular.ttf 36 | - asset: assets/fonts/Ubuntu/Ubuntu-Light.ttf 37 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "kamranbekirovcom-website", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "kamranbekirovcom-website (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "kamranbekirovcom-website (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | /macos/ 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 | -------------------------------------------------------------------------------- /.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. 5 | 6 | version: 7 | revision: eb6d86ee27deecba4a83536aa20f366a6044895c 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 17 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 18 | - platform: macos 19 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 20 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "short_name": "app", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kamran Bekirov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /lib/landing/widgets/social_media_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/landing/widgets/animated_opacity_when_hovered.dart'; 2 | import 'package:app/landing/widgets/delayed_widget.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:app/utilities/url_launcher.dart'; 5 | 6 | class SocialMediaButton extends StatelessWidget { 7 | final String url; 8 | final IconData iconData; 9 | final double size; 10 | final int index; 11 | 12 | const SocialMediaButton({ 13 | required this.url, 14 | required this.iconData, 15 | required this.index, 16 | this.size = 30.0, 17 | super.key, 18 | }); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return DelayedWidget( 23 | delayDuration: Duration(milliseconds: 1500 + ((index + 1) * 125)), 24 | from: DelayFrom.bottom, 25 | child: AnimatedOpacityWhenHovered( 26 | child: IconButton( 27 | onPressed: () => launchUrl(url), 28 | icon: Icon( 29 | iconData, 30 | color: Colors.white, 31 | size: size, 32 | ), 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/landing/widgets/animated_opacity_when_hovered.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // A widget that has 0.6 opacity on normal state, but when hovered it will have 4 | // full visibility (1.0 opacity) and click cursor on it. 5 | class AnimatedOpacityWhenHovered extends StatefulWidget { 6 | final Widget child; 7 | 8 | const AnimatedOpacityWhenHovered({ 9 | required this.child, 10 | super.key, 11 | }); 12 | 13 | @override 14 | State createState() => _AnimatedOpacityWhenHoveredState(); 15 | } 16 | 17 | class _AnimatedOpacityWhenHoveredState extends State { 18 | late bool _hovered; 19 | 20 | @override 21 | void initState() { 22 | _hovered = false; 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return MouseRegion( 29 | onEnter: (event) => setState(() => _hovered = true), 30 | onExit: (event) => setState(() => _hovered = false), 31 | cursor: SystemMouseCursors.click, 32 | child: AnimatedOpacity( 33 | duration: kThemeAnimationDuration, 34 | opacity: _hovered ? 1.0 : .6, 35 | child: widget.child, 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/landing/landing_footer.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/landing/widgets/animated_opacity_when_hovered.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | import 'package:app/utilities/app_constants.dart'; 5 | import 'package:app/utilities/url_launcher.dart'; 6 | 7 | class LandingFooter extends StatelessWidget { 8 | const LandingFooter({ 9 | Key? key, 10 | }) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Row( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | // GitHub repository reference. 18 | AnimatedOpacityWhenHovered( 19 | child: TextButton( 20 | onPressed: () { 21 | launchUrl(AppConstants.openSourceRepoURL); 22 | }, 23 | child: const Padding( 24 | padding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), 25 | child: Icon( 26 | FontAwesomeIcons.github, 27 | color: Colors.white, 28 | size: 44.0, 29 | ), 30 | ), 31 | // iconSize: 120.0, 32 | ), 33 | ), 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/landing/widgets/external_link_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:app/utilities/url_launcher.dart'; 3 | 4 | class ExternalLinkButton extends StatelessWidget { 5 | final String url; 6 | final String label; 7 | final IconData iconData; 8 | 9 | const ExternalLinkButton({ 10 | required this.url, 11 | required this.label, 12 | required this.iconData, 13 | super.key, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return TextButton( 19 | onPressed: () { 20 | launchUrl(url); 21 | }, 22 | child: Padding( 23 | padding: const EdgeInsets.symmetric(vertical: 10.0), 24 | child: Row( 25 | children: [ 26 | Icon( 27 | iconData, 28 | color: Colors.white, 29 | ), 30 | const SizedBox(width: 24.0), 31 | Text( 32 | label.toUpperCase(), 33 | style: const TextStyle( 34 | fontSize: 16.0, 35 | fontWeight: FontWeight.w600, 36 | color: Colors.white, 37 | letterSpacing: 1.4, 38 | ), 39 | ), 40 | ], 41 | ), 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:app/utilities/app_constants.dart'; 3 | import 'package:app/landing/landing_screen.dart'; 4 | import 'package:responsive_framework/responsive_framework.dart'; 5 | 6 | class App extends StatelessWidget { 7 | const App({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | debugShowCheckedModeBanner: false, 13 | title: AppConstants.appTitle, 14 | 15 | // Initializing responsive_framework here. 16 | builder: (context, child) { 17 | return ResponsiveWrapper.builder( 18 | BouncingScrollWrapper.builder(context, child!), 19 | minWidth: 300, 20 | debugLog: true, 21 | defaultScale: true, 22 | breakpoints: [ 23 | const ResponsiveBreakpoint.autoScaleDown(450, name: MOBILE), 24 | const ResponsiveBreakpoint.autoScale(800, name: TABLET), 25 | const ResponsiveBreakpoint.resize(1200, name: DESKTOP), 26 | ], 27 | background: Container(color: primaryColor), 28 | ); 29 | }, 30 | home: const LandingScreen(), 31 | theme: ThemeData( 32 | visualDensity: VisualDensity.adaptivePlatformDensity, 33 | fontFamily: 'Ubuntu', 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/utilities/app_constants.dart: -------------------------------------------------------------------------------- 1 | // Almost all texts used through out the app. 2 | abstract class AppConstants { 3 | static const appTitle = 'Kamran Bekirov'; 4 | static const disclaimer = 'I do not claim ownership of the projects below as some of them were developed for companies I worked for and some for clients/clients of my clients while the backend side is provided.'; 5 | static const showcaseTitle = 'SHOWCASE'; 6 | static const showcaseDescription = 'MOBILE APPLICATIONS EITHER DEVELOPED COMPLETELY BY ME OR BY A TEAM WHERE I PARTICIPATED SIGNIFICANTLY'; 7 | static const landingTitle = 'KAMRAN BEKIROV'; 8 | static const landingMotto = 'FLUTTER BY DAY, FLUTTER BY NIGHT (INCLUDING WEEKENDS)'; 9 | 10 | static const webSiteURL = 'kamranbekirov.com'; 11 | static const gitHubProfileURL = 'https://github.com/kamranbekirovyz'; 12 | static const linkedInProfileURL = 'https://linkedin.com/in/kamranbekirovyz'; 13 | static const twitterURL = 'https://twitter.com/kamranbekirovyz'; 14 | static const instagramProfileURL = 'https://instagram.com/kamranbekirovyz'; 15 | static const facebookProfileURL = 'https://www.facebook.com/profile.php?id=100067620484373'; 16 | static const eMail = 'mailto:kamranbekirovyz@gmail.com'; 17 | static const flutterWebSiteURL = 'https://flutter.dev'; 18 | static const openSourceRepoURL = 'https://github.com/kamranbekirovyz/flutter-web-portfolio'; 19 | } 20 | -------------------------------------------------------------------------------- /lib/landing/widgets/animated_image_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // A widget that shows black overlay with app's topic as a text. 4 | class AnimatedImageOverlay extends StatefulWidget { 5 | final String topic; 6 | 7 | const AnimatedImageOverlay( 8 | this.topic, { 9 | Key? key, 10 | }) : super(key: key); 11 | 12 | @override 13 | State createState() => _AnimatedImageOverlayState(); 14 | } 15 | 16 | class _AnimatedImageOverlayState extends State { 17 | late bool _hovered; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | _hovered = false; 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return MouseRegion( 28 | cursor: SystemMouseCursors.click, 29 | onEnter: (_) => setState(() => _hovered = true), 30 | onExit: (_) => setState(() => _hovered = false), 31 | child: AnimatedOpacity( 32 | duration: kThemeAnimationDuration, 33 | opacity: _hovered ? 1.0 : 0.0, 34 | child: Container( 35 | height: 552.0, 36 | alignment: Alignment.center, 37 | color: Colors.black45, 38 | child: Text( 39 | widget.topic, 40 | style: const TextStyle( 41 | fontSize: 24.0, 42 | color: Colors.white, 43 | fontWeight: FontWeight.w500, 44 | ), 45 | ), 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /lib/landing/widgets/animated_background_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/utilities/extensions.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AnimatedBackgroundImage extends StatefulWidget { 5 | final ScrollController scrollController; 6 | 7 | const AnimatedBackgroundImage(this.scrollController, {super.key}); 8 | 9 | @override 10 | State createState() => _AnimatedBackgroundImageState(); 11 | } 12 | 13 | class _AnimatedBackgroundImageState extends State { 14 | late double _y; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | 20 | _y = 0.0; 21 | 22 | widget.scrollController.addListener(() { 23 | final offset = widget.scrollController.offset; 24 | 25 | /// Only update alignment of the background image while it is visible. 26 | if (offset < 500) { 27 | /// Divide current scroll offset by 1000 to make it more smooth. 28 | setState(() => _y = widget.scrollController.offset / 1000); 29 | } 30 | }); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | final height = context.isMobile ? 440.0 : 540.0; 36 | return SizedBox( 37 | height: height, 38 | width: double.maxFinite, 39 | child: Opacity( 40 | opacity: 0.3, 41 | child: FadeInImage.assetNetwork( 42 | placeholder: 'assets/images/transparent.png', 43 | image: 'https://porelarte.tech/kamranbekirovcom/background-min.jpg', 44 | fit: BoxFit.cover, 45 | alignment: Alignment(0.0, _y), 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Portfolio website using Flutter Web 2 | 3 | A responsive layout; open-source, comment-rich, and clean codebase website built with Flutter web. 4 | 5 | cover 6 | 7 | ## 🚀 Motivation 8 | 9 | 10 | Previously, I had a web portfolio for myself hosted at kamranbekirov.com built with HTML and CSS. While learning Flutter web I thought it might be interesting and challenging to build the same website but now using Flutter web and responsiveness (mobile, tablet, desktop) in mind. On that journey, I added many animations to make the website interactive. 11 | 12 | Demo link: kamranbekirovyz.github.io 13 | 14 | The codebase is open-source and rich in comments. Although you can use it any way you want, beware that it (my old website that was built with HTML and CSS to be more specific) was inspired and cloned from an open-source template called Solid state. So, in case of usage, I suggest you respect its authors and do not use my personal logo as it is yours. 🤓 15 | 16 | ## 🙏 Credits 17 | 18 | The design is inspired by a template from html5up.net called Solid state. 19 | 20 | ## 🐞 Bugs/Requests 21 | 22 | If you encounter any problems please open an issue. If you feel the library is missing a feature, please raise a ticket on GitHub and we'll look into it. Pull requests are welcome. 23 | 24 | ## 📃 License 25 | 26 | [MIT License](https://github.com/kamranbekirovyz/flutter-web-portfolio/blob/master/LICENSE) 27 | -------------------------------------------------------------------------------- /lib/landing/widgets/social_media_buttons.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/utilities/app_constants.dart'; 2 | import 'package:app/landing/widgets/social_media_button.dart'; 3 | import 'package:app/utilities/extensions.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 6 | 7 | class SocialMediaButtons extends StatelessWidget { 8 | const SocialMediaButtons({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final alignment = context.isDesktop ? WrapAlignment.start : WrapAlignment.center; 13 | final wrapAlignment = context.isMobile ? Alignment.center : null; 14 | 15 | return Container( 16 | alignment: wrapAlignment, 17 | child: Wrap( 18 | spacing: 16.0, 19 | runSpacing: 16.0, 20 | alignment: alignment, 21 | children: const [ 22 | SocialMediaButton( 23 | index: 0, 24 | url: AppConstants.gitHubProfileURL, 25 | iconData: FontAwesomeIcons.github, 26 | ), 27 | SocialMediaButton( 28 | index: 1, 29 | url: AppConstants.eMail, 30 | iconData: Icons.alternate_email_rounded, 31 | ), 32 | SocialMediaButton( 33 | index: 2, 34 | url: AppConstants.linkedInProfileURL, 35 | iconData: FontAwesomeIcons.linkedin, 36 | ), 37 | SocialMediaButton( 38 | index: 3, 39 | url: AppConstants.twitterURL, 40 | iconData: FontAwesomeIcons.twitter, 41 | ), 42 | SocialMediaButton( 43 | index: 4, 44 | url: AppConstants.facebookProfileURL, 45 | iconData: FontAwesomeIcons.facebook, 46 | ), 47 | SocialMediaButton( 48 | index: 5, 49 | url: AppConstants.instagramProfileURL, 50 | iconData: FontAwesomeIcons.instagram, 51 | ), 52 | ], 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/landing/landing_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/landing/landing_header.dart'; 2 | import 'package:app/landing/landing_body.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:app/landing/widgets/scroll_up_indicator.dart'; 5 | 6 | const dividerColor = Color(0xFF464751); 7 | const primaryColor = Color.fromRGBO(47, 49, 64, 1.0); 8 | const cardColor = Color.fromRGBO(54, 56, 72, 1.0); 9 | 10 | class LandingScreen extends StatefulWidget { 11 | const LandingScreen({super.key}); 12 | 13 | @override 14 | State createState() => _LandingScreenState(); 15 | } 16 | 17 | class _LandingScreenState extends State { 18 | late final ScrollController _scrollController; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _scrollController = ScrollController(); 24 | } 25 | 26 | @override 27 | void dispose() { 28 | _scrollController.dispose(); 29 | super.dispose(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | backgroundColor: primaryColor, 36 | // Stack widgets on top of each other 37 | body: Stack( 38 | children: [ 39 | // For better control on the web, we supply our ScrollController to 40 | // both parent Scrollbar and child SingleChildScrollView widgets. 41 | Scrollbar( 42 | controller: _scrollController, 43 | child: SingleChildScrollView( 44 | controller: _scrollController, 45 | child: Column( 46 | children: [ 47 | // Header with texts and social media buttons. 48 | LandingHeader(_scrollController), 49 | const SizedBox(height: 56.0), 50 | 51 | // Body with showcase apps. 52 | const LandingBody(), 53 | ], 54 | ), 55 | ), 56 | ), 57 | 58 | // Bar on top, visible when header is invisible, with 2 buttons: 59 | // 60 | // 1. Scrolls page to up when clicked. 61 | // 62 | // 2. "BUILT WITH Flutter" indicator, redirects to open-source 63 | // repostitory containing source codes of this web app. 64 | ScrollUpIndicator(_scrollController), 65 | ], 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/landing/widgets/interactive_image_viewer.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/landing/landing_screen.dart'; 2 | import 'package:app/landing/widgets/source_aware_image.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class InteractiveImageViewer extends StatefulWidget { 6 | final String image; 7 | final bool isNetworkImage; 8 | 9 | const InteractiveImageViewer({ 10 | required this.image, 11 | required this.isNetworkImage, 12 | super.key, 13 | }); 14 | 15 | @override 16 | State createState() => _InteractiveImageViewerState(); 17 | } 18 | 19 | class _InteractiveImageViewerState extends State { 20 | late final TransformationController _transformationController; 21 | late TapDownDetails _doubleTapDetails; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _transformationController = TransformationController(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | _transformationController.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Scaffold( 38 | backgroundColor: primaryColor.withOpacity(.25), 39 | appBar: AppBar( 40 | automaticallyImplyLeading: false, 41 | backgroundColor: primaryColor, 42 | actions: const [ 43 | CircleAvatar( 44 | backgroundColor: primaryColor, 45 | child: CloseButton(), 46 | ), 47 | SizedBox(width: 24.0), 48 | ], 49 | ), 50 | body: InteractiveViewer( 51 | transformationController: _transformationController, 52 | child: SizedBox( 53 | // Set width as "double.maxFinite" so that image can expand its width 54 | // while zooming. 55 | width: double.maxFinite, 56 | child: GestureDetector( 57 | // Zoom image 2x, when double tapped. 58 | onDoubleTapDown: _handleDoubleTapDown, 59 | onDoubleTap: _handleDoubleTap, 60 | child: SourceAwareImage( 61 | image: widget.image, 62 | isNetworkImage: widget.isNetworkImage, 63 | ), 64 | ), 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | void _handleDoubleTapDown(TapDownDetails details) { 71 | _doubleTapDetails = details; 72 | } 73 | 74 | void _handleDoubleTap() { 75 | if (_transformationController.value != Matrix4.identity()) { 76 | _transformationController.value = Matrix4.identity(); 77 | } else { 78 | final position = _doubleTapDetails.localPosition; 79 | _transformationController.value = Matrix4.identity() 80 | ..translate(-position.dx, -position.dy) 81 | ..scale(2.0); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/landing/widgets/delayed_widget.dart: -------------------------------------------------------------------------------- 1 | // Extracted from: https://pub.dev/packages/delayed_widget 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | enum DelayFrom { 6 | left, 7 | right, 8 | top, 9 | bottom, 10 | } 11 | 12 | class DelayedWidget extends StatefulWidget { 13 | final Widget child; 14 | final Duration delayDuration; 15 | final Duration duration; 16 | final DelayFrom from; 17 | 18 | const DelayedWidget({ 19 | Key? key, 20 | required this.child, 21 | this.duration = const Duration(milliseconds: 500), 22 | this.delayDuration = Duration.zero, 23 | this.from = DelayFrom.bottom, 24 | }) : super(key: key); 25 | 26 | @override 27 | DelayedWidgetState createState() => DelayedWidgetState(); 28 | } 29 | 30 | class DelayedWidgetState extends State with SingleTickerProviderStateMixin { 31 | late AnimationController _animationController; 32 | late Animation _animation; 33 | bool _animationControllerDisposed = false; 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | 39 | _animationController = AnimationController( 40 | duration: widget.duration, 41 | vsync: this, 42 | ); 43 | 44 | _animation = Tween( 45 | begin: 1.0, 46 | end: 0.0, 47 | ).animate( 48 | CurvedAnimation( 49 | parent: _animationController, 50 | curve: Curves.easeOut, 51 | ), 52 | ); 53 | 54 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { 55 | await Future.delayed(widget.delayDuration); 56 | if (!_animationControllerDisposed) { 57 | _animationController.forward(); 58 | } 59 | }); 60 | } 61 | 62 | @override 63 | void dispose() { 64 | _animationController.dispose(); 65 | _animationControllerDisposed = true; 66 | super.dispose(); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return AnimatedBuilder( 72 | animation: _animation, 73 | builder: (context, child) { 74 | return Transform.translate( 75 | offset: _offset(), 76 | child: Opacity( 77 | opacity: 1.0 - (_animation.value), 78 | child: widget.child, 79 | ), 80 | ); 81 | }, 82 | ); 83 | } 84 | 85 | Offset _offset() { 86 | double extent = 30.0; 87 | 88 | switch (widget.from) { 89 | case DelayFrom.left: 90 | extent = extent - (extent * 2); 91 | 92 | return Offset(extent * _animation.value, 0.0); 93 | 94 | case DelayFrom.right: 95 | return Offset(extent * _animation.value, 0.0); 96 | 97 | case DelayFrom.top: 98 | extent = extent - (extent * 2); 99 | 100 | return Offset(0.0, extent * _animation.value); 101 | 102 | case DelayFrom.bottom: 103 | return Offset(0.0, extent * _animation.value); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Kamran Bekirov 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 |
42 | 95 | Loading indicator 96 |
97 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /lib/landing/widgets/scroll_up_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/utilities/app_constants.dart'; 2 | import 'package:app/utilities/url_launcher.dart'; 3 | import 'package:app/landing/landing_screen.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class ScrollUpIndicator extends StatefulWidget { 7 | final ScrollController scrollController; 8 | 9 | const ScrollUpIndicator(this.scrollController, {super.key}); 10 | 11 | @override 12 | State createState() => _ScrollUpIndicatorState(); 13 | } 14 | 15 | class _ScrollUpIndicatorState extends State { 16 | late bool _visible = false; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | 22 | widget.scrollController.addListener(() { 23 | final offset = widget.scrollController.offset; 24 | 25 | if (offset > 360.0) { 26 | if (!_visible) setState(() => _visible = true); 27 | } else if (offset <= 360.0) { 28 | if (_visible) setState(() => _visible = false); 29 | } 30 | }); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return AnimatedSwitcher( 36 | duration: kThemeAnimationDuration, 37 | child: _visible 38 | ? Container( 39 | width: double.maxFinite, 40 | height: 56.0, 41 | alignment: Alignment.centerLeft, 42 | color: cardColor.withOpacity(.9), 43 | child: GestureDetector( 44 | onTap: () { 45 | widget.scrollController.animateTo( 46 | 0, 47 | duration: const Duration(seconds: 1), 48 | curve: Curves.ease, 49 | ); 50 | }, 51 | child: Row( 52 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 53 | children: [ 54 | Flexible( 55 | child: MouseRegion( 56 | cursor: SystemMouseCursors.click, 57 | child: _buildScrollUpTrigger(), 58 | ), 59 | ), 60 | MouseRegion( 61 | cursor: SystemMouseCursors.click, 62 | child: _buildFlutterIndicator(), 63 | ), 64 | ], 65 | ), 66 | ), 67 | ) 68 | : const SizedBox.shrink(), 69 | ); 70 | } 71 | 72 | Widget _buildScrollUpTrigger() { 73 | return Row( 74 | mainAxisSize: MainAxisSize.min, 75 | children: [ 76 | const SizedBox(width: 24.0), 77 | const Icon( 78 | Icons.arrow_upward_rounded, 79 | color: Colors.white, 80 | size: 20.0, 81 | ), 82 | const SizedBox(width: 16.0), 83 | Text( 84 | AppConstants.webSiteURL.toUpperCase(), 85 | style: const TextStyle( 86 | fontSize: 14.0, 87 | fontWeight: FontWeight.w600, 88 | letterSpacing: 2.5, 89 | color: Colors.white, 90 | ), 91 | ), 92 | const SizedBox(width: 24.0), 93 | ], 94 | ); 95 | } 96 | 97 | Widget _buildFlutterIndicator() { 98 | return GestureDetector( 99 | onTap: () { 100 | launchUrl(AppConstants.openSourceRepoURL); 101 | }, 102 | behavior: HitTestBehavior.translucent, 103 | child: Row( 104 | mainAxisSize: MainAxisSize.min, 105 | children: const [ 106 | SizedBox(width: 24.0), 107 | Text( 108 | 'BUILT\nWITH', 109 | style: TextStyle( 110 | color: Colors.white, 111 | fontSize: 10.0, 112 | fontWeight: FontWeight.bold, 113 | ), 114 | ), 115 | SizedBox(width: 4.0), 116 | FlutterLogo(size: 36.0), 117 | SizedBox(width: 24.0), 118 | ], 119 | ), 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/landing/landing_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/landing/landing_screen.dart'; 2 | import 'package:app/landing/widgets/delayed_widget.dart'; 3 | import 'package:app/utilities/extensions.dart'; 4 | import 'package:app/utilities/showcase_app_model.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:app/utilities/app_constants.dart'; 7 | import 'package:app/landing/landing_footer.dart'; 8 | import 'package:app/landing/widgets/showcase_app_item.dart'; 9 | 10 | class LandingBody extends StatelessWidget { 11 | const LandingBody({ 12 | Key? key, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | // Keeps UI clean and centered when screen size is bigger than 1200.0 19 | constraints: const BoxConstraints(maxWidth: 1200.0), 20 | padding: const EdgeInsets.symmetric(horizontal: 24.0), 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | const DelayedWidget( 25 | delayDuration: Duration(milliseconds: 2800), 26 | from: DelayFrom.bottom, 27 | child: SelectableText( 28 | AppConstants.showcaseTitle, 29 | style: TextStyle( 30 | fontSize: 20.0, 31 | fontWeight: FontWeight.bold, 32 | color: Colors.white, 33 | ), 34 | ), 35 | ), 36 | const SizedBox(height: 16.0), 37 | const DelayedWidget( 38 | delayDuration: Duration(milliseconds: 2900), 39 | from: DelayFrom.bottom, 40 | child: Divider( 41 | thickness: 1.75, 42 | color: dividerColor, 43 | ), 44 | ), 45 | const SizedBox(height: 16.0), 46 | const DelayedWidget( 47 | delayDuration: Duration(milliseconds: 3000), 48 | from: DelayFrom.bottom, 49 | child: SelectableText( 50 | AppConstants.showcaseDescription, 51 | style: TextStyle( 52 | fontSize: 18.0, 53 | fontWeight: FontWeight.bold, 54 | color: Colors.white, 55 | letterSpacing: 1.8, 56 | ), 57 | ), 58 | ), 59 | const SizedBox(height: 24.0), 60 | const DelayedWidget( 61 | delayDuration: Duration(milliseconds: 3100), 62 | from: DelayFrom.bottom, 63 | child: SelectableText( 64 | AppConstants.disclaimer, 65 | style: TextStyle( 66 | fontSize: 16.0, 67 | color: Colors.white, 68 | ), 69 | ), 70 | ), 71 | const SizedBox(height: 56.0), 72 | 73 | DelayedWidget( 74 | delayDuration: const Duration(milliseconds: 3200), 75 | from: DelayFrom.bottom, 76 | child: LayoutBuilder( 77 | builder: (context, constraints) { 78 | return Wrap( 79 | spacing: 16.0, 80 | runSpacing: 16.0, 81 | children: apps.map((e) { 82 | final availableWidth = constraints.maxWidth; 83 | 84 | final rowItemCount = context.isDesktop 85 | ? 4 86 | : context.isTablet 87 | ? 3 88 | : 1; 89 | 90 | final itemWidth = (availableWidth - ((rowItemCount - 1) * 16.0)) / rowItemCount; 91 | 92 | return SizedBox( 93 | width: itemWidth, 94 | child: ShowcaseAppItem(e), 95 | ); 96 | }).toList(), 97 | ); 98 | }, 99 | ), 100 | ), 101 | const SizedBox(height: 120.0), 102 | 103 | // 2 Buttons at bottom of landing: flutter.dev, github.com. 104 | const LandingFooter(), 105 | const SizedBox(height: 120.0), 106 | ], 107 | ), 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/landing/widgets/showcase_app_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/landing/widgets/interactive_image_viewer.dart'; 2 | import 'package:app/landing/widgets/source_aware_image.dart'; 3 | import 'package:app/utilities/showcase_app_model.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 6 | import 'package:app/landing/landing_screen.dart'; 7 | import 'package:app/landing/widgets/animated_image_overlay.dart'; 8 | import 'package:app/landing/widgets/external_link_button.dart'; 9 | 10 | class ShowcaseAppItem extends StatelessWidget { 11 | final ShowcaseAppModel app; 12 | 13 | const ShowcaseAppItem( 14 | this.app, { 15 | Key? key, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ClipRRect( 21 | borderRadius: const BorderRadius.only( 22 | topLeft: Radius.circular(16.0), 23 | topRight: Radius.circular(16.0), 24 | bottomLeft: Radius.circular(4.0), 25 | bottomRight: Radius.circular(4.0), 26 | ), 27 | child: Stack( 28 | children: [ 29 | _buildChild(), 30 | Positioned( 31 | top: 0.0, 32 | // bottom: 192.0, 33 | left: 0.0, 34 | right: 0.0, 35 | child: GestureDetector( 36 | // When overlay tapped, open full screen interactive image viewer. 37 | onTap: () { 38 | showGeneralDialog( 39 | context: context, 40 | pageBuilder: (_, __, ___) { 41 | return InteractiveImageViewer( 42 | image: app.image, 43 | isNetworkImage: app.isNetworkImage, 44 | ); 45 | }, 46 | ); 47 | }, 48 | child: AnimatedImageOverlay(app.topic), 49 | ), 50 | ), 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | Widget _buildChild() { 57 | return Container( 58 | color: cardColor, 59 | child: Column( 60 | crossAxisAlignment: CrossAxisAlignment.start, 61 | children: [ 62 | SourceAwareImage( 63 | image: app.image, 64 | isNetworkImage: app.isNetworkImage, 65 | ), 66 | _buildBottom(), 67 | ], 68 | ), 69 | ); 70 | } 71 | 72 | Widget _buildBottom() { 73 | return Padding( 74 | padding: const EdgeInsets.all(24.0).copyWith(bottom: 8.0), 75 | child: Column( 76 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 77 | crossAxisAlignment: CrossAxisAlignment.start, 78 | children: [ 79 | Column( 80 | crossAxisAlignment: CrossAxisAlignment.start, 81 | children: [ 82 | _buildAppName(), 83 | const Divider( 84 | color: dividerColor, 85 | thickness: 1.5, 86 | height: 32.0, 87 | ), 88 | ], 89 | ), 90 | if (app.playStoreURL != null) ...[ 91 | ExternalLinkButton( 92 | url: app.playStoreURL!, 93 | iconData: FontAwesomeIcons.googlePlay, 94 | label: 'Play Store', 95 | ), 96 | const SizedBox(height: 10.0), 97 | ], 98 | if (app.appStoreURL != null) ...[ 99 | ExternalLinkButton( 100 | url: app.appStoreURL!, 101 | iconData: FontAwesomeIcons.appStoreIos, 102 | label: 'App Store', 103 | ), 104 | const SizedBox(height: 10.0), 105 | ], 106 | if (app.githubURL != null) 107 | ExternalLinkButton( 108 | url: app.githubURL!, 109 | iconData: FontAwesomeIcons.squareGithub, 110 | label: 'GitHub', 111 | ), 112 | ], 113 | ), 114 | ); 115 | } 116 | 117 | Widget _buildAppName() { 118 | return Text( 119 | app.name, 120 | style: const TextStyle( 121 | fontSize: 18.0, 122 | fontWeight: FontWeight.w500, 123 | color: Colors.white, 124 | letterSpacing: 1.4, 125 | ), 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.9.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.1" 25 | clock: 26 | dependency: transitive 27 | description: 28 | name: clock 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.1" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.16.0" 39 | fake_async: 40 | dependency: transitive 41 | description: 42 | name: fake_async 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.3.1" 46 | flutter: 47 | dependency: "direct main" 48 | description: flutter 49 | source: sdk 50 | version: "0.0.0" 51 | flutter_lints: 52 | dependency: "direct dev" 53 | description: 54 | name: flutter_lints 55 | url: "https://pub.dartlang.org" 56 | source: hosted 57 | version: "2.0.1" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | flutter_web_plugins: 64 | dependency: "direct main" 65 | description: flutter 66 | source: sdk 67 | version: "0.0.0" 68 | font_awesome_flutter: 69 | dependency: "direct main" 70 | description: 71 | name: font_awesome_flutter 72 | url: "https://pub.dartlang.org" 73 | source: hosted 74 | version: "10.2.1" 75 | js: 76 | dependency: transitive 77 | description: 78 | name: js 79 | url: "https://pub.dartlang.org" 80 | source: hosted 81 | version: "0.6.4" 82 | lints: 83 | dependency: transitive 84 | description: 85 | name: lints 86 | url: "https://pub.dartlang.org" 87 | source: hosted 88 | version: "2.0.0" 89 | matcher: 90 | dependency: transitive 91 | description: 92 | name: matcher 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "0.12.12" 96 | material_color_utilities: 97 | dependency: transitive 98 | description: 99 | name: material_color_utilities 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "0.1.5" 103 | meta: 104 | dependency: transitive 105 | description: 106 | name: meta 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.8.0" 110 | path: 111 | dependency: transitive 112 | description: 113 | name: path 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.8.2" 117 | responsive_framework: 118 | dependency: "direct main" 119 | description: 120 | name: responsive_framework 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "0.2.0" 124 | sky_engine: 125 | dependency: transitive 126 | description: flutter 127 | source: sdk 128 | version: "0.0.99" 129 | source_span: 130 | dependency: transitive 131 | description: 132 | name: source_span 133 | url: "https://pub.dartlang.org" 134 | source: hosted 135 | version: "1.9.0" 136 | stack_trace: 137 | dependency: transitive 138 | description: 139 | name: stack_trace 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "1.10.0" 143 | stream_channel: 144 | dependency: transitive 145 | description: 146 | name: stream_channel 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "2.1.0" 150 | string_scanner: 151 | dependency: transitive 152 | description: 153 | name: string_scanner 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "1.1.1" 157 | term_glyph: 158 | dependency: transitive 159 | description: 160 | name: term_glyph 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "1.2.1" 164 | test_api: 165 | dependency: transitive 166 | description: 167 | name: test_api 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "0.4.12" 171 | vector_math: 172 | dependency: transitive 173 | description: 174 | name: vector_math 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "2.1.2" 178 | sdks: 179 | dart: ">=2.18.2 <3.0.0" 180 | flutter: ">=3.0.0" 181 | -------------------------------------------------------------------------------- /lib/landing/landing_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/landing/widgets/delayed_widget.dart'; 2 | import 'package:app/utilities/app_constants.dart'; 3 | import 'package:app/landing/landing_screen.dart'; 4 | import 'package:app/landing/widgets/animated_background_image.dart'; 5 | import 'package:app/landing/widgets/social_media_buttons.dart'; 6 | import 'package:app/utilities/diagonal_path_clipper.dart'; 7 | import 'package:app/utilities/extensions.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:responsive_framework/responsive_framework.dart'; 10 | 11 | class LandingHeader extends StatelessWidget { 12 | final ScrollController scrollController; 13 | 14 | const LandingHeader( 15 | this.scrollController, { 16 | super.key, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ClipPath( 22 | clipper: DiagonalPathClipper(), 23 | child: Stack( 24 | fit: StackFit.loose, 25 | children: [ 26 | // Widget that has animated background image while scrolling 27 | AnimatedBackgroundImage(scrollController), 28 | 29 | // The part that is on the "AnimatedBackgroundImage" widget. 30 | Align( 31 | alignment: Alignment.center, 32 | child: _buildSurface(context), 33 | ), 34 | ], 35 | ), 36 | ); 37 | } 38 | 39 | Widget _buildSurface(BuildContext context) { 40 | // Altough the "responsive_framework" package handles most of the 41 | // responsiveness-related stuff for us, we need some cusotm styling based on 42 | // the current view whether it is mobile, tablet or desktop. 43 | // 44 | // At that moment, ResponsiveValue comes very handy and I've added some 45 | // BuildContext extensions to it. 46 | 47 | // Title text size: when Tablet and Mobile = 24, when Desktop = 40 48 | final titleSize = ResponsiveValue( 49 | context, 50 | defaultValue: 24.0, 51 | valueWhen: [ 52 | const Condition.equals(name: TABLET, value: 24.0), 53 | const Condition.largerThan(name: TABLET, value: 40.0), 54 | ], 55 | ).value; 56 | 57 | // Logo size: when Tablet and Mobile = 56, when Desktop = 64 58 | final logoSize = ResponsiveValue( 59 | context, 60 | defaultValue: 40.0, 61 | valueWhen: [ 62 | const Condition.equals(name: TABLET, value: 56.0), 63 | const Condition.largerThan(name: TABLET, value: 64.0), 64 | ], 65 | ).value; 66 | 67 | // Motto text size: when Tablet and Mobile = 14, when Desktop = 16 68 | final mottoSize = ResponsiveValue( 69 | context, 70 | defaultValue: 14.0, 71 | valueWhen: [ 72 | const Condition.equals(name: TABLET, value: 14.0), 73 | const Condition.largerThan(name: TABLET, value: 16.0), 74 | ], 75 | ).value; 76 | 77 | // Motto text alignment: when Desktop = start, when Mobile and Tablet = center. 78 | final mottoTextAlignment = context.isDesktop ? TextAlign.start : TextAlign.center; 79 | 80 | // Max width of centered view when Mobile = 602, Tablet = 800, when Desktop = 1200 81 | final maxWidth = ResponsiveValue( 82 | context, 83 | defaultValue: 602.0, 84 | valueWhen: [ 85 | const Condition.equals(name: TABLET, value: 800.0), 86 | const Condition.largerThan(name: TABLET, value: 1200.0), 87 | ], 88 | ).value!; 89 | 90 | return Container( 91 | alignment: Alignment.center, 92 | constraints: BoxConstraints(maxWidth: maxWidth), 93 | padding: const EdgeInsets.symmetric( 94 | vertical: 96.0, 95 | horizontal: 24.0, 96 | ), 97 | child: Column( 98 | crossAxisAlignment: CrossAxisAlignment.start, 99 | children: [ 100 | Row( 101 | mainAxisSize: MainAxisSize.min, 102 | children: [ 103 | // Personal logo 104 | ClipOval( 105 | child: FadeInImage.assetNetwork( 106 | image: 'https://porelarte.tech/kamranbekirovcom/personal-logo.png', 107 | placeholder: 'assets/images/transparent.png', 108 | height: logoSize, 109 | width: logoSize, 110 | ), 111 | ), 112 | const SizedBox(width: 16.0), 113 | 114 | // Text: "KAMRAN BEKIROV" 115 | Expanded( 116 | child: DelayedWidget( 117 | delayDuration: const Duration(milliseconds: 1000), 118 | from: DelayFrom.right, 119 | child: SelectableText( 120 | AppConstants.landingTitle, 121 | style: TextStyle( 122 | fontSize: titleSize, 123 | fontWeight: FontWeight.bold, 124 | color: Colors.white, 125 | letterSpacing: 4.0, 126 | ), 127 | ), 128 | ), 129 | ), 130 | ], 131 | ), 132 | const SizedBox(height: 22.0), 133 | 134 | // Divider between 135 | const DelayedWidget( 136 | delayDuration: Duration(milliseconds: 1400), 137 | from: DelayFrom.top, 138 | child: Divider( 139 | thickness: 1.75, 140 | color: dividerColor, 141 | ), 142 | ), 143 | const SizedBox(height: 30.0), 144 | 145 | // Text: "FLUTTER BY DAY, FLUTTER BY NIGHT (INCLUDING WEEKENDS)" 146 | DelayedWidget( 147 | delayDuration: const Duration(milliseconds: 1500), 148 | from: DelayFrom.top, 149 | child: SelectableText( 150 | AppConstants.landingMotto, 151 | textAlign: mottoTextAlignment, 152 | style: TextStyle( 153 | fontSize: mottoSize, 154 | fontWeight: FontWeight.w400, 155 | color: Colors.white, 156 | letterSpacing: 1.8, 157 | ), 158 | ), 159 | ), 160 | const SizedBox(height: 40.0), 161 | 162 | // Obvious from its name :) 163 | const SocialMediaButtons() 164 | ], 165 | ), 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/utilities/showcase_app_model.dart: -------------------------------------------------------------------------------- 1 | // A model that represents portfolio apps on landing page. 2 | class ShowcaseAppModel { 3 | final String name; 4 | final String? appStoreURL; 5 | final String? playStoreURL; 6 | final String? githubURL; 7 | final String image; 8 | final String topic; 9 | 10 | const ShowcaseAppModel.withNetworkAsset({ 11 | required this.name, 12 | this.appStoreURL, 13 | this.playStoreURL, 14 | this.githubURL, 15 | required this.image, 16 | required this.topic, 17 | }) : _isNetworkImage = true; 18 | 19 | const ShowcaseAppModel.withLocalAsset({ 20 | required this.name, 21 | this.appStoreURL, 22 | this.playStoreURL, 23 | this.githubURL, 24 | required this.image, 25 | required this.topic, 26 | }) : _isNetworkImage = true; 27 | 28 | final bool _isNetworkImage; 29 | bool get isNetworkImage => _isNetworkImage; 30 | } 31 | 32 | // List of apps that will be listed on landing page. 33 | const apps = [ 34 | ShowcaseAppModel.withNetworkAsset( 35 | name: 'Bonpara', 36 | image: 'https://porelarte.tech/kamranbekirovcom/apps/bonaz.png', 37 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.bonpara.app', 38 | appStoreURL: 'https://apps.apple.com/us/app/bonpara/id1636142117', 39 | topic: 'Cashback, Coupons', 40 | ), 41 | ShowcaseAppModel.withNetworkAsset( 42 | name: 'Badamlı', 43 | image: 'https://porelarte.tech/kamranbekirovcom/apps/badamli-min.png', 44 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.badamli.app', 45 | appStoreURL: 'https://apps.apple.com/az/app/badaml%C4%B1/id1627914279', 46 | topic: 'M-commerce', 47 | ), 48 | ShowcaseAppModel.withNetworkAsset( 49 | name: 'Tentony', 50 | image: 'https://porelarte.tech/kamranbekirovcom/apps/tentony-min.png', 51 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.tentony.app', 52 | appStoreURL: 'https://apps.apple.com/az/app/tentony/id1630425777', 53 | topic: 'M-commerce', 54 | ), 55 | ShowcaseAppModel.withNetworkAsset( 56 | name: 'KOLI', 57 | image: 'https://play-lh.googleusercontent.com/atPmL9orJu8IbwlnyENdPm1PRnN04yoWkyT258tfQIsGek5O5Ij43b34rJoPDFphZ4_u=w3024-h1730-rw', 58 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.koli.app', 59 | appStoreURL: 'https://apps.apple.com/az/app/koli/id1610779509', 60 | topic: 'Cargo', 61 | ), 62 | ShowcaseAppModel.withNetworkAsset( 63 | name: 'CİB', 64 | image: 'https://porelarte.tech/kamranbekirovcom/apps/cib-min.png', 65 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.cib.app', 66 | appStoreURL: 'https://apps.apple.com/us/app/cib-az/id1541577214', 67 | topic: 'FinTech', 68 | ), 69 | ShowcaseAppModel.withNetworkAsset( 70 | name: 'Vadi', 71 | image: 'https://play-lh.googleusercontent.com/3Jt4FmNGu-VMmj4FitOLHhl3jcL_JpV8s-wAKJ8JSIA6QshURf-FaQavN7yc49uFNkyD=w3024-h1730-rw', 72 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.vadi.app', 73 | appStoreURL: 'https://apps.apple.com/az/app/vadi-karqo/id1605375862', 74 | topic: 'Cargo', 75 | ), 76 | ShowcaseAppModel.withNetworkAsset( 77 | name: 'MASHIN.AL', 78 | image: 'https://porelarte.tech/kamranbekirovcom/apps/mashinal-min.png', 79 | playStoreURL: 'https://play.google.com/store/apps/details?id=ventures.al.mashinal', 80 | appStoreURL: 'https://apps.apple.com/ru/app/mashin-al/id1588371190', 81 | topic: 'Announcements', 82 | ), 83 | ShowcaseAppModel.withNetworkAsset( 84 | name: 'Dynamex', 85 | image: 'https://play-lh.googleusercontent.com/bGC6p_dqbxHI5NNVRvxtoK-7gBXWTKPu_M9_3DUDQDyKdlM7c8pa2a3lC_LuJSEE3oE=w2880-h1640', 86 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.dynamex.app', 87 | appStoreURL: 'https://apps.apple.com/us/app/dynamex/id1559258269', 88 | topic: 'Cargo', 89 | ), 90 | ShowcaseAppModel.withNetworkAsset( 91 | name: 'wibty', 92 | image: 'https://porelarte.tech/kamranbekirovcom/apps/wibty-min.png', 93 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.wibty.wibty', 94 | appStoreURL: 'https://apps.apple.com/az/app/wibty/id1568298650', 95 | topic: 'Social Media & Music', 96 | ), 97 | ShowcaseAppModel.withNetworkAsset( 98 | name: 'Eyol', 99 | image: 'https://play-lh.googleusercontent.com/p4_q8cI6QSi-6iRrhYI5xOUru_abYX-CjEYZHsjaZW1Uz0sr3kAv6YxrJ2I2idKVzFM=w2880-h1640', 100 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.eyol.app', 101 | appStoreURL: 'https://apps.apple.com/us/app/eyol/id1584765283', 102 | topic: 'Cargo', 103 | ), 104 | ShowcaseAppModel.withNetworkAsset( 105 | name: 'CASHIM', 106 | image: 'https://play-lh.googleusercontent.com/lgGaIiZMigLQ2GMdkGUDABPGuOum9PKHgm7VH3oJcfNBKKv3Fj9kxB5-60UR3-a5dzc=w5120-h2880-rw', 107 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.cashim.app', 108 | appStoreURL: 'https://apps.apple.com/us/app/cashim/id1621054850', 109 | topic: 'Cashback', 110 | ), 111 | ShowcaseAppModel.withNetworkAsset( 112 | name: 'DoctOnline', 113 | image: 'https://porelarte.tech/kamranbekirovcom/apps/doctonline-min.png', 114 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.doctazer.flutterAndroid', 115 | appStoreURL: 'https://apps.apple.com/az/app/doctonline/id1487301839', 116 | topic: 'Medic Assistant', 117 | ), 118 | ShowcaseAppModel.withNetworkAsset( 119 | name: 'Zamex', 120 | image: 'https://play-lh.googleusercontent.com/HU5C9c7N8dHE2MTDd90c_Pok3J3Ly4svwZaGtV8G3WHwMrhJROVB-8GFpNXPMJKQY4fL=w2880-h1640', 121 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.zamex.app', 122 | appStoreURL: 'https://apps.apple.com/us/app/zamex/id1568168785', 123 | topic: 'Cargo', 124 | ), 125 | ShowcaseAppModel.withNetworkAsset( 126 | name: 'Ormado Cashbook', 127 | image: 'https://porelarte.tech/kamranbekirovcom/apps/ormado-cashbook-min.png', 128 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.ormado.app', 129 | appStoreURL: 'https://apps.apple.com/kw/app/ormado-cashbook/id1529717238', 130 | topic: 'Cash Management', 131 | ), 132 | ShowcaseAppModel.withNetworkAsset( 133 | name: 'Azex', 134 | image: 'https://play-lh.googleusercontent.com/Qs1kNkhB8Wdm44zNASJiguxFaITIiikmTs8A9Hx0c7GIAmrm01M84mutRyaLpLoa9BrB=w2880-h1640', 135 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.azex.app', 136 | appStoreURL: 'https://apps.apple.com/us/app/azex-karqo/id1559676955', 137 | topic: 'Cargo', 138 | ), 139 | ShowcaseAppModel.withNetworkAsset( 140 | name: 'ManatX', 141 | image: 'https://porelarte.tech/kamranbekirovcom/apps/manatx-min.png', 142 | githubURL: 'https://github.com/kamranbekirovyz/manatx-currency-converter', 143 | topic: 'Currency Converter', 144 | ), 145 | ShowcaseAppModel.withNetworkAsset( 146 | name: 'Morex', 147 | image: 'https://play-lh.googleusercontent.com/nGqy-QBmLaVsbC_YXNT_u8sd2U92mSgxueQHTYdv3g4vVTMj-GvsbfCpkSWuc62LHKA=w3024-h1730-rw', 148 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.morex.app', 149 | appStoreURL: 'https://apps.apple.com/us/app/morex/id1605376153', 150 | topic: 'Cargo', 151 | ), 152 | ShowcaseAppModel.withNetworkAsset( 153 | name: 'In-App Help Chatbot', 154 | image: 'https://porelarte.tech/kamranbekirovcom/apps/cib-chatbot-min.png', 155 | topic: 'Automated Chatbot', 156 | ), 157 | ShowcaseAppModel.withNetworkAsset( 158 | name: 'Glob', 159 | image: 'https://play-lh.googleusercontent.com/LKxlmFJQ3k2jW3eZRbZKZ6Z0xhCctll1P-ZYyAlTSycttSmng_gmBknYLfoSr7QcdHIN=w3024-h1730-rw', 160 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.glob.app', 161 | appStoreURL: 'https://apps.apple.com/us/app/glob/id1572865371', 162 | topic: 'Cargo', 163 | ), 164 | ShowcaseAppModel.withNetworkAsset( 165 | name: 'Warehouse Management', 166 | image: 'https://porelarte.tech/kamranbekirovcom/apps/zamex-station-min.png', 167 | topic: 'Warehouse Utility', 168 | ), 169 | ShowcaseAppModel.withNetworkAsset( 170 | name: 'Vizz', 171 | image: 'https://play-lh.googleusercontent.com/DbwKumksl4TzOCi3cXXB7KSnRksmNQ4FW0lC1fsn32ZSMQmxtALtwTjc60ubPhAFK1c=w3360-h1940', 172 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.vizz.vizz', 173 | appStoreURL: 'https://apps.apple.com/az/app/vizz/id1558819171', 174 | topic: 'Car Wash', 175 | ), 176 | ShowcaseAppModel.withNetworkAsset( 177 | name: 'Teammers', 178 | image: 'https://porelarte.tech/kamranbekirovcom/apps/tentony-min.png', 179 | topic: 'Jobfinder', 180 | ), 181 | ShowcaseAppModel.withNetworkAsset( 182 | name: 'Trendex', 183 | image: 'https://play-lh.googleusercontent.com/0K7eTRgeECyz3SNSLa0_5qE_wiMCdqccMDInfvRjkFAtkQClBm1mc8lsAvIrQstWbnQ=w2880-h1640', 184 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.trendex.app', 185 | appStoreURL: 'https://apps.apple.com/us/app/trendex-karqo/id1604065036', 186 | topic: 'Cargo', 187 | ), 188 | ShowcaseAppModel.withNetworkAsset( 189 | name: 'DentiStore', 190 | image: 'https://porelarte.tech/kamranbekirovcom/apps/dentistore-min.png', 191 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.abasoft.dentistore', 192 | appStoreURL: 'https://apps.apple.com/us/app/dentistore/id1576104680', 193 | topic: 'E-commerce', 194 | ), 195 | ShowcaseAppModel.withNetworkAsset( 196 | name: 'jetex', 197 | image: 'https://play-lh.googleusercontent.com/j4tsWZkn1f34bInvklE9c77E874gaPyM0qIf9iFggBH3BKT6oCip0tUwKizQTB0YfG7g=w3024-h1730-rw', 198 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.jetex.app', 199 | appStoreURL: 'https://apps.apple.com/us/app/jetex/id1611723529', 200 | topic: 'Cargo', 201 | ), 202 | ShowcaseAppModel.withNetworkAsset( 203 | name: 'SLOOOVA', 204 | image: 'https://porelarte.tech/kamranbekirovcom/apps/slooova-min.png', 205 | topic: 'Word Game', 206 | ), 207 | ShowcaseAppModel.withNetworkAsset( 208 | name: 'Bahar Karqo', 209 | image: 'https://play-lh.googleusercontent.com/IP4Ketx5uxaf4NrgdzrD_5SZELY1E3-JKWeukNNdHaqThVp76FPCIf-wfTLjssy1QQ=w5120-h2880-rw', 210 | playStoreURL: 'https://play.google.com/store/apps/details?id=az.baharkargo.app', 211 | appStoreURL: 'https://apps.apple.com/az/app/bahar-karqo/id1621054829', 212 | topic: 'Cargo', 213 | ), 214 | ShowcaseAppModel.withNetworkAsset( 215 | name: 'FLO Azerbaijan', 216 | image: 'https://play-lh.googleusercontent.com/0YFGTvYE-5qK7AC4fMjsPI7EZPGeE_y5kG0MxybYSvT5X8TH5Rob4K_adCteVPuFZBs=w3360-h1940', 217 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.onlinestore.flo', 218 | appStoreURL: 'https://apps.apple.com/az/app/flostore-az/id1514948886', 219 | topic: 'E-commerce', 220 | ), 221 | ShowcaseAppModel.withNetworkAsset( 222 | name: 'Lilac', 223 | image: 'https://play-lh.googleusercontent.com/pvvGGpEMR6LS95aFIyr4d_BK3yorFJD2WwQegICMJ0kdd48_u3Xn1ggzEP6XG-IJeBY=w3360-h1940', 224 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazec.onlinestore.lilac', 225 | appStoreURL: 'https://apps.apple.com/az/app/lilac-az/id1535781509', 226 | topic: 'E-commerce', 227 | ), 228 | ShowcaseAppModel.withNetworkAsset( 229 | name: 'BakuTransport', 230 | image: 'https://play-lh.googleusercontent.com/vnL2g2rQgjsSElaJSpKT8asCoPfgvgBrF8xjBUWcQbThvoIfQoK_nOIkYJnUXddR7bA=w3360-h1940', 231 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.azcode.bakutransport', 232 | topic: 'Bus Tracking', 233 | ), 234 | ShowcaseAppModel.withNetworkAsset( 235 | name: 'Rahat Kart', 236 | image: 'https://porelarte.tech/kamranbekirovcom/apps/rahat-min.png', 237 | playStoreURL: 'https://play.google.com/store/apps/details?id=frazex.com.inloya.rahat', 238 | appStoreURL: 'https://apps.apple.com/us/app/rahat-kart/id1478512091', 239 | topic: 'Grocery store', 240 | ), 241 | ShowcaseAppModel.withNetworkAsset( 242 | name: 'Tezibu Delivery', 243 | image: 'https://porelarte.tech/kamranbekirovcom/apps/tezibu-client-min.png', 244 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.a7575.tezibu.client', 245 | appStoreURL: 'https://apps.apple.com/az/app/tezibu/id1518022392', 246 | topic: 'Food Delivery', 247 | ), 248 | ShowcaseAppModel.withNetworkAsset( 249 | name: 'Tezibu Partner', 250 | image: 'https://porelarte.tech/kamranbekirovcom/apps/tezibu-partner-min.png', 251 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.tezibu.partner', 252 | appStoreURL: 'https://apps.apple.com/az/app/tezibu-partner/id1516765771', 253 | topic: 'Food Delivery', 254 | ), 255 | ShowcaseAppModel.withNetworkAsset( 256 | name: 'Tezibu Courier', 257 | image: 'https://porelarte.tech/kamranbekirovcom/apps/tezibu-courier-min.png', 258 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.u7575.tezibu.courier', 259 | appStoreURL: 'https://apps.apple.com/az/app/tezibu-courier/id1517005869', 260 | topic: 'Food Delivery', 261 | ), 262 | ShowcaseAppModel.withNetworkAsset( 263 | name: 'GAGA Beauty Factory', 264 | image: 'https://play-lh.googleusercontent.com/Nh5Q8ZRElGmDsjM47heZfQesRt4estpTds0b6-Xklb3xJtiGV8mxll2hqnd-ft5q6b8=w3360-h1940', 265 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.loyalty.gagabf', 266 | appStoreURL: 'https://apps.apple.com/az/app/gaga-beauty/id1521427476', 267 | topic: 'Customer Loyalty', 268 | ), 269 | ShowcaseAppModel.withNetworkAsset( 270 | name: 'Unity Food', 271 | image: 'https://play-lh.googleusercontent.com/hjVGBNzSFeaZZvYcRHe5jFcBKirdOx_tGmRWRzOZYsdxMKQD_qzjSPdhdCLi4ZkadqI=w3360-h1940', 272 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.onlinestore.neptunexpress', 273 | appStoreURL: 'https://apps.apple.com/az/app/unity-food/id1539019198', 274 | topic: 'E-commerce', 275 | ), 276 | ShowcaseAppModel.withNetworkAsset( 277 | name: 'Nata Studio', 278 | image: 'https://porelarte.tech/kamranbekirovcom/apps/nata-studio-min.png', 279 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.loyalty.natastudio', 280 | appStoreURL: 'https://apps.apple.com/az/app/nata-studio/id1535811378', 281 | topic: 'Customer Loyalty', 282 | ), 283 | ShowcaseAppModel.withNetworkAsset( 284 | name: 'BIG CHEFS', 285 | image: 'https://play-lh.googleusercontent.com/FKHFiTqsZN0czAbEqbDMHHBeCPwMyE5UG2OM21C3Cr97HDxFhLlVaZIehw3Z5QSuJMc=w3360-h1940', 286 | playStoreURL: 'https://play.google.com/store/apps/details?id=frazex.com.inloya.bigchefs', 287 | appStoreURL: 'https://apps.apple.com/az/app/bigchefs-az/id1517277535', 288 | topic: 'Customer Loyalty', 289 | ), 290 | ShowcaseAppModel.withNetworkAsset( 291 | name: 'Music Downloader & Player', 292 | image: 'https://porelarte.tech/kamranbekirovcom/apps/docswis-min.png', 293 | topic: 'Music Downloader', 294 | ), 295 | ShowcaseAppModel.withNetworkAsset( 296 | name: 'StatsUpp - WhatsApp Status Downloader', 297 | image: 'https://porelarte.tech/kamranbekirovcom/apps/statsupp-min.png', 298 | topic: 'WhatsApp Utility', 299 | ), 300 | ShowcaseAppModel.withNetworkAsset( 301 | name: 'LePlaisire', 302 | image: 'https://porelarte.tech/kamranbekirovcom/apps/le-plaisire-min.png', 303 | playStoreURL: 'https://play.google.com/store/apps/details?id=frazex.com.inloya.leplaisir', 304 | appStoreURL: 'https://apps.apple.com/az/app/le-plaisir/id1517308551', 305 | topic: 'Customer Loyalty', 306 | ), 307 | ShowcaseAppModel.withNetworkAsset( 308 | name: 'Gunka Beauty House', 309 | image: 'https://porelarte.tech/kamranbekirovcom/apps/gunka-beauty-house-min.png', 310 | playStoreURL: 'https://play.google.com/store/apps/details?id=frazex.com.inloya.paintnail', 311 | appStoreURL: 'https://apps.apple.com/az/app/gunka-beauty-house/id1518607512', 312 | topic: 'Customer Loyalty', 313 | ), 314 | ShowcaseAppModel.withNetworkAsset( 315 | name: 'Polymart', 316 | image: 'https://play-lh.googleusercontent.com/wisTAwjkzedaLcvwLPA-vsrdzoA10jX4Cq0-8pb6LHejnv5iveBZFbyTC4CyPzP2Vo_C=w3360-h1940', 317 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.onlinestore.polymart', 318 | appStoreURL: 'https://apps.apple.com/az/app/polymart/id1518597954', 319 | topic: 'E-commerce', 320 | ), 321 | ShowcaseAppModel.withNetworkAsset( 322 | name: 'Monteleone', 323 | image: 'https://play-lh.googleusercontent.com/NjllrmWrfATgfcvw3G5wIMIKpBV8Nim3rbnLFoT9ojpzZQhxUThyFVFtN-HnQKYAgT0=w3360-h1996', 324 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.loyalty.monteleone', 325 | appStoreURL: 'https://apps.apple.com/az/app/monteleone/id1521424575', 326 | topic: 'Customer Loyalty', 327 | ), 328 | ShowcaseAppModel.withNetworkAsset( 329 | name: 'Zoobastik', 330 | image: 'https://play-lh.googleusercontent.com/-qU7YDfc3CnQC3Hk5QNCU_fFnKvyozAvz3YljgxzlRnuSXNMglSnJqZQxSjuIq6xlNg=w3360-h1940', 331 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.onlinestore.zoobastik', 332 | appStoreURL: 'https://apps.apple.com/az/app/zoobastik/id1514949924', 333 | topic: 'E-commerce', 334 | ), 335 | ShowcaseAppModel.withNetworkAsset( 336 | name: 'NAMU Cosmetics', 337 | image: 'https://play-lh.googleusercontent.com/4Clyh5R1nWknklHMvpkGO6lSTZ3wXZDn_jdXuIVdHkuCZ6OdLK6577zfZsjr46HnkK4=w3360-h1940', 338 | playStoreURL: 'https://play.google.com/store/apps/details?id=com.frazex.onlinestore.namu', 339 | appStoreURL: 'https://apps.apple.com/az/app/namu-az/id1516466622', 340 | topic: 'Customer Loyalty', 341 | ), 342 | ]; 343 | --------------------------------------------------------------------------------