├── .gitignore ├── .metadata ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── gskinner │ │ │ │ ├── flutter_folio │ │ │ │ └── MainActivity.kt │ │ │ │ └── flutterfolio │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── network_security_config.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets ├── fonts │ ├── EmojiOneColor.otf │ ├── Fraunces_72pt-SemiBold.ttf │ ├── Raleway-Bold.ttf │ ├── Raleway-BoldItalic.ttf │ ├── Raleway-ExtraBold.ttf │ ├── Raleway-ExtraBoldItalic.ttf │ ├── Raleway-Italic.ttf │ ├── Raleway-Medium.ttf │ ├── Raleway-MediumItalic.ttf │ ├── Raleway-Regular.ttf │ └── scraps │ │ ├── AlfaSlabOne-Regular.ttf │ │ ├── Amiri-Regular.ttf │ │ ├── Caveat-Medium.ttf │ │ ├── Lato-Regular.ttf │ │ ├── Mali-Medium.ttf │ │ └── PathwayGothicOne-Regular.ttf └── images │ ├── emoji │ ├── beers.svg │ ├── checkmark.svg │ ├── confetti.svg │ ├── cool.svg │ ├── crying-face.svg │ ├── dizzy-face.svg │ ├── exclamation-question.svg │ ├── fire.svg │ ├── folded-hands.svg │ ├── heart-eyes.svg │ ├── hundred-points.svg │ ├── kissing-face.svg │ ├── location-pin.svg │ ├── musical-notes.svg │ ├── palms-up.svg │ ├── pile-of-poo.svg │ ├── red-heart.svg │ ├── shooting-star.svg │ ├── smiling-eyes.svg │ ├── sparkles.svg │ ├── squinting-face.svg │ ├── sunglasses-face.svg │ ├── tears-of-joy-face.svg │ └── warning-sign.svg │ ├── empty-background.png │ ├── empty_home_bg.png │ ├── icons │ ├── 2.0x │ │ ├── add-page.png │ │ ├── add.png │ │ ├── camera.png │ │ ├── emoji.png │ │ ├── github.png │ │ ├── image.png │ │ ├── link-out.png │ │ ├── microsite.png │ │ ├── move-forward.png │ │ ├── scraps.png │ │ ├── send-backward.png │ │ ├── share.png │ │ ├── star.png │ │ ├── text.png │ │ ├── toggle-carousel.png │ │ ├── toggle-list.png │ │ ├── trashcan.png │ │ └── view.png │ ├── 3.0x │ │ ├── add-page.png │ │ ├── add.png │ │ ├── camera.png │ │ ├── emoji.png │ │ ├── github.png │ │ ├── image.png │ │ ├── link-out.png │ │ ├── microsite.png │ │ ├── move-forward.png │ │ ├── scraps.png │ │ ├── send-backward.png │ │ ├── share.png │ │ ├── star.png │ │ ├── text.png │ │ ├── toggle-carousel.png │ │ ├── toggle-list.png │ │ ├── trashcan.png │ │ └── view.png │ ├── add-page.png │ ├── add.png │ ├── camera.png │ ├── emoji.png │ ├── github.png │ ├── image.png │ ├── link-out.png │ ├── move-forward.png │ ├── scraps.png │ ├── send-backward.png │ ├── share.png │ ├── star.png │ ├── text.png │ ├── toggle-carousel.png │ ├── toggle-list.png │ ├── trashcan.png │ ├── view.png │ └── website.png │ ├── landing_page │ ├── dashedLine-desktop.png │ ├── dashedLine-mobile.png │ ├── laptop.png │ ├── phone.png │ ├── tablet.png │ └── web.png │ ├── logos │ ├── 2.0x │ │ └── flutterfolio-logo.png │ └── flutterfolio-logo.png │ ├── orange.jpg │ └── profile.png ├── benchmark ├── .gitignore ├── README.md ├── app.dart └── app_benchmark.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── GoogleService-Info.plist ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── 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-83.5x83.5@2x.png │ │ ├── Contents.json │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── build │ ├── Runner.build │ └── Release-iphoneos │ │ └── Runner.build │ │ └── dgph │ └── XCBuildData │ ├── 0aad0ddea01bc8265bfcfad241336ce9-desc.xcbuild │ ├── 0aad0ddea01bc8265bfcfad241336ce9-manifest.xcbuild │ ├── 27425e4797678a5d73e74b82b71b1b00-desc.xcbuild │ ├── 27425e4797678a5d73e74b82b71b1b00-manifest.xcbuild │ ├── 3acdea3adc952cfbd8fb351a0572bba6-desc.xcbuild │ ├── 3acdea3adc952cfbd8fb351a0572bba6-manifest.xcbuild │ ├── 5966525ad93b4840a9524e3d2dc8d5be-desc.xcbuild │ ├── 5966525ad93b4840a9524e3d2dc8d5be-manifest.xcbuild │ ├── BuildDescriptionCacheIndex-622a40702ce4e765a0d75779c6de3e7a │ └── build.db ├── lib ├── _spikes │ ├── button_sheet.dart │ ├── firebase_realtime_db.dart │ ├── firedart_stream_test.dart │ ├── optimized_drag_stack │ │ ├── my_movable_box.dart │ │ └── optimized_drag_stack.dart │ ├── popup_menu_spike │ │ └── popup_panel_spike.dart │ └── tab_bug_repro.dart ├── _utils │ ├── build_utils.dart │ ├── color_utils.dart │ ├── context_utils.dart │ ├── data_utils.dart │ ├── device_info.dart │ ├── easy_notifier.dart │ ├── file_utils.dart │ ├── input_utils.dart │ ├── keyboard_utils.dart │ ├── list_utils.dart │ ├── logger.dart │ ├── native_window_utils │ │ ├── macos_window_utils.dart │ │ ├── titlebar_wrappers │ │ │ ├── linux_title_bar.dart │ │ │ ├── linux_title_bar_buttons.dart │ │ │ ├── linux_title_bar_icons.dart │ │ │ ├── macos_title_bar.dart │ │ │ └── windows_title_bar.dart │ │ ├── window_utils.dart │ │ ├── window_utils_native.dart │ │ └── window_utils_no_op.dart │ ├── notifications │ │ └── close_notification.dart │ ├── path_utils.dart │ ├── string_utils.dart │ ├── time_utils.dart │ ├── timed │ │ ├── cooldown.dart │ │ ├── debouncer.dart │ │ └── throttler.dart │ └── universal_file │ │ ├── io_file.dart │ │ ├── universal_file.dart │ │ ├── universal_file_locator.dart │ │ └── web_file.dart ├── _widgets │ ├── alignments.dart │ ├── animate_do_extensions.dart │ ├── animated │ │ ├── animated_fractional_offset.dart │ │ ├── animated_index_stack.dart │ │ ├── animated_offset.dart │ │ ├── animated_rotation.dart │ │ ├── animated_scale.dart │ │ ├── animated_shadow.dart │ │ ├── animated_size.dart │ │ ├── auto_fade.dart │ │ └── opening_card.dart │ ├── app_image.dart │ ├── clickable_extensions.dart │ ├── context_menu_overlay.dart │ ├── decorated_container.dart │ ├── flexibles │ │ ├── padded_flexibles.dart │ │ └── seperated_flexibles.dart │ ├── gradient_container.dart │ ├── listenable_builder.dart │ ├── measure_size.dart │ ├── mixins │ │ ├── animated_state_mixins.dart │ │ ├── focus_node_mixin.dart │ │ ├── loading_state_mixin.dart │ │ └── raw_keyboard_listener_mixin.dart │ ├── no_animation_page.dart │ ├── no_glow_scroll_behavior.dart │ ├── positioned_all.dart │ ├── rando_colored_box.dart │ ├── rotation_3d.dart.dart │ ├── rounded_card.dart │ └── sized_and_translated.dart ├── app_keys.dart ├── commands │ ├── app │ │ ├── authenticate_user_command.dart │ │ ├── bootstrap_command.dart │ │ ├── copy_share_link_command.dart │ │ ├── refresh_menubar_command.dart │ │ ├── save_image_to_disk_command.dart │ │ ├── save_window_size_command.dart │ │ ├── set_current_user_command.dart │ │ └── update_user_command.dart │ ├── books │ │ ├── create_folio_command.dart │ │ ├── create_page_command.dart │ │ ├── create_placed_scraps_command.dart │ │ ├── delete_book_command.dart │ │ ├── delete_page_command.dart │ │ ├── delete_page_scrap_command.dart │ │ ├── delete_scraps_command.dart │ │ ├── refresh_all_books_command.dart │ │ ├── refresh_current_book_command.dart │ │ ├── refresh_current_page_command.dart │ │ ├── set_current_book_command.dart │ │ ├── set_current_page_command.dart │ │ ├── shift_placed_scraps_sort_order_command.dart │ │ ├── update_book_command.dart │ │ ├── update_book_modified_command.dart │ │ ├── update_current_book_cover_photo_command.dart │ │ ├── update_page_command.dart │ │ ├── update_page_count_command.dart │ │ ├── update_placed_scrap_command.dart │ │ └── upload_image_scraps_command.dart │ ├── commands.dart │ └── pick_images_command.dart ├── core_packages.dart ├── data │ ├── app_user.dart │ ├── app_user.freezed.dart │ ├── app_user.g.dart │ ├── book_data.dart │ ├── book_data.freezed.dart │ └── book_data.g.dart ├── main.dart ├── main_app_scaffold.dart ├── models │ ├── app_model.dart │ └── books_model.dart ├── routing │ ├── app_link.dart │ ├── app_route_parser.dart │ └── app_router.dart ├── services │ ├── cloudinary │ │ └── cloud_storage_service.dart │ └── firebase │ │ ├── firebase_service.dart │ │ ├── firebase_service_firedart.dart │ │ └── firebase_service_native.dart ├── styled_widgets │ ├── app_icons.dart │ ├── app_logo_text.dart │ ├── book_cover_image.dart │ ├── buttons │ │ ├── raw_styled_btn.dart │ │ ├── styled_buttons.dart │ │ └── styled_share_btn.dart │ ├── circle_avatar.dart │ ├── context_menus │ │ ├── app_context_menu.dart │ │ ├── book_context_menu.dart │ │ ├── context_menu_widgets.dart │ │ ├── scrap_context_menu.dart │ │ └── styled_context_menu_overlay.dart │ ├── dialogs │ │ ├── base_dialog.dart │ │ ├── delete_dialog.dart │ │ └── edit_text_dialog.dart │ ├── emoji.dart │ ├── glass_cards.dart │ ├── inline_text_editor.dart │ ├── labeled_text_input.dart │ ├── material_icon.dart │ ├── shadowed_box.dart │ ├── styled_bottom_sheet.dart │ ├── styled_load_spinner.dart │ ├── styled_page_scaffold.dart │ ├── styled_scrollbar.dart │ ├── styled_spacers.dart │ ├── styled_spinner.dart │ ├── styled_toggle_switch.dart │ ├── styled_tooltip.dart │ ├── styled_widgets.dart │ ├── toaster.dart │ └── ui_text.dart ├── styles.dart ├── themes.dart └── views │ ├── app_title_bar │ ├── app_title_bar.dart │ ├── rounded_profile_button.dart │ └── touch_mode_toggle_btn.dart │ ├── auth_page │ ├── auth_form.dart │ ├── auth_page.dart │ └── device_screens.dart │ ├── editor_page │ ├── draggable_page_menu │ │ ├── draggable_page_menu.dart │ │ └── draggable_page_title_btn.dart │ ├── editor_page.dart │ ├── empty_editor_view.dart │ ├── networked_scrapboard.dart │ ├── panels │ │ ├── collapsible_info_panel.dart │ │ ├── collapsible_pages_panel.dart │ │ ├── collapsible_panels.dart │ │ ├── content_picker_emoji_panel.dart │ │ ├── content_picker_scraps_panel.dart │ │ ├── content_picker_tab_menu.dart │ │ └── simple_pages_menu.dart │ ├── placed_scrap_keyboard_listener.dart │ ├── placed_scrap_renderer.dart │ ├── scrap_popup_editor │ │ ├── animated_menu_panel.dart │ │ ├── scrap_popup_editor.dart │ │ ├── scrap_popup_panel_alignment.dart │ │ ├── scrap_popup_panel_button_strip.dart │ │ ├── scrap_popup_panel_color.dart │ │ ├── scrap_popup_panel_fonts.dart │ │ └── scrap_popup_panel_rotation.dart │ └── scrapboard │ │ ├── movable_scrap.dart │ │ ├── movable_scrap_selection_box.dart │ │ ├── scrap_data.dart │ │ └── scrapboard.dart │ ├── home_page │ ├── book_cover │ │ ├── book_cover.dart │ │ ├── book_cover_large.dart │ │ └── book_cover_small.dart │ ├── covers_flow_list.dart │ ├── covers_flow_list_mobile.dart │ ├── covers_sortable_list.dart │ ├── home_nav_bar.dart │ ├── home_nav_bar_mobile.dart │ └── home_page.dart │ ├── scrap_pile_picker │ ├── scrap_pile_picker.dart │ ├── scrap_pile_picker_title_bar.dart │ ├── scrap_pile_picker_view.dart │ └── selectable_scrap_btn.dart │ ├── splash_page.dart │ └── user_profile_card │ ├── user_profile_card.dart │ └── user_profile_form.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ ├── app_icon_64.png │ │ ├── folio-icon-1024.png │ │ ├── folio-icon-128.png │ │ ├── folio-icon-16.png │ │ ├── folio-icon-256.png │ │ ├── folio-icon-257.png │ │ ├── folio-icon-32.png │ │ ├── folio-icon-33.png │ │ ├── folio-icon-512.png │ │ ├── folio-icon-513.png │ │ └── folio-icon-64.png │ └── Contents.json │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── GoogleService-Info.plist │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── snap ├── gui │ ├── flutter-folio.desktop │ └── flutter-folio.png └── snapcraft.yaml ├── source-assets ├── app-flutter-folio-no-alpha.png ├── app-flutter-folio.png └── flutterfolio.dmgCanvas │ ├── Disk Image │ └── QuickLook │ └── Preview.jpg ├── web ├── favicon.ico ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ └── icon-1024.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── run_loop.cpp ├── run_loop.h ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.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 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /.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: 198df796aa80073ef22bdf249e614e2ff33c6895 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.lineLength": 120 3 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 gskinner.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - "**/*.g.dart" 6 | - "**/*.freezed.dart" 7 | language: 8 | strict-inference: true 9 | strong-mode: 10 | implicit-casts: false 11 | errors: 12 | missing_required_param: error 13 | linter: 14 | rules: 15 | avoid_print: false 16 | constant_identifier_names: false 17 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "150221469863", 4 | "project_id": "flutter-folio-demo", 5 | "storage_bucket": "flutter-folio-demo.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:150221469863:android:9144b399e40b6759d6c382", 11 | "android_client_info": { 12 | "package_name": "com.gskinner.flutterfolio" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "150221469863-0altlsfpu1cn9icosantcrefs27b6n6k.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyAYThw0BP9fTuvbAtmV2DMYeggQLeI7v7Q" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "150221469863-0altlsfpu1cn9icosantcrefs27b6n6k.apps.googleusercontent.com", 31 | "client_type": 3 32 | }, 33 | { 34 | "client_id": "150221469863-fricoi1555bg294u9j0i61tqfvpoo3th.apps.googleusercontent.com", 35 | "client_type": 2, 36 | "ios_info": { 37 | "bundle_id": "com.gskinner.flutterfolio" 38 | } 39 | } 40 | ] 41 | } 42 | } 43 | } 44 | ], 45 | "configuration_version": "1" 46 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/gskinner/flutter_folio/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gskinner.flutter_folio 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/gskinner/flutterfolio/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gskinner.flutterfolio 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.4' // Google Services plugin 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/fonts/EmojiOneColor.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/EmojiOneColor.otf -------------------------------------------------------------------------------- /assets/fonts/Fraunces_72pt-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Fraunces_72pt-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-ExtraBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Raleway-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/Raleway-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/scraps/AlfaSlabOne-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/scraps/AlfaSlabOne-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/scraps/Amiri-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/scraps/Amiri-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/scraps/Caveat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/scraps/Caveat-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/scraps/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/scraps/Lato-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/scraps/Mali-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/scraps/Mali-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/scraps/PathwayGothicOne-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/fonts/scraps/PathwayGothicOne-Regular.ttf -------------------------------------------------------------------------------- /assets/images/emoji/checkmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/cool.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/crying-face.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/dizzy-face.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/exclamation-question.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/fire.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/heart-eyes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/hundred-points.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/kissing-face.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/location-pin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/musical-notes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/pile-of-poo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/red-heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/shooting-star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/smiling-eyes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/sparkles.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/squinting-face.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/sunglasses-face.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/tears-of-joy-face.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/emoji/warning-sign.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/empty-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/empty-background.png -------------------------------------------------------------------------------- /assets/images/empty_home_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/empty_home_bg.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/add-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/add-page.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/add.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/camera.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/emoji.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/github.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/image.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/link-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/link-out.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/microsite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/microsite.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/move-forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/move-forward.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/scraps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/scraps.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/send-backward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/send-backward.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/share.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/star.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/text.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/toggle-carousel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/toggle-carousel.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/toggle-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/toggle-list.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/trashcan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/trashcan.png -------------------------------------------------------------------------------- /assets/images/icons/2.0x/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/2.0x/view.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/add-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/add-page.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/add.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/camera.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/emoji.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/github.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/image.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/link-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/link-out.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/microsite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/microsite.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/move-forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/move-forward.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/scraps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/scraps.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/send-backward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/send-backward.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/share.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/star.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/text.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/toggle-carousel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/toggle-carousel.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/toggle-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/toggle-list.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/trashcan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/trashcan.png -------------------------------------------------------------------------------- /assets/images/icons/3.0x/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/3.0x/view.png -------------------------------------------------------------------------------- /assets/images/icons/add-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/add-page.png -------------------------------------------------------------------------------- /assets/images/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/add.png -------------------------------------------------------------------------------- /assets/images/icons/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/camera.png -------------------------------------------------------------------------------- /assets/images/icons/emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/emoji.png -------------------------------------------------------------------------------- /assets/images/icons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/github.png -------------------------------------------------------------------------------- /assets/images/icons/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/image.png -------------------------------------------------------------------------------- /assets/images/icons/link-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/link-out.png -------------------------------------------------------------------------------- /assets/images/icons/move-forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/move-forward.png -------------------------------------------------------------------------------- /assets/images/icons/scraps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/scraps.png -------------------------------------------------------------------------------- /assets/images/icons/send-backward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/send-backward.png -------------------------------------------------------------------------------- /assets/images/icons/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/share.png -------------------------------------------------------------------------------- /assets/images/icons/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/star.png -------------------------------------------------------------------------------- /assets/images/icons/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/text.png -------------------------------------------------------------------------------- /assets/images/icons/toggle-carousel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/toggle-carousel.png -------------------------------------------------------------------------------- /assets/images/icons/toggle-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/toggle-list.png -------------------------------------------------------------------------------- /assets/images/icons/trashcan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/trashcan.png -------------------------------------------------------------------------------- /assets/images/icons/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/view.png -------------------------------------------------------------------------------- /assets/images/icons/website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/icons/website.png -------------------------------------------------------------------------------- /assets/images/landing_page/dashedLine-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/landing_page/dashedLine-desktop.png -------------------------------------------------------------------------------- /assets/images/landing_page/dashedLine-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/landing_page/dashedLine-mobile.png -------------------------------------------------------------------------------- /assets/images/landing_page/laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/landing_page/laptop.png -------------------------------------------------------------------------------- /assets/images/landing_page/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/landing_page/phone.png -------------------------------------------------------------------------------- /assets/images/landing_page/tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/landing_page/tablet.png -------------------------------------------------------------------------------- /assets/images/landing_page/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/landing_page/web.png -------------------------------------------------------------------------------- /assets/images/logos/2.0x/flutterfolio-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/logos/2.0x/flutterfolio-logo.png -------------------------------------------------------------------------------- /assets/images/logos/flutterfolio-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/logos/flutterfolio-logo.png -------------------------------------------------------------------------------- /assets/images/orange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/orange.jpg -------------------------------------------------------------------------------- /assets/images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/assets/images/profile.png -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | *.benchmark 2 | *.json 3 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Performance profiling 2 | 3 | Run the following to gather new benchmark data: 4 | 5 | ```text 6 | $ flutter drive --target=benchmark/app.dart --driver=benchmark/app_benchmark.dart --profile --endless-trace-buffer --purge-persistent-cache 7 | ``` 8 | 9 | You can do many runs one after another to minimize the effect of random noise 10 | in measurements. 11 | 12 | ```text 13 | for n in {1..10}; do echo "=== Run number ${n}/10 ==="; \ 14 | flutter drive \ 15 | --target=benchmark/app.dart \ 16 | --driver=benchmark/app_benchmark.dart \ 17 | --profile \ 18 | --endless-trace-buffer \ 19 | --purge-persistent-cache; \ 20 | done 21 | ``` 22 | 23 | (You can then combine the separate runs into one file using `benchmerge`.) 24 | 25 | Install Benchmarkhor (`pub global activate benchmarkhor`), then run: 26 | 27 | ```text 28 | $ benchextract benchmark/*.json 29 | ``` 30 | 31 | Finally, compare different benchmark runs with 32 | 33 | ```text 34 | $ benchcompare benchmark/some-baseline.benchmark benchmark/new.benchmark 35 | ``` 36 | -------------------------------------------------------------------------------- /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/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 3 | #include "Generated.xcconfig" 4 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 3 | #include "Generated.xcconfig" 4 | -------------------------------------------------------------------------------- /ios/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 150221469863-fricoi1555bg294u9j0i61tqfvpoo3th.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.150221469863-fricoi1555bg294u9j0i61tqfvpoo3th 9 | API_KEY 10 | AIzaSyDV0SE4WzZX--KRsdVZeS2pk8_81VrAJMk 11 | GCM_SENDER_ID 12 | 150221469863 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.gskinner.flutterfolio 17 | PROJECT_ID 18 | flutter-folio-demo 19 | STORAGE_BUCKET 20 | flutter-folio-demo.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:150221469863:ios:67573e189598eff0d6c382 33 | DATABASE_URL 34 | https://flutter-folio-demo-default-rtdb.firebaseio.com 35 | 36 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '10.3' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | target.build_configurations.each do |config| 41 | config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.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.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/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/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/build/Runner.build/Release-iphoneos/Runner.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Dec 21 202021:57:39/UsersshawnDevgskinner flutter-folioios -------------------------------------------------------------------------------- /ios/build/XCBuildData/0aad0ddea01bc8265bfcfad241336ce9-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/build/XCBuildData/0aad0ddea01bc8265bfcfad241336ce9-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/27425e4797678a5d73e74b82b71b1b00-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/build/XCBuildData/27425e4797678a5d73e74b82b71b1b00-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/3acdea3adc952cfbd8fb351a0572bba6-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/build/XCBuildData/3acdea3adc952cfbd8fb351a0572bba6-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/5966525ad93b4840a9524e3d2dc8d5be-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/build/XCBuildData/5966525ad93b4840a9524e3d2dc8d5be-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/BuildDescriptionCacheIndex-622a40702ce4e765a0d75779c6de3e7a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/build/XCBuildData/BuildDescriptionCacheIndex-622a40702ce4e765a0d75779c6de3e7a -------------------------------------------------------------------------------- /ios/build/XCBuildData/build.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/ios/build/XCBuildData/build.db -------------------------------------------------------------------------------- /lib/_spikes/tab_bug_repro.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TabBugRepro extends StatelessWidget { 4 | const TabBugRepro({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Column( 11 | children: [ 12 | TextButton(onPressed: () {}, child: const Text("btn")), 13 | Flexible( 14 | child: IndexedStack( 15 | index: 0, 16 | children: [ 17 | const SomeView(), 18 | ExcludeFocus( 19 | excluding: true, 20 | child: FocusTraversalGroup(child: const SomeView()), 21 | ) 22 | ], 23 | )), 24 | ], 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | 31 | class SomeView extends StatelessWidget { 32 | const SomeView({Key? key}) : super(key: key); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Row(children: [ 37 | TextButton(onPressed: () {}, child: const Text("FOO")), 38 | TextButton(onPressed: () {}, child: const Text("BAR")), 39 | ]); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/_utils/build_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BuildUtils { 4 | static void getFutureSizeFromGlobalKey(GlobalKey key, void Function(Size size) callback) { 5 | Future.microtask(() { 6 | Size? size = getSizeFromContext(key.currentContext); 7 | if (size != null) { 8 | callback(size); 9 | } 10 | }); 11 | } 12 | 13 | static Size? getSizeFromContext(BuildContext? context) { 14 | if (context == null) return null; 15 | RenderBox? rb = context.findRenderObject() as RenderBox?; 16 | return rb?.size; 17 | } 18 | 19 | static Offset? getOffsetFromContext(BuildContext? context, [Offset offset = Offset.zero]) { 20 | if (context == null) return null; 21 | RenderBox? rb = context.findRenderObject() as RenderBox?; 22 | return rb?.localToGlobal(offset); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/_utils/color_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class ColorUtils { 4 | static Color shiftHsl(Color c, [double amt = 0]) { 5 | HSLColor hslColor = HSLColor.fromColor(c); 6 | return hslColor.withLightness((hslColor.lightness + amt).clamp(0.0, 1.0).toDouble()).toColor(); 7 | } 8 | 9 | static Color parseHex(String value) => Color(int.parse(value.substring(1, 7), radix: 16) + 0xFF000000); 10 | 11 | static Color blend(Color dst, Color src, double opacity) { 12 | return Color.fromARGB( 13 | 255, 14 | (dst.red.toDouble() * (1.0 - opacity) + src.red.toDouble() * opacity).toInt(), 15 | (dst.green.toDouble() * (1.0 - opacity) + src.green.toDouble() * opacity).toInt(), 16 | (dst.blue.toDouble() * (1.0 - opacity) + src.blue.toDouble() * opacity).toInt(), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/_utils/context_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ContextUtils { 4 | // Utility methods to get the size/pos of our render boxes in global & local space 5 | static Size getSize(BuildContext c) { 6 | try { 7 | RenderBox? rb = c.findRenderObject() as RenderBox?; 8 | return rb?.size ?? Size.zero; 9 | } catch (e) { 10 | print(e); 11 | } 12 | return const Size(1, 1); 13 | } 14 | 15 | static Offset localToGlobal(BuildContext c, {Offset local = Offset.zero}) { 16 | try { 17 | return (c.findRenderObject() as RenderBox?)?.localToGlobal(local) ?? Offset.zero; 18 | } catch (e) { 19 | //print(e); 20 | } 21 | return Offset.zero; 22 | } 23 | 24 | static Offset globalToLocal(BuildContext c, Offset global) { 25 | try { 26 | return (c.findRenderObject() as RenderBox?)?.globalToLocal(global) ?? Offset.zero; 27 | } catch (e) { 28 | //print(e); 29 | } 30 | return Offset.zero; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/_utils/data_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/data/book_data.dart'; 2 | 3 | class _IndexPair { 4 | int index1, index2; 5 | _IndexPair(this.index1, this.index2); 6 | } 7 | 8 | class DataUtils { 9 | static List sortListById(List pages, List? pageIds) { 10 | if (pageIds?.isEmpty ?? true) return pages; 11 | List<_IndexPair> listIndices = []; 12 | List sortedList = []; 13 | for (int i = 0; i < pages.length; ++i) { 14 | int elementOrder = pageIds!.indexWhere((pageId) => pageId == pages[i].documentId); 15 | listIndices.add(_IndexPair(elementOrder != -1 ? elementOrder : 1000000000, i)); 16 | } 17 | listIndices.sort((a, b) => a.index1.compareTo(b.index1)); 18 | for (int i = 0; i < listIndices.length; ++i) { 19 | sortedList.add(pages[listIndices[i].index2]); 20 | } 21 | return sortedList; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/_utils/easy_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Has a notify() method that reduces boilerplate in ChangeNotifiers, similar to setState((){}) in a StatefulWidget; 4 | // Also allows external .notify() calls, being un-opinionated about whether this is called externally. 5 | class EasyNotifier extends ChangeNotifier { 6 | void notify([VoidCallback? action]) { 7 | action?.call(); 8 | notifyListeners(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/_utils/file_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class FileUtils { 4 | Future readAsString(String path) async { 5 | try { 6 | return await File(path).readAsString(); 7 | } catch (e) { 8 | print("$e"); 9 | } 10 | return null; 11 | } 12 | 13 | Future writeAsString(String path, String contents) async { 14 | try { 15 | await File(path).writeAsString(contents, flush: true); 16 | } catch (e) { 17 | print("$e"); 18 | } 19 | return; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/_utils/input_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | class InputUtils { 6 | static void hideKeyboard() { 7 | SystemChannels.textInput.invokeMethod('TextInput.hide'); 8 | } 9 | 10 | static bool get isMouseConnected => RendererBinding.instance?.mouseTracker.mouseIsConnected ?? false; 11 | 12 | static void unFocus() { 13 | primaryFocus?.unfocus(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/_utils/list_utils.dart: -------------------------------------------------------------------------------- 1 | class ListUtils { 2 | static List interleave(List listA, List listB) { 3 | List result = []; 4 | for (var i = 0; i < listA.length; i++) { 5 | result..add(listA[i])..add(listB[i]); 6 | } 7 | return result; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/_utils/logger.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | _Dispatcher logHistory = _Dispatcher(""); 7 | 8 | void log(String? value) { 9 | String v = value ?? ""; 10 | logHistory.value = v + "\n" + logHistory.value; 11 | if (kReleaseMode == false) { 12 | print(v); 13 | } 14 | } 15 | 16 | void logError(String? value) => log("[ERROR] " + (value ?? "")); 17 | 18 | // Take from: https://flutter.dev/docs/testing/errors 19 | void initLogger(VoidCallback runApp) { 20 | runZonedGuarded(() async { 21 | WidgetsFlutterBinding.ensureInitialized(); 22 | FlutterError.onError = (FlutterErrorDetails details) { 23 | FlutterError.dumpErrorToConsole(details); 24 | logError(details.stack.toString()); 25 | }; 26 | runApp.call(); 27 | }, (Object error, StackTrace stack) { 28 | logError(stack.toString()); 29 | }); 30 | } 31 | 32 | class _Dispatcher extends ValueNotifier { 33 | _Dispatcher(String value) : super(value); 34 | } 35 | -------------------------------------------------------------------------------- /lib/_utils/native_window_utils/macos_window_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | 4 | import 'package:flutter/services.dart'; 5 | 6 | class MacosWindowUtils { 7 | static const methodChannel = MethodChannel("flutterfolio.com/io"); 8 | static const kMinTitlebarHeight = 24.0; 9 | static const kDefaultTitlebarHeight = 24.0; 10 | static double _calculatedTitlebarHeight = 0.0; 11 | 12 | static Future requestTitlebarHeight() async { 13 | if (Platform.isMacOS == false) return kDefaultTitlebarHeight; 14 | if (_calculatedTitlebarHeight > 0.0) { 15 | return _calculatedTitlebarHeight; 16 | } 17 | try { 18 | final double h = await methodChannel.invokeMethod("getTitlebarHeight") as double; 19 | _calculatedTitlebarHeight = max(h, kMinTitlebarHeight); 20 | } catch (e) { 21 | print("MethodChannel error: $e"); 22 | _calculatedTitlebarHeight = kDefaultTitlebarHeight; 23 | } 24 | return _calculatedTitlebarHeight; 25 | } 26 | 27 | static void zoom() async { 28 | if (Platform.isMacOS == false) return; 29 | try { 30 | await methodChannel.invokeMethod("zoom"); 31 | } catch (e) { 32 | print("MethodChannel error: $e"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/_utils/native_window_utils/titlebar_wrappers/macos_title_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_utils/native_window_utils/macos_window_utils.dart'; 3 | 4 | class MacosTitleBar extends StatefulWidget { 5 | const MacosTitleBar(this.child, {Key? key}) : super(key: key); 6 | final Widget child; 7 | 8 | @override 9 | _MacosTitleBarState createState() => _MacosTitleBarState(); 10 | } 11 | 12 | class _MacosTitleBarState extends State { 13 | late Future _futureHeight; 14 | 15 | @override 16 | void initState() { 17 | _futureHeight = MacosWindowUtils.requestTitlebarHeight(); 18 | super.initState(); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | double defaultHeight = MacosWindowUtils.kDefaultTitlebarHeight; 24 | return FutureBuilder( 25 | // Request the transparent titlebar height from the OS (async) 26 | future: _futureHeight, 27 | builder: (BuildContext context, AsyncSnapshot snapshot) { 28 | bool isFutureDone = snapshot.connectionState == ConnectionState.done; 29 | double height = isFutureDone ? snapshot.data ?? defaultHeight : defaultHeight; 30 | return GestureDetector( 31 | onDoubleTap: () => MacosWindowUtils.zoom(), 32 | child: Container(color: Colors.transparent, height: height, child: widget.child), 33 | ); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/_utils/native_window_utils/titlebar_wrappers/windows_title_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:bitsdojo_window/bitsdojo_window.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// Native TitleBar for Windows, uses BitDojo platform 5 | class WindowsTitleBar extends StatelessWidget { 6 | const WindowsTitleBar(this.child, {Key? key}) : super(key: key); 7 | final Widget child; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final WindowButtonColors _btnColors = WindowButtonColors( 12 | iconNormal: Colors.black, 13 | mouseOver: Colors.black.withOpacity(.2), 14 | mouseDown: Colors.black.withOpacity(.3), 15 | normal: Colors.black.withOpacity(0), 16 | ); 17 | return SizedBox( 18 | height: 40, 19 | child: Stack( 20 | children: [ 21 | MoveWindow(), 22 | child, 23 | Align( 24 | alignment: Alignment.topRight, 25 | child: Row( 26 | mainAxisAlignment: MainAxisAlignment.end, 27 | children: [ 28 | MinimizeWindowButton(colors: _btnColors), 29 | MaximizeWindowButton(colors: _btnColors), 30 | CloseWindowButton(), 31 | ], 32 | ), 33 | ), 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/_utils/native_window_utils/window_utils.dart: -------------------------------------------------------------------------------- 1 | library native_utils; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'window_utils_no_op.dart' if (dart.library.io) 'window_utils_native.dart' as pkg; 6 | 7 | abstract class IoUtils { 8 | static IoUtils get instance => pkg.getInstance(); 9 | 10 | void setTitle(String title); 11 | void showWindowWhenReady(); 12 | Widget wrapNativeTitleBarIfRequired(Widget child); 13 | } 14 | -------------------------------------------------------------------------------- /lib/_utils/native_window_utils/window_utils_native.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bitsdojo_window/bitsdojo_window.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_folio/_utils/device_info.dart'; 7 | import 'package:flutter_folio/_utils/native_window_utils/titlebar_wrappers/linux_title_bar.dart'; 8 | import 'package:flutter_folio/_utils/native_window_utils/titlebar_wrappers/macos_title_bar.dart'; 9 | import 'package:flutter_folio/_utils/native_window_utils/titlebar_wrappers/windows_title_bar.dart'; 10 | import 'package:flutter_folio/_utils/native_window_utils/window_utils.dart'; 11 | 12 | IoUtils _instance = IoUtilsNative(); 13 | IoUtils getInstance() => _instance; 14 | 15 | class IoUtilsNative implements IoUtils { 16 | IoUtilsNative(); 17 | 18 | @override 19 | void showWindowWhenReady() { 20 | if (Platform.isWindows == false) return; 21 | doWhenWindowReady(() { 22 | // Apply min-window size, allow a smaller size when developing for easier responsive testing. 23 | appWindow.minSize = kReleaseMode ? const Size(800, 600) : const Size(300, 400); 24 | appWindow.show(); 25 | }); 26 | } 27 | 28 | @override 29 | Widget wrapNativeTitleBarIfRequired(Widget child) { 30 | if (DeviceOS.isWindows) { 31 | return WindowsTitleBar(child); 32 | } else if (DeviceOS.isLinux) { 33 | return LinuxTitleBar(child); 34 | } else if (DeviceOS.isMacOS) { 35 | return MacosTitleBar(child); 36 | } 37 | return child; 38 | } 39 | 40 | @override 41 | void setTitle(String title) { 42 | appWindow.title = title; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/_utils/native_window_utils/window_utils_no_op.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'window_utils.dart'; 4 | 5 | IoUtils _instance = IoUtilsNoOp(); 6 | IoUtils getInstance() => _instance; 7 | 8 | class IoUtilsNoOp implements IoUtils { 9 | @override 10 | void showWindowWhenReady() {} 11 | @override 12 | Widget wrapNativeTitleBarIfRequired(Widget child) => child; 13 | void setMinSize(Size size) {} 14 | 15 | @override 16 | void setTitle(String title) {} 17 | } 18 | -------------------------------------------------------------------------------- /lib/_utils/notifications/close_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CloseNotification extends Notification {} 4 | -------------------------------------------------------------------------------- /lib/_utils/path_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path_provider/path_provider.dart'; 4 | import 'package:xdg_directories/xdg_directories.dart' as xdg_directories; 5 | 6 | class PathUtil { 7 | static Future get dataPath async { 8 | String? result; 9 | if (Platform.isLinux) { 10 | result = "${xdg_directories.dataHome.path}/flutterfolio"; 11 | } else { 12 | try { 13 | return (await getApplicationSupportDirectory()).path; 14 | } catch (e) { 15 | print("$e"); 16 | } 17 | } 18 | return result; 19 | } 20 | 21 | static Future get homePath async { 22 | return "~/"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/_utils/time_utils.dart: -------------------------------------------------------------------------------- 1 | class TimeUtils { 2 | static int get nowMillis => DateTime.now().millisecondsSinceEpoch; 3 | static int get nowSeconds => (nowMillis * .001).round(); 4 | } 5 | -------------------------------------------------------------------------------- /lib/_utils/timed/cooldown.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CoolDown { 4 | int _lastCallMs = 0; 5 | 6 | CoolDown(this.duration); 7 | final Duration duration; 8 | 9 | void run(VoidCallback action) { 10 | if (DateTime.now().millisecondsSinceEpoch - _lastCallMs > duration.inMilliseconds) { 11 | action.call(); 12 | _lastCallMs = DateTime.now().millisecondsSinceEpoch; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/_utils/timed/debouncer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class Debouncer { 6 | Debouncer(this.duration); 7 | Duration duration; 8 | Timer? _timer; 9 | 10 | void run(VoidCallback action) { 11 | _timer?.cancel(); 12 | _timer = Timer(duration, action); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/_utils/timed/throttler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class Throttler { 6 | Throttler(this.interval); 7 | final Duration interval; 8 | 9 | VoidCallback? _action; 10 | Timer? _timer; 11 | 12 | void call(VoidCallback action, {bool immediateCall = false}) { 13 | _action = action; 14 | if (_timer == null) { 15 | // If no timer is running, and immediateCall is true, just handle the action now 16 | if (immediateCall) { 17 | _action?.call(); 18 | _action = null; // Set it to null since we've already handled it 19 | } 20 | _timer = Timer(interval, () { 21 | _action?.call(); 22 | _timer = null; 23 | }); 24 | } 25 | } 26 | 27 | void cancelPending() => _action = null; 28 | } 29 | -------------------------------------------------------------------------------- /lib/_utils/universal_file/universal_file.dart: -------------------------------------------------------------------------------- 1 | //If in web, this class will write a string to the prefs file, using filename as key 2 | //If on desktop or mobile, write to the appData folder 3 | 4 | import 'universal_file_locator.dart' if (dart.library.html) 'web_file.dart' if (dart.library.io) 'io_file.dart'; 5 | 6 | abstract class UniversalFile { 7 | late final String fileName; 8 | 9 | Future write(String value, [bool append = false]); 10 | 11 | Future read(); 12 | 13 | factory UniversalFile(String fileName) => getPlatformFileWriter(fileName); 14 | } 15 | -------------------------------------------------------------------------------- /lib/_utils/universal_file/universal_file_locator.dart: -------------------------------------------------------------------------------- 1 | import 'universal_file.dart'; 2 | 3 | UniversalFile getPlatformFileWriter(String fileName) => 4 | throw UnsupportedError('Cannot create a fileWriter for "$fileName" without the packages dart:html or dart:io'); 5 | -------------------------------------------------------------------------------- /lib/_utils/universal_file/web_file.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | import 'universal_file.dart'; 4 | 5 | class WebFileWriter implements UniversalFile { 6 | SharedPreferences? prefs; 7 | 8 | @override 9 | String fileName; 10 | 11 | String _lastWrite = ""; 12 | 13 | WebFileWriter(this.fileName); 14 | 15 | Future initPrefs() async { 16 | prefs ??= await SharedPreferences.getInstance(); 17 | } 18 | 19 | @override 20 | Future read() async { 21 | await initPrefs(); 22 | String? value = prefs?.getString(fileName); 23 | //print("Reading pref: $fileName = $value"); 24 | return value; 25 | } 26 | 27 | @override 28 | Future write(String value, [bool append = false]) async { 29 | await initPrefs(); 30 | if (append) { 31 | _lastWrite = await read() ?? ""; 32 | value = _lastWrite + value; 33 | } 34 | //print("Write: $fileName = $value"); 35 | _lastWrite = value; 36 | await prefs?.setString(fileName, value); 37 | } 38 | } 39 | 40 | UniversalFile getPlatformFileWriter(String fileName) => WebFileWriter(fileName); 41 | -------------------------------------------------------------------------------- /lib/_widgets/animate_do_extensions.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/_widgets/animated/animated_fractional_offset.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimatedFractionalOffset extends StatelessWidget { 4 | const AnimatedFractionalOffset({ 5 | required this.child, 6 | required this.duration, 7 | this.begin, 8 | required this.end, 9 | this.curve = Curves.easeOut, 10 | Key? key, 11 | }) : super(key: key); 12 | final Widget child; 13 | final Duration duration; 14 | final Offset? begin; 15 | final Offset end; 16 | final Curve curve; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return TweenAnimationBuilder( 21 | duration: duration, 22 | curve: curve, 23 | tween: Tween(begin: begin ?? end, end: end), 24 | builder: (context, offset, _) => FractionalTranslation( 25 | translation: offset, 26 | child: child, 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/_widgets/animated/animated_index_stack.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimatedIndexStack extends StatefulWidget { 4 | final int index; 5 | final List children; 6 | final Duration duration; 7 | 8 | const AnimatedIndexStack({ 9 | Key? key, 10 | required this.index, 11 | required this.children, 12 | this.duration = const Duration(milliseconds: 250), 13 | }) : super(key: key); 14 | 15 | @override 16 | _AnimatedIndexStackState createState() => _AnimatedIndexStackState(); 17 | } 18 | 19 | class _AnimatedIndexStackState extends State with SingleTickerProviderStateMixin { 20 | late AnimationController _controller; 21 | 22 | @override 23 | void initState() { 24 | _controller = AnimationController(vsync: this, duration: widget.duration); 25 | _controller.forward(); 26 | super.initState(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | _controller.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | @override 36 | void didUpdateWidget(AnimatedIndexStack oldWidget) { 37 | if (widget.index != oldWidget.index) { 38 | _controller.forward(from: 0.0); 39 | } 40 | super.didUpdateWidget(oldWidget); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return FadeTransition( 46 | opacity: _controller, 47 | child: IndexedStack(index: widget.index, children: widget.children), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/_widgets/animated/animated_offset.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_widgets/alignments.dart'; 3 | 4 | class AnimatedOffset extends StatelessWidget { 5 | const AnimatedOffset({ 6 | required this.child, 7 | required this.duration, 8 | this.begin, 9 | required this.end, 10 | this.curve = Curves.easeOut, 11 | Key? key, 12 | }) : super(key: key); 13 | final Widget child; 14 | final Duration duration; 15 | final Offset? begin; 16 | final Offset end; 17 | final Curve curve; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return TweenAnimationBuilder( 22 | duration: duration, 23 | curve: curve, 24 | tween: Tween(begin: begin ?? end, end: end), 25 | builder: (context, offset, _) => Transform.translate( 26 | offset: offset, 27 | child: TopLeft(child: child), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/_widgets/animated/animated_rotation.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class AnimatedRotation extends StatelessWidget { 6 | const AnimatedRotation({ 7 | this.begin, 8 | required this.end, 9 | required this.duration, 10 | required this.child, 11 | this.curve = Curves.linear, 12 | Key? key, 13 | }) : super(key: key); 14 | final Widget child; 15 | final Duration duration; 16 | final double? begin; 17 | final double end; 18 | final Curve curve; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return TweenAnimationBuilder( 23 | duration: duration, 24 | curve: curve, 25 | tween: Tween(begin: begin ?? end, end: end), 26 | builder: (context, tweenValue, _) { 27 | // Convert degrees to rads 28 | return Transform.rotate(angle: (tweenValue * pi) / 180, child: child); 29 | }, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/_widgets/animated/animated_scale.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimatedScale extends StatelessWidget { 4 | const AnimatedScale( 5 | {Key? key, required this.child, required this.end, required this.duration, this.begin, this.curve}) 6 | : super(key: key); 7 | final Widget child; 8 | final Duration duration; 9 | final double? begin; 10 | final double end; 11 | final Curve? curve; 12 | 13 | @override 14 | Widget build(BuildContext context) => TweenAnimationBuilder( 15 | tween: Tween(begin: begin ?? .2, end: end), 16 | curve: curve ?? Curves.easeOut, 17 | duration: duration, 18 | child: child, 19 | builder: (_, value, cachedChild) { 20 | return Transform.scale(scale: value, child: cachedChild); 21 | }, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/_widgets/animated/animated_shadow.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class AnimatedShadow extends StatelessWidget { 6 | AnimatedShadow({ 7 | Key? key, 8 | required this.child, 9 | required this.duration, 10 | required this.blurs, 11 | required this.colors, 12 | this.begin, 13 | required this.end, 14 | this.curve, 15 | }) : super(key: key) { 16 | assert(blurs.length == colors.length, "blurs.length and colors.length must match"); 17 | } 18 | 19 | final Widget child; 20 | final Duration duration; 21 | final double? begin; 22 | final double end; 23 | final List blurs; 24 | final List colors; 25 | final Curve? curve; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return TweenAnimationBuilder( 30 | tween: Tween(begin: begin ?? end, end: end), 31 | curve: curve ?? Curves.easeOut, 32 | builder: (_, double value, _child) { 33 | return Container( 34 | decoration: BoxDecoration(boxShadow: [ 35 | ...blurs.map((b) { 36 | Color c = colors[blurs.indexOf(b)]; 37 | // Like a real shadow, it blurs more when raised, but the strength actually goes down 38 | return BoxShadow( 39 | blurRadius: max(0, b * value), 40 | color: c.withOpacity(value < .1 ? 0 : max(0, 1 - (value * (1 - c.opacity)))), 41 | ); 42 | }) 43 | ]), 44 | child: _child, 45 | ); 46 | }, 47 | child: child, 48 | duration: duration, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/_widgets/app_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | // Simple wrapper around CachedNetworkImage that provides any boilerplate we need for images in the app 5 | class HostedImage extends StatelessWidget { 6 | const HostedImage(this.url, {Key? key, this.fit = BoxFit.cover}) : super(key: key); 7 | final String url; 8 | final BoxFit fit; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | String secureUrl = url; 13 | if (url.contains("http://")) { 14 | secureUrl = secureUrl.replaceAll("http://", "https://"); 15 | } 16 | return CachedNetworkImage(imageUrl: secureUrl, fit: fit); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/_widgets/clickable_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | extension ClickableExtensions on Widget { 4 | Widget clickable(void Function() action, {bool opaque = true}) { 5 | return GestureDetector( 6 | behavior: opaque ? HitTestBehavior.opaque : HitTestBehavior.deferToChild, 7 | onTap: action, 8 | child: MouseRegion( 9 | cursor: SystemMouseCursors.click, 10 | opaque: opaque, 11 | child: this, 12 | ), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/_widgets/listenable_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Alternative to [AnimatedBuilder], same functionality but it reads better and follows the other builders (ValueListenableBuilder). 4 | class ListenableBuilder extends AnimatedWidget { 5 | const ListenableBuilder({Key? key, this.child, required Listenable listenable, required this.builder}) 6 | : super(key: key, listenable: listenable); 7 | 8 | final Widget Function(BuildContext context, Widget? child) builder; 9 | final Widget? child; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return builder(context, child); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/_widgets/measure_size.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | class MeasureSizeRenderObject extends RenderProxyBox { 5 | MeasureSizeRenderObject(this.onChange); 6 | void Function(Size size) onChange; 7 | 8 | Size? _prevSize; 9 | @override 10 | void performLayout() { 11 | super.performLayout(); 12 | Size newSize = child?.size ?? Size.zero; 13 | if (_prevSize == newSize) return; 14 | _prevSize = newSize; 15 | WidgetsBinding.instance?.addPostFrameCallback((_) => onChange(newSize)); 16 | } 17 | } 18 | 19 | class MeasureSize extends SingleChildRenderObjectWidget { 20 | const MeasureSize({Key? key, required this.onChange, required Widget child}) : super(key: key, child: child); 21 | final void Function(Size size) onChange; 22 | @override 23 | RenderObject createRenderObject(BuildContext context) => MeasureSizeRenderObject(onChange); 24 | } 25 | -------------------------------------------------------------------------------- /lib/_widgets/mixins/focus_node_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | mixin SingleFocusNodeMixin on State { 4 | late FocusNode focusNode; 5 | 6 | @override 7 | void initState() { 8 | super.initState(); 9 | focusNode = FocusNode(); 10 | focusNode.addListener(() => setState(() {})); 11 | } 12 | 13 | @override 14 | void dispose() { 15 | focusNode.dispose(); 16 | super.dispose(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/_widgets/mixins/loading_state_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Provides the common functionalist of having a isLoading toggle for a view, that is turned on while loading some content. 4 | mixin LoadingStateMixin on State { 5 | bool _isLoading = false; 6 | 7 | bool get isLoading => _isLoading; 8 | 9 | set isLoading(bool isLoading) { 10 | if (!mounted) return; 11 | setState(() => _isLoading = isLoading); 12 | } 13 | 14 | Future load(Future Function() action) async { 15 | isLoading = true; 16 | R result = await action(); 17 | isLoading = false; 18 | return result; 19 | } 20 | } 21 | /* 22 | class LoadingStateMixinExample extends StatefulWidget { 23 | @override 24 | _LoadingStateMixinExampleState createState() => _LoadingStateMixinExampleState(); 25 | } 26 | 27 | class _LoadingStateMixinExampleState extends State with LoadingStateMixin { 28 | 29 | void onPress() async { 30 | bool result = await load(doStuffAndReturnBool); 31 | await load(() => doStuffAndTakeBool(false)); 32 | } 33 | 34 | Future doStuffAndReturnBool() async => true; 35 | Future doStuffAndTakeBool(bool value) async => value; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Scaffold( 40 | body: isLoading 41 | // Loading State 42 | ? Text("Loading...") 43 | // Main Tree 44 | : Column( 45 | children: [], 46 | ), 47 | ); 48 | } 49 | } 50 | */ 51 | -------------------------------------------------------------------------------- /lib/_widgets/mixins/raw_keyboard_listener_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | mixin RawKeyboardListenerMixin on State { 5 | // Must be provided by implementing class 6 | bool get enableKeyListener; 7 | 8 | @override 9 | void initState() { 10 | super.initState(); 11 | RawKeyboard.instance.addListener(_handleKey); 12 | } 13 | 14 | @override 15 | void dispose() { 16 | RawKeyboard.instance.removeListener(_handleKey); 17 | super.dispose(); 18 | } 19 | 20 | void handleKeyUp(RawKeyUpEvent value) {} 21 | 22 | void handleKeyDown(RawKeyDownEvent value) {} 23 | 24 | void _handleKey(RawKeyEvent value) { 25 | if (enableKeyListener == false) return; 26 | if (value is RawKeyDownEvent) handleKeyDown(value); 27 | if (value is RawKeyUpEvent) handleKeyUp(value); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/_widgets/no_animation_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NoAnimationPage extends Page { 4 | final Widget child; 5 | const NoAnimationPage({required this.child, LocalKey? key}) : super(key: key); 6 | 7 | @override 8 | Route createRoute(BuildContext context) { 9 | return PageRouteBuilder( 10 | maintainState: true, 11 | transitionDuration: Duration.zero, 12 | reverseTransitionDuration: Duration.zero, 13 | settings: this, 14 | pageBuilder: (context, animation, animation2) => child); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/_widgets/no_glow_scroll_behavior.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NoGlowScrollBehavior extends ScrollBehavior { 4 | @override 5 | Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) { 6 | return child; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/_widgets/positioned_all.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PositionedAll extends StatelessWidget { 4 | const PositionedAll({Key? key, this.all = 0, required this.child}) : super(key: key); 5 | final Widget child; 6 | final double all; 7 | 8 | @override 9 | Widget build(BuildContext context) => Positioned(left: all, top: all, right: all, bottom: all, child: child); 10 | } 11 | -------------------------------------------------------------------------------- /lib/_widgets/rando_colored_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:random_color/random_color.dart'; 3 | 4 | class RandoColoredBox extends StatelessWidget { 5 | const RandoColoredBox({required this.child, Key? key}) : super(key: key); 6 | final Widget child; 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | color: RandomColor().randomColor(colorBrightness: ColorBrightness.light), 11 | child: child, 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/_widgets/rotation_3d.dart.dart: -------------------------------------------------------------------------------- 1 | //Takes a x,y or z rotation, in degrees, and rotates. Good for spins & 3d flip effects 2 | import 'dart:math'; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | class Rotation3d extends StatelessWidget { 7 | //Degrees to rads constant 8 | static const double degrees2Radians = pi / 180; 9 | 10 | final Widget child; 11 | final double rotationX; 12 | final double rotationY; 13 | final double rotationZ; 14 | 15 | const Rotation3d({Key? key, required this.child, this.rotationX = 0, this.rotationY = 0, this.rotationZ = 0}) 16 | : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Transform( 21 | alignment: FractionalOffset.center, 22 | transform: Matrix4.identity() 23 | ..setEntry(3, 2, 0.001) 24 | ..rotateX(rotationX * degrees2Radians) 25 | ..rotateY(rotationY * degrees2Radians) 26 | ..rotateZ(rotationZ * degrees2Radians), 27 | child: child); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/_widgets/rounded_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_widgets/decorated_container.dart'; 3 | import 'package:flutter_folio/styles.dart'; 4 | 5 | class RoundedCard extends StatelessWidget { 6 | const RoundedCard({Key? key, required this.child, this.radius}) : super(key: key); 7 | final Widget child; 8 | final double? radius; 9 | 10 | @override 11 | Widget build(BuildContext context) => ClipRRect( 12 | borderRadius: BorderRadius.all(Radius.circular(radius ?? 24)), 13 | child: child, 14 | ); 15 | } 16 | 17 | class RoundedBorder extends StatelessWidget { 18 | const RoundedBorder({Key? key, this.color, this.width, this.radius, this.ignorePointer = true, this.child}) 19 | : super(key: key); 20 | final Color? color; 21 | final double? width; 22 | final double? radius; 23 | final Widget? child; 24 | final bool ignorePointer; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return IgnorePointer( 29 | ignoring: ignorePointer, 30 | child: DecoratedContainer( 31 | borderRadius: radius ?? Corners.med, 32 | borderColor: color ?? Colors.white, 33 | borderWidth: width ?? Strokes.thin, 34 | child: child ?? Container(), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/_widgets/sized_and_translated.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SizedAndTranslated extends StatelessWidget { 4 | const SizedAndTranslated({ 5 | Key? key, 6 | required this.size, 7 | required this.offset, 8 | required this.child, 9 | this.pivotPoint, 10 | }) : super(key: key); 11 | final Size size; 12 | final Offset offset; 13 | final Widget child; 14 | final Alignment? pivotPoint; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | Alignment pivot = pivotPoint ?? const Alignment(.5, .5); 19 | return Transform.translate( 20 | offset: offset, 21 | child: Transform.translate( 22 | offset: Offset(-size.width * pivot.x, -size.height * pivot.y), 23 | child: Align( 24 | alignment: Alignment.topLeft, 25 | child: SizedBox(width: size.width, height: size.height, child: child), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/app_keys.dart: -------------------------------------------------------------------------------- 1 | class AppKeys { 2 | static String cloudinaryCloud = "flutterfoliodemo"; 3 | static String cloudinaryPreset = "ujszrrfn"; 4 | static String firebaseApiKey = "AIzaSyDIMnzUz9TshIyRSSl7iUpp5QxPhAcL1ZQ"; 5 | static String firestoreProjectId = "flutter-folio-demo"; 6 | } 7 | -------------------------------------------------------------------------------- /lib/commands/app/authenticate_user_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/logger.dart'; 2 | import 'package:flutter_folio/commands/app/set_current_user_command.dart'; 3 | import 'package:flutter_folio/commands/commands.dart'; 4 | import 'package:flutter_folio/data/app_user.dart'; 5 | 6 | class AuthenticateUserCommand extends BaseAppCommand { 7 | Future run({required String email, required String pass, required bool createNew}) async { 8 | AppUser? user; 9 | try { 10 | // Authenticate user 11 | user = await firebase.signIn(email: email, password: pass, createAccount: createNew); 12 | // If they are new, create a database record to hold their content 13 | if (user != null && createNew) { 14 | user = user.copyWith(documentId: email, firstName: "", lastName: ""); 15 | // Set the userId here, so firebase can update this new users data, this is also set in [SetCurrentUserCommand] 16 | firebase.userId = email; 17 | await firebase.addUser(user); 18 | } 19 | log("Authentication complete, user=$user"); 20 | // Login?? 21 | if (user != null) { 22 | SetCurrentUserCommand().run(user); 23 | return true; 24 | } 25 | } on Exception catch (e) { 26 | log(e.toString()); 27 | } 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/commands/app/copy_share_link_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_folio/_utils/device_info.dart'; 3 | import 'package:flutter_folio/commands/commands.dart'; 4 | import 'package:flutter_folio/routing/app_link.dart'; 5 | import 'package:flutter_folio/styled_widgets/toaster.dart'; 6 | import 'package:share/share.dart'; 7 | 8 | import '../../_utils/timed/cooldown.dart'; 9 | 10 | class CopyShareLinkCommand extends BaseAppCommand { 11 | String get baseUrl => "https://flutterfolio.com/#"; 12 | static CoolDown mobileShareCooldown = CoolDown(const Duration(seconds: 1)); 13 | 14 | Future run(String bookId, {String? pageId}) async { 15 | // Form a url using an AppLink 16 | String url = baseUrl + 17 | AppLink( 18 | user: appModel.currentUserEmail, 19 | bookId: bookId, 20 | pageId: pageId, 21 | ).toLocation(); 22 | 23 | // Device clipboard 24 | if (DeviceOS.isDesktopOrWeb) { 25 | Toaster.showToast(mainContext, "Share link copied!"); 26 | Clipboard.setData(ClipboardData(text: url)); 27 | } 28 | // Mobile share sheet 29 | else { 30 | // Put this on a cool-down, prevents a bug in the share api where it can open multiple sheets. 31 | mobileShareCooldown.run(() => Share.share(url)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/commands/app/save_image_to_disk_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:file_selector/file_selector.dart' as file_selector; 2 | import 'package:file_selector/file_selector.dart'; 3 | import 'package:flutter_folio/_utils/device_info.dart'; 4 | import 'package:flutter_folio/commands/commands.dart'; 5 | 6 | class SaveImageToDiskCommand extends BaseAppCommand { 7 | //TODO: Add support for web https://github.com/flutter/flutter/issues/78142 8 | static bool get canUse => DeviceOS.isDesktop; 9 | 10 | Future run(String url) async { 11 | if (canUse == false) return; 12 | String fileName = url.split("/").last; 13 | final path = await file_selector.getSavePath(acceptedTypeGroups: [ 14 | XTypeGroup(label: 'images', extensions: ['jpg', 'jpeg', 'png']) 15 | ], suggestedName: fileName, confirmButtonText: "Save"); 16 | print(path); 17 | // if (path != null) { 18 | // final ByteData imageData = await NetworkAssetBundle(Uri.parse(url)).load(""); 19 | // final Uint8List bytes = imageData.buffer.asUint8List(); 20 | // final file = XFile.fromData(bytes); 21 | // file.saveTo(path); 22 | //} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/commands/app/save_window_size_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:desktop_window/desktop_window.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_folio/_utils/device_info.dart'; 4 | import 'package:flutter_folio/commands/commands.dart'; 5 | 6 | class SaveWindowSizeCommand extends BaseAppCommand { 7 | Future run() async { 8 | // Only save window size to disk on desktop platforms. 9 | if (DeviceOS.isDesktop) { 10 | Size size = await DesktopWindow.getWindowSize(); 11 | if (size != appModel.windowSize) { 12 | appModel.windowSize = size; 13 | appModel.scheduleSave(); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/commands/app/set_current_user_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/string_utils.dart'; 2 | import 'package:flutter_folio/commands/commands.dart'; 3 | import 'package:flutter_folio/core_packages.dart'; 4 | import 'package:flutter_folio/data/app_user.dart'; 5 | 6 | import 'refresh_menubar_command.dart'; 7 | 8 | class SetCurrentUserCommand extends BaseAppCommand { 9 | Future run(AppUser? user) async { 10 | log("SetCurrentUserCommand: $user"); 11 | // Update appController with new user. If user is null, this acts as a logout command. 12 | firebase.userId = user?.email; 13 | appModel.currentUser = user; 14 | if (StringUtils.isNotEmpty(firebase.userId)) { 15 | AppUser? user = await firebase.getUser(); 16 | if (user != null) { 17 | appModel.currentUser = user; 18 | log("User loaded from firebase: ${user.toJson()}"); 19 | } 20 | } 21 | // If currentUser is null here, then we've either logged out, or auth failed. 22 | if (appModel.currentUser == null) { 23 | appModel.reset(); 24 | booksModel.reset(); 25 | } 26 | RefreshMenuBarCommand().run(); 27 | appModel.save(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/commands/app/update_user_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/commands/commands.dart'; 2 | import 'package:flutter_folio/data/app_user.dart'; 3 | 4 | class UpdateUserCommand extends BaseAppCommand { 5 | Future run(AppUser user) async { 6 | if (appModel.currentUser == null) return; 7 | appModel.currentUser = user; 8 | await firebase.setUserData(user); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/commands/books/create_folio_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/string_utils.dart'; 2 | import 'package:flutter_folio/_utils/time_utils.dart'; 3 | import 'package:flutter_folio/commands/books/refresh_all_books_command.dart'; 4 | import 'package:flutter_folio/commands/books/set_current_book_command.dart'; 5 | import 'package:flutter_folio/commands/commands.dart'; 6 | import 'package:flutter_folio/data/book_data.dart'; 7 | import 'package:flutter_folio/styles.dart'; 8 | import 'package:uuid/uuid.dart'; 9 | 10 | class CreateFolioCommand extends BaseAppCommand { 11 | Future run({String? title, String? desc}) async { 12 | // Create an empty book 13 | ScrapBookData book = ScrapBookData( 14 | documentId: const Uuid().v1(), 15 | title: title ?? "", 16 | desc: desc ?? "", 17 | creationTime: TimeUtils.nowMillis, 18 | lastModifiedTime: TimeUtils.nowMillis, 19 | ); 20 | // Send to server 21 | String documentId = await firebase.addBook(book); 22 | if (StringUtils.isNotEmpty(documentId)) { 23 | // Set as selected book once we get the id back. This will change views 24 | SetCurrentBookCommand().run(book.copyWith(documentId: documentId)); 25 | // Refresh the book list after a small delay. This allows any transitions to happen before the books lists are updated 26 | Future.delayed(Times.medium, () => RefreshAllBooks().run()); 27 | } 28 | return documentId; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/commands/books/create_page_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/commands/books/create_placed_scraps_command.dart'; 3 | import 'package:flutter_folio/commands/books/update_page_count_command.dart'; 4 | import 'package:flutter_folio/commands/commands.dart'; 5 | import 'package:flutter_folio/data/book_data.dart'; 6 | import 'package:uuid/uuid.dart'; 7 | 8 | class CreatePageCommand extends BaseAppCommand { 9 | Future run() async { 10 | ScrapBookData? currentBook = booksModel.currentBook; 11 | List? currentPages = booksModel.currentBookPages; 12 | if (currentBook == null || currentPages == null) return; 13 | // Increment pageCount 14 | int count = await UpdatePageCountCommand().run(currentPages.length + 1); 15 | // Create new page 16 | ScrapPageData newPage = ScrapPageData( 17 | documentId: const Uuid().v1(), 18 | bookId: currentBook.documentId, 19 | title: "Page $count", 20 | desc: "Add a description...", 21 | boxOrder: [], 22 | ); 23 | 24 | /// Add page locally 25 | booksModel.currentBookPages = List.from(currentPages)..add(newPage); 26 | booksModel.currentPage ??= newPage; 27 | 28 | /// Add to database 29 | String pageId = await firebase.addPage(newPage); 30 | 31 | // Add a hidden scrap, this sidesteps a bug in firedart regarding empty collections. 32 | ScrapItem emptyScrap = ScrapItem(bookId: newPage.bookId, contentType: ContentType.Hidden, data: ""); 33 | await CreatePlacedScrapCommand().run(pageId: pageId, size: Size.zero, scraps: [emptyScrap]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/commands/books/delete_book_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/commands/commands.dart'; 3 | import 'package:flutter_folio/data/book_data.dart'; 4 | import 'package:flutter_folio/styled_widgets/dialogs/delete_dialog.dart'; 5 | 6 | class DeleteBookCommand extends BaseAppCommand { 7 | Future run(ScrapBookData book) async { 8 | // Show dialog 9 | bool doDelete = await showDialog( 10 | context: mainContext, 11 | builder: (_) => DeleteDialog( 12 | title: "Delete Folio", 13 | desc1: "Are you sure you want to permanently\ndelete the selected folio?", 14 | desc2: "\"${book.title}\"", 15 | )) ?? 16 | false; 17 | //Delete 18 | if (doDelete) { 19 | // Delete locally right away 20 | booksModel.removeBookById(book.documentId); 21 | // Sent to database 22 | firebase.deleteBook(book); 23 | 24 | while ((booksModel.books?.length ?? 0) > 30) { 25 | final book = booksModel.books!.last; 26 | booksModel.removeBookById(book.documentId); 27 | firebase.deleteBook(book); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/commands/books/delete_page_scrap_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/commands/commands.dart'; 2 | import 'package:flutter_folio/data/book_data.dart'; 3 | 4 | import 'update_book_modified_command.dart'; 5 | 6 | class DeletePageScrapCommand extends BaseAppCommand { 7 | Future run(PlacedScrapItem scrap) async { 8 | booksModel.removePageScrapById(scrap.documentId); 9 | UpdateBookModifiedCommand().run(bookId: scrap.bookId); 10 | await firebase.deletePlacedScrap(scrap); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/commands/books/delete_scraps_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_utils/string_utils.dart'; 3 | import 'package:flutter_folio/commands/commands.dart'; 4 | import 'package:flutter_folio/styled_widgets/dialogs/delete_dialog.dart'; 5 | 6 | import 'update_book_modified_command.dart'; 7 | 8 | class DeleteScrapsCommand extends BaseAppCommand { 9 | Future run({required String bookId, required List scrapIds}) async { 10 | // Guard against empty ids 11 | if ((scrapIds.isEmpty)) return false; 12 | 13 | // Show dialog 14 | String pluralScraps = StringUtils.pluralize("scrap", scrapIds.length); 15 | bool doDelete = await showDialog( 16 | context: mainContext, 17 | builder: (_) { 18 | return DeleteDialog( 19 | title: "Delete ${scrapIds.length} $pluralScraps?", 20 | desc1: "Are you sure you want to permanently\ndelete the selected $pluralScraps?", 21 | ); 22 | }) ?? 23 | false; 24 | //Delete 25 | if (doDelete) { 26 | for (final id in scrapIds) { 27 | // Clear local data 28 | booksModel.removeBookScrapById(id); 29 | // Delete from db 30 | firebase.deleteBookScrap(bookId: bookId, scrapId: id); 31 | } 32 | // Mark book as changed 33 | UpdateBookModifiedCommand().run(bookId: bookId); 34 | return true; 35 | } 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/commands/books/refresh_all_books_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/commands/commands.dart'; 2 | import 'package:flutter_folio/data/book_data.dart'; 3 | import 'package:flutter_folio/services/cloudinary/cloud_storage_service.dart'; 4 | 5 | class RefreshAllBooks extends BaseAppCommand { 6 | Future?> run() async { 7 | List? allBooks = await firebase.getAllBooks(); 8 | if (allBooks != null) { 9 | CloudStorageService.addMaxSizeToUrlList( 10 | allBooks, (s) => s.imageUrl, (s, url) => s.copyWith(imageUrl: url)); 11 | booksModel.books = allBooks; 12 | } 13 | return booksModel.books; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/commands/books/refresh_current_book_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/commands/commands.dart'; 2 | import 'package:flutter_folio/data/book_data.dart'; 3 | import 'package:flutter_folio/services/cloudinary/cloud_storage_service.dart'; 4 | 5 | class RefreshCurrentBookCommand extends BaseAppCommand { 6 | Future run({bool book = true, bool pages = true, bool scraps = true}) async { 7 | String? bookId = booksModel.currentBookId; 8 | if (bookId == null) return; 9 | List futures = [ 10 | if (book) 11 | firebase.getBook(bookId: bookId).then((value) { 12 | if (value == null) return; 13 | booksModel.currentBook = value; 14 | }), 15 | if (pages) 16 | firebase.getAllPages(bookId: bookId).then((value) { 17 | if (value == null) return; 18 | booksModel.currentBookPages = value..removeWhere((p) => p.documentId == ""); 19 | }), 20 | if (scraps) 21 | firebase.getAllBookScraps(bookId: bookId).then((value) { 22 | if (value == null) return; 23 | CloudStorageService.addMaxSizeToUrlList( 24 | value, 25 | (s) => s.data, 26 | (s, url) => s.copyWith(data: url), 27 | ); 28 | booksModel.currentBookScraps = value; 29 | }), 30 | ]; 31 | await Future.wait(futures); 32 | } 33 | 34 | void onlyScraps() => run(book: false, pages: false); 35 | void onlyPages() => run(book: false); 36 | } 37 | -------------------------------------------------------------------------------- /lib/commands/books/refresh_current_page_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/commands/commands.dart'; 2 | import 'package:flutter_folio/data/book_data.dart'; 3 | import 'package:flutter_folio/services/cloudinary/cloud_storage_service.dart'; 4 | 5 | class RefreshCurrentPageCommand extends BaseAppCommand { 6 | Future run() async { 7 | String? bookId = booksModel.currentPage?.bookId; 8 | String? pageId = booksModel.currentPage?.documentId; 9 | if (bookId == null || pageId == null) return; 10 | List futures = [ 11 | firebase.getPage(bookId: bookId, pageId: pageId).then((value) { 12 | if (value == null) return; 13 | booksModel.currentPage = value; 14 | }), 15 | firebase.getAllPlacedScraps(bookId: bookId, pageId: pageId).then((value) { 16 | if (value == null) return; 17 | CloudStorageService.addMaxSizeToUrlList( 18 | value, (s) => s.data, (s, url) => s.copyWith(data: url)); 19 | booksModel.currentPageScraps = value 20 | ..removeWhere((element) { 21 | return element.documentId == ""; 22 | }); 23 | }), 24 | ]; 25 | await Future.wait(futures); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/commands/books/set_current_book_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/input_utils.dart'; 2 | import 'package:flutter_folio/commands/books/refresh_current_book_command.dart'; 3 | import 'package:flutter_folio/commands/commands.dart'; 4 | import 'package:flutter_folio/data/book_data.dart'; 5 | 6 | import 'set_current_page_command.dart'; 7 | 8 | class SetCurrentBookCommand extends BaseAppCommand { 9 | Future run(ScrapBookData? book, {bool setInitialPage = true}) async { 10 | booksModel.currentBook = book; 11 | if (book != null) { 12 | // Because TextEditing relies on FocusOut for saving, we have to make sure we focus out before changing books 13 | InputUtils.unFocus(); 14 | // Load new book contents 15 | await RefreshCurrentBookCommand().run(); 16 | // If we have any pages, set the first one as the current page 17 | bool hasPages = booksModel.currentBookPages?.isNotEmpty ?? false; 18 | if (hasPages) { 19 | ScrapPageData? firstPage = booksModel.currentBookPages?.first; 20 | if (setInitialPage && firstPage != null) { 21 | await SetCurrentPageCommand().run(firstPage); 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/commands/books/set_current_page_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/input_utils.dart'; 2 | import 'package:flutter_folio/commands/books/refresh_current_page_command.dart'; 3 | import 'package:flutter_folio/commands/commands.dart'; 4 | import 'package:flutter_folio/data/book_data.dart'; 5 | 6 | class SetCurrentPageCommand extends BaseAppCommand { 7 | Future run(ScrapPageData? page) async { 8 | // Because TextEditing relies on FocusOut for saving, we have to make sure we focus out before changing pages 9 | InputUtils.unFocus(); 10 | booksModel.currentPage = page; 11 | if (page != null) { 12 | RefreshCurrentPageCommand().run(); 13 | } 14 | return booksModel.currentPage; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/commands/books/shift_placed_scraps_sort_order_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/commands/commands.dart'; 2 | import 'package:flutter_folio/data/book_data.dart'; 3 | import 'package:flutter_folio/styled_widgets/toaster.dart'; 4 | 5 | class ShiftPlacedScrapsSortOrderCommand extends BaseAppCommand { 6 | Future run(int indexesToShift, PlacedScrapItem scrapItem) async { 7 | // Fetch the book and try to get the current sortIndex of this page 8 | ScrapPageData? page = booksModel.currentPage; 9 | if (page == null) return; 10 | 11 | page = page.copyWith( 12 | boxOrder: _move(page.boxOrder, scrapItem.documentId, indexesToShift), 13 | ); 14 | Toaster.showToast(mainContext, indexesToShift < 0 ? "Sent back" : "Moved forward"); 15 | booksModel.replacePage(page); 16 | booksModel.currentPageScraps = List.from(booksModel.currentPageScraps ?? []); 17 | // Update firebase 18 | firebase.setPage(page); 19 | } 20 | 21 | List _move(List existing, String value, int indexesToShift) { 22 | int i = existing.indexOf(value); 23 | // If it doesn't exist in the list, then use the last item or -1, and move from there. 24 | if (i == -1) i = existing.length - 1; 25 | int newIndex = (i + indexesToShift).clamp(0, existing.length - 1).toInt(); 26 | // Remove the item if it existed in the list 27 | if (i != -1) { 28 | existing.removeAt(i); 29 | } 30 | // Insert the new item back in at the correct index, or 0 if it didn't previous exist 31 | return existing..insert(newIndex, value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/commands/books/update_book_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/time_utils.dart'; 2 | import 'package:flutter_folio/commands/commands.dart'; 3 | import 'package:flutter_folio/data/book_data.dart'; 4 | 5 | class UpdateBookCommand extends BaseAppCommand { 6 | Future run(ScrapBookData book) async { 7 | booksModel.replaceBook(book); 8 | await firebase.setBook(book.copyWith( 9 | lastModifiedTime: TimeUtils.nowMillis, 10 | )); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/commands/books/update_book_modified_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/string_utils.dart'; 2 | import 'package:flutter_folio/_utils/time_utils.dart'; 3 | import 'package:flutter_folio/commands/commands.dart'; 4 | import 'package:flutter_folio/data/book_data.dart'; 5 | 6 | class UpdateBookModifiedCommand extends BaseAppCommand { 7 | Future run({String? bookId, ScrapBookData? book}) async { 8 | assert( 9 | StringUtils.isNotEmpty(bookId) || book != null, "You must pass either an id or an instance to this Command."); 10 | // fetch a book, or use the one passed in 11 | if (bookId != null) { 12 | book ??= await firebase.getBook(bookId: bookId); 13 | } 14 | if (book != null) { 15 | book = book.copyWith(lastModifiedTime: TimeUtils.nowMillis); 16 | booksModel.replaceBook(book); 17 | firebase.setBook(book); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/commands/books/update_current_book_cover_photo_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/time_utils.dart'; 2 | import 'package:flutter_folio/commands/commands.dart'; 3 | import 'package:flutter_folio/data/book_data.dart'; 4 | import 'package:flutter_folio/styled_widgets/toaster.dart'; 5 | 6 | class UpdateCurrentBookCoverPhotoCommand extends BaseAppCommand { 7 | Future run(PlacedScrapItem item) async { 8 | // Guard against non-photo content types 9 | if (item.contentType != ContentType.Photo) return; 10 | // Protect against non-changes so the views don't need to check 11 | if (item.data == booksModel.currentBook?.imageUrl) return; 12 | ScrapBookData? book = booksModel.currentBook; 13 | if (book != null) { 14 | book = book.copyWith( 15 | imageUrl: item.data, 16 | lastModifiedTime: TimeUtils.nowMillis, 17 | ); 18 | // Update local 19 | booksModel.replaceBook(book); 20 | // Update db 21 | firebase.setBook(book); 22 | Toaster.showToast(mainContext, "Cover photo changed!"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/commands/books/update_page_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/commands/books/update_book_modified_command.dart'; 2 | import 'package:flutter_folio/commands/commands.dart'; 3 | import 'package:flutter_folio/data/book_data.dart'; 4 | 5 | class UpdatePageCommand extends BaseAppCommand { 6 | Future run(ScrapPageData page) async { 7 | booksModel.replacePage(page); 8 | await firebase.setPage(page); 9 | UpdateBookModifiedCommand().run(bookId: page.bookId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/commands/books/update_page_count_command.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter_folio/_utils/time_utils.dart'; 4 | import 'package:flutter_folio/commands/books/update_book_modified_command.dart'; 5 | import 'package:flutter_folio/commands/commands.dart'; 6 | import 'package:flutter_folio/data/book_data.dart'; 7 | 8 | class UpdatePageCountCommand extends BaseAppCommand { 9 | Future run(int value, {ScrapBookData? book}) async { 10 | book ??= booksModel.currentBook; 11 | assert(book != null, "UpdatePageCountCommand was called but there is no current book."); 12 | if (book != null) { 13 | value = max(0, value); 14 | ScrapBookData newBook = book.copyWith( 15 | lastModifiedTime: TimeUtils.nowMillis, 16 | pageCount: value, 17 | ); 18 | booksModel.replaceBook(newBook); 19 | await firebase.setBook(newBook); 20 | UpdateBookModifiedCommand().run(book: newBook); 21 | } 22 | return value; 23 | } 24 | 25 | Future incrementCurrent() async { 26 | ScrapBookData? book = booksModel.currentBook; 27 | if (book == null) return 0; 28 | return await run(book.pageCount + 1, book: book); 29 | } 30 | 31 | Future decrementCurrent() async { 32 | ScrapBookData? book = booksModel.currentBook; 33 | if (book == null) return 0; 34 | return await run(book.pageCount - 1, book: book); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/commands/books/update_placed_scrap_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/time_utils.dart'; 2 | import 'package:flutter_folio/commands/books/update_book_modified_command.dart'; 3 | import 'package:flutter_folio/commands/commands.dart'; 4 | import 'package:flutter_folio/data/book_data.dart'; 5 | 6 | class UpdatePageScrapCommand extends BaseAppCommand { 7 | Future run(PlacedScrapItem scrapItem, {bool localOnly = false}) async { 8 | PlacedScrapItem newScrap = scrapItem.copyWith(lastModifiedTime: TimeUtils.nowMillis); 9 | booksModel.replaceCurrentPageScrap(newScrap); 10 | if (localOnly == false) { 11 | firebase.setPlacedScrap(newScrap); 12 | UpdateBookModifiedCommand().run(bookId: scrapItem.bookId); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/commands/commands.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/models/app_model.dart'; 3 | import 'package:flutter_folio/models/books_model.dart'; 4 | import 'package:flutter_folio/services/cloudinary/cloud_storage_service.dart'; 5 | import 'package:flutter_folio/services/firebase/firebase_service.dart'; 6 | import 'package:flutter_folio/themes.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | BuildContext? _mainContext; 10 | BuildContext get mainContext => _mainContext!; 11 | bool get hasContext => _mainContext != null; 12 | 13 | /// Someone needs to call this so our Commands can access models and services. 14 | void setContext(BuildContext c) { 15 | _mainContext = c; 16 | } 17 | 18 | class BaseAppCommand { 19 | /// Provide quick lookups for the main Models and Services in the App. 20 | T getProvided() { 21 | assert(_mainContext != null, "You must call `setContext(BuildContext)` method before calling Commands."); 22 | return _mainContext!.read(); 23 | } 24 | 25 | AppTheme get appTheme => getProvided(); 26 | 27 | FirebaseService get firebase => getProvided(); 28 | CloudStorageService get cloudStorage => getProvided(); 29 | AppModel get appModel => getProvided(); 30 | BooksModel get booksModel => getProvided(); 31 | } 32 | -------------------------------------------------------------------------------- /lib/core_packages.dart: -------------------------------------------------------------------------------- 1 | export 'package:darq/darq.dart'; // Extra list sorting methods sortByDescending, etc 2 | export 'package:flutter_folio/_utils/logger.dart'; 3 | export 'package:flutter_folio/_utils/logger.dart'; 4 | export 'package:flutter_folio/styled_widgets/styled_widgets.dart'; 5 | export 'package:flutter_folio/styles.dart'; 6 | export 'package:flutter_folio/themes.dart'; 7 | export 'package:provider/provider.dart'; // Context extensions for Provider, .watch() etc 8 | export 'package:sized_context/sized_context.dart'; // Shortcut for MediaQuery info, context.sizePx.with, etc 9 | -------------------------------------------------------------------------------- /lib/data/app_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_folio/_utils/string_utils.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'app_user.freezed.dart'; 5 | part 'app_user.g.dart'; 6 | 7 | @freezed 8 | class AppUser with _$AppUser { 9 | static String kDefaultImageUrl = 10 | "https://images.unsplash.com/photo-1481627834876-b7833e8f5570?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=50&q=80"; 11 | const AppUser._(); 12 | factory AppUser({ 13 | @Default("") String documentId, 14 | required String email, 15 | required String fireId, 16 | String? firstName, 17 | String? lastName, 18 | String? imageUrl, 19 | }) = _AppUser; 20 | 21 | factory AppUser.fromJson(Map json) => _$AppUserFromJson(json); 22 | 23 | String? getDisplayName() { 24 | String? result = firstName; 25 | if (StringUtils.isNotEmpty(lastName)) result = (result ?? "") + " $lastName"; 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/data/app_user.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'app_user.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_AppUser _$_$_AppUserFromJson(Map json) { 10 | return _$_AppUser( 11 | documentId: json['documentId'] as String? ?? '', 12 | email: json['email'] as String, 13 | fireId: json['fireId'] as String, 14 | firstName: json['firstName'] as String?, 15 | lastName: json['lastName'] as String?, 16 | imageUrl: json['imageUrl'] as String?, 17 | ); 18 | } 19 | 20 | Map _$_$_AppUserToJson(_$_AppUser instance) => 21 | { 22 | 'documentId': instance.documentId, 23 | 'email': instance.email, 24 | 'fireId': instance.fireId, 25 | 'firstName': instance.firstName, 26 | 'lastName': instance.lastName, 27 | 'imageUrl': instance.imageUrl, 28 | }; 29 | -------------------------------------------------------------------------------- /lib/routing/app_route_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/routing/app_link.dart'; 3 | 4 | /// Converts browser location strings to [AppLink], and vice-versa. 5 | /// This leans on [AppLink] to the actual parsing, so this is largely boilerplate. 6 | class AppRouteParser extends RouteInformationParser { 7 | @override 8 | // Take a url bar location, and create an AppLink from it 9 | Future parseRouteInformation(RouteInformation routeInformation) async { 10 | AppLink link = AppLink.fromLocation(routeInformation.location); 11 | //safePrint("parseRouteInfo: ${routeInformation.location} == ${link.toLocation()}"); 12 | //safePrint("link.user=${link.user},link.pageId=${link.pageId},link.bookId=${link.bookId},"); 13 | return link; 14 | } 15 | 16 | @override 17 | // Convert an applink into a string used for the browser location 18 | RouteInformation restoreRouteInformation(AppLink configuration) { 19 | // Ask the applink to give us a string 20 | String location = configuration.toLocation(); 21 | //safePrint("restoreRouteInfo: $location"); 22 | // Pass that string back to the OS so it can update the url bar 23 | return RouteInformation(location: location); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/styled_widgets/app_icons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | enum AppIcons { 5 | add, 6 | camera, 7 | emoji, 8 | image, 9 | link_out, 10 | move_forward, 11 | scraps, 12 | send_backward, 13 | share, 14 | star, 15 | text, 16 | toggle_carousel, 17 | toggle_list, 18 | trashcan, 19 | view, 20 | github, 21 | website 22 | } 23 | 24 | class AppIcon extends StatelessWidget { 25 | final AppIcons icon; 26 | final double size; 27 | final Color color; 28 | 29 | const AppIcon(this.icon, {Key? key, required this.size, required this.color}) : super(key: key); 30 | @override 31 | Widget build(BuildContext context) { 32 | String i = describeEnum(icon).toLowerCase().replaceAll("_", "-"); 33 | String path = 'assets/images/icons/' + i + '.png'; 34 | //print(path); 35 | return SizedBox( 36 | width: size, 37 | height: size, 38 | child: Center( 39 | child: Image.asset(path, width: size, height: size, color: color, filterQuality: FilterQuality.high), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/styled_widgets/app_logo_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class AppLogoText extends StatelessWidget { 5 | const AppLogoText({Key? key, this.constraints, this.color}) : super(key: key); 6 | final BoxConstraints? constraints; 7 | final Color? color; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | AppTheme theme = context.watch(); 12 | Widget img = Image.asset( 13 | "assets/images/logos/flutterfolio-logo.png", 14 | fit: BoxFit.contain, 15 | color: color ?? theme.accent1, 16 | //TODO: Log bug for blurry image filtering on windows 17 | filterQuality: FilterQuality.high, 18 | ); 19 | return (constraints == null) ? img : ConstrainedBox(constraints: constraints!, child: img); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/styled_widgets/book_cover_image.dart: -------------------------------------------------------------------------------- 1 | // Wraps a CachedNetworkImage + a fallback placeholder if no image is set. 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_folio/_utils/string_utils.dart'; 4 | import 'package:flutter_folio/_widgets/app_image.dart'; 5 | import 'package:flutter_folio/data/book_data.dart'; 6 | 7 | /// An image that falls back to a placeholder img 8 | class BookCoverImage extends StatefulWidget { 9 | const BookCoverImage(this.data, {Key? key}) : super(key: key); 10 | final ScrapBookData data; 11 | 12 | @override 13 | _BookCoverImageState createState() => _BookCoverImageState(); 14 | } 15 | 16 | class _BookCoverImageState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | bool usePlaceholder = StringUtils.isEmpty(widget.data.imageUrl); 20 | if (!usePlaceholder) { 21 | //print(widget.data.imageUrl); 22 | return HostedImage(widget.data.imageUrl, fit: BoxFit.cover); 23 | } else { 24 | return Image.asset("assets/images/empty-background.png", fit: BoxFit.cover); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/styled_widgets/buttons/styled_share_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:anchored_popups/anchored_popup_region.dart'; 2 | import 'package:anchored_popups/anchored_popups.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_folio/commands/app/copy_share_link_command.dart'; 5 | import 'package:flutter_folio/core_packages.dart'; 6 | import 'package:flutter_folio/data/book_data.dart'; 7 | 8 | class StyledSharedBtn extends StatelessWidget { 9 | const StyledSharedBtn({ 10 | Key? key, 11 | required this.book, 12 | this.iconColor, 13 | }) : super(key: key); 14 | final Color? iconColor; 15 | final ScrapBookData book; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | void _handleSharePressed() { 20 | // Close popup when pressed 21 | AnchoredPopups.of(context)?.hide(); 22 | CopyShareLinkCommand().run(book.documentId); 23 | } 24 | 25 | AppTheme theme = context.watch(); 26 | return AnchoredPopUpRegion.hover( 27 | //TODO: anchors should be configurable here? 28 | anchor: Alignment.centerRight, 29 | popAnchor: Alignment.centerLeft, 30 | popChild: const StyledTooltip("Copy Share Link", arrowAlignment: Alignment.centerLeft), 31 | child: SimpleBtn( 32 | child: Padding( 33 | padding: EdgeInsets.all(Insets.sm), 34 | child: Icon(Icons.share, color: iconColor ?? theme.surface1), 35 | ), 36 | onPressed: _handleSharePressed)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/styled_widgets/circle_avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_widgets/app_image.dart'; 3 | import 'package:flutter_folio/_widgets/decorated_container.dart'; 4 | import 'package:flutter_folio/core_packages.dart'; 5 | 6 | class StyledCircleImage extends StatelessWidget { 7 | const StyledCircleImage({Key? key, required this.url, this.padding}) : super(key: key); 8 | 9 | final EdgeInsets? padding; 10 | final String url; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | AppTheme theme = Provider.of(context); 15 | return Padding( 16 | padding: padding ?? EdgeInsets.zero, 17 | child: AspectRatio( 18 | aspectRatio: 1, 19 | child: DecoratedContainer( 20 | clipChild: true, 21 | borderColor: theme.greyWeak, 22 | borderWidth: 2, 23 | borderRadius: 99, 24 | child: HostedImage(url, fit: BoxFit.cover), 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/styled_widgets/context_menus/app_context_menu.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:context_menus/context_menus.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_folio/commands/app/set_current_user_command.dart'; 7 | import 'package:flutter_folio/core_packages.dart'; 8 | import 'package:flutter_folio/models/app_model.dart'; 9 | 10 | class AppContextMenu extends StatefulWidget { 11 | const AppContextMenu({Key? key}) : super(key: key); 12 | 13 | @override 14 | _AppContextMenuState createState() => _AppContextMenuState(); 15 | } 16 | 17 | class _AppContextMenuState extends State with ContextMenuStateMixin { 18 | void _handleSignoutPressed() => SetCurrentUserCommand().run(null); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | bool isLoggedIn = context.select((AppModel am) => am.isAuthenticated); 23 | return cardBuilder( 24 | context, 25 | [ 26 | if (isLoggedIn) ...[ 27 | buttonBuilder(context, 28 | ContextMenuButtonConfig("Logout", onPressed: () => handlePressed(context, _handleSignoutPressed))), 29 | ], 30 | if (kIsWeb == false) ...[ 31 | buttonBuilder( 32 | context, ContextMenuButtonConfig("Exit Application", onPressed: () => handlePressed(context, exit(0)))), 33 | ], 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/styled_widgets/context_menus/context_menu_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class ContextMenuIcon extends StatelessWidget { 5 | final AppIcons icon; 6 | final Color? color; 7 | 8 | const ContextMenuIcon({Key? key, required this.icon, this.color}) : super(key: key); 9 | @override 10 | Widget build(BuildContext context) { 11 | AppTheme theme = context.watch(); 12 | return AppIcon(icon, size: 14, color: color ?? theme.greyStrong); 13 | } 14 | } 15 | 16 | class ContextMenuIconHovered extends StatelessWidget { 17 | const ContextMenuIconHovered({Key? key, required this.icon}) : super(key: key); 18 | final AppIcons icon; 19 | @override 20 | Widget build(BuildContext context) { 21 | AppTheme theme = context.watch(); 22 | return ContextMenuIcon(icon: icon, color: theme.surface1); 23 | } 24 | } 25 | 26 | class ContextDivider extends StatelessWidget { 27 | const ContextDivider({Key? key}) : super(key: key); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | AppTheme theme = context.watch(); 32 | return Padding( 33 | padding: const EdgeInsets.symmetric(vertical: 2), 34 | child: Divider(color: theme.greyWeak, height: .5), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/styled_widgets/context_menus/styled_context_menu_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:context_menus/context_menus.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_folio/core_packages.dart'; 4 | import 'package:flutter_folio/styled_widgets/context_menus/context_menu_widgets.dart'; 5 | 6 | class StyledContextMenuOverlay extends StatelessWidget { 7 | const StyledContextMenuOverlay({Key? key, required this.child}) : super(key: key); 8 | final Widget child; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | AppTheme theme = context.watch(); 13 | return ContextMenuOverlay( 14 | dividerBuilder: (_) => const ContextDivider(), 15 | cardBuilder: (_, children) => ContextMenuCard(children: children, padding: EdgeInsets.zero), 16 | buttonStyle: ContextMenuButtonStyle( 17 | textStyle: TextStyles.body2, 18 | shortcutTextStyle: TextStyles.body2.copyWith(color: theme.grey), 19 | hoverFgColor: theme.surface1, 20 | hoverBgColor: theme.accent1, 21 | ), 22 | child: child, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/styled_widgets/dialogs/base_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class BaseStyledDialog extends StatelessWidget { 5 | final Color? bgColor; 6 | final EdgeInsets? padding; 7 | final Widget child; 8 | 9 | const BaseStyledDialog({Key? key, required this.child, this.bgColor, this.padding}) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | var theme = context.watch(); 13 | return Dialog( 14 | backgroundColor: bgColor ?? theme.bg1, 15 | elevation: 0, 16 | child: ConstrainedBox( 17 | constraints: const BoxConstraints(minWidth: 280), 18 | child: Padding( 19 | padding: padding ?? EdgeInsets.symmetric(vertical: Insets.lg), 20 | child: IntrinsicWidth(child: child), 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/styled_widgets/dialogs/edit_text_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/commands/books/update_placed_scrap_command.dart'; 3 | import 'package:flutter_folio/data/book_data.dart'; 4 | import 'package:flutter_folio/styled_widgets/dialogs/base_dialog.dart'; 5 | import 'package:flutter_folio/styled_widgets/labeled_text_input.dart'; 6 | import 'package:flutter_folio/styles.dart'; 7 | 8 | class ScrapTextEditorDialog extends StatelessWidget { 9 | const ScrapTextEditorDialog(this.item, {Key? key}) : super(key: key); 10 | final PlacedScrapItem item; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | void _handleSubmit(String value) => Navigator.pop(context); 15 | 16 | void _handleTextChanged(String value) { 17 | UpdatePageScrapCommand().run(item.copyWith(data: value)); 18 | } 19 | 20 | return BaseStyledDialog( 21 | child: Column( 22 | mainAxisSize: MainAxisSize.min, 23 | children: [ 24 | Padding( 25 | padding: EdgeInsets.symmetric(horizontal: Insets.lg), 26 | child: LabeledTextInput( 27 | label: "Edit Text", 28 | text: item.data, 29 | onChanged: _handleTextChanged, 30 | onSubmit: _handleSubmit, 31 | ), 32 | ), 33 | ], 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/styled_widgets/emoji.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | 5 | enum Emojis { 6 | beers, 7 | checkmark, 8 | confetti, 9 | cool, 10 | crying_face, 11 | dizzy_face, 12 | exclamation_question, 13 | fire, 14 | folded_hands, 15 | heart_eyes, 16 | hundred_points, 17 | kissing_face, 18 | location_pin, 19 | musical_notes, 20 | palms_up, 21 | pile_of_poo, 22 | red_heart, 23 | shooting_star, 24 | smiling_eyes, 25 | sparkles, 26 | squinting_face, 27 | sunglasses_face, 28 | tears_of_joy_face, 29 | warning_sign, 30 | } 31 | 32 | class Emoji extends StatelessWidget { 33 | final Emojis? emoji; 34 | final double? size; 35 | 36 | const Emoji(this.emoji, {Key? key, this.size}) : super(key: key); 37 | @override 38 | Widget build(BuildContext context) { 39 | if (emoji == null) return Container(); 40 | String fileName = describeEnum(emoji!).toLowerCase().replaceAll("_", "-"); 41 | String path = 'assets/images/emoji/' + fileName + '.svg'; 42 | return SvgPicture.asset(path, width: size, height: size); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/styled_widgets/material_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class MaterialIcon extends StatelessWidget { 5 | final IconData icon; 6 | final double size; 7 | final Color? color; 8 | 9 | const MaterialIcon(this.icon, {Key? key, this.size = 20, this.color}) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | AppTheme theme = context.watch(); 13 | return Icon(icon, size: size, color: color ?? theme.greyStrong); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/styled_widgets/shadowed_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_widgets/decorated_container.dart'; 3 | import 'package:flutter_folio/core_packages.dart'; 4 | 5 | class ShadowedBg extends StatelessWidget { 6 | const ShadowedBg(this.color, {Key? key, this.ignorePointer = true}) : super(key: key); 7 | final Color color; 8 | final bool ignorePointer; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return IgnorePointer( 13 | ignoring: ignorePointer, 14 | child: DecoratedContainer(color: color, shadows: Shadows.universal), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/styled_widgets/styled_bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_widgets/decorated_container.dart'; 3 | import 'package:flutter_folio/core_packages.dart'; 4 | 5 | class StyledBottomSheet extends StatelessWidget { 6 | const StyledBottomSheet({required this.child, Key? key}) : super(key: key); 7 | 8 | final Widget child; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | AppTheme theme = context.watch(); 13 | return Container( 14 | decoration: BoxDecoration( 15 | borderRadius: const BorderRadius.vertical(top: Corners.medRadius, bottom: Radius.zero), 16 | color: theme.surface1, 17 | ), 18 | child: Column(children: [ 19 | VSpace.sm, 20 | 21 | /// Drag Handle 22 | DecoratedContainer( 23 | width: 96, 24 | height: 4, 25 | borderRadius: Corners.med, 26 | color: theme.greyWeak, 27 | ), 28 | 29 | /// Content 30 | child 31 | ]), 32 | ); 33 | } 34 | } 35 | 36 | Future showStyledBottomSheet(BuildContext context, {required Widget child}) async { 37 | return showModalBottomSheet( 38 | isScrollControlled: true, 39 | context: context, 40 | shape: const RoundedRectangleBorder( 41 | borderRadius: BorderRadius.vertical(top: Corners.medRadius, bottom: Radius.zero), 42 | ), 43 | builder: (BuildContext context) { 44 | return Wrap( 45 | children: [ 46 | StyledBottomSheet(child: child), 47 | ], 48 | ); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /lib/styled_widgets/styled_load_spinner.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class StyledLoadSpinner extends StatelessWidget { 5 | const StyledLoadSpinner({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | AppTheme theme = context.watch(); 10 | return SizedBox( 11 | width: 24, 12 | height: 24, 13 | child: CircularProgressIndicator( 14 | backgroundColor: theme.greyWeak, 15 | valueColor: AlwaysStoppedAnimation(theme.greyStrong), 16 | )); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/styled_widgets/styled_page_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'package:context_menus/context_menus.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_folio/_utils/input_utils.dart'; 4 | import 'package:flutter_folio/core_packages.dart'; 5 | 6 | class StyledPageScaffold extends StatelessWidget { 7 | const StyledPageScaffold({Key? key, required this.body}) : super(key: key); 8 | final Widget body; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | AppTheme theme = context.watch(); 13 | //TODO: Add a FocusTraversalGroup() when this bug is addressed:https://github.com/flutter/flutter/issues/74656 14 | return GestureDetector( 15 | onTap: InputUtils.unFocus, 16 | child: Scaffold( 17 | backgroundColor: theme.bg1, 18 | body: Stack( 19 | children: [ 20 | ContextMenuRegion(child: Container(), contextMenu: const AppContextMenu()), 21 | body, 22 | ], 23 | ), 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/styled_widgets/styled_scrollbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | import 'package:flutter_folio/models/app_model.dart'; 4 | 5 | class StyledScrollbar extends StatelessWidget { 6 | const StyledScrollbar({ 7 | Key? key, 8 | required this.child, 9 | required this.controller, 10 | this.padding, 11 | this.enabled = true, 12 | }) : super(key: key); 13 | final bool enabled; 14 | final Widget child; 15 | final ScrollController controller; 16 | final EdgeInsets? padding; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | bool touchMode = context.select((AppModel m) => m.enableTouchMode); 21 | Widget paddedChild = Padding(padding: padding ?? EdgeInsets.only(right: Insets.lg), child: child); 22 | return enabled 23 | ? Scrollbar( 24 | controller: controller, 25 | radius: Corners.smRadius, 26 | thickness: touchMode ? 6 : 10, 27 | showTrackOnHover: false, 28 | isAlwaysShown: touchMode == false, 29 | child: paddedChild, 30 | ) 31 | : paddedChild; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/styled_widgets/styled_spacers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class VSpace extends StatelessWidget { 5 | final double size; 6 | 7 | const VSpace(this.size, {Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) => SizedBox(height: size); 11 | 12 | static VSpace get xs => VSpace(Insets.xs); 13 | static VSpace get sm => VSpace(Insets.sm); 14 | static VSpace get med => VSpace(Insets.med); 15 | static VSpace get lg => VSpace(Insets.lg); 16 | static VSpace get xl => VSpace(Insets.xl); 17 | } 18 | 19 | class HSpace extends StatelessWidget { 20 | final double size; 21 | 22 | const HSpace(this.size, {Key? key}) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) => SizedBox(width: size); 26 | 27 | static HSpace get xs => HSpace(Insets.xs); 28 | static HSpace get sm => HSpace(Insets.sm); 29 | static HSpace get med => HSpace(Insets.med); 30 | static HSpace get lg => HSpace(Insets.lg); 31 | static HSpace get xl => HSpace(Insets.xl); 32 | } 33 | -------------------------------------------------------------------------------- /lib/styled_widgets/styled_spinner.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class LoadingIndicator extends StatelessWidget { 5 | const LoadingIndicator({Key? key, this.size = 30}) : super(key: key); 6 | final double size; 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | alignment: Alignment.center, 12 | child: Text( 13 | "Fetching data, please wait...", 14 | style: TextStyles.caption, 15 | ) 16 | //child: SizedBox(width: size, height: size, child: CircularProgressIndicator()), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/styled_widgets/styled_widgets.dart: -------------------------------------------------------------------------------- 1 | export 'app_icons.dart'; 2 | export 'app_logo_text.dart'; 3 | export 'book_cover_image.dart'; 4 | export 'buttons/raw_styled_btn.dart'; 5 | export 'buttons/styled_buttons.dart'; 6 | export 'buttons/styled_share_btn.dart'; 7 | export 'circle_avatar.dart'; 8 | export 'context_menus/app_context_menu.dart'; 9 | export 'context_menus/book_context_menu.dart'; 10 | export 'context_menus/scrap_context_menu.dart'; 11 | export 'context_menus/styled_context_menu_overlay.dart'; 12 | export 'dialogs/base_dialog.dart'; 13 | export 'dialogs/delete_dialog.dart'; 14 | export 'emoji.dart'; 15 | export 'glass_cards.dart'; 16 | export 'inline_text_editor.dart'; 17 | export 'labeled_text_input.dart'; 18 | export 'material_icon.dart'; 19 | export 'shadowed_box.dart'; 20 | export 'styled_bottom_sheet.dart'; 21 | export 'styled_load_spinner.dart'; 22 | export 'styled_page_scaffold.dart'; 23 | export 'styled_scrollbar.dart'; 24 | export 'styled_spacers.dart'; 25 | export 'styled_spinner.dart'; 26 | export 'styled_spinner.dart'; 27 | export 'styled_toggle_switch.dart'; 28 | export 'styled_tooltip.dart'; 29 | export 'toaster.dart'; 30 | export 'ui_text.dart'; 31 | -------------------------------------------------------------------------------- /lib/styled_widgets/toaster.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | class Toaster { 5 | static void showToast(BuildContext context, String content) { 6 | AppTheme theme = context.read(); 7 | TextStyle textStyle = TextStyles.body2.copyWith(color: theme.inverseTextColor); 8 | ScaffoldMessenger.of(context).clearSnackBars(); 9 | ScaffoldMessenger.of(context).showSnackBar( 10 | SnackBar( 11 | behavior: SnackBarBehavior.floating, 12 | duration: const Duration(milliseconds: 1700), 13 | content: Container( 14 | padding: EdgeInsets.all(Insets.sm), 15 | child: Text(content, style: textStyle), 16 | ), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/styled_widgets/ui_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Extends SelectableText, providing default web-like behavior of a non-focuseable but selectable text region 4 | class UiText extends StatefulWidget { 5 | const UiText({Key? key, this.style, this.text, this.span}) : super(key: key); 6 | final String? text; 7 | final TextSpan? span; 8 | final TextStyle? style; 9 | 10 | @override 11 | _UiTextState createState() => _UiTextState(); 12 | } 13 | 14 | class _UiTextState extends State { 15 | final FocusNode _focusNode = FocusNode(skipTraversal: true); 16 | @override 17 | Widget build(BuildContext context) { 18 | if (widget.span != null) { 19 | return SelectableText.rich(widget.span!, style: widget.style, focusNode: _focusNode); 20 | } else { 21 | return SelectableText(widget.text ?? "", style: widget.style, focusNode: _focusNode); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/views/editor_page/scrap_popup_editor/scrap_popup_panel_rotation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_folio/core_packages.dart'; 3 | 4 | import 'scrap_popup_editor.dart'; 5 | 6 | class ScrapPopupPanelRotation extends StatelessWidget { 7 | final bool isOpen; 8 | final double degrees; 9 | final void Function(double value) onDegreesChanged; 10 | const ScrapPopupPanelRotation({Key? key, required this.isOpen, required this.degrees, required this.onDegreesChanged}) 11 | : super(key: key); 12 | @override 13 | Widget build(BuildContext context) { 14 | return Stack( 15 | fit: StackFit.expand, 16 | children: [ 17 | /// Closed State 18 | if (!isOpen) ...[ 19 | const PopPanelIconBtn(), 20 | ] else ...[ 21 | SingleChildScrollView( 22 | scrollDirection: Axis.horizontal, 23 | child: SingleChildScrollView( 24 | child: SizedBox( 25 | width: 300 - Insets.sm * 2, 26 | child: Column( 27 | children: [ 28 | const PanelHeader(label: "Rotation", showBackArrow: false), 29 | SizedBox( 30 | width: 280, 31 | child: CupertinoSlider(min: -180, max: 180, value: degrees, onChanged: onDegreesChanged)), 32 | VSpace(Insets.sm), 33 | Text("${degrees.round()}", style: TextStyles.body3), 34 | ], 35 | ), 36 | ), 37 | ), 38 | ), 39 | ] 40 | ], 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/views/editor_page/scrapboard/scrap_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_utils/easy_notifier.dart'; 3 | 4 | class ScrapData extends EasyNotifier { 5 | ScrapData(this.data, {this.aspect = 1}); 6 | 7 | Offset _offset = Offset.zero; 8 | Offset get offset => _offset; 9 | set offset(Offset value) => notify(() => _offset = value); 10 | 11 | Size _size = const Size(100, 100); 12 | Size get size => _size; 13 | set size(Size value) => notify(() => _size = value); 14 | 15 | double _rot = 1; 16 | double get rot => _rot; 17 | set rot(double value) => notify(() => _rot = value); 18 | 19 | double aspect; 20 | T data; 21 | } 22 | -------------------------------------------------------------------------------- /lib/views/home_page/book_cover/book_cover_small.dart: -------------------------------------------------------------------------------- 1 | import 'package:animate_do/animate_do.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_folio/core_packages.dart'; 4 | import 'package:flutter_folio/data/book_data.dart'; 5 | 6 | class SmallBookCover extends StatelessWidget { 7 | const SmallBookCover(this.book, {Key? key, this.topTitle = false}) : super(key: key); 8 | final ScrapBookData book; 9 | final bool topTitle; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | AppTheme theme = context.watch(); 14 | return FadeInUp( 15 | delay: Times.medium, 16 | child: Text( 17 | book.title, 18 | style: TextStyles.h3.copyWith(color: theme.surface1), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/views/scrap_pile_picker/selectable_scrap_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_folio/_utils/string_utils.dart'; 3 | import 'package:flutter_folio/_widgets/app_image.dart'; 4 | import 'package:flutter_folio/_widgets/decorated_container.dart'; 5 | import 'package:flutter_folio/core_packages.dart'; 6 | import 'scrap_pile_picker.dart'; 7 | 8 | class SelectableScrapBtn extends StatelessWidget { 9 | const SelectableScrapBtn({Key? key, required this.img, required this.onPressed, this.isSelected = false}) 10 | : super(key: key); 11 | 12 | final VoidCallback onPressed; 13 | final String img; 14 | final bool isSelected; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | AppTheme theme = context.watch(); 19 | if (StringUtils.isEmpty(img)) { 20 | return const Center(child: StyledLoadSpinner()); 21 | } 22 | return Stack( 23 | children: [ 24 | Padding( 25 | padding: EdgeInsets.all(Insets.xs), 26 | child: GridBtn( 27 | onPressed: onPressed, 28 | bgColor: theme.greyStrong, 29 | child: HostedImage( 30 | img, 31 | fit: BoxFit.contain, 32 | )), 33 | ), 34 | if (isSelected) ...[ 35 | DecoratedContainer(borderWidth: 2, borderColor: theme.focus, borderRadius: Corners.lg, ignorePointer: true), 36 | ], 37 | ], 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void fl_register_plugins(FlPluginRegistry* registry) { 15 | g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar = 16 | fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin"); 17 | bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar); 18 | g_autoptr(FlPluginRegistrar) desktop_window_registrar = 19 | fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWindowPlugin"); 20 | desktop_window_plugin_register_with_registrar(desktop_window_registrar); 21 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 22 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 23 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 24 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 25 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 26 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 27 | } 28 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | bitsdojo_window_linux 7 | desktop_window 8 | file_selector_linux 9 | url_launcher_linux 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 3 | #include "ephemeral/Flutter-Generated.xcconfig" 4 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 3 | #include "ephemeral/Flutter-Generated.xcconfig" 4 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import bitsdojo_window_macos 9 | import cloud_firestore 10 | import desktop_window 11 | import file_selector_macos 12 | import firebase_auth 13 | import firebase_core 14 | import path_provider_macos 15 | import shared_preferences_macos 16 | import sqflite 17 | import url_launcher_macos 18 | 19 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 20 | BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin")) 21 | FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) 22 | DesktopWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWindowPlugin")) 23 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 24 | FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) 25 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) 26 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 27 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 28 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 29 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 30 | } 31 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.12' 2 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 3 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 4 | 5 | project 'Runner', { 6 | 'Debug' => :debug, 7 | 'Profile' => :release, 8 | 'Release' => :release, 9 | } 10 | 11 | def flutter_root 12 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 13 | unless File.exist?(generated_xcode_build_settings_path) 14 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 15 | end 16 | 17 | File.foreach(generated_xcode_build_settings_path) do |line| 18 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 19 | return matches[1].strip if matches 20 | end 21 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 22 | end 23 | 24 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 25 | 26 | flutter_macos_podfile_setup 27 | 28 | target 'Runner' do 29 | use_frameworks! 30 | use_modular_headers! 31 | 32 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 33 | end 34 | 35 | post_install do |installer| 36 | installer.pods_project.targets.each do |target| 37 | flutter_additional_macos_build_settings(target) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "folio-icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "folio-icon-32.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "folio-icon-33.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "folio-icon-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "folio-icon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "folio-icon-256.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "folio-icon-257.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "folio-icon-512.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "folio-icon-513.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "folio-icon-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-257.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-33.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-513.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-513.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/macos/Runner/Assets.xcassets/AppIcon.appiconset/folio-icon-64.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = FlutterFolio; 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.gskinner.travelApp 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2020 com.gskinner. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.network.server 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /macos/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 150221469863-fricoi1555bg294u9j0i61tqfvpoo3th.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.150221469863-fricoi1555bg294u9j0i61tqfvpoo3th 9 | API_KEY 10 | AIzaSyDV0SE4WzZX--KRsdVZeS2pk8_81VrAJMk 11 | GCM_SENDER_ID 12 | 150221469863 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.gskinner.flutterfolio 17 | PROJECT_ID 18 | flutter-folio-demo 19 | STORAGE_BUCKET 20 | flutter-folio-demo.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:150221469863:ios:67573e189598eff0d6c382 33 | 34 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSApplicationCategoryType 24 | public.app-category.lifestyle 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | $(PRODUCT_COPYRIGHT) 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /snap/gui/flutter-folio.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Flutter Folio 3 | Comment=A scrapbooking app that feels great on all your devices 4 | Exec=flutter-folio 5 | Icon=${SNAP}/meta/gui/flutter-folio.png 6 | Terminal=false 7 | Type=Application 8 | Categories=Lifestyle 9 | -------------------------------------------------------------------------------- /snap/gui/flutter-folio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/snap/gui/flutter-folio.png -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: flutter-folio 2 | version: 1.1.4 3 | summary: A scrapbooking app that feels great on all your devices 4 | description: Make your digital scrapbooks with Flutter Folio! You can upload photos from your phone, edit them on a desktop or tablet and share them with friends and family on the web. 5 | 6 | confinement: strict 7 | base: core18 8 | grade: stable 9 | 10 | apps: 11 | flutter-folio: 12 | command: flutter_folio 13 | extensions: [flutter-dev] 14 | plugs: 15 | - network 16 | - home 17 | 18 | parts: 19 | flutter-folio: 20 | source: . 21 | plugin: flutter 22 | flutter-target: lib/main.dart 23 | 24 | -------------------------------------------------------------------------------- /source-assets/app-flutter-folio-no-alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/source-assets/app-flutter-folio-no-alpha.png -------------------------------------------------------------------------------- /source-assets/app-flutter-folio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/source-assets/app-flutter-folio.png -------------------------------------------------------------------------------- /source-assets/flutterfolio.dmgCanvas/Disk Image: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/source-assets/flutterfolio.dmgCanvas/Disk Image -------------------------------------------------------------------------------- /source-assets/flutterfolio.dmgCanvas/QuickLook/Preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/source-assets/flutterfolio.dmgCanvas/QuickLook/Preview.jpg -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/web/favicon.ico -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/web/icons/icon-1024.png -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flutter Folio", 3 | "short_name": "Flutter Folio", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Flutter Folio is a scrapbooking app that was designed to showcase Flutter’s capabilities to create apps that feel at home on every platform and device", 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 | } 24 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | BitsdojoWindowPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); 17 | DesktopWindowPluginRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("DesktopWindowPlugin")); 19 | FileSelectorPluginRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("FileSelectorPlugin")); 21 | UrlLauncherPluginRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("UrlLauncherPlugin")); 23 | } 24 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | bitsdojo_window_windows 7 | desktop_window 8 | file_selector_windows 9 | url_launcher_windows 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | #include 10 | auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP); 11 | 12 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 13 | _In_ wchar_t *command_line, _In_ int show_command) { 14 | // Attach to console when present (e.g., 'flutter run') or create a 15 | // new console when running with a debugger. 16 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 17 | CreateAndAttachConsole(); 18 | } 19 | 20 | // Initialize COM, so that it is available for use in the library and/or 21 | // plugins. 22 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 23 | 24 | RunLoop run_loop; 25 | 26 | flutter::DartProject project(L"data"); 27 | 28 | std::vector command_line_arguments = 29 | GetCommandLineArguments(); 30 | 31 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 32 | 33 | FlutterWindow window(&run_loop, project); 34 | Win32Window::Point origin(10, 10); 35 | Win32Window::Size size(1280, 720); 36 | if (!window.CreateAndShow(L"flutter_folio", origin, size)) { 37 | return EXIT_FAILURE; 38 | } 39 | window.SetQuitOnClose(true); 40 | 41 | run_loop.Run(); 42 | 43 | ::CoUninitialize(); 44 | return EXIT_SUCCESS; 45 | } 46 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskinnerTeam/flutter-folio/be0cfa2fa54a234d0e91beebda0f88f3c485a850/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------