├── .gitignore ├── LICENSE ├── README.md ├── flutter_clock_helper ├── .metadata ├── CHANGELOG.md ├── README.md ├── customizer.png ├── lib │ ├── customizer.dart │ └── model.dart └── pubspec.yaml ├── iso_clock ├── .gitignore ├── .metadata ├── README.md ├── lib │ ├── digit_data.dart │ ├── iso_clock.dart │ └── main.dart ├── pubspec.lock └── pubspec.yaml ├── iso_clock_dark_theme.gif ├── iso_clock_light_theme.gif └── iso_clock_scott_cook.zip /.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 | # Build stuff 24 | **/build/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | .dart_tool/ 29 | .flutter-plugins 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Other items since we are using a global .gitignore. 39 | **/.dart_tool 40 | **/.pubspec.lock 41 | **/.flutter-plugins 42 | **/.packages 43 | **/.pub-cache/ 44 | **/.pub/ 45 | **/build/ 46 | 47 | # Remove pubspec.lock for libraries 48 | flutter_clock_helper/pubspec.lock 49 | 50 | # Other items grabbed from flutter/flutter repo 51 | flutter_*.png 52 | linked_*.ds 53 | unlinked.ds 54 | unlinked_spec.ds 55 | 56 | # Android related 57 | **/android 58 | **/android/**/gradle-wrapper.jar 59 | **/android/.gradle 60 | **/android/captures/ 61 | **/android/gradlew 62 | **/android/gradlew.bat 63 | **/android/local.properties 64 | **/android/**/GeneratedPluginRegistrant.java 65 | **/android/key.properties 66 | *.jks 67 | 68 | # iOS/XCode related 69 | **/ios 70 | **/ios/**/*.mode1v3 71 | **/ios/**/*.mode2v3 72 | **/ios/**/*.moved-aside 73 | **/ios/**/*.pbxuser 74 | **/ios/**/*.perspectivev3 75 | **/ios/**/*sync/ 76 | **/ios/**/.sconsign.dblite 77 | **/ios/**/.tags* 78 | **/ios/**/.vagrant/ 79 | **/ios/**/DerivedData/ 80 | **/ios/**/Icon? 81 | **/ios/**/Pods/ 82 | **/ios/**/.symlinks/ 83 | **/ios/**/profile 84 | **/ios/**/xcuserdata 85 | **/ios/.generated/ 86 | **/ios/Flutter/App.framework 87 | **/ios/Flutter/Flutter.framework 88 | **/ios/Flutter/Flutter.podspec 89 | **/ios/Flutter/Generated.xcconfig 90 | **/ios/Flutter/app.flx 91 | **/ios/Flutter/app.zip 92 | **/ios/Flutter/flutter_assets/ 93 | **/ios/Flutter/flutter_export_environment.sh 94 | **/ios/ServiceDefinitions.json 95 | **/ios/Runner/GeneratedPluginRegistrant.* 96 | 97 | # Exceptions to above rules. 98 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 The Chromium Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iso Clock - Flutter Challenge 2 | 3 | This is my take on a classic digital clock look with a modern 3D "flat" design and playful animations. I come from a web background and this is my first Flutter App, so go easy! It has been an absolute delight working with Flutter and I'm excited to encorporate it into future projects. 4 | 5 | 6 | 7 | 8 | **Design Decisions...** 9 | - The Digit faces use color gradients to invoke more playful depth 10 | - I kept this clock a clock, weather and dates on screen were visually distracting 11 | - Digits are large and in charge for readability 12 | - Lay flat diametric digits look super cool, but upright isometric design had better readability 13 | - Dark mode is very dark while keeping enough contrast so light emission is low at night 14 | 15 | **Things I've learned...** 16 | - dartpad.dev is a-m-a-z-i-n-g for sketch-ups and quick prototyping of ideas 17 | - `Stack` Class's `Overflow.clip` does not work on transformed children per https://github.com/flutter/flutter/issues/40216- 18 | - Running in profile mode on your device with performance monitor is perfect for testing "real-life" app performance quickly 19 | - Flutter can take a lot you throw at it, so I had to be careful that I used best practices 20 | 21 | **Anti-aliasiang Artifacts Workaround...** 22 | 23 | The `ClipRect` Class's anti-aliasing technique causes artifacts on shape's borders with fractional coordinates. This can be seen in this flutter [issue](https://github.com/flutter/flutter/issues/25009). The edges of the digits in my app were experiencing this issue, and it was pertinent to the design that edges were flush and continuous with the backaground. 24 | 25 | To work around this issue, I essentially put a visual band-aid on top of the artifacts by: 26 | - wrap the main Digit face (DigitFacade) with a parent container 27 | - scale down the size of the Digit face so the perimeter does not share sides with parent 28 | - add an inset border overlay on top of the digit facade that covers the artifacts created by the anti-aliasing algorithm 29 | -------------------------------------------------------------------------------- /flutter_clock_helper/.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: 9bd02a1787bd264fde13a445a900ca28d08e3cef 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /flutter_clock_helper/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.1] - TODO: Add release date. 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /flutter_clock_helper/README.md: -------------------------------------------------------------------------------- 1 | # Flutter Clock Helper 2 | 3 | This package provides scaffolding code for the clock in the Flutter Clock contest. 4 | 5 | Contestants: Do not edit this code. 6 | 7 | 8 | ## Model 9 | Provides data that can change in the clock. Look in model.dart for more details. 10 | 11 | * Time format (12- or 24-hour) 12 | * Location 13 | * Temperature 14 | * Temperature high 15 | * Temperature low 16 | * Temperature unit 17 | * Weather unit 18 | 19 | 20 | ## Clock Customizer 21 | Provides customizations for your clock (based on the model). 22 | You can change the behavior of your clock based on these customizations. 23 | 24 | 25 | 26 | To use inside your app's `main.dart`: 27 | 28 | ``` 29 | runApp(ClockCustomizer((ClockModel model) => AnalogClock(model))); 30 | ``` 31 | 32 | For more information, see the code inside [lib/](lib). 33 | For a full example, see the [Analog Clock](../analog_clock) or [Digital Clock](../digital_clock) in this GitHub repo. 34 | -------------------------------------------------------------------------------- /flutter_clock_helper/customizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookmscott/FlutterClockChallenge/03cd17593f7734517582b0c5b78e919e2cd0b541/flutter_clock_helper/customizer.png -------------------------------------------------------------------------------- /flutter_clock_helper/lib/customizer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'model.dart'; 8 | 9 | /// Returns a clock [Widget] with [ClockModel]. 10 | /// 11 | /// Example: 12 | /// final myClockBuilder = (ClockModel model) => AnalogClock(model); 13 | /// 14 | /// Contestants: Do not edit this. 15 | typedef Widget ClockBuilder(ClockModel model); 16 | 17 | /// Wrapper for clock widget to allow for customizations. 18 | /// 19 | /// Puts the clock in landscape orientation with an aspect ratio of 5:3. 20 | /// Provides a drawer where users can customize the data that is sent to the 21 | /// clock. To show/hide the drawer, double-tap the clock. 22 | /// 23 | /// To use the [ClockCustomizer], pass your clock into it, using a ClockBuilder. 24 | /// 25 | /// ``` 26 | /// final myClockBuilder = (ClockModel model) => AnalogClock(model); 27 | /// return ClockCustomizer(myClockBuilder); 28 | /// ``` 29 | /// Contestants: Do not edit this. 30 | class ClockCustomizer extends StatefulWidget { 31 | const ClockCustomizer(this._clock); 32 | 33 | /// The clock widget with [ClockModel], to update and display. 34 | final ClockBuilder _clock; 35 | 36 | @override 37 | _ClockCustomizerState createState() => _ClockCustomizerState(); 38 | } 39 | 40 | class _ClockCustomizerState extends State { 41 | final _model = ClockModel(); 42 | ThemeMode _themeMode = ThemeMode.light; 43 | bool _configButtonShown = false; 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | _model.addListener(_handleModelChange); 49 | } 50 | 51 | @override 52 | void dispose() { 53 | _model.removeListener(_handleModelChange); 54 | _model.dispose(); 55 | super.dispose(); 56 | } 57 | 58 | void _handleModelChange() => setState(() {}); 59 | 60 | Widget _enumMenu( 61 | String label, T value, List items, ValueChanged onChanged) { 62 | return InputDecorator( 63 | decoration: InputDecoration( 64 | labelText: label, 65 | ), 66 | child: DropdownButtonHideUnderline( 67 | child: DropdownButton( 68 | value: value, 69 | isDense: true, 70 | onChanged: onChanged, 71 | items: items.map((T item) { 72 | return DropdownMenuItem( 73 | value: item, 74 | child: Text(enumToString(item)), 75 | ); 76 | }).toList(), 77 | ), 78 | ), 79 | ); 80 | } 81 | 82 | Widget _switch(String label, bool value, ValueChanged onChanged) { 83 | return Row( 84 | children: [ 85 | Expanded(child: Text(label)), 86 | Switch( 87 | value: value, 88 | onChanged: onChanged, 89 | ), 90 | ], 91 | ); 92 | } 93 | 94 | Widget _textField( 95 | String currentValue, String label, ValueChanged onChanged) { 96 | return TextField( 97 | decoration: InputDecoration( 98 | hintText: currentValue, 99 | helperText: label, 100 | ), 101 | onChanged: onChanged, 102 | ); 103 | } 104 | 105 | Widget _configDrawer(BuildContext context) { 106 | return SafeArea( 107 | child: Drawer( 108 | child: Padding( 109 | padding: const EdgeInsets.all(16.0), 110 | child: SingleChildScrollView( 111 | child: Column( 112 | children: [ 113 | _textField(_model.location, 'Location', (String location) { 114 | setState(() { 115 | _model.location = location; 116 | }); 117 | }), 118 | _textField(_model.temperature.toString(), 'Temperature', 119 | (String temperature) { 120 | setState(() { 121 | _model.temperature = double.parse(temperature); 122 | }); 123 | }), 124 | _enumMenu('Theme', _themeMode, 125 | ThemeMode.values.toList()..remove(ThemeMode.system), 126 | (ThemeMode mode) { 127 | setState(() { 128 | _themeMode = mode; 129 | }); 130 | }), 131 | _switch('24-hour format', _model.is24HourFormat, (bool value) { 132 | setState(() { 133 | _model.is24HourFormat = value; 134 | }); 135 | }), 136 | _enumMenu( 137 | 'Weather', _model.weatherCondition, WeatherCondition.values, 138 | (WeatherCondition condition) { 139 | setState(() { 140 | _model.weatherCondition = condition; 141 | }); 142 | }), 143 | _enumMenu('Units', _model.unit, TemperatureUnit.values, 144 | (TemperatureUnit unit) { 145 | setState(() { 146 | _model.unit = unit; 147 | }); 148 | }), 149 | ], 150 | ), 151 | ), 152 | ), 153 | ), 154 | ); 155 | } 156 | 157 | Widget _configButton() { 158 | return Builder( 159 | builder: (BuildContext context) { 160 | return IconButton( 161 | icon: Icon(Icons.settings), 162 | tooltip: 'Configure clock', 163 | onPressed: () { 164 | Scaffold.of(context).openEndDrawer(); 165 | setState(() { 166 | _configButtonShown = false; 167 | }); 168 | }, 169 | ); 170 | }, 171 | ); 172 | } 173 | 174 | @override 175 | Widget build(BuildContext context) { 176 | final clock = Center( 177 | child: AspectRatio( 178 | aspectRatio: 5 / 3, 179 | child: Container( 180 | decoration: BoxDecoration( 181 | border: Border.all( 182 | width: 2, 183 | color: Theme.of(context).unselectedWidgetColor, 184 | ), 185 | ), 186 | child: widget._clock(_model), 187 | ), 188 | ), 189 | ); 190 | 191 | return MaterialApp( 192 | theme: ThemeData.light(), 193 | darkTheme: ThemeData.dark(), 194 | themeMode: _themeMode, 195 | debugShowCheckedModeBanner: false, 196 | home: Scaffold( 197 | resizeToAvoidBottomPadding: false, 198 | endDrawer: _configDrawer(context), 199 | body: SafeArea( 200 | child: GestureDetector( 201 | behavior: HitTestBehavior.opaque, 202 | onTap: () { 203 | setState(() { 204 | _configButtonShown = !_configButtonShown; 205 | }); 206 | }, 207 | child: Stack( 208 | children: [ 209 | clock, 210 | if (_configButtonShown) 211 | Positioned( 212 | top: 0, 213 | right: 0, 214 | child: Opacity( 215 | opacity: 0.7, 216 | child: _configButton(), 217 | ), 218 | ), 219 | ], 220 | ), 221 | ), 222 | ), 223 | ), 224 | ); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /flutter_clock_helper/lib/model.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | /// This is the model that contains the customization options for the clock. 9 | /// 10 | /// It is a [ChangeNotifier], so use [ChangeNotifier.addListener] to listen to 11 | /// changes to the model. Be sure to call [ChangeNotifier.removeListener] in 12 | /// your `dispose` method. 13 | /// 14 | /// Contestants: Do not edit this. 15 | class ClockModel extends ChangeNotifier { 16 | get is24HourFormat => _is24HourFormat; 17 | bool _is24HourFormat = true; 18 | set is24HourFormat(bool is24HourFormat) { 19 | if (_is24HourFormat != is24HourFormat) { 20 | _is24HourFormat = is24HourFormat; 21 | notifyListeners(); 22 | } 23 | } 24 | 25 | /// Current location String, for example 'Mountain View, CA'. 26 | get location => _location; 27 | String _location = 'Mountain View, CA'; 28 | set location(String location) { 29 | if (location != _location) { 30 | _location = location; 31 | notifyListeners(); 32 | } 33 | } 34 | 35 | /// Current temperature string, for example '22°C'. 36 | get temperature => _convertFromCelsius(_temperature); 37 | // Stored in degrees celsius, and converted based on the current unit setting 38 | num _temperature = 22.0; 39 | set temperature(num temperature) { 40 | temperature = _convertToCelsius(temperature); 41 | if (temperature != _temperature) { 42 | _temperature = temperature; 43 | _low = _temperature - 3.0; 44 | _high = _temperature + 4.0; 45 | notifyListeners(); 46 | } 47 | } 48 | 49 | /// Daily high temperature, for example '26'. 50 | get high => _convertFromCelsius(_high); 51 | // Stored in degrees celsius, and converted based on the current unit setting 52 | num _high = 26.0; 53 | set high(num high) { 54 | high = _convertToCelsius(high); 55 | if (high != _high) { 56 | _high = high; 57 | notifyListeners(); 58 | } 59 | } 60 | 61 | /// Daily low temperature, for example '19'. 62 | get low => _convertFromCelsius(_low); 63 | num _low = 19.0; 64 | set low(num low) { 65 | low = _convertToCelsius(low); 66 | if (low != _low) { 67 | _low = low; 68 | notifyListeners(); 69 | } 70 | } 71 | 72 | /// Weather condition text for the current weather, for example 'cloudy'. 73 | WeatherCondition get weatherCondition => _weatherCondition; 74 | WeatherCondition _weatherCondition = WeatherCondition.sunny; 75 | set weatherCondition(WeatherCondition weatherCondition) { 76 | if (weatherCondition != _weatherCondition) { 77 | _weatherCondition = weatherCondition; 78 | notifyListeners(); 79 | } 80 | } 81 | 82 | /// [WeatherCondition] value without the enum type. 83 | String get weatherString => enumToString(weatherCondition); 84 | 85 | /// Temperature unit, for example 'celsius'. 86 | TemperatureUnit get unit => _unit; 87 | TemperatureUnit _unit = TemperatureUnit.celsius; 88 | set unit(TemperatureUnit unit) { 89 | if (unit != _unit) { 90 | _unit = unit; 91 | notifyListeners(); 92 | } 93 | } 94 | 95 | /// Temperature with unit of measurement. 96 | String get temperatureString { 97 | return '${temperature.toStringAsFixed(1)}$unitString'; 98 | } 99 | 100 | /// Temperature high with unit of measurement. 101 | String get highString { 102 | return '${high.toStringAsFixed(1)}$unitString'; 103 | } 104 | 105 | /// Temperature low with unit of measurement. 106 | String get lowString { 107 | return '${low.toStringAsFixed(1)}$unitString'; 108 | } 109 | 110 | /// Temperature unit of measurement with degrees. 111 | String get unitString { 112 | switch (unit) { 113 | case TemperatureUnit.fahrenheit: 114 | return '°F'; 115 | case TemperatureUnit.celsius: 116 | default: 117 | return '°C'; 118 | } 119 | } 120 | 121 | num _convertFromCelsius(num degreesCelsius) { 122 | switch (unit) { 123 | case TemperatureUnit.fahrenheit: 124 | return 32.0 + degreesCelsius * 9.0 / 5.0; 125 | case TemperatureUnit.celsius: 126 | default: 127 | return degreesCelsius; 128 | break; 129 | } 130 | } 131 | 132 | num _convertToCelsius(num degrees) { 133 | switch (unit) { 134 | case TemperatureUnit.fahrenheit: 135 | return (degrees - 32.0) * 5.0 / 9.0; 136 | case TemperatureUnit.celsius: 137 | default: 138 | return degrees; 139 | break; 140 | } 141 | } 142 | } 143 | 144 | /// Weather condition in English. 145 | enum WeatherCondition { 146 | cloudy, 147 | foggy, 148 | rainy, 149 | snowy, 150 | sunny, 151 | thunderstorm, 152 | windy, 153 | } 154 | 155 | /// Temperature unit of measurement. 156 | enum TemperatureUnit { 157 | celsius, 158 | fahrenheit, 159 | } 160 | 161 | /// Removes the enum type and returns the value as a String. 162 | String enumToString(Object e) => e.toString().split('.').last; 163 | -------------------------------------------------------------------------------- /flutter_clock_helper/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_clock_helper 2 | description: Helper classes for Flutter Clock contest. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.2.2 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | intl: "^0.16.0" 13 | 14 | cupertino_icons: ^0.1.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter: 21 | uses-material-design: true 22 | -------------------------------------------------------------------------------- /iso_clock/.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 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /iso_clock/.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: 68587a0916366e9512a78df22c44163d041dd5f3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /iso_clock/README.md: -------------------------------------------------------------------------------- 1 | # iso_clock 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /iso_clock/lib/digit_data.dart: -------------------------------------------------------------------------------- 1 | class DeviceDimensions { 2 | final double parentWidth = 70.0; 3 | final double parentHeight = 110.0; 4 | } 5 | 6 | class RelativeBox { 7 | double left; 8 | double top; 9 | double width; 10 | double height; 11 | 12 | RelativeBox({this.left, this.top, this.width, this.height}); 13 | } 14 | 15 | 16 | class DigitData with DeviceDimensions { 17 | bool hasTwo; 18 | RelativeBox box1; 19 | RelativeBox box2; 20 | 21 | DigitData({this.hasTwo, this.box1, this.box2}); 22 | 23 | List get box1LTWH { 24 | List absoluteLTWH = new List(4); 25 | absoluteLTWH[0] = box1.left * parentWidth; 26 | absoluteLTWH[1] = box1.top * parentHeight; 27 | absoluteLTWH[2] = box1.width * parentWidth; 28 | absoluteLTWH[3] = box1.height * parentHeight; 29 | return(absoluteLTWH); 30 | } 31 | 32 | List get box2LTWH { 33 | List absoluteLTWH = new List(4); 34 | absoluteLTWH[0] = box2.left * parentWidth; 35 | absoluteLTWH[1] = box2.top * parentHeight; 36 | absoluteLTWH[2] = box2.width * parentWidth; 37 | absoluteLTWH[3] = box2.height * parentHeight; 38 | return(absoluteLTWH); 39 | } 40 | } 41 | 42 | class ListOfDigitData { 43 | List _digits = [ 44 | // 0 45 | DigitData( 46 | hasTwo: false, 47 | box1: RelativeBox(left: 0.3, top: 0.2, width: 0.4, height: 0.6), 48 | ), 49 | // 1 50 | DigitData( 51 | hasTwo: true, 52 | box1: RelativeBox(left: 0.0, top: 0.0, width: 0.35, height: 1.0), 53 | box2: RelativeBox(left: 0.65, top: 0.0, width: 0.35, height: 1.0), 54 | ), 55 | // 2 56 | DigitData( 57 | hasTwo: true, 58 | box1: RelativeBox(left: 0.0, top: 0.2, width: 0.7, height: 0.2), 59 | box2: RelativeBox(left: 0.3, top: 0.6, width: 0.7, height: 0.2), 60 | ), 61 | // 3 62 | DigitData( 63 | hasTwo: true, 64 | box1: RelativeBox(left: 0.0, top: 0.2, width: 0.7, height: 0.2), 65 | box2: RelativeBox(left: 0.0, top: 0.6, width: 0.7, height: 0.2), 66 | ), 67 | // 4 68 | DigitData( 69 | hasTwo: true, 70 | box1: RelativeBox(left: 0.3, top: 0.0, width: 0.4, height: 0.4), 71 | box2: RelativeBox(left: 0.0, top: 0.6, width: 0.7, height: 0.4), 72 | ), 73 | // 5 74 | DigitData( 75 | hasTwo: true, 76 | box1: RelativeBox(left: 0.3, top: 0.2, width: 0.7, height: 0.2), 77 | box2: RelativeBox(left: 0.0, top: 0.6, width: 0.7, height: 0.2), 78 | ), 79 | // 6 80 | DigitData( 81 | hasTwo: true, 82 | box1: RelativeBox(left: 0.3, top: 0.2, width: 0.7, height: 0.2), 83 | box2: RelativeBox(left: 0.3, top: 0.6, width: 0.4, height: 0.2), 84 | ), 85 | // 7 86 | DigitData( 87 | hasTwo: false, 88 | box1: RelativeBox(left: 0.0, top: 0.2, width: 0.7, height: 0.8), 89 | ), 90 | // 8 91 | DigitData( 92 | hasTwo: true, 93 | box1: RelativeBox(left: 0.3, top: 0.2, width: 0.4, height: 0.2), 94 | box2: RelativeBox(left: 0.3, top: 0.6, width: 0.4, height: 0.2), 95 | ), 96 | // 9 97 | DigitData( 98 | hasTwo: true, 99 | box1: RelativeBox(left: 0.3, top: 0.2, width: 0.4, height: 0.2), 100 | box2: RelativeBox(left: 0.0, top: 0.6, width: 0.7, height: 0.2), 101 | ), 102 | 103 | ]; 104 | 105 | get digits => _digits; 106 | } -------------------------------------------------------------------------------- /iso_clock/lib/iso_clock.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_clock_helper/model.dart'; 4 | import 'package:intl/intl.dart'; 5 | import 'package:iso_clock/digit_data.dart'; 6 | 7 | enum _Element { 8 | background, 9 | gradientLighter, 10 | gradientDarker, 11 | } 12 | 13 | final _lightTheme = { 14 | _Element.background: Colors.white, 15 | _Element.gradientLighter: [Colors.lightBlue[600], Colors.cyan[100]], 16 | _Element.gradientDarker: [Colors.lightBlue[300], Colors.cyan[100]], 17 | }; 18 | 19 | final _darkTheme = { 20 | _Element.background: Colors.black, 21 | _Element.gradientLighter: [Colors.grey[500], Colors.grey[900]], 22 | _Element.gradientDarker: [Colors.grey[700], Colors.grey[900]], 23 | }; 24 | 25 | final double _digitWidth = 70.0; 26 | final double _digitHeight = 110.0; 27 | 28 | var _currentTheme = _lightTheme; 29 | 30 | class IsoClock extends StatefulWidget { 31 | const IsoClock(this.model); 32 | 33 | final ClockModel model; 34 | 35 | @override 36 | _IsoClockState createState() => _IsoClockState(); 37 | } 38 | 39 | class _IsoClockState extends State { 40 | var _dateTime = DateTime.now(); 41 | Timer _timer; 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | widget.model.addListener(_updateModel); 47 | _updateTime(); 48 | _updateModel(); 49 | } 50 | 51 | @override 52 | void didUpdateWidget(IsoClock oldWidget) { 53 | super.didUpdateWidget(oldWidget); 54 | if (widget.model != oldWidget.model) { 55 | oldWidget.model.removeListener(_updateModel); 56 | widget.model.addListener(_updateModel); 57 | } 58 | } 59 | 60 | @override 61 | void dispose() { 62 | _timer?.cancel(); 63 | widget.model.removeListener(_updateModel); 64 | widget.model.dispose(); 65 | super.dispose(); 66 | } 67 | 68 | void _updateModel() { 69 | setState(() { 70 | // Cause the clock to rebuild when the model changes. 71 | }); 72 | } 73 | 74 | void _updateTime() { 75 | setState(() { 76 | _dateTime = DateTime.now(); 77 | _timer = Timer( 78 | Duration(minutes: 1) - 79 | Duration(seconds: _dateTime.second) - 80 | Duration(milliseconds: _dateTime.millisecond), 81 | _updateTime, 82 | ); 83 | }); 84 | } 85 | 86 | _firstDigit(int n) { 87 | if (n <= 9) { 88 | return 0; 89 | } else { 90 | return int.parse(n.toString().substring(0, 1)); 91 | } 92 | } 93 | 94 | _secondDigit(int n) { 95 | if (n <= 9) { 96 | return n; 97 | } else { 98 | return int.parse(n.toString().substring(1, 2)); 99 | } 100 | } 101 | 102 | @override 103 | Widget build(BuildContext context) { 104 | // set theme so it can be accessed by all children widgets 105 | _currentTheme = Theme.of(context).brightness == Brightness.light 106 | ? _lightTheme 107 | : _darkTheme; 108 | final hour = int.parse(DateFormat(widget.model.is24HourFormat ? 'HH' : 'hh') 109 | .format(_dateTime)); 110 | final minute = int.parse(DateFormat('mm').format(_dateTime)); 111 | 112 | return new Scaffold( 113 | backgroundColor: _currentTheme[_Element.background], 114 | body: Center( 115 | child: Row( 116 | mainAxisAlignment: MainAxisAlignment.spaceAround, 117 | children: [ 118 | SizedBox(width: 10), 119 | Digit(time: _firstDigit(hour), delay: 360), 120 | Digit(time: _secondDigit(hour), delay: 240), 121 | Colon(), 122 | Digit(time: _firstDigit(minute), delay: 120), 123 | Digit(time: _secondDigit(minute), delay: 0), 124 | SizedBox(width: 10), 125 | ], 126 | ), 127 | ), 128 | ); 129 | } 130 | } 131 | 132 | /// Digit 133 | /// ├── DigitFacade 134 | /// | ├── ThreeDimensionalHole 135 | /// | └── ThreeDimensionalBox 136 | /// └── AntiAliasPerimeterPadding 137 | 138 | class Digit extends StatelessWidget { 139 | final int time; 140 | final int delay; 141 | 142 | Digit({Key key, this.time, this.delay}) : super(key: key); 143 | 144 | @override 145 | Widget build(BuildContext context) { 146 | return Center( 147 | child: Transform( 148 | alignment: FractionalOffset.center, 149 | transform: Matrix4.skewX(-0.2), 150 | child: Container( 151 | width: _digitWidth, 152 | height: _digitHeight, 153 | color: _currentTheme[_Element.background], 154 | child: Stack( 155 | children: [ 156 | DigitFacade(time: time, delay: delay), 157 | AntiAliasPerimeterPadding(), 158 | ], 159 | ), 160 | ), 161 | ), 162 | ); 163 | } 164 | } 165 | 166 | /// DigitFacade contains the Digit's backdrop, ThreeDimensionalHole, 167 | /// and animations for ThreeDimensionalBox. 168 | /// See README for explanation of anti-aliasing work around 169 | class DigitFacade extends StatefulWidget { 170 | final int time; 171 | final int delay; 172 | 173 | DigitFacade({Key key, this.time, this.delay}) : super(key: key); 174 | @override 175 | _DigitFacadeState createState() => _DigitFacadeState(); 176 | } 177 | 178 | class _DigitFacadeState extends State 179 | with SingleTickerProviderStateMixin { 180 | AnimationController _controller; 181 | 182 | int _currentTime; 183 | bool _box1First = true; 184 | 185 | final Interval _quickerCurve = Interval(0.05, 1, curve: Curves.easeInOutQuad); 186 | final Interval _slowerCurve = Interval(0.15, 1, curve: Curves.easeInOutQuad); 187 | 188 | final List _digits = ListOfDigitData().digits; 189 | 190 | // Box One 191 | _animationStateBox1(time) { 192 | return new RelativeRectTween( 193 | begin: new RelativeRect.fromLTRB( 194 | _digits[time].box1LTWH[0], _digits[time].box1LTWH[1], 0.0, 0.0), 195 | end: new RelativeRect.fromLTRB(_digits[time].box1LTWH[0] + 120.0, 196 | _digits[time].box1LTWH[1] + 120.0, 0.0, 0.0), 197 | ).animate(CurvedAnimation( 198 | parent: _controller, 199 | curve: _box1First ? _quickerCurve : _slowerCurve, 200 | )); 201 | } 202 | 203 | // Box Two 204 | _animationStateBox2(time) { 205 | return new RelativeRectTween( 206 | begin: new RelativeRect.fromLTRB( 207 | _digits[time].box2LTWH[0], _digits[time].box2LTWH[1], 0.0, 0.0), 208 | end: new RelativeRect.fromLTRB(_digits[time].box2LTWH[0] + 120.0, 209 | _digits[time].box2LTWH[1] + 120.0, 0.0, 0.0), 210 | ).animate(CurvedAnimation( 211 | parent: _controller, 212 | curve: _box1First ? _slowerCurve : _quickerCurve, 213 | )); 214 | } 215 | 216 | @override 217 | void initState() { 218 | super.initState(); 219 | _controller = AnimationController( 220 | duration: Duration(seconds: 1), 221 | vsync: this, 222 | ); 223 | 224 | _currentTime = widget.time; 225 | } 226 | 227 | @override 228 | void didUpdateWidget(DigitFacade oldWidget) { 229 | super.didUpdateWidget(oldWidget); 230 | if (widget.time != oldWidget.time) { 231 | // Recess the Boxes currently in view 232 | Future.delayed(Duration(milliseconds: widget.delay), () { 233 | setState(() { 234 | _controller.forward(); 235 | _currentTime == 1 ? _box1First = false : _box1First = true; 236 | }); 237 | }); 238 | // Bring new Boxes into view 239 | Future.delayed(Duration(milliseconds: 600 + widget.delay), () { 240 | setState(() { 241 | _currentTime = widget.time; 242 | _controller.reverse(); 243 | _currentTime == 1 ? _box1First = true : _box1First = false; 244 | }); 245 | }); 246 | } 247 | } 248 | 249 | @override 250 | Widget build(BuildContext context) { 251 | return Center( 252 | child: Container( 253 | width: _digitWidth - 1, 254 | height: _digitHeight -1, 255 | child: ClipRect( 256 | clipBehavior: Clip.antiAlias, 257 | child: Stack( 258 | children: [ 259 | ThreeDimensionalHole(), 260 | PositionedTransition( 261 | rect: _animationStateBox1(_currentTime), 262 | child: ThreeDimensionalBox(ltwh: _digits[_currentTime].box1LTWH), 263 | ), 264 | if (_digits[_currentTime].hasTwo) 265 | PositionedTransition( 266 | rect: _animationStateBox2(_currentTime), 267 | child: ThreeDimensionalBox(ltwh: _digits[_currentTime].box2LTWH), 268 | ), 269 | ], 270 | ), 271 | ), 272 | ), 273 | ); 274 | } 275 | } 276 | 277 | class ThreeDimensionalHole extends StatelessWidget { 278 | @override 279 | Widget build(BuildContext context) { 280 | return Container( 281 | decoration: BoxDecoration( 282 | gradient: LinearGradient( 283 | begin: Alignment.centerLeft, 284 | end: Alignment.centerRight, 285 | colors: _currentTheme[_Element.gradientDarker], 286 | ), 287 | ), 288 | child: ClipRect( 289 | clipBehavior: Clip.antiAlias, 290 | child: Transform( 291 | alignment: FractionalOffset.topLeft, 292 | transform: Matrix4.skewX(0.7), 293 | child: Container( 294 | decoration: BoxDecoration( 295 | gradient: LinearGradient( 296 | begin: Alignment.topCenter, 297 | end: Alignment.bottomCenter, 298 | colors: _currentTheme[_Element.gradientLighter], 299 | ), 300 | ), 301 | ), 302 | ), 303 | ), 304 | ); 305 | } 306 | } 307 | 308 | class ThreeDimensionalBox extends StatelessWidget { 309 | /// ltwh: left, top, width, height of Box in relation 310 | /// to top left corner of parent container DigitFacade 311 | final List ltwh; 312 | ThreeDimensionalBox({this.ltwh}); 313 | 314 | @override 315 | Widget build(BuildContext context) { 316 | return Stack( 317 | children: [ 318 | // Main Face 319 | Container( 320 | width: ltwh[2], 321 | height: ltwh[3], 322 | color: _currentTheme[_Element.background], 323 | ), 324 | 325 | // Right Face 326 | Padding( 327 | padding: EdgeInsets.only(left: ltwh[2]), 328 | child: Transform( 329 | alignment: FractionalOffset.topLeft, 330 | transform: Matrix4.skewY(0.9), 331 | child: Container( 332 | width: 80.0, // grows down 333 | height: ltwh[3], 334 | decoration: BoxDecoration( 335 | gradient: LinearGradient( 336 | begin: Alignment.centerLeft, 337 | end: Alignment.centerRight, 338 | colors: _currentTheme[_Element.gradientDarker], 339 | ), 340 | ), 341 | ), 342 | ), 343 | ), 344 | 345 | // Bottom Face 346 | Padding( 347 | padding: EdgeInsets.only(top: ltwh[3]), 348 | child: Transform( 349 | alignment: FractionalOffset.topLeft, 350 | transform: Matrix4.skewX(0.7), // skew will not go out of y bounds 351 | child: Container( 352 | width: ltwh[2], 353 | height: 80.0, 354 | decoration: BoxDecoration( 355 | gradient: LinearGradient( 356 | begin: Alignment.topCenter, 357 | end: Alignment.bottomCenter, 358 | colors: _currentTheme[_Element.gradientLighter], 359 | ), 360 | ), 361 | ), 362 | ), 363 | ), 364 | ], 365 | ); 366 | } 367 | } 368 | 369 | /// Perimeter padding is an inset "border" used to cover the scaled 370 | /// down DigitFacade's edges that contain anti-aliasing artifacts 371 | class AntiAliasPerimeterPadding extends StatelessWidget { 372 | Widget build(BuildContext context) { 373 | return Stack( 374 | children: [ 375 | Row( 376 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 377 | crossAxisAlignment: CrossAxisAlignment.stretch, 378 | children: [ 379 | _aliasBumperLR(), 380 | _aliasBumperLR(), 381 | ], 382 | ), 383 | Column( 384 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 385 | crossAxisAlignment: CrossAxisAlignment.stretch, 386 | children: [ 387 | _aliasBumperTB(), 388 | _aliasBumperTB(), 389 | ], 390 | ), 391 | ], 392 | ); 393 | } 394 | 395 | Widget _aliasBumperLR() { 396 | return Container( 397 | width: 1, 398 | color: _currentTheme[_Element.background], 399 | ); 400 | } 401 | 402 | Widget _aliasBumperTB() { 403 | return Container( 404 | height: 1, 405 | color: _currentTheme[_Element.background], 406 | ); 407 | } 408 | } 409 | 410 | class Colon extends StatelessWidget { 411 | @override 412 | Widget build(BuildContext context) { 413 | return Transform( 414 | alignment: FractionalOffset.center, 415 | transform: Matrix4.skewX(-0.2), 416 | child: Container( 417 | height: 90, 418 | child: Column( 419 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 420 | children: [ 421 | Container( 422 | height: 15, 423 | width: 15, 424 | child: ThreeDimensionalHole(), 425 | ), 426 | Container( 427 | height: 15, 428 | width: 15, 429 | child: ThreeDimensionalHole(), 430 | ), 431 | ], 432 | ), 433 | ), 434 | ); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /iso_clock/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_clock_helper/customizer.dart'; 4 | import 'package:flutter_clock_helper/model.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'iso_clock.dart'; 9 | 10 | void main() { 11 | // A temporary measure until Platform supports web and TargetPlatform supports 12 | // macOS. 13 | if (!kIsWeb && Platform.isMacOS) { 14 | // https://github.com/flutter/flutter/issues/31366 15 | // See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override. 16 | debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; 17 | } 18 | runApp(ClockCustomizer((ClockModel model) => IsoClock(model))); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /iso_clock/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.11" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.5" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.3" 60 | cupertino_icons: 61 | dependency: "direct main" 62 | description: 63 | name: cupertino_icons 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.1.3" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_clock_helper: 73 | dependency: "direct main" 74 | description: 75 | path: "..\\flutter_clock_helper" 76 | relative: true 77 | source: path 78 | version: "1.0.0+1" 79 | flutter_test: 80 | dependency: "direct dev" 81 | description: flutter 82 | source: sdk 83 | version: "0.0.0" 84 | image: 85 | dependency: transitive 86 | description: 87 | name: image 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "2.1.4" 91 | intl: 92 | dependency: transitive 93 | description: 94 | name: intl 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.16.0" 98 | matcher: 99 | dependency: transitive 100 | description: 101 | name: matcher 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "0.12.6" 105 | meta: 106 | dependency: transitive 107 | description: 108 | name: meta 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "1.1.8" 112 | path: 113 | dependency: transitive 114 | description: 115 | name: path 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.6.4" 119 | pedantic: 120 | dependency: transitive 121 | description: 122 | name: pedantic 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.8.0+1" 126 | petitparser: 127 | dependency: transitive 128 | description: 129 | name: petitparser 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "2.4.0" 133 | quiver: 134 | dependency: transitive 135 | description: 136 | name: quiver 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "2.0.5" 140 | sky_engine: 141 | dependency: transitive 142 | description: flutter 143 | source: sdk 144 | version: "0.0.99" 145 | source_span: 146 | dependency: transitive 147 | description: 148 | name: source_span 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.5.5" 152 | stack_trace: 153 | dependency: transitive 154 | description: 155 | name: stack_trace 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.9.3" 159 | stream_channel: 160 | dependency: transitive 161 | description: 162 | name: stream_channel 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.0.0" 166 | string_scanner: 167 | dependency: transitive 168 | description: 169 | name: string_scanner 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.0.5" 173 | term_glyph: 174 | dependency: transitive 175 | description: 176 | name: term_glyph 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.1.0" 180 | test_api: 181 | dependency: transitive 182 | description: 183 | name: test_api 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "0.2.11" 187 | typed_data: 188 | dependency: transitive 189 | description: 190 | name: typed_data 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.1.6" 194 | vector_math: 195 | dependency: transitive 196 | description: 197 | name: vector_math 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "2.0.8" 201 | xml: 202 | dependency: transitive 203 | description: 204 | name: xml 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "3.5.0" 208 | sdks: 209 | dart: ">=2.4.0 <3.0.0" 210 | -------------------------------------------------------------------------------- /iso_clock/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: iso_clock 2 | description: Isometric Representation of Clock 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | flutter_clock_helper: 23 | path: ../flutter_clock_helper 24 | cupertino_icons: ^0.1.2 25 | 26 | dev_dependencies: 27 | flutter_test: 28 | sdk: flutter 29 | 30 | 31 | # For information on the generic Dart part of this file, see the 32 | # following page: https://dart.dev/tools/pub/pubspec 33 | 34 | # The following section is specific to Flutter. 35 | flutter: 36 | 37 | # The following line ensures that the Material Icons font is 38 | # included with your application, so that you can use the icons in 39 | # the material Icons class. 40 | uses-material-design: true 41 | 42 | # To add assets to your application, add an assets section, like this: 43 | # assets: 44 | # - images/a_dot_burr.jpeg 45 | # - images/a_dot_ham.jpeg 46 | 47 | # An image asset can refer to one or more resolution-specific "variants", see 48 | # https://flutter.dev/assets-and-images/#resolution-aware. 49 | 50 | # For details regarding adding assets from package dependencies, see 51 | # https://flutter.dev/assets-and-images/#from-packages 52 | 53 | # To add custom fonts to your application, add a fonts section here, 54 | # in this "flutter" section. Each entry in this list should have a 55 | # "family" key with the font family name, and a "fonts" key with a 56 | # list giving the asset and other descriptors for the font. For 57 | # example: 58 | # fonts: 59 | # - family: Schyler 60 | # fonts: 61 | # - asset: fonts/Schyler-Regular.ttf 62 | # - asset: fonts/Schyler-Italic.ttf 63 | # style: italic 64 | # - family: Trajan Pro 65 | # fonts: 66 | # - asset: fonts/TrajanPro.ttf 67 | # - asset: fonts/TrajanPro_Bold.ttf 68 | # weight: 700 69 | # 70 | # For details regarding fonts from package dependencies, 71 | # see https://flutter.dev/custom-fonts/#from-packages 72 | -------------------------------------------------------------------------------- /iso_clock_dark_theme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookmscott/FlutterClockChallenge/03cd17593f7734517582b0c5b78e919e2cd0b541/iso_clock_dark_theme.gif -------------------------------------------------------------------------------- /iso_clock_light_theme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookmscott/FlutterClockChallenge/03cd17593f7734517582b0c5b78e919e2cd0b541/iso_clock_light_theme.gif -------------------------------------------------------------------------------- /iso_clock_scott_cook.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookmscott/FlutterClockChallenge/03cd17593f7734517582b0c5b78e919e2cd0b541/iso_clock_scott_cook.zip --------------------------------------------------------------------------------