diff --git a/clj/src/cljd/flutter.cljd b/clj/src/cljd/flutter.cljd index 8e2f8535..d631af42 100644 --- a/clj/src/cljd/flutter.cljd +++ b/clj/src/cljd/flutter.cljd @@ -1279,7 +1279,7 @@ :managed [*hud-enabled (atom nil) :dispose nil _ (println "[* RDY)_") :dispose nil] (widgets/Stack - .alignment widgets/AlignmentGeometry.topLeft + .alignment widgets/Alignment.topLeft .fit widgets/StackFit.expand .children [(widget diff --git a/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd b/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd index ec857784..e9b5d2de 100644 --- a/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd +++ b/samples/bottom_navigation_bar/src/sample/bottom_navigation_bar.cljd @@ -4,36 +4,28 @@ ["package:flutter/material.dart" :as m] [cljd.flutter :as f])) -(defn main +(defn bottom-nav-widget + "Reusable bottom navigation bar widget that can be embedded in any app." [] - (let - [title "Bottom Navigation Bar Demo" - pages [(m/Icon. m/Icons.directions_car) - (m/Icon. m/Icons.directions_transit) - (m/Icon. m/Icons.directions_bike)] - selected-index (atom 0)] - (f/run - (m/MaterialApp .title title) + (let [pages [(m/Icon. m/Icons.directions_car .size 64) + (m/Icon. m/Icons.directions_transit .size 64) + (m/Icon. m/Icons.directions_bike .size 64)] + selected-index (atom 0)] + (f/widget + :watch [idx selected-index] + (m/Scaffold + .appBar (m/AppBar .title (m/Text "Bottom Navigation")) + .body (m/Center .child (get pages idx)) + .bottomNavigationBar + (m/BottomNavigationBar + .currentIndex idx + .onTap #(reset! selected-index %) + .items + [(m/BottomNavigationBarItem .icon (m/Icon m/Icons.directions_car) .label "Car") + (m/BottomNavigationBarItem .icon (m/Icon m/Icons.directions_transit) .label "Transit") + (m/BottomNavigationBarItem .icon (m/Icon m/Icons.directions_bike) .label "Bike")]))))) - .home - (m/Scaffold - - .appBar - (m/AppBar - .title - (m/Text "BottomNavigationBar")) - - .body - (m/Center - .child - (f/widget :watch [current-index selected-index] (get pages current-index))) - - .bottomNavigationBar - (f/widget - :watch [current-index selected-index] - (m/BottomNavigationBar - .items [(m/BottomNavigationBarItem .icon (m/Icon. m/Icons.directions_car) .label "directions_car") - (m/BottomNavigationBarItem .icon (m/Icon. m/Icons.directions_transit) .label "directions_transit") - (m/BottomNavigationBarItem .icon (m/Icon. m/Icons.directions_bike) .label "directions_bike")] - .currentIndex current-index - .onTap (fn [index] (reset! selected-index index)))))))) +(defn main [] + (f/run + (m/MaterialApp .title "Bottom Navigation Bar Demo") + .home (bottom-nav-widget))) diff --git a/samples/counter/src/sample/counter.cljd b/samples/counter/src/sample/counter.cljd index fa824060..d197f527 100644 --- a/samples/counter/src/sample/counter.cljd +++ b/samples/counter/src/sample/counter.cljd @@ -4,27 +4,32 @@ ["package:flutter/material.dart" :as m] [cljd.flutter :as f])) -(defn main [] +(defn counter-widget + "Reusable counter widget that can be embedded in any app." + [] (let [counter (atom 0)] - (f/run - (m/MaterialApp - .title "Cljd Demo" - .theme (m/ThemeData .primarySwatch m/Colors.blue)) - .home - (m/Scaffold - .appBar (m/AppBar .title (m/Text "ClojureDart Home Page")) - .floatingActionButton - (f/widget - (m/FloatingActionButton - .onPressed #(swap! counter inc) - .tooltip "Increment") - (m/Icon m/Icons.add))) - .body - m/Center - (m/Column .mainAxisAlignment m/MainAxisAlignment.center) - .children - [(m/Text "You have pushed the button this many times:") - (f/widget - :get {{{:flds [displayLarge]} .-textTheme} m/Theme} - :watch [N counter] - (m/Text (str N) .style displayLarge))]))) + (f/widget + (m/Scaffold + .appBar (m/AppBar .title (m/Text "Counter Demo")) + .floatingActionButton + (f/widget + (m/FloatingActionButton + .onPressed #(swap! counter inc) + .tooltip "Increment") + (m/Icon m/Icons.add))) + .body + m/Center + (m/Column .mainAxisAlignment m/MainAxisAlignment.center) + .children + [(m/Text "You have pushed the button this many times:") + (f/widget + :get {{{:flds [displayLarge]} .-textTheme} m/Theme} + :watch [N counter] + (m/Text (str N) .style displayLarge))]))) + +(defn main [] + (f/run + (m/MaterialApp + .title "Cljd Demo" + .theme (m/ThemeData .primarySwatch m/Colors.blue)) + .home (counter-widget))) diff --git a/samples/fetch-data/src/sample/fetch_data.cljd b/samples/fetch-data/src/sample/fetch_data.cljd index 6f02b1ce..484c6287 100644 --- a/samples/fetch-data/src/sample/fetch_data.cljd +++ b/samples/fetch-data/src/sample/fetch_data.cljd @@ -6,18 +6,22 @@ ["package:http/http.dart" :as http] [cljd.flutter :as f])) +(def fetch-data-widget + "Reusable fetch data widget that can be embedded in any app." + (f/widget + (m/Scaffold .appBar (m/AppBar .title (m/Text "Fetch Data Example"))) + .body + m/Center + :watch [response (http/get (Uri/parse "https://jsonplaceholder.typicode.com/albums/1"))] + (if-some [{sc .-statusCode body .-body} ^http/Response response] + (case sc + 200 (m/Text (get (c/json.decode body) "title")) + (m/Text (str "Something wrong happened, status code: " sc))) + (m/CircularProgressIndicator)))) + (defn main [] (f/run - (m/MaterialApp - .title "Fetch Data Example" - .theme (m/ThemeData .primarySwatch m/Colors.blue)) - .home - (m/Scaffold .appBar (m/AppBar .title (m/Text "Fetch Data Example"))) - .body - m/Center - :watch [response (http/get (Uri/parse "https://jsonplaceholder.typicode.com/albums/1"))] - (if-some [{sc .-statusCode body .-body} ^http/Response response] ;; type hint is not necessary here, it removes compiler warnings - (case sc - 200 (m/Text (get (c/json.decode body) "title")) - (m/Text (str "Something wrong happened, status code: " sc))) - (m/CircularProgressIndicator)))) + (m/MaterialApp + .title "Fetch Data Example" + .theme (m/ThemeData .primarySwatch m/Colors.blue)) + .home fetch-data-widget)) diff --git a/samples/fizzbuzz/src/sample/fizzbuzz.cljd b/samples/fizzbuzz/src/sample/fizzbuzz.cljd index 85d07fc9..bbab681a 100644 --- a/samples/fizzbuzz/src/sample/fizzbuzz.cljd +++ b/samples/fizzbuzz/src/sample/fizzbuzz.cljd @@ -5,26 +5,30 @@ (def ^m/TextStyle text-style (m/TextStyle .fontWeight m/FontWeight.w700 .fontSize 32)) +(def fizzbuzz-widget + "Reusable fizzbuzz widget that can be embedded in any app." + (f/widget + (m/Scaffold .appBar (m/AppBar .title (m/Text "Fizz Buzz Demo"))) + .body + :let [s1 (-> (Stream/periodic (Duration .seconds 1) identity) .asBroadcastStream) + s3 (Stream/periodic (Duration .seconds 3) #(* 3 (inc %))) + s5 (Stream/periodic (Duration .seconds 5) #(* 5 (inc %)))] + (m/Column .mainAxisAlignment m/MainAxisAlignment.center .crossAxisAlignment m/CrossAxisAlignment.stretch) + .children + [(f/widget + :watch [n s1 + :default 0] + (m/Text (str n) .textAlign m/TextAlign.center .style text-style)) + (f/widget + :watch [n (f/sub [s1 s3] (fn [[n n3]] (if (= n n3) "Fizz" " "))) + :default " "] + (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.red.shade200))) + (f/widget + :watch [n (f/sub [s1 s5] (fn [[n n5]] (if (= n n5) "Buzz" " "))) + :default " "] + (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.green.shade200)))])) + (defn main [] (f/run - (m/MaterialApp .title "Fizz buzz Demo") - .home - (m/Scaffold .appBar (m/AppBar .title (m/Text "Fizz buzz Demo"))) - .body - :let [s1 (-> (Stream/periodic (Duration .seconds 1) identity) .asBroadcastStream) - s3 (Stream/periodic (Duration .seconds 3) #(* 3 (inc %))) - s5 (Stream/periodic (Duration .seconds 5) #(* 5 (inc %)))] - (m/Column .mainAxisAlignment m/MainAxisAlignment.center .crossAxisAlignment m/CrossAxisAlignment.stretch) - .children - [(f/widget - :watch [n s1 - :default 0] - (m/Text (str n) .textAlign m/TextAlign.center .style text-style)) - (f/widget - :watch [n (f/sub [s1 s3] (fn [[n n3]] (if (= n n3) "Fizz" " "))) - :default " "] - (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.red.shade200))) - (f/widget - :watch [n (f/sub [s1 s5] (fn [[n n5]] (if (= n n5) "Buzz" " "))) - :default " "] - (m/Text n .textAlign m/TextAlign.center .style (.apply text-style .color m/Colors.green.shade200)))])) + (m/MaterialApp .title "Fizz Buzz Demo") + .home fizzbuzz-widget)) diff --git a/samples/form_handle_change_textfield/src/sample/form_handle_change.cljd b/samples/form_handle_change_textfield/src/sample/form_handle_change.cljd new file mode 100644 index 00000000..0f368880 --- /dev/null +++ b/samples/form_handle_change_textfield/src/sample/form_handle_change.cljd @@ -0,0 +1,28 @@ +(ns sample.form-handle-change + "Faithful port of https://docs.flutter.dev/cookbook/forms/text-field-changes" + (:require ["package:flutter/material.dart" :as m] + [cljd.flutter :as f])) + +(def form-handle-change-widget + "Reusable form handle change widget that can be embedded in any app." + (f/widget + (m/Scaffold .appBar (m/AppBar .title (m/Text "Handle Text Changes"))) + .body + (m/Padding .padding (m/EdgeInsets.all 16.0)) + :managed [text-controller (m/TextEditingController)] + :bg-watcher ([^m/TextEditingValue {second-input-text .-text} text-controller] + (dart:core/print (str "Second text field: " second-input-text))) + m/Column + .children + [(m/TextField + .decoration (m/InputDecoration .labelText "First field (onChanged)") + .onChanged (fn [text] (dart:core/print (str "First text field: " text)))) + (m/SizedBox .height 16) + (m/TextField + .decoration (m/InputDecoration .labelText "Second field (controller)") + .controller text-controller)])) + +(defn main [] + (f/run + (m/MaterialApp .title "Handle Text Changes") + .home form-handle-change-widget)) diff --git a/samples/form_retrieve_input/src/sample/form_retrieve_input.cljd b/samples/form_retrieve_input/src/sample/form_retrieve_input.cljd new file mode 100644 index 00000000..cbacea37 --- /dev/null +++ b/samples/form_retrieve_input/src/sample/form_retrieve_input.cljd @@ -0,0 +1,32 @@ +(ns sample.form-retrieve-input + "Faithful port of https://docs.flutter.dev/cookbook/forms/retrieve-input" + (:require ["package:flutter/material.dart" :as m] + [cljd.flutter :as f])) + +(defn form-retrieve-input-widget + "Reusable form retrieve input widget that can be embedded in any app." + [] + (f/widget + :context ctx + :managed [tc (m/TextEditingController)] + (m/Scaffold + .appBar (m/AppBar .title (m/Text "Retrieve Text Input")) + .body + (m/Padding. .padding (m/EdgeInsets.all 16.0) + .child (m/TextField .controller tc)) + .floatingActionButton + (m/FloatingActionButton + .onPressed (fn [] + (m/showDialog + .context ctx + .builder + (f/build + :let [{:flds [text]} tc] + (m/AlertDialog .content (m/Text text)))) nil) + .tooltip "Show me the value!" + .child (m/Icon m/Icons.text_fields))))) + +(defn main [] + (f/run + (m/MaterialApp .title "Retrieve text input") + .home (form-retrieve-input-widget))) diff --git a/samples/form_validate/src/sample/form_validate.cljd b/samples/form_validate/src/sample/form_validate.cljd new file mode 100644 index 00000000..becdbb33 --- /dev/null +++ b/samples/form_validate/src/sample/form_validate.cljd @@ -0,0 +1,32 @@ +(ns sample.form-validate + "Faithful port of https://docs.flutter.dev/cookbook/forms/validation" + (:require ["package:flutter/material.dart" :as m] + [cljd.flutter :as f] + [cljd.string :as str])) + +(defn form-validate-widget + "Reusable form validation widget that can be embedded in any app." + [] + (let [form-key (#/(m/GlobalKey m/FormState))] + (f/widget + :get [m/ScaffoldMessenger] + (m/Scaffold .appBar (m/AppBar .title (m/Text "Form Validation"))) + .body + (m/Form .key form-key) + (m/Padding .padding (m/EdgeInsets.all 16)) + (m/Column .crossAxisAlignment m/CrossAxisAlignment.start) + .children + [(m/TextFormField + .decoration (m/InputDecoration .labelText "Enter some text") + .validator (fn [value] (when (str/blank? value) "Please enter some text"))) + (m/SizedBox .height 16) + (m/ElevatedButton + .onPressed #(when (.validate (.-currentState form-key)) + (.showSnackBar scaffold-messenger (m/SnackBar .content (m/Text "Processing Data"))) + nil) + .child (m/Text "Submit"))]))) + +(defn main [] + (f/run + (m/MaterialApp .title "Form Validation Demo") + .home (form-validate-widget))) diff --git a/samples/gridlist/src/sample/gridlist.cljd b/samples/gridlist/src/sample/gridlist.cljd index 703c2340..1e0991aa 100644 --- a/samples/gridlist/src/sample/gridlist.cljd +++ b/samples/gridlist/src/sample/gridlist.cljd @@ -4,16 +4,19 @@ ["package:flutter/material.dart" :as m] [cljd.flutter :as f])) +(def gridlist-widget + "Reusable grid list widget that can be embedded in any app." + (f/widget + (m/Scaffold .appBar (m/AppBar .title (m/Text "Grid List"))) + .body + :get {{{:flds [headlineMedium]} .-textTheme} m/Theme} + (m/GridView.count .crossAxisCount 2) + .children + (for [i (range 100)] + (m/Center .child (m/Text (str "Item " i) .style headlineMedium))))) + (defn main [] - (let [title "Grid List"] - (m/runApp - (f/widget - (m/MaterialApp .title title) - .home - (m/Scaffold .appBar (m/AppBar .title (m/Text title))) - .body - :get {{{:flds [headline3]} .-textTheme} m/Theme} - (m/GridView.count .crossAxisCount 2) - .children - (for [i (range 100)] - (m/Center .child (m/Text (str "Item " i) .style headline3))))))) + (m/runApp + (f/widget + (m/MaterialApp .title "Grid List") + .home gridlist-widget))) diff --git a/samples/keep_alive/src/sample/keep_alive.cljd b/samples/keep_alive/src/sample/keep_alive.cljd index 65598089..028dba17 100644 --- a/samples/keep_alive/src/sample/keep_alive.cljd +++ b/samples/keep_alive/src/sample/keep_alive.cljd @@ -2,36 +2,34 @@ (:require ["package:flutter/material.dart" :as m] [cljd.flutter :as f])) +(def keep-alive-widget + "Reusable keep-alive demo widget that can be embedded in any app." + (m/Scaffold + .appBar (m/AppBar .title (m/Text "Keep Alive Demo")) + .body + (m/ListView.builder + .itemCount 100 + .itemBuilder + (f/build [idx] + :key idx + :height 80 + :let [kept-alive (odd? idx)] + :color (cond + (zero? idx) m/Colors.white + kept-alive m/Colors.red.shade200 + :else m/Colors.blue.shade200) + :keep-alive kept-alive + :let [now (DateTime.now)] + m/Center + (m/Text + (if (zero? idx) + "Scroll back and forth to see kept-alive vs ephemeral items" + (print-str idx (if kept-alive "KEPT ALIVE" "EPHEMERAL") + " at: " (.-hour now) "h" (.-minute now) "m" (.-second now) "s")) + .style (m/TextStyle .fontSize 16 .fontWeight m/FontWeight.w700) + .textAlign m/TextAlign.center))))) + (defn main [] (f/run - m/MaterialApp - .home - m/Scaffold - .body - (m/ListView.builder - .itemCount 100 - .itemBuilder - (f/build [idx] - :key idx - :height 80 - :let [kept-alive (odd? idx)] - :color (cond - (zero? idx) m/Colors.white - kept-alive m/Colors.red.shade200 - :else m/Colors.blue.shade200) - :keep-alive kept-alive - :let [now (DateTime.now)] - m/Center - (m/Text - (if (zero? idx) - "Scroll back and forth and you should see ephemeral items being rebuilt while kept-alive items are reused." - (print-str idx - (if kept-alive - "KEPT ALIVE" - "EPHEMERAL") - " rebuilt at: " - (.-hour now) "h" - (.-minute now) "m" - (.-second now) "s")) - .style (m/TextStyle .fontSize 18 .fontWeight m/FontWeight.w700) - .textAlign m/TextAlign.center))))) + m/MaterialApp + .home keep-alive-widget)) diff --git a/samples/scoped_watch/src/sample/scoped_watch.cljd b/samples/scoped_watch/src/sample/scoped_watch.cljd index b0f6e6d7..b06dccd1 100644 --- a/samples/scoped_watch/src/sample/scoped_watch.cljd +++ b/samples/scoped_watch/src/sample/scoped_watch.cljd @@ -4,36 +4,37 @@ ["package:flutter/material.dart" :as m] [cljd.flutter :as f])) -(defn main [] +(defn scoped-watch-widget + "Reusable scoped watch demo widget that can be embedded in any app." + [] (let [flip (atom false)] - (f/run - (m/MaterialApp - .title "Cljd Demo" - .theme (m/ThemeData .primarySwatch m/Colors.blue)) - .home - (m/Scaffold - .appBar (m/AppBar .title (m/Text "ClojureDart Home Page")) - .floatingActionButton - (f/widget - (m/FloatingActionButton - .onPressed #(swap! flip not) - .tooltip "Flip color") - (m/Icon m/Icons.add))) - .body - m/Row - .children - [(f/widget - m/Expanded - :watch [is-flipped flip] - :color (if is-flipped m/Colors.blue m/Colors.green) - m/Center - :let [_ (prn 'flat)] - (m/Text "I shouldn't rebuild when flip changes")) + (f/widget + (m/Scaffold + .appBar (m/AppBar .title (m/Text "Scoped Watch Demo")) + .floatingActionButton + (f/widget + (m/FloatingActionButton .onPressed #(swap! flip not) .tooltip "Flip color") + (m/Icon m/Icons.swap_horiz))) + .body + m/Row + .children + [(f/widget + m/Expanded + :watch [is-flipped flip] + :color (if is-flipped m/Colors.blue m/Colors.green) + m/Center + (m/Text "Flat watch\n(rebuilds container)" .textAlign m/TextAlign.center)) + (f/widget + m/Expanded (f/widget - m/Expanded - (f/widget - :watch [is-flipped flip] - :color (if is-flipped m/Colors.blue m/Colors.green)) - m/Center - :let [_ (prn 'scoped)] - (m/Text "I shouldn't rebuild when flip changes"))]))) + :watch [is-flipped flip] + :color (if is-flipped m/Colors.blue m/Colors.green)) + m/Center + (m/Text "Scoped watch\n(only color rebuilds)" .textAlign m/TextAlign.center))]))) + +(defn main [] + (f/run + (m/MaterialApp + .title "Scoped Watch Demo" + .theme (m/ThemeData .primarySwatch m/Colors.blue)) + .home (scoped-watch-widget))) diff --git a/samples/tabs/src/sample/tabs.cljd b/samples/tabs/src/sample/tabs.cljd index 4305bd4f..a69a8037 100644 --- a/samples/tabs/src/sample/tabs.cljd +++ b/samples/tabs/src/sample/tabs.cljd @@ -4,23 +4,26 @@ ["package:flutter/material.dart" :as m] [cljd.flutter :as f])) -(defn main - [] - (let [title "Tabs Demo"] - (f/run - (m/MaterialApp .title title) - .home - (m/DefaultTabController .length 3) - (m/Scaffold - .appBar - (m/AppBar - .bottom - (m/TabBar - .tabs [(m/Tab .icon (m/Icon. m/Icons.directions_car)), - (m/Tab .icon (m/Icon. m/Icons.directions_transit)), - (m/Tab .icon (m/Icon. m/Icons.directions_bike))]))) - .body - (m/TabBarView - .children [(m/Tab .icon (m/Icon. m/Icons.directions_car)), - (m/Tab .icon (m/Icon. m/Icons.directions_transit)), - (m/Tab .icon (m/Icon. m/Icons.directions_bike))])))) +(def tabs-widget + "Reusable tabs widget that can be embedded in any app." + (f/widget + (m/DefaultTabController .length 3) + (m/Scaffold + .appBar + (m/AppBar + .title (m/Text "Tabs Demo") + .bottom + (m/TabBar + .tabs [(m/Tab .icon (m/Icon. m/Icons.directions_car)) + (m/Tab .icon (m/Icon. m/Icons.directions_transit)) + (m/Tab .icon (m/Icon. m/Icons.directions_bike))]))) + .body + (m/TabBarView + .children [(m/Center .child (m/Icon m/Icons.directions_car .size 64)) + (m/Center .child (m/Icon m/Icons.directions_transit .size 64)) + (m/Center .child (m/Icon m/Icons.directions_bike .size 64))]))) + +(defn main [] + (f/run + (m/MaterialApp .title "Tabs Demo") + .home tabs-widget)) diff --git a/samples/widgetbook/README.md b/samples/widgetbook/README.md new file mode 100644 index 00000000..afd8da50 --- /dev/null +++ b/samples/widgetbook/README.md @@ -0,0 +1,27 @@ +# ClojureDart Widgetbook + +A [Widgetbook](https://www.widgetbook.io/) catalog showcasing all ClojureDart sample widgets in one place. + +## Running + +```bash +cd samples/widgetbook +clj -M:cljd init +clj -M:cljd flutter +``` + +## What's Included + +This widgetbook imports widgets from all the sample projects: + +- **Animations**: AnimatedContainer, AnimatedString, FadeWidget, PhysicsSimulation, HeroAnimations +- **Basic Widgets**: Counter, TwoCounters, Snackbar, WidgetTest +- **Navigation**: Tabs, BottomNavigationBar, Drawer, BasicNavigation, NamedRoutes +- **Data Display**: DataTable, GridList, FetchData +- **Forms**: FormValidation, FormRetrieveInput, FormHandleChange +- **Gestures & Custom Paint**: GestureDetector +- **State Management**: ScopedWatch, KeepAlive +- **Advanced Components**: ExpandableFAB, Shopper, Isolates +- **Streams**: FizzBuzz +- **Media**: VideoPlayer +- **Tutorial Apps**: FirstFlutterApp \ No newline at end of file diff --git a/samples/widgetbook/analysis_options.yaml b/samples/widgetbook/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/samples/widgetbook/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/samples/widgetbook/deps.edn b/samples/widgetbook/deps.edn new file mode 100644 index 00000000..b11e9bfe --- /dev/null +++ b/samples/widgetbook/deps.edn @@ -0,0 +1,36 @@ +{:paths ["src" + ;; All sample sources + "../animated_container/src" + "../animated_string/src" + "../bottom_navigation_bar/src" + "../counter/src" + "../datatable/src" + "../drawer/src" + "../drawer_navigate_named/src" + "../fab/src" + "../fade_widget/src" + "../fetch-data/src" + "../first_flutter_app_codelabs/src" + "../fizzbuzz/src" + "../form_validate/src" + "../form_retrieve_input/src" + "../form_handle_change_textfield/src" + "../gesture_detector/src" + "../gridlist/src" + "../hero_animations/src" + "../isolates/src" + "../keep_alive/src" + "../navigate_named_routes/src" + "../navigation/src" + "../physics_simulation/src" + "../scoped_watch/src" + "../shopper/src" + "../snackbar/src" + "../tabs/src" + "../twocounters/src" + "../video_player/src" + "../widget_tests/src"] + :deps {tensegritics/clojuredart {:local/root "../../"}} + :aliases {:cljd {:main-opts ["-m" "cljd.build"]}} + :cljd/opts {:main sample.widgetbook + :kind :flutter}} diff --git a/samples/widgetbook/lib/main.dart b/samples/widgetbook/lib/main.dart new file mode 100644 index 00000000..8d4884a4 --- /dev/null +++ b/samples/widgetbook/lib/main.dart @@ -0,0 +1 @@ +export "cljd-out/sample/widgetbook.dart" show main; diff --git a/samples/widgetbook/macos/Runner/DebugProfile.entitlements b/samples/widgetbook/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..3ba6c126 --- /dev/null +++ b/samples/widgetbook/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/samples/widgetbook/macos/Runner/Release.entitlements b/samples/widgetbook/macos/Runner/Release.entitlements new file mode 100644 index 00000000..ee95ab7e --- /dev/null +++ b/samples/widgetbook/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/samples/widgetbook/pubspec.yaml b/samples/widgetbook/pubspec.yaml new file mode 100644 index 00000000..687f6329 --- /dev/null +++ b/samples/widgetbook/pubspec.yaml @@ -0,0 +1,93 @@ +name: cljd_widgetbook +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.8.1 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + widgetbook: ^3.20.2 + http: ^1.2.0 + english_words: ^4.0.0 + video_player: ^2.8.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/samples/widgetbook/src/sample/widgetbook.cljd b/samples/widgetbook/src/sample/widgetbook.cljd new file mode 100644 index 00000000..44177386 --- /dev/null +++ b/samples/widgetbook/src/sample/widgetbook.cljd @@ -0,0 +1,227 @@ +(ns sample.widgetbook + "Widgetbook catalog importing all ClojureDart sample widgets" + (:require + ["package:flutter/material.dart" :as m] + ["package:widgetbook/widgetbook.dart" :as wb] + [cljd.flutter :as f] + [sample.animated-container :as animated-container] + [sample.animated-string :as animated-string] + [sample.bottom-navigation-bar :as bottom-nav] + [sample.counter :as counter] + [sample.datatable :as datatable] + [sample.drawer :as drawer] + [sample.fab :as fab] + [sample.fade-widget :as fade-widget] + [sample.fetch-data :as fetch-data] + [sample.first-flutter-app-codelabs :as codelabs] + [sample.fizzbuzz :as fizzbuzz] + [sample.form-validate :as form-validate] + [sample.form-retrieve-input :as form-retrieve] + [sample.form-handle-change :as form-handle] + [sample.gesture-detector :as gesture] + [sample.gridlist :as gridlist] + [sample.hero-animations :as hero] + [sample.isolates :as isolates] + [sample.keep-alive :as keep-alive] + [sample.navigate-named-routes :as named-routes] + [sample.navigation :as navigation] + [sample.physics-sim :as physics] + [sample.scoped-watch :as scoped-watch] + [sample.shopper :as shopper] + [sample.snackbar :as snackbar] + [sample.tabs :as tabs] + [sample.two-counters :as two-counters] + [sample.video-player :as video-player] + [samples.widget :as widget-test])) + +;; ============================================================================ +;; Generic Widgetbook Component Helper +;; ============================================================================ + +(defn make-component + "Creates a WidgetbookComponent from a map with :name and :use-cases. + Each use-case is a map with :name and :widget (or :builder fn)." + [{:keys [name use-cases]}] + (wb/WidgetbookComponent + .name name + .useCases (mapv (fn [{uc-name :name widget :widget builder :builder}] + (wb/WidgetbookUseCase + .name uc-name + .builder (or builder (fn [_] widget)))) + use-cases))) + +(defn make-category + "Creates a WidgetbookCategory from a map with :name and :components." + [{:keys [name components]}] + (wb/WidgetbookCategory + .name name + .children (mapv make-component components))) + +;; ============================================================================ +;; Component Definitions (Data-driven) +;; ============================================================================ + +(def widget-catalog + [{:name "Animations" + :components + [{:name "AnimatedContainer" + :use-cases [{:name "Default" + :widget animated-container/animated-container}]} + {:name "AnimatedString" + :use-cases [{:name "Default" + :widget animated-string/animated-text}]} + {:name "FadeWidget" + :use-cases [{:name "Default" + :builder (fn [_] (fade-widget/my-home-page "Opacity Demo"))}]} + {:name "PhysicsSimulation" + :use-cases [{:name "DraggableCard" + :widget physics/physics-card-demo}]} + {:name "HeroAnimations" + :use-cases [{:name "Default" + :widget hero/main-screen}]}]} + + {:name "Basic Widgets" + :components + [{:name "Counter" + :use-cases [{:name "Default" + :builder (fn [_] (counter/counter-widget))}]} + {:name "TwoCounters" + :use-cases [{:name "Default" + :builder (fn [_] + (let [st {:left 0 + :right 0}] + (f/widget + :bind {:counters (atom st)} + two-counters/home)))}]} + {:name "Snackbar" + :use-cases [{:name "Default" + :builder (fn [_] + (m/Scaffold + .appBar (m/AppBar + .title (m/Text "Snackbar Demo")) + .body snackbar/snackbar-demo))}]} + {:name "WidgetTest" + :use-cases [{:name "Default" + :builder (fn [_] (widget-test/my-widget "Widget Test" "Hello!"))}]}]} + {:name "Navigation" + :components + [{:name "Tabs" + :use-cases [{:name "Default" + :widget tabs/tabs-widget}]} + {:name "BottomNavigationBar" + :use-cases [{:name "Default" + :builder (fn [_] + (bottom-nav/bottom-nav-widget))}]} + {:name "Drawer" + :use-cases [{:name "Default" + :builder (fn [_] + (drawer/my-home-page "Drawer Demo"))}]} + {:name "BasicNavigation" + :use-cases [{:name "Default" + :widget navigation/first-route}]} + {:name "NamedRoutes" + :use-cases [{:name "FirstScreen" + :builder (fn [ctx] + (named-routes/first-screen ctx))} + {:name "SecondScreen" + :builder (fn [ctx] + (named-routes/second-screen ctx))}]}]} + {:name "Data Display" + :components + [{:name "DataTable" + :use-cases [{:name "Default" + :widget datatable/demo}]} + {:name "GridList" + :use-cases [{:name "Default" + :widget gridlist/gridlist-widget}]} + {:name "FetchData" + :use-cases [{:name "Default" + :widget fetch-data/fetch-data-widget}]}]} + {:name "Forms" + :components + [{:name "FormValidation" + :use-cases [{:name "Default" + :builder (fn [_] + (form-validate/form-validate-widget))}]} + {:name "FormRetrieveInput" + :use-cases [{:name "Default" + :builder (fn [_] + (form-retrieve/form-retrieve-input-widget))}]} + {:name "FormHandleChange" + :use-cases [{:name "Default" + :widget form-handle/form-handle-change-widget}]}]} + + {:name "Gestures & Custom Paint" + :components + [{:name "GestureDetector" + :use-cases [{:name "Parallelogram" + :builder (fn [_] + (m/Scaffold + .appBar (m/AppBar .title (m/Text "Gesture Detector")) + .body gesture/gesture-parallelogram))}]}]} + + {:name "State Management" + :components + [{:name "ScopedWatch" + :use-cases [{:name "Default" + :builder (fn [_] + (scoped-watch/scoped-watch-widget))}]} + {:name "KeepAlive" + :use-cases [{:name "Default" + :widget keep-alive/keep-alive-widget}]}]} + + {:name "Advanced Components" + :components + [{:name "ExpandableFAB" + :use-cases [{:name "Default" + :widget fab/example-expandable-fab}]} + {:name "Shopper" + :use-cases [{:name "Catalog" + :widget shopper/catalog} + {:name "Cart" + :widget shopper/my-cart}]} + {:name "Isolates" + :use-cases [{:name "Default" + :builder (fn [_] + (m/Scaffold + .appBar (m/AppBar .title (m/Text "Isolates Demo")) + .body isolates/isolate-ui))}]}]} + + {:name "Streams" + :components + [{:name "FizzBuzz" + :use-cases [{:name "Default" + :widget fizzbuzz/fizzbuzz-widget}]}]} + + {:name "Media" + :components + [{:name "VideoPlayer" + :use-cases [{:name "Default" + :widget video-player/video-player-widget}]}]} + + {:name "Tutorial Apps" + :components + [{:name "FirstFlutterApp" + :use-cases [{:name "GeneratorPage" + :widget codelabs/generator-page} + {:name "FavoritesPage" + :widget codelabs/favorites-page}]}]}]) + +;; ============================================================================ +;; Widgetbook Main +;; ============================================================================ +(defn main [] + (m/runApp + (wb/Widgetbook.material + .directories (mapv make-category widget-catalog) + .addons + [(wb/MaterialThemeAddon + .themes + [(#/(wb/WidgetbookTheme m/ThemeData) .name "Light" .data (m/ThemeData.light)) + (#/(wb/WidgetbookTheme m/ThemeData) .name "Dark" .data (m/ThemeData.dark)) + (#/(wb/WidgetbookTheme m/ThemeData) .name "Shopper" .data shopper/theme)]) + (wb/DeviceFrameAddon + .devices + [wb/Devices.ios.iPhone13 + wb/Devices.ios.iPad + wb/Devices.android.samsungGalaxyS20])]))) diff --git a/samples/widgetbook/test/widget_test.dart b/samples/widgetbook/test/widget_test.dart new file mode 100644 index 00000000..3b6f93a2 --- /dev/null +++ b/samples/widgetbook/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cljd_widgetbook/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}