├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ └── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── project.pbxproj ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── Podfile.lock ├── .gitignore └── Podfile ├── store ├── icon.png ├── phone_1.png ├── phone_2.png ├── feature_graphic.png └── README.md ├── lib ├── clocks │ ├── generative_clock │ │ ├── ATTRIBUTION.md │ │ ├── globals.dart │ │ ├── custom_randomizer.dart │ │ ├── LICENSE │ │ ├── digit_checker.dart │ │ ├── node.dart │ │ └── generative_clock.dart │ ├── particle_clock │ │ ├── ATTRIBUTION.md │ │ ├── palette.dart │ │ ├── clock_bg_particle_painter.dart │ │ ├── LICENSE │ │ ├── bg_fx.dart │ │ ├── clock_face_painter.dart │ │ ├── particle.dart │ │ ├── clock_seconds_painter.dart │ │ ├── particle_clock.dart │ │ ├── clock_fx_painter.dart │ │ ├── utils │ │ │ └── rnd.dart │ │ ├── clock_fx.dart │ │ ├── scene.dart │ │ └── particle_clock_fx.dart │ └── clock_face.dart ├── main.dart └── update_button.dart ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── 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 │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── dev │ │ │ │ │ └── shorebird │ │ │ │ │ └── u_shorebird_clock │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── shorebird.yaml ├── pubspec.yaml ├── .gitignore ├── LICENSE ├── .github └── workflows │ ├── push_to_deploy_android.yaml │ └── patch.yaml ├── .metadata ├── analysis_options.yaml ├── README.md ├── assets └── palettes.json └── pubspec.lock /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /store/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/store/icon.png -------------------------------------------------------------------------------- /store/phone_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/store/phone_1.png -------------------------------------------------------------------------------- /store/phone_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/store/phone_2.png -------------------------------------------------------------------------------- /lib/clocks/generative_clock/ATTRIBUTION.md: -------------------------------------------------------------------------------- 1 | This was adapted from https://github.com/Fabian-Stein/flutterClock. -------------------------------------------------------------------------------- /lib/clocks/particle_clock/ATTRIBUTION.md: -------------------------------------------------------------------------------- 1 | This was adapted from https://github.com/miickel/flutter_particle_clock. -------------------------------------------------------------------------------- /store/feature_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/store/feature_graphic.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | feature_graphic is based on: 2 | https://unsplash.com/photos/KMn4VEeEPR8 3 | 4 | icon is based on: 5 | https://unsplash.com/photos/Xd_H7iOwKN0 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/time_shift/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/dev/shorebird/u_shorebird_clock/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.shorebird.u_shorebird_clock 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /shorebird.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to configure the Shorebird updater used by your application. 2 | # Learn more at https://shorebird.dev 3 | # This file should be checked into version control. 4 | 5 | # This is the unique identifier assigned to your app. 6 | # It is used by your app to request the correct patches from Shorebird servers. 7 | app_id: b3be02c7-97a8-4135-be71-814cc43d096e 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/clocks/particle_clock/palette.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | /// Holds a list of colors. 4 | class Palette { 5 | /// The palette's color members. All unique. 6 | List? components; 7 | 8 | Palette({this.components}); 9 | 10 | /// Creates a new palette from JSON. 11 | factory Palette.fromJson(List json) { 12 | var components = json.map((c) => Color(int.tryParse(c)!)).toList(); 13 | return Palette(components: components); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 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 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: time_shift 2 | description: Demo app showing Shorebird updates. 3 | publish_to: "none" 4 | 5 | version: 1.0.5+1 6 | 7 | environment: 8 | sdk: ">=2.19.4 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | restart_app: ^1.2.1 14 | shorebird_code_push: ^1.1.3 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter_lints: ^2.0.0 21 | 22 | flutter: 23 | uses-material-design: true 24 | assets: 25 | - assets/palettes.json 26 | - shorebird.yaml 27 | -------------------------------------------------------------------------------- /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/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - restart_app (0.0.1): 4 | - Flutter 5 | 6 | DEPENDENCIES: 7 | - Flutter (from `Flutter`) 8 | - restart_app (from `.symlinks/plugins/restart_app/ios`) 9 | 10 | EXTERNAL SOURCES: 11 | Flutter: 12 | :path: Flutter 13 | restart_app: 14 | :path: ".symlinks/plugins/restart_app/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 18 | restart_app: 806659942bf932f6ce51c5372f91ce5e81c8c14a 19 | 20 | PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 21 | 22 | COCOAPODS: 1.11.0 23 | -------------------------------------------------------------------------------- /lib/clocks/generative_clock/globals.dart: -------------------------------------------------------------------------------- 1 | import 'custom_randomizer.dart'; 2 | import 'digit_checker.dart'; 3 | 4 | double aspectRatio = 5 / 3; 5 | 6 | CustomRandomizer randomizer = CustomRandomizer(); 7 | DigitChecker digitChecker = DigitChecker(); 8 | 9 | double maxDistanceBetweenNodes = 0.05; 10 | // the number of columns and rows, with which the screen is partitioned 11 | // we do this as an optimization, the reason is explained in the paint method in digital clock 12 | int columns = (aspectRatio / maxDistanceBetweenNodes).ceil(); 13 | int rows = (1.0 / maxDistanceBetweenNodes).ceil(); 14 | 15 | int quadrants() { 16 | return rows * columns; 17 | } 18 | -------------------------------------------------------------------------------- /lib/clocks/clock_face.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:time_shift/clocks/generative_clock/generative_clock.dart'; 3 | import 'package:time_shift/clocks/particle_clock/particle_clock.dart'; 4 | 5 | /// All available clock faces. 6 | enum ClockFace { 7 | generative, 8 | particle, 9 | } 10 | 11 | extension ClockFaceWidget on ClockFace { 12 | /// Builds the clock face widget associated with this [ClockFace]. 13 | Widget get widget { 14 | switch (this) { 15 | case ClockFace.generative: 16 | return const GenerativeClock(); 17 | case ClockFace.particle: 18 | return const ParticleClock(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.2.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/clocks/generative_clock/custom_randomizer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | /// a custom randomizer, which offers convenience functions 4 | class CustomRandomizer { 5 | Random rng = Random(); 6 | 7 | /// returns a random floating point value between [low] and [high]. 8 | double doubleInRange(double low, high) { 9 | assert(high > low); 10 | return rng.nextDouble() * (high - low) + low; 11 | } 12 | 13 | /// returns a random floating point value between 0.0 and [high]. 14 | double doubleUpTo(double high) { 15 | return rng.nextDouble() * high; 16 | } 17 | 18 | /// returns a random [Point] with a x value between [xLow] and [xHigh] 19 | /// and a y value between [yLow] and [yHigh]. 20 | Point point(double xLow, xHigh, yLow, yHigh) { 21 | return Point( 22 | doubleInRange(xLow, xHigh), 23 | doubleInRange(yLow, yHigh), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | 46 | # Shorebird related 47 | .shorebird/ -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/clock_bg_particle_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'clock_fx.dart'; 4 | 5 | class ClockBgParticlePainter extends CustomPainter { 6 | ClockFx? fx; 7 | 8 | // ChangeNotifier used as repaint notifier. 9 | ClockBgParticlePainter({required this.fx}) : super(repaint: fx); 10 | 11 | @override 12 | void paint(Canvas canvas, Size size) { 13 | for (var p in fx!.particles) { 14 | var pos = Offset(p!.x, p.y); 15 | 16 | var paint = Paint() 17 | ..color = p.color.withAlpha((255 * p.a).floor()) 18 | ..strokeWidth = p.size * .2 19 | ..style = p.isFilled ? PaintingStyle.fill : PaintingStyle.stroke; 20 | 21 | if (p.isFilled) { 22 | var rect = Rect.fromCenter( 23 | center: pos, 24 | width: p.size, 25 | height: p.size, 26 | ); 27 | 28 | canvas.drawRect(rect, paint); 29 | } else { 30 | canvas.drawCircle(pos, p.size / 1.2, paint); 31 | } 32 | } 33 | } 34 | 35 | @override 36 | bool shouldRepaint(oldDelegate) => false; 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Mickel Andersson mickel@ultimatemachine.se (ultimatemachine.se & tayl.app) 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 | -------------------------------------------------------------------------------- /.github/workflows/push_to_deploy_android.yaml: -------------------------------------------------------------------------------- 1 | name: Shorebird Push to Deploy Android 2 | 3 | on: 4 | push: 5 | branches: 6 | - releases/* 7 | 8 | env: 9 | SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }} 10 | 11 | jobs: 12 | push_to_deploy_android: 13 | defaults: 14 | run: 15 | shell: bash 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: 📚 Git Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: 🔐 Setup Keystore 24 | run: | 25 | echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/keystore.jks 26 | echo "${{ secrets.KEY_PROPERTIES }}" > android/key.properties 27 | 28 | - name: 🐦 Setup Shorebird 29 | uses: shorebirdtech/setup-shorebird@v1 30 | 31 | - name: 🚀 Shorebird Release 32 | if: ${{ github.event.created }} 33 | uses: shorebirdtech/shorebird-release@v0 34 | with: 35 | platform: android 36 | 37 | - name: 🚀 Shorebird Patch 38 | if: ${{ !github.event.created }} 39 | uses: shorebirdtech/shorebird-patch@v0 40 | with: 41 | platform: android 42 | 43 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Mickel Andersson ([ultimatemachine.se](https://ultimatemachine.se) & [tayl.app](https://tayl.app)) 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. -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66 17 | base_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66 18 | - platform: android 19 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 20 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 21 | - platform: ios 22 | create_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66 23 | base_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /.github/workflows/patch.yaml: -------------------------------------------------------------------------------- 1 | name: Shorebird Patch 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | clock_face: 7 | description: "Clock face to use" 8 | required: true 9 | type: choice 10 | options: 11 | - particle 12 | - generative 13 | default: "particle" 14 | 15 | jobs: 16 | create_patch: 17 | defaults: 18 | run: 19 | shell: bash 20 | 21 | runs-on: macos-latest 22 | 23 | steps: 24 | - name: 📚 Git Checkout 25 | uses: actions/checkout@v3 26 | 27 | - name: ☕️ Setup Java 28 | uses: actions/setup-java@v3 29 | with: 30 | distribution: "temurin" 31 | java-version: "11" 32 | 33 | - name: 🐦 Setup Shorebird 34 | uses: shorebirdtech/setup-shorebird@v0 35 | 36 | - name: 🔐 Setup Keystore 37 | run: | 38 | echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/keystore.jks 39 | echo "${{ secrets.KEY_PROPERTIES }}" > android/key.properties 40 | 41 | - name: ✨ Create Patch 42 | run: shorebird patch android --force -- --dart-define clock_face=${{ inputs.clock_face }} 43 | env: 44 | SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }} 45 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'clocks/clock_face.dart'; 5 | 6 | /// Use this argument to choose a clock face. Defaults to particle if value is 7 | /// provided or if the value provided is unrecognized. 8 | /// 9 | /// Example: 10 | /// `shorebird run -- --dart-define=clock_face=generative` 11 | const clockFaceArgName = 'clock_face'; 12 | 13 | /// Use this argument to show the performance overlay. 14 | /// 15 | /// Example: 16 | /// `shorebird run -- --dart-define=show_perf_overlay=true` 17 | const showPerfOverlayArgName = 'show_perf_overlay'; 18 | 19 | void main() { 20 | WidgetsFlutterBinding.ensureInitialized(); 21 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); 22 | 23 | const clockName = String.fromEnvironment(clockFaceArgName); 24 | final clock = ClockFace.values.firstWhere( 25 | (clock) => clock.name == clockName, 26 | orElse: () => ClockFace.generative, 27 | ); 28 | 29 | runApp( 30 | MaterialApp( 31 | showPerformanceOverlay: 32 | const bool.fromEnvironment(showPerfOverlayArgName), 33 | debugShowCheckedModeBanner: false, 34 | home: Scaffold( 35 | body: clock.widget, 36 | ), 37 | ), 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/bg_fx.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | 5 | import 'clock_fx.dart'; 6 | import 'particle.dart'; 7 | import 'utils/rnd.dart'; 8 | 9 | const easingDelayDuration = Duration(seconds: 15); 10 | 11 | class BgFx extends ClockFx { 12 | BgFx({required Size size, required DateTime? time}) 13 | : super( 14 | size: size, 15 | time: time, 16 | numParticles: 120, 17 | ); 18 | 19 | @override 20 | void tick(Duration duration) { 21 | var secFrac = 1 - (DateTime.now().millisecond / 1000); 22 | 23 | var vecSpeed = duration.compareTo(easingDelayDuration) > 0 24 | ? max(.2, Curves.easeInOutSine.transform(secFrac)) 25 | : 1; 26 | 27 | for (var p in particles) { 28 | p!.y -= p.vy * vecSpeed; 29 | 30 | if (p.y > height || p.y < 0 || p.life == 0) { 31 | _activateParticle(p); 32 | } 33 | } 34 | 35 | super.tick(duration); 36 | } 37 | 38 | void _activateParticle(Particle p) { 39 | var xRnd = Rnd.getDouble(0, width / 5); 40 | p.x = Rnd.getBool() ? width - xRnd : 0 + xRnd; 41 | p.y = Rnd.getDouble(0, height); 42 | p.a = Rnd.ratio > .95 ? Rnd.getDouble(.6, .8) : Rnd.getDouble(.08, .4); 43 | p.isFilled = Rnd.getBool(); 44 | p.size = Rnd.getDouble(height / 20, height / 5); 45 | 46 | p.life = 1; 47 | 48 | p.vx = 0; 49 | p.vy = Rnd.getDouble(-3, 3); 50 | 51 | double v = Rnd.getDouble(.1, .5); 52 | 53 | p.vx = 0; 54 | p.vy *= v; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 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 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/clocks/generative_clock/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. -------------------------------------------------------------------------------- /lib/clocks/particle_clock/clock_face_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// The number of sections to divide the clock face. 5 | const int arms = 60; 6 | 7 | /// Which parts of the sections to indicate quarters. 8 | final int quarters = (arms / 12).floor(); 9 | 10 | class ClockFacePainter extends CustomPainter { 11 | final Color? accentColor; 12 | 13 | ClockFacePainter({required this.accentColor}); 14 | 15 | @override 16 | void paint(Canvas canvas, Size size) { 17 | var cx = size.width / 2; 18 | var cy = size.height / 2; 19 | var radius = min(size.width / 2, size.height / 2); 20 | var circleRaidus = radius * .012; 21 | 22 | var paint = Paint() 23 | ..style = PaintingStyle.fill 24 | ..strokeWidth = circleRaidus * .5 25 | ..color = accentColor!; 26 | 27 | for (var i = 1; i <= arms; i++) { 28 | var angle = i * 360 / arms; 29 | var a = -angle * pi / 180; 30 | var point = Offset(cx + radius * cos(a), cy + radius * sin(a)); 31 | 32 | if (i % quarters == 0) { 33 | canvas.drawCircle( 34 | point, 35 | circleRaidus * 2, 36 | paint 37 | ..color = accentColor! 38 | ..style = PaintingStyle.fill, 39 | ); 40 | } else { 41 | canvas.drawCircle( 42 | point, 43 | circleRaidus, 44 | paint 45 | ..color = accentColor!.withAlpha(100) 46 | ..style = PaintingStyle.stroke, 47 | ); 48 | } 49 | } 50 | } 51 | 52 | @override 53 | bool shouldRepaint(oldDelegate) => false; 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Time Shift 2 | 3 | This repository contains a sample Flutter application using Shorebird's CodePush solution. 4 | 5 | This example is built using the [Flutter Particle Clock](https://github.com/miickel/flutter_particle_clock). 6 | 7 | App icons generated from icon.png using [App Icon Generator](https://www.appicon.co/). 8 | 9 | ## Goal 10 | 11 | The purpose of this app is to be a test-balloon for store approvals. Currently it's only 12 | been submitted to Google Play App Store, but we will eventually submit it to other stores. 13 | 14 | * [Google Play App Store](https://play.google.com/store/apps/details?id=dev.shorebird.u_shorebird_clock) 15 | 16 | ## Design 17 | 18 | Purpose is to have something worth code-pushing to. In this case, we figured it might 19 | be fun to have an app to which we could code-push a different clockface every hour. 20 | 21 | Flutter ran a clock contest a few years back and there are numerous gorgeous examples 22 | of clocks built with Flutter: 23 | https://docs.flutter.dev/clock 24 | 25 | The current build contains two clocks. It is currently hard coded to show the Particle 26 | Clock, but a straightforward expansion would be to control which clock face is shown 27 | with a compile-time flag and then write a script which builds N-different versions of 28 | the app for the N clock faces and pushes a new one to Shorebird every hour. 29 | 30 | Another feature I'd like to add is more prevalent display of the author's name 31 | and link. e.g. if you tap the clock it could say "@miickel" in the corner 32 | and when you click on it it could open the original source in a browser window. 33 | 34 | Patches welcome. 35 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/particle.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Particle { 4 | /// The particle's X-position. 5 | double x; 6 | 7 | /// The particle's Y-position. 8 | double y; 9 | 10 | /// The particle's emit angle in radians. 11 | double a; 12 | 13 | /// Speed vector (horizontally). 14 | double vx; 15 | 16 | /// Speed vector (vertically). 17 | double vy; 18 | 19 | /// The distance from canvas center. 20 | double dist; 21 | 22 | /// The distance from canvas center in percentage (0-1). 23 | double distFrac; 24 | 25 | /// The particle's size. 26 | double size; 27 | 28 | /// The life length of the particle (0-1). 29 | double life; 30 | 31 | /// The life remaining of the particle (0-1). 32 | double lifeLeft; 33 | 34 | /// If the particle is filled or only stroked. 35 | bool isFilled; 36 | 37 | /// If the particle should have "speed marks". 38 | bool? isFlowing; 39 | 40 | /// The color of the particle. 41 | Color color; 42 | 43 | /// The particle's distribution value. 44 | int distribution; 45 | 46 | /// The particle's type. 47 | ParticleType type; 48 | 49 | Particle({ 50 | this.x = 0, 51 | this.y = 0, 52 | this.a = 0, 53 | this.vx = 0, 54 | this.vy = 0, 55 | this.dist = 0, 56 | this.distFrac = 0, 57 | this.size = 0, 58 | this.life = 0, 59 | this.lifeLeft = 0, 60 | this.isFilled = false, 61 | this.isFlowing = false, 62 | this.color = Colors.black, 63 | this.distribution = 0, 64 | this.type = ParticleType.noise, 65 | }); 66 | } 67 | 68 | enum ParticleType { 69 | hour, 70 | minute, 71 | noise, 72 | } 73 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Shorebird Clock 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | time_shift 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/clock_seconds_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class ClockSecondsPainter extends CustomPainter { 6 | final Color? accentColor; 7 | 8 | ClockSecondsPainter({required this.accentColor}); 9 | 10 | @override 11 | void paint(Canvas canvas, Size size) { 12 | var center = Offset(size.width / 2, size.height / 2); 13 | 14 | var now = DateTime.now(); 15 | var secSize = pi / 50; 16 | 17 | // Animate smoothly with easing. 18 | var msecs = Curves.easeOutCirc.transform(now.millisecond / 1000); 19 | msecs = msecs * 2 * pi / 60; 20 | 21 | var timeAngle = (now.second - 1) * (2 * pi / 60) + msecs - secSize / 2; 22 | timeAngle = timeAngle - pi / 2; 23 | 24 | var radiusInner = size.height * .13; 25 | var radiusOuter = size.height * .9; 26 | 27 | var paint = Paint() 28 | ..style = PaintingStyle.stroke 29 | ..strokeWidth = center.dy * .003 30 | ..strokeCap = StrokeCap.round 31 | ..color = accentColor!.withAlpha(40); 32 | 33 | var outerRect = Rect.fromCenter( 34 | center: center, 35 | width: radiusOuter, 36 | height: radiusOuter, 37 | ); 38 | 39 | var innerRect = Rect.fromCenter( 40 | center: center, 41 | width: radiusInner, 42 | height: radiusInner, 43 | ); 44 | 45 | // Outer track. 46 | canvas.drawArc(outerRect, 0, pi * 2, false, paint); 47 | 48 | // Inner track. 49 | canvas.drawArc(innerRect, 0, pi * 2, false, paint); 50 | 51 | paint 52 | ..strokeWidth = center.dy * .015 53 | ..color = accentColor!; 54 | 55 | // Outer indicator. 56 | canvas.drawArc(outerRect, timeAngle, secSize, false, paint); 57 | 58 | // Inner indicator. 59 | canvas.drawArc( 60 | innerRect, timeAngle - (secSize * 6), secSize * 12, false, paint); 61 | } 62 | 63 | @override 64 | bool shouldRepaint(oldDelegate) => false; 65 | } 66 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /lib/clocks/generative_clock/digit_checker.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | /// The [DigitChecker] stores the information of the 10 digits and can determine if a point intersects with a given digit 4 | class DigitChecker { 5 | // ten digits with 15 "pixels" each 6 | // we "paint" the digits by defining the 1 as filled and 0 as unfilled 7 | List digits = [ 8 | 1, 1, 1, // 0 9 | 1, 0, 1, 10 | 1, 0, 1, 11 | 1, 0, 1, 12 | 1, 1, 1, 13 | 14 | 0, 1, 1, // 1 15 | 0, 0, 1, 16 | 0, 0, 1, 17 | 0, 0, 1, 18 | 0, 0, 1, 19 | 20 | 1, 1, 1, // 2 21 | 0, 0, 1, 22 | 1, 1, 1, 23 | 1, 0, 0, 24 | 1, 1, 1, 25 | 26 | 1, 1, 1, // 3 27 | 0, 0, 1, 28 | 0, 1, 1, 29 | 0, 0, 1, 30 | 1, 1, 1, 31 | 32 | 1, 0, 1, // 4 33 | 1, 0, 1, 34 | 1, 1, 1, 35 | 0, 0, 1, 36 | 0, 0, 1, 37 | 38 | 1, 1, 1, // 5 39 | 1, 0, 0, 40 | 1, 1, 1, 41 | 0, 0, 1, 42 | 1, 1, 1, 43 | 44 | 1, 1, 1, // 6 45 | 1, 0, 0, 46 | 1, 1, 1, 47 | 1, 0, 1, 48 | 1, 1, 1, 49 | 50 | 1, 1, 1, // 7 51 | 0, 0, 1, 52 | 0, 0, 1, 53 | 0, 0, 1, 54 | 0, 0, 1, 55 | 56 | 1, 1, 1, // 8 57 | 1, 0, 1, 58 | 1, 1, 1, 59 | 1, 0, 1, 60 | 1, 1, 1, 61 | 62 | 1, 1, 1, // 9 63 | 1, 0, 1, 64 | 1, 1, 1, 65 | 0, 0, 1, 66 | 1, 1, 1 67 | ]; 68 | 69 | /// is the [point] inside the [digit] stretched over the [rect] 70 | bool isIn(int digit, Point point, Rectangle rect) { 71 | if (rect.containsPoint(point)) { 72 | double x = point.x - rect.topLeft.x; 73 | double y = point.y - rect.topLeft.y; 74 | 75 | int column = (3 * x / rect.width).floor(); 76 | int row = (5 * y / rect.height).floor(); 77 | 78 | int offset = digit * 15; 79 | 80 | int index = offset + column + row * 3; 81 | 82 | if (digits[index] == 1) { 83 | return true; 84 | } 85 | } 86 | return false; 87 | } 88 | 89 | /// is the [point] outside the [digit] stretched over the [rect] 90 | bool isOut(int digit, Point point, Rectangle rect) { 91 | return !isIn(digit, point, rect); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/particle_clock.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'palette.dart'; 7 | import 'scene.dart'; 8 | 9 | class ParticleClock extends StatefulWidget { 10 | const ParticleClock({super.key}); 11 | 12 | @override 13 | State createState() => _ParticleClockState(); 14 | } 15 | 16 | class _ParticleClockState extends State 17 | with SingleTickerProviderStateMixin { 18 | DateTime _dateTime = DateTime.now(); 19 | Timer? _timer; 20 | double seek = 0.0; 21 | double seekIncrement = 1 / 3600; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _updateTime(); 27 | _updateModel(); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | _timer?.cancel(); 33 | super.dispose(); 34 | } 35 | 36 | Future> _loadPalettes() async { 37 | String data = 38 | await DefaultAssetBundle.of(context).loadString("assets/palettes.json"); 39 | var palettes = json.decode(data) as List; 40 | return palettes.map((p) => Palette.fromJson(p)).toList(); 41 | } 42 | 43 | void _updateModel() { 44 | // Cause the clock to rebuild when the model changes. 45 | setState(() {}); 46 | } 47 | 48 | void _updateTime() { 49 | setState(() { 50 | _dateTime = DateTime.now(); 51 | _timer = Timer( 52 | const Duration(seconds: 1) - 53 | Duration(milliseconds: _dateTime.millisecond), 54 | _updateTime, 55 | ); 56 | }); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return Directionality( 62 | textDirection: TextDirection.ltr, 63 | child: FutureBuilder( 64 | future: _loadPalettes(), 65 | builder: (context, snapshot) { 66 | if (!snapshot.hasData) { 67 | return const SizedBox( 68 | child: Center( 69 | child: Text("Could not load palettes."), 70 | ), 71 | ); 72 | } 73 | 74 | List? palettes = snapshot.data; 75 | 76 | return LayoutBuilder( 77 | builder: (context, constraints) { 78 | return Scene( 79 | size: constraints.biggest, 80 | palettes: palettes, 81 | time: _dateTime, 82 | brightness: Theme.of(context).brightness, 83 | ); 84 | }, 85 | ); 86 | }, 87 | ), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/clock_fx_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'clock_fx.dart'; 6 | import 'particle.dart'; 7 | 8 | /// Where handles should start, denoted in percentage of full radius. 9 | const double handlesStart = .1; 10 | 11 | /// Where noise should start, denoted in percentage of full radius. 12 | const double noiseStart = .15; 13 | 14 | /// Alpha value for noise particles. 15 | const double noiseAlpha = 160; 16 | 17 | class ClockFxPainter extends CustomPainter { 18 | ClockFx? fx; 19 | 20 | // ChangeNotifier used as repaint notifier. 21 | ClockFxPainter({required this.fx}) : super(repaint: fx); 22 | 23 | @override 24 | void paint(Canvas canvas, Size size) { 25 | for (var p in fx!.particles) { 26 | double a; 27 | 28 | // Fade in particles by calculating alpha based on distance. 29 | if (p!.type == ParticleType.noise) { 30 | a = max(0.0, (p.distFrac - .13) / p.distFrac) * 255; 31 | a = min(a, min(noiseAlpha, p.lifeLeft * 3 * 255)); 32 | } else { 33 | a = p.distFrac <= handlesStart 34 | ? 255 * Curves.easeIn.transform(p.distFrac / handlesStart) 35 | : min(255, p.lifeLeft * 10 * 255); 36 | } 37 | 38 | var alpha = a.floor(); 39 | 40 | var circlePaint = Paint() 41 | ..style = PaintingStyle.stroke 42 | ..strokeWidth = p.size / 4 43 | ..color = p.color.withAlpha(alpha); 44 | 45 | var particlePosition = Offset(p.x, p.y); 46 | 47 | // Fill some particles. 48 | if (p.isFilled) { 49 | circlePaint.style = PaintingStyle.fill; 50 | 51 | // Draw gradient arc stroke on some filled particles. 52 | if (p.isFlowing!) { 53 | var threshold = p.distribution < 2 ? .2 : .4; 54 | var flowAlpha = max(0, (p.distFrac - threshold) / p.distFrac) * alpha; 55 | var shader = SweepGradient( 56 | colors: [ 57 | p.color.withAlpha(0), 58 | p.color.withAlpha(flowAlpha.floor()), 59 | ], 60 | startAngle: pi, 61 | endAngle: 2 * pi, 62 | transform: GradientRotation(p.a), 63 | stops: const [.6, 1], 64 | ).createShader(Rect.fromCircle( 65 | center: fx!.center, 66 | radius: p.dist, 67 | )); 68 | 69 | var gradientPaint = Paint() 70 | ..strokeWidth = p.size / 2 71 | ..shader = shader 72 | ..style = PaintingStyle.stroke; 73 | 74 | canvas.drawCircle(fx!.center, p.dist, gradientPaint); 75 | } 76 | } 77 | 78 | canvas.drawCircle(particlePosition, p.size, circlePaint); 79 | } 80 | } 81 | 82 | @override 83 | bool shouldRepaint(oldDelegate) => true; 84 | } 85 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/utils/rnd.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/painting.dart'; 4 | 5 | import '../palette.dart'; 6 | 7 | class Rnd { 8 | static int _seed = DateTime.now().millisecondsSinceEpoch; 9 | static Random random = Random(_seed); 10 | 11 | static set seed(int val) => random = Random(_seed = val); 12 | static int get seed => _seed; 13 | 14 | /// Gets the next double. 15 | static get ratio => random.nextDouble(); 16 | 17 | /// Gets a random int between [min] and [max]. 18 | static int getInt(int min, int max) { 19 | return min + random.nextInt(max - min); 20 | } 21 | 22 | /// Gets a random double between [min] and [max]. 23 | static double getDouble(double min, double max) { 24 | return min + random.nextDouble() * (max - min); 25 | } 26 | 27 | /// Gets a random boolean with chance [chance]. 28 | static bool getBool([double chance = 0.5]) { 29 | return random.nextDouble() < chance; 30 | } 31 | 32 | /// Randomize the positions of items in a list. 33 | static List shuffle(List list) { 34 | for (int i = 0, l = list.length; i < l; i++) { 35 | int j = random.nextInt(l); 36 | if (j == i) { 37 | continue; 38 | } 39 | dynamic item = list[j]; 40 | list[j] = list[i]; 41 | list[i] = item; 42 | } 43 | return list; 44 | } 45 | 46 | /// Randomly selects an item from a list. 47 | static dynamic getItem(List list) { 48 | return list[random.nextInt(list.length)]; 49 | } 50 | 51 | /// Gets a random palette from a list of palettes and sorts its' colors by luminance. 52 | /// 53 | /// Given if [dark] or not, this method makes sure the luminance of the background color is valid. 54 | static Palette getPalette(List? palettes, bool dark) { 55 | Palette? result; 56 | 57 | while (result == null) { 58 | Palette palette = Rnd.getItem(palettes!); 59 | List colors = Rnd.shuffle(palette.components!) as List; 60 | 61 | var luminance = colors[0].computeLuminance(); 62 | 63 | if (dark ? luminance <= .1 : luminance >= .1) { 64 | var lumDiff = colors 65 | .sublist(1) 66 | .asMap() 67 | .map( 68 | (i, color) => MapEntry( 69 | i, 70 | [i, (luminance - color.computeLuminance()).abs()], 71 | ), 72 | ) 73 | .values 74 | .toList(); 75 | 76 | lumDiff.sort((List a, List b) { 77 | return a[1].compareTo(b[1]); 78 | }); 79 | 80 | List sortedColors = 81 | lumDiff.map((d) => colors[d[0] + 1 as int]).toList(); 82 | 83 | result = Palette( 84 | components: [colors[0]] + sortedColors, 85 | ); 86 | } 87 | } 88 | return result; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystoreProperties = new Properties() 29 | def keystorePropertiesFile = rootProject.file('key.properties') 30 | if (keystorePropertiesFile.exists()) { 31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 32 | } 33 | 34 | android { 35 | compileSdkVersion flutter.compileSdkVersion 36 | ndkVersion flutter.ndkVersion 37 | 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | 43 | kotlinOptions { 44 | jvmTarget = '1.8' 45 | } 46 | 47 | sourceSets { 48 | main.java.srcDirs += 'src/main/kotlin' 49 | } 50 | 51 | defaultConfig { 52 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 53 | applicationId "dev.shorebird.u_shorebird_clock" 54 | // You can update the following values to match your application needs. 55 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 56 | minSdkVersion flutter.minSdkVersion 57 | targetSdkVersion flutter.targetSdkVersion 58 | versionCode flutterVersionCode.toInteger() 59 | versionName flutterVersionName 60 | } 61 | 62 | signingConfigs { 63 | release { 64 | keyAlias keystoreProperties['keyAlias'] 65 | keyPassword keystoreProperties['keyPassword'] 66 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 67 | storePassword keystoreProperties['storePassword'] 68 | } 69 | } 70 | buildTypes { 71 | release { 72 | signingConfig signingConfigs.release 73 | } 74 | } 75 | } 76 | 77 | flutter { 78 | source '../..' 79 | } 80 | 81 | dependencies { 82 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 83 | } 84 | -------------------------------------------------------------------------------- /lib/update_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:restart_app/restart_app.dart'; 3 | import 'package:shorebird_code_push/shorebird_code_push.dart'; 4 | 5 | class UpdateButton extends StatefulWidget { 6 | const UpdateButton({super.key}); 7 | 8 | @override 9 | State createState() => _UpdateButtonState(); 10 | } 11 | 12 | enum UpdateStatus { 13 | idle, 14 | checking, 15 | finishingCheck, 16 | } 17 | 18 | class _UpdateButtonState extends State { 19 | UpdateStatus status = UpdateStatus.idle; 20 | bool _haveUpdate = false; 21 | final _shorebirdCodePush = ShorebirdCodePush(); 22 | 23 | Future _doUpdateCheck() { 24 | // Ask the Shorebird servers if there is a new patch available. 25 | return _shorebirdCodePush.isNewPatchAvailableForDownload(); 26 | } 27 | 28 | Future _checkForUpdate() async { 29 | setState(() { 30 | status = UpdateStatus.checking; 31 | }); 32 | final isUpdateAvailable = await _doUpdateCheck(); 33 | 34 | if (!mounted) return; 35 | 36 | if (isUpdateAvailable) { 37 | await _shorebirdCodePush.downloadUpdateIfAvailable(); 38 | } 39 | setState(() { 40 | _haveUpdate = isUpdateAvailable; 41 | status = UpdateStatus.finishingCheck; 42 | }); 43 | Future.delayed(const Duration(milliseconds: 500), () { 44 | setState(() { 45 | status = UpdateStatus.idle; 46 | }); 47 | }); 48 | } 49 | 50 | Widget widgetForStatus(UpdateStatus status) { 51 | switch (status) { 52 | case UpdateStatus.idle: 53 | if (_haveUpdate) { 54 | return const Icon(Icons.restart_alt); 55 | } else { 56 | // TODO(eseidel): Use a color so the rocket isn't blue, maybe a 57 | // partially tranparent gray instead? Maybe get a color from the 58 | // current clock face? 59 | return const Icon(Icons.rocket); 60 | } 61 | case UpdateStatus.checking: 62 | return const SizedBox( 63 | width: 25, 64 | height: 25, 65 | child: CircularProgressIndicator( 66 | strokeWidth: 2, 67 | ), 68 | ); 69 | case UpdateStatus.finishingCheck: 70 | return const Icon(Icons.thumb_up_outlined); 71 | } 72 | } 73 | 74 | void Function()? actionForStatus(UpdateStatus status) { 75 | switch (status) { 76 | case UpdateStatus.idle: 77 | if (_haveUpdate) { 78 | return Restart.restartApp; 79 | } else { 80 | return _checkForUpdate; 81 | } 82 | case UpdateStatus.checking: 83 | return null; 84 | case UpdateStatus.finishingCheck: 85 | return null; 86 | } 87 | } 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | final widget = widgetForStatus(status); 92 | final action = actionForStatus(status); 93 | 94 | return TextButton( 95 | onPressed: action, 96 | child: widget, 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/clock_fx.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | 5 | import 'palette.dart'; 6 | import 'particle.dart'; 7 | import 'utils/rnd.dart'; 8 | 9 | const Color transparent = Color.fromARGB(0, 0, 0, 0); 10 | 11 | abstract class ClockFx with ChangeNotifier { 12 | /// The available canvas width for the FX. 13 | late double width; 14 | 15 | /// The available canvas height for the FX. 16 | late double height; 17 | 18 | /// The minimum value of [width] and [height]. 19 | late double sizeMin; 20 | 21 | /// The center of the canvas. 22 | late Offset center; 23 | 24 | /// The area from wich to spawn new particles. 25 | late Rect spawnArea; 26 | 27 | /// The colors used for painting. 28 | Palette? palette; 29 | 30 | /// All the FX's particles. 31 | late List particles; 32 | 33 | /// The maximum number of particles. 34 | int numParticles; 35 | 36 | /// Date and time used for rendering time-specific effects. 37 | DateTime? time; 38 | 39 | ClockFx({ 40 | required Size size, 41 | this.time, 42 | this.numParticles = 5000, 43 | }) { 44 | particles = List.filled(numParticles, null); 45 | palette = Palette(components: [transparent, transparent]); 46 | setSize(size); 47 | } 48 | 49 | /// Initializes the particle effect by resetting all the particles and assigning a random color from the palette. 50 | void init() { 51 | if (palette == null) return; 52 | for (int i = 0; i < numParticles; i++) { 53 | var color = Rnd.getItem(palette!.components!); 54 | particles[i] = Particle(color: color); 55 | resetParticle(i); 56 | } 57 | } 58 | 59 | /// Sets the palette used for coloring. 60 | void setPalette(Palette palette) { 61 | this.palette = palette; 62 | var colors = palette.components!.sublist(1); 63 | var accentColor = colors[colors.length - 1]; 64 | particles.where((p) => p != null).forEach((p) => p!.color = 65 | p.type == ParticleType.noise ? Rnd.getItem(colors) : accentColor); 66 | } 67 | 68 | /// Sets the time used for time-specific effects. 69 | void setTime(DateTime? time) { 70 | this.time = time; 71 | } 72 | 73 | /// Sets the canvas size and updates dependent values. 74 | void setSize(Size size) { 75 | width = size.width; 76 | height = size.height; 77 | sizeMin = min(width, height); 78 | center = Offset(width / 2, height / 2); 79 | spawnArea = Rect.fromLTRB( 80 | center.dx - sizeMin / 100, 81 | center.dy - sizeMin / 100, 82 | center.dx + sizeMin / 100, 83 | center.dy + sizeMin / 100, 84 | ); 85 | init(); 86 | } 87 | 88 | /// Resets a particle's values. 89 | Particle resetParticle(int i) { 90 | Particle p = particles[i]!; 91 | p.size = p.a = p.vx = p.vy = p.life = p.lifeLeft = 0; 92 | p.x = center.dx; 93 | p.y = center.dy; 94 | return p; 95 | } 96 | 97 | void tick(Duration duration) { 98 | notifyListeners(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /lib/clocks/generative_clock/node.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'globals.dart'; 3 | 4 | /// [Node] represents one of the moving elements in the net 5 | /// [coordinate] of the node 6 | /// the coordinate system we use has its origin in the middle of the screen for convinience 7 | /// the maximal y range is -0.5 to 0.5 8 | /// the maximal x range is -0.5 * aspectRatio to 0.5 * aspectRatio 9 | /// [velocity]: how does the node move every timestep 10 | class Node { 11 | late Point coordinate; 12 | Point velocity = randomizer.point(-0.5, 0.5, -0.5, 0.5); 13 | int quadrant = 0; 14 | int index = 0; 15 | bool isStatic; 16 | 17 | /// returns a node with randomized values 18 | /// if [isStatic] is true, then the node does not move and its coordinate is more more likely to be around the (0,0) 19 | Node(this.isStatic) { 20 | if (!isStatic) { 21 | coordinate = 22 | randomizer.point(-aspectRatio / 2, aspectRatio / 2, -0.5, 0.5); 23 | // velocity = randomizer.doubleInRange(0.2, 1.0); 24 | velocity = randomizer.point(-0.5, 0.5, -0.5, 0.5); 25 | } else { 26 | Point randomPoint = randomizer.point( 27 | -aspectRatio / 2 + 1.5 / 12.0, 28 | aspectRatio / 2 - 1.5 / 12.0, 29 | -0.5 + 3.0 / 12.0, 30 | 0.5 - 3.0 / 12.0, 31 | ); 32 | coordinate = randomPoint; 33 | //velocity = 0.0; 34 | velocity = const Point(0, 0); 35 | } 36 | } 37 | 38 | // update the position of the node and compute in which quadrant it is located 39 | void update() { 40 | if (!isStatic) { 41 | //update position 42 | coordinate += velocity * 0.004; 43 | 44 | double maxX = aspectRatio / 2, maxY = 0.5; 45 | 46 | // if the point has reached a wall, then we move it to the opposite wall 47 | // i think this is called a periodic boundary condition 48 | if (coordinate.x > maxX || coordinate.x < -maxX) { 49 | coordinate = Point(-coordinate.x, coordinate.y); 50 | } 51 | if (coordinate.y > maxY || coordinate.y < -maxY) { 52 | coordinate = Point(coordinate.x, -coordinate.y); 53 | } 54 | 55 | //compute quadrant of node 56 | int xQuadrant = 57 | (columns * (coordinate.x + aspectRatio / 2) / aspectRatio).floor(); 58 | int yQuadrant = (rows * (coordinate.y + 0.5)).floor(); 59 | quadrant = max(0, min(columns * yQuadrant + xQuadrant, quadrants() - 1)); 60 | } 61 | } 62 | 63 | /// gives back the result of the right Outside/Inside check, according to the [checkInside] flag 64 | bool checkCoordinate(int hour, minute, bool checkInside) { 65 | if (checkInside) { 66 | return _isInsideOfNumbers(hour, minute); 67 | } else { 68 | return _isOutsideOfNumbers(hour, minute); 69 | } 70 | } 71 | 72 | /// check if the coordinate of the node is in one of the four digits, which represent [hour] and [minute] 73 | bool _isOutsideOfNumbers(int hour, minute) { 74 | int digit1 = hour ~/ 10; 75 | int digit2 = hour % 10; 76 | 77 | int digit3 = minute ~/ 10; 78 | int digit4 = minute % 10; 79 | 80 | double width = 3 * aspectRatio / 20; 81 | double height = 5 * width / 3; 82 | double distance = width / 3; 83 | 84 | Rectangle rect1 = 85 | Rectangle(-2 * (width + distance), -height / 2, width, height); 86 | Rectangle rect2 = 87 | Rectangle(-1 * (width + distance), -height / 2, width, height); 88 | Rectangle rect3 = Rectangle(distance, -height / 2, width, height); 89 | Rectangle rect4 = 90 | Rectangle(2 * distance + width, -height / 2, width, height); 91 | 92 | return digitChecker.isOut(digit1, coordinate, rect1) && 93 | digitChecker.isOut(digit2, coordinate, rect2) && 94 | digitChecker.isOut(digit3, coordinate, rect3) && 95 | digitChecker.isOut(digit4, coordinate, rect4); 96 | } 97 | 98 | // the inverse of the function above 99 | bool _isInsideOfNumbers(int hour, minute) { 100 | return !_isOutsideOfNumbers(hour, minute); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/scene.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/scheduler.dart'; 5 | 6 | import 'bg_fx.dart'; 7 | import 'clock_bg_particle_painter.dart'; 8 | import 'clock_face_painter.dart'; 9 | import 'clock_fx.dart'; 10 | import 'clock_fx_painter.dart'; 11 | import 'clock_seconds_painter.dart'; 12 | import 'palette.dart'; 13 | import 'particle_clock_fx.dart'; 14 | import 'utils/rnd.dart'; 15 | 16 | class Scene extends StatefulWidget { 17 | final Size? size; 18 | final List? palettes; 19 | final DateTime? time; 20 | final Brightness? brightness; 21 | 22 | const Scene({ 23 | Key? key, 24 | this.size, 25 | this.palettes, 26 | this.time, 27 | this.brightness, 28 | }) : super(key: key); 29 | 30 | @override 31 | SceneState createState() => SceneState(); 32 | } 33 | 34 | class SceneState extends State with SingleTickerProviderStateMixin { 35 | late Ticker _ticker; 36 | ClockFx? _fx; 37 | BgFx? _bgFx; 38 | late Palette _palette; 39 | Color? _bgColor; 40 | Color? _accentColor; 41 | 42 | @override 43 | void initState() { 44 | _ticker = createTicker(_tick)..start(); 45 | _fx = ParticleClockFx( 46 | size: widget.size!, 47 | time: widget.time, 48 | ); 49 | _bgFx = BgFx( 50 | size: widget.size!, 51 | time: widget.time, 52 | ); 53 | _updatePalette(); 54 | super.initState(); 55 | } 56 | 57 | @override 58 | void dispose() { 59 | _ticker.dispose(); 60 | super.dispose(); 61 | } 62 | 63 | @override 64 | void didUpdateWidget(Scene oldWidget) { 65 | super.didUpdateWidget(oldWidget); 66 | if (oldWidget.time != widget.time || 67 | oldWidget.brightness != widget.brightness) { 68 | _fx!.setTime(widget.time); 69 | _bgFx!.setTime(widget.time); 70 | 71 | // Change palette every 15th second to keep things interesting. 72 | if (widget.time!.second % 15 == 0 || 73 | oldWidget.brightness != widget.brightness) { 74 | _updatePalette(); 75 | } 76 | } 77 | 78 | if (oldWidget.size != widget.size) { 79 | _fx!.setSize(widget.size!); 80 | _bgFx!.setSize(widget.size!); 81 | _bgFx!.tick(const Duration(days: 0)); // Trigger repaint. 82 | } 83 | } 84 | 85 | /// Randomly selects a new palette that conforms to the clock's brightness setting. 86 | /// The background color is set to the first component of the palette, and the accent 87 | /// color is set to the color of the palette that is furthest from the background color 88 | /// in terms of luminosity. 89 | void _updatePalette() { 90 | var isDarkMode = widget.brightness == Brightness.dark; 91 | _palette = Rnd.getPalette(widget.palettes, isDarkMode); 92 | _bgColor = _palette.components![0]; 93 | _accentColor = _palette.components![_palette.components!.length - 1]; 94 | _fx!.setPalette(_palette); 95 | _bgFx!.setPalette(_palette); 96 | } 97 | 98 | void _tick(Duration duration) { 99 | _fx!.tick(duration); 100 | if (widget.time!.second % 5 == 0) _bgFx!.tick(duration); 101 | } 102 | 103 | @override 104 | Widget build(BuildContext context) { 105 | return AnimatedContainer( 106 | duration: const Duration(milliseconds: 1500), 107 | curve: Curves.easeOut, 108 | color: _bgColor, 109 | child: ClipRect( 110 | child: Stack( 111 | children: [ 112 | _buildBgBlurFx(), 113 | _buildClockFace(), 114 | CustomPaint( 115 | painter: ClockFxPainter(fx: _fx), 116 | child: Container(), 117 | ), 118 | ], 119 | ), 120 | ), 121 | ); 122 | } 123 | 124 | Widget _buildBgBlurFx() { 125 | return RepaintBoundary( 126 | child: Stack( 127 | children: [ 128 | CustomPaint( 129 | painter: ClockBgParticlePainter( 130 | fx: _bgFx, 131 | ), 132 | child: const SizedBox(), 133 | ), 134 | BackdropFilter( 135 | filter: ImageFilter.blur( 136 | sigmaX: widget.size!.width * .05, 137 | sigmaY: 0, 138 | ), 139 | child: const Text(" "), 140 | ), 141 | ], 142 | ), 143 | ); 144 | } 145 | 146 | Widget _buildClockFace() { 147 | var faceSize = widget.size!.height * .85; 148 | return Center( 149 | child: SizedBox( 150 | width: faceSize, 151 | height: faceSize, 152 | child: Stack( 153 | children: [ 154 | // Paint the second hand. 155 | CustomPaint( 156 | size: Size(faceSize, faceSize), 157 | painter: ClockSecondsPainter( 158 | accentColor: _accentColor, 159 | ), 160 | ), 161 | 162 | // Paint the clock face. 163 | RepaintBoundary( 164 | key: Key(_accentColor.toString()), 165 | child: CustomPaint( 166 | size: widget.size!, 167 | painter: ClockFacePainter( 168 | accentColor: _accentColor, 169 | ), 170 | ), 171 | ), 172 | ], 173 | ), 174 | ), 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /lib/clocks/particle_clock/particle_clock_fx.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | 5 | import 'clock_fx.dart'; 6 | import 'particle.dart'; 7 | import 'utils/rnd.dart'; 8 | 9 | const easingDelayDuration = Duration(seconds: 10); 10 | 11 | /// Probabilities of Hour, Minute, Noise. 12 | final particleDistributions = [2, 4, 100]; 13 | 14 | /// Number of "arms" to emit noise particles from center. 15 | const int noiseAngles = 2000; 16 | 17 | /// Threshold for particles to go rouge. Lower = more particles. 18 | const rougeDistributionLmt = 85; 19 | 20 | /// Threshold for particles to go jelly. Lower = more particles. 21 | const jellyDistributionLmt = 97; 22 | 23 | class ParticleClockFx extends ClockFx { 24 | ParticleClockFx({required Size size, required DateTime? time}) 25 | : super( 26 | size: size, 27 | time: time, 28 | ); 29 | 30 | @override 31 | void tick(Duration duration) { 32 | var secFrac = DateTime.now().millisecond / 1000; 33 | 34 | var vecSpeed = duration.compareTo(easingDelayDuration) > 0 35 | ? max(.2, Curves.easeInOutSine.transform(1 - secFrac)) 36 | : 1; 37 | 38 | var vecSpeedInv = Curves.easeInSine.transform(secFrac); 39 | 40 | // Used to avoid emitting all particles at once. 41 | var maxSpawnPerTick = 10; 42 | 43 | particles.asMap().forEach((i, p) { 44 | // Movement 45 | p!.x -= p.vx * vecSpeed; 46 | p.y -= p.vy * vecSpeed; 47 | 48 | p.dist = _getDistanceFromCenter(p); 49 | p.distFrac = p.dist / (sizeMin / 2); 50 | 51 | p.lifeLeft = p.life - p.distFrac; 52 | 53 | // Gradually reduce the speed of Noise particles. 54 | if (p.type == ParticleType.noise) { 55 | p.vx -= p.lifeLeft * p.vx * .001; 56 | p.vy -= p.lifeLeft * p.vy * .001; 57 | } 58 | 59 | // Gradually reduce the size of all particles. 60 | if (p.lifeLeft < .3) { 61 | p.size -= p.size * .0015; 62 | } 63 | 64 | // Make some of the particles go rouge. 65 | if (p.distribution > rougeDistributionLmt && 66 | p.distribution < jellyDistributionLmt) { 67 | var r = Rnd.getDouble(.2, 2.5) * vecSpeedInv * p.distFrac; 68 | p.x -= p.vx * r + (p.distFrac * Rnd.getDouble(-.4, .4)); 69 | p.y -= p.vy * r + (p.distFrac * Rnd.getDouble(-.4, .4)); 70 | } 71 | 72 | // Make some of the particles ease back, for a jelly effect. 73 | if (p.distribution >= jellyDistributionLmt) { 74 | var r = Rnd.getDouble(.1, .9) * vecSpeedInv * (1 - p.lifeLeft); 75 | p.x += p.vx * r; 76 | p.y += p.vy * r; 77 | } 78 | 79 | // Reset particles once they are invisible or at the edge. 80 | if (p.lifeLeft <= 0 || p.size <= .5) { 81 | resetParticle(i); 82 | if (maxSpawnPerTick > 0) { 83 | _activateParticle(p); 84 | maxSpawnPerTick--; 85 | } 86 | } 87 | }); 88 | 89 | super.tick(duration); 90 | } 91 | 92 | void _activateParticle(Particle p) { 93 | p.x = Rnd.getDouble(spawnArea.left, spawnArea.right); 94 | p.y = Rnd.getDouble(spawnArea.top, spawnArea.bottom); 95 | p.isFilled = Rnd.getBool(); 96 | p.size = Rnd.getDouble(3, 8); 97 | p.isFlowing = false; 98 | p.distFrac = 0; 99 | 100 | p.distribution = Rnd.getInt(1, particleDistributions[2]); 101 | 102 | if (p.distribution < particleDistributions[0]) { 103 | p.type = ParticleType.hour; 104 | } else if (p.distribution < particleDistributions[1]) { 105 | p.type = ParticleType.minute; 106 | } else { 107 | p.type = ParticleType.noise; 108 | } 109 | 110 | double? angle; 111 | 112 | switch (p.type) { 113 | case ParticleType.hour: 114 | angle = _getHourRadians(); 115 | p.life = Rnd.getDouble(.5, .55); 116 | p.size = sizeMin * .010; 117 | p.isFlowing = Rnd.ratio > .85; 118 | p.color = palette!.components![palette!.components!.length - 1]; 119 | break; 120 | 121 | case ParticleType.minute: 122 | angle = _getMinuteRadians(); 123 | p.life = Rnd.getDouble(.68, .73); 124 | p.size = sizeMin * .008; 125 | p.isFlowing = Rnd.ratio > .6; 126 | p.color = palette!.components![palette!.components!.length - 1]; 127 | break; 128 | 129 | case ParticleType.noise: 130 | // Find a random angle while avoiding clutter at the hour & minute hands. 131 | var am = _getMinuteRadians(); 132 | var ah = _getHourRadians() % (pi * 2); 133 | var d = pi / 18; 134 | 135 | // Probably not the most efficient solution right here. 136 | do { 137 | angle = Rnd.ratio * pi * 2; 138 | } while (_isBetween(angle!, am - d, am + d) || 139 | _isBetween(angle, ah - d, ah + d)); 140 | 141 | p.life = Rnd.getDouble(0.75, .8); 142 | p.size = sizeMin * 143 | (Rnd.ratio > .8 144 | ? Rnd.getDouble(.0015, .003) 145 | : Rnd.getDouble(.002, .006)); 146 | break; 147 | } 148 | 149 | // Particle movement vector. 150 | p.vx = sin(-angle); 151 | p.vy = cos(-angle); 152 | 153 | // Particle movement angle. 154 | p.a = atan2(p.vy, p.vx) + pi; 155 | 156 | // Add some speed randomeness. 157 | double v = p.type == ParticleType.noise 158 | ? Rnd.getDouble(.5, 1) 159 | : Rnd.getDouble(.3, .4); 160 | 161 | p.vx *= v; 162 | p.vy *= v; 163 | } 164 | 165 | /// Gets the radians of the hour hand. 166 | double _getHourRadians() => 167 | (time!.hour * pi / 6) + 168 | (time!.minute * pi / (6 * 60)) + 169 | (time!.second * pi / (360 * 60)); 170 | 171 | /// Gets the radians of the minute hand. 172 | double _getMinuteRadians() => 173 | (time!.minute * (2 * pi) / 60) + (time!.second * pi / (30 * 60)); 174 | 175 | /// Checks if a value is between two other values. 176 | bool _isBetween(double value, double min, double max) { 177 | return value >= min && value <= max; 178 | } 179 | 180 | /// Calculates the distance from center using pythagoras rate. 181 | double _getDistanceFromCenter(Particle p) { 182 | var a = pow(center.dx - p.x, 2); 183 | var b = pow(center.dy - p.y, 2); 184 | return sqrt(a + b); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /assets/palettes.json: -------------------------------------------------------------------------------- 1 | [["0xffCC5D4C","0xffFFFEC6","0xffC7D1AF","0xff96B49C","0xff5B5847"],["0xffE4E4C5","0xffB9D48B","0xff8D2036","0xffCE0A31","0xffD3E4C5"],["0xffEB9C4D","0xffF2D680","0xffF3FFCF","0xffBAC9A9","0xff697060"],["0xff360745","0xffD61C59","0xffE7D84B","0xffEFEAC5","0xff1B8798"],["0xffF2E8C4","0xff98D9B6","0xff3EC9A7","0xff2B879E","0xff616668"],["0xffFFF3DB","0xffE7E4D5","0xffD3C8B4","0xffC84648","0xff703E3B"],["0xffF5DD9D","0xffBCC499","0xff92A68A","0xff7B8F8A","0xff506266"],["0xff041122","0xff259073","0xff7FDA89","0xffC8E98E","0xffE6F99D"],["0xff1D1313","0xff24B694","0xffD22042","0xffA3B808","0xff30C4C9"],["0xffF0FFC9","0xffA9DA88","0xff62997A","0xff72243D","0xff3B0819"],["0xffC6CCA5","0xff8AB8A8","0xff6B9997","0xff54787D","0xff615145"],["0xff429398","0xff6B5D4D","0xffB0A18F","0xffDFCDB4","0xffFBEED3"],["0xff2D1B33","0xffF36A71","0xffEE887A","0xffE4E391","0xff9ABC8A"],["0xff4B1139","0xff3B4058","0xff2A6E78","0xff7A907C","0xffC9B180"],["0xff322938","0xff89A194","0xffCFC89A","0xffCC883A","0xffA14016"],["0xff540045","0xffC60052","0xffFF714B","0xffEAFF87","0xffACFFE9"],["0xff79254A","0xff795C64","0xff79927D","0xffAEB18E","0xffE3CF9E"],["0xff452E3C","0xffFF3D5A","0xffFFB969","0xffEAF27E","0xff3B8C88"],["0xff2B2726","0xff0A516D","0xff018790","0xff7DAD93","0xffBACCA4"],["0xff027B7F","0xffFFA588","0xffD62957","0xffBF1E62","0xff572E4F"],["0xffFA6A64","0xff7A4E48","0xff4A4031","0xffF6E2BB","0xff9EC6B8"],["0xff23192D","0xffFD0A54","0xffF57576","0xffFEBF97","0xffF5ECB7"],["0xffA3C68C","0xff879676","0xff6E6662","0xff4F364A","0xff340735"],["0xffA32C28","0xff1C090B","0xff384030","0xff7B8055","0xffBCA875"],["0xff6D9788","0xff1E2528","0xff7E1C13","0xffBF0A0D","0xffE6E1C2"],["0xff373737","0xff8DB986","0xffACCE91","0xffBADB73","0xffEFEAE4"],["0xff280904","0xff680E34","0xff9A151A","0xffC21B12","0xffFC4B2A"],["0xff4B3E4D","0xff1E8C93","0xffDBD8A2","0xffC4AC30","0xffD74F33"],["0xffA69E80","0xffE0BA9B","0xffE7A97E","0xffD28574","0xff3B1922"],["0xff161616","0xffC94D65","0xffE7C049","0xff92B35A","0xff1F6764"],["0xff234D20","0xff36802D","0xff77AB59","0xffC9DF8A","0xffF0F7DA"],["0xffE6EBA9","0xffABBB9F","0xff6F8B94","0xff706482","0xff703D6F"],["0xffFDCFBF","0xffFEB89F","0xffE23D75","0xff5F0D3B","0xff742365"],["0xff641F5E","0xff676077","0xff65AC92","0xffC2C092","0xffEDD48E"],["0xff26251C","0xffEB0A44","0xffF2643D","0xffF2A73D","0xffA0E8B7"],["0xff4F364C","0xff5E405F","0xff6B6B6B","0xff8F9E6F","0xffB1CF72"],["0xff230B00","0xffA29D7F","0xffD4CFA5","0xffF8ECD4","0xffAABE9B"],["0xff85847E","0xffAB6A6E","0xffF7345B","0xff353130","0xffCBCFB4"],["0xffD24858","0xffEA8676","0xffEAB05E","0xffFDEECD","0xff493831"],["0xffD3D5B0","0xffB5CEA4","0xff9DC19D","0xff8C7C62","0xff71443F"],["0xffFFFF99","0xffD9CC8C","0xffB39980","0xff8C6673","0xff663366"],["0xffED6464","0xffBF6370","0xff87586C","0xff574759","0xff1A1B1C"],["0xffCCB24C","0xffF7D683","0xffFFFDC0","0xffFFFFFD","0xff457D97"],["0xffF0F0D8","0xffB4DEBE","0xff77CCA4","0xff666666","0xffB4DF37"],["0xff1A081F","0xff4D1D4D","0xff05676E","0xff489C79","0xffEBC288"],["0xff7A5B3E","0xffFAFAFA","0xffFA4B00","0xffCDBDAE","0xff1F1F1F"],["0xffD31900","0xffFF6600","0xffFFF2AF","0xff7CB490","0xff000000"],["0xffE8C382","0xffB39D69","0xffA86B4C","0xff7D1A0C","0xff340A0B"],["0xffEBEAA9","0xffEBC588","0xff7D2948","0xff3B0032","0xff0E0B29"],["0xff063940","0xff195E63","0xff3E838C","0xff8EBDB6","0xffECE1C3"],["0xff595B5A","0xff14C3A2","0xff0DE5A8","0xff7CF49A","0xffB8FD99"],["0xff411F2D","0xffAC4147","0xffF88863","0xffFFC27F","0xffFFE29A"],["0xffE7E79D","0xffC0D890","0xff78A890","0xff606078","0xffD8A878"],["0xff9DBCBC","0xffF0F0AF","0xffFF370F","0xff332717","0xff6BACBF"],["0xff94654C","0xffF89FA1","0xffFABDBD","0xffFAD6D6","0xffFEFCD0"],["0xff1F1F20","0xff2B4C7E","0xff567EBB","0xff606D80","0xffDCE0E6"],["0xffCDDBC2","0xffF7E4C6","0xffFB9274","0xffF5565B","0xff875346"],["0xffECBE13","0xff738C79","0xff6A6B5F","0xff2C2B26","0xffA43955"],["0xffFFF5DE","0xffB8D9C8","0xff917081","0xff750E49","0xff4D002B"],["0xffF0DDBD","0xffBA3622","0xff851E25","0xff520C30","0xff1C997F"],["0xff312C20","0xff494D4B","0xff7C7052","0xffB3A176","0xffE2CB92"],["0xff3F2C26","0xffDD423E","0xffA2A384","0xffEAC388","0xffC5AD4B"],["0xff0A0310","0xff49007E","0xffFF005B","0xffFF7D10","0xffFFB238"],["0xffCDECCC","0xffEDD269","0xffE88460","0xffF23460","0xff321D2E"],["0xff574C41","0xffE36B6B","0xffE3A56B","0xffE3C77B","0xff96875A"],["0xffE7DD96","0xffE16639","0xffAD860A","0xffB7023F","0xff55024A"],["0xff213435","0xff46685B","0xff648A64","0xffA6B985","0xffE1E3AC"],["0xff181419","0xff4A073C","0xff9E0B41","0xffCC3E18","0xffF0971C"],["0xff413040","0xff6C6368","0xffB9A173","0xffEAA353","0xffFFEFA9"],["0xff4D3B36","0xffEB613B","0xffF98F6F","0xffC1D9CD","0xffF7EADC"],["0xffFFCDB8","0xffFDEECF","0xffC8C696","0xff97BEA9","0xff37260C"],["0xff130912","0xff3E1C33","0xff602749","0xffB14623","0xffF6921D"],["0xffFFFF00","0xffCCD91A","0xff99B333","0xff668C4D","0xff336666"],["0xff001449","0xff012677","0xff005BC5","0xff00B4FC","0xff17F9FF"],["0xffED5672","0xff160E32","0xff9EAE8A","0xffCDBB93","0xffFBC599"],["0xff785D56","0xffBE4C54","0xffC6B299","0xffE6D5C1","0xffFFF4E3"],["0xff2B1719","0xff02483E","0xff057C46","0xff9BB61B","0xffF8BE00"],["0xffE6A06F","0xff9E9C71","0xff5E8271","0xff33454E","0xff242739"],["0xffF7F3D5","0xffFFDABF","0xffFA9B9B","0xffE88087","0xff635063"],["0xffE7EED0","0xffCAD1C3","0xff948E99","0xff51425F","0xff2E1437"],["0xffE25858","0xffE9D6AF","0xffFFFFDD","0xffC0EFD2","0xff384252"],["0xffACDEB2","0xffE1EAB5","0xffEDAD9E","0xffFE4B74","0xff390D2D"],["0xff42282C","0xff6CA19E","0xff84ABAA","0xffDED1B6","0xff6D997A"],["0xffFDFFD9","0xff73185E","0xff36BBA6","0xff0C0D02","0xff8B911A"],["0xff9F0A28","0xffD55C2B","0xffF6E7D3","0xff89A46F","0xff55203C"],["0xffA69A90","0xff4A403D","0xffFFF1C1","0xffFACF7D","0xffEA804C"],["0xff418E8E","0xff5A4E3C","0xffC4D428","0xffD8E472","0xffE9EBBF"],["0xffC9D1D3","0xffF7F7F7","0xff9DD3DF","0xff3B3737","0xff991818"],["0xffFAF6D0","0xffC7D8AB","0xff909A92","0xff744F78","0xff30091E"],["0xffF6C7B7","0xffF7A398","0xffFA7F77","0xffB42529","0xff000000"],["0xffA7321C","0xffFFDC68","0xffCC982A","0xff928941","0xff352504"],["0xffF0371A","0xff000000","0xffF7E6A6","0xff3E6B48","0xffB5B479"],["0xffF4F4F4","0xff9BA657","0xffF0E5C9","0xffA68C69","0xff594433"],["0xffF1E8B4","0xffB2BB91","0xffD7BF5E","0xffD16344","0xff83555E"],["0xff000000","0xff001F36","0xff1C5560","0xff79AE92","0xffFBFFCD"],["0xffE0DC8B","0xffF6AA3D","0xffED4C57","0xff574435","0xff6CC4B9"],["0xff42393B","0xff75C9A3","0xffBAC99A","0xffFFC897","0xffF7EFA2"],["0xffF2CC67","0xffF38264","0xffF40034","0xff5F051F","0xff75BAA8"],["0xffFBFEE5","0xffC91842","0xff98173D","0xff25232D","0xffA8E7CA"],["0xff998496","0xffF7E0AE","0xffFA748F","0xff2D2C26","0xffC3B457"],["0xffDBD9B7","0xffC1C9C8","0xffA5B5AB","0xff949A8E","0xff615566"],["0xffF3E6BC","0xffF1C972","0xffF5886B","0xff72AE95","0xff5A3226"],["0xffD9D4A8","0xffD15C57","0xffCC3747","0xff5C374B","0xff4A5F67"],["0xff674F23","0xffE48B69","0xffE1B365","0xffE5DB84","0xffFFEEAC"],["0xffBF2A23","0xffA6AD3C","0xffF0CE4E","0xffCF872E","0xff8A211D"],["0xff84C1B1","0xffAD849A","0xffD64783","0xffFD135A","0xff40202A"],["0xff020304","0xff541F14","0xff938172","0xffCC9E61","0xff626266"],["0xff002C2B","0xffFF3D00","0xffFFBC11","0xff0A837F","0xff076461"]] -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | ffi: 53 | dependency: transitive 54 | description: 55 | name: ffi 56 | sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.0.2" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_lints: 66 | dependency: "direct dev" 67 | description: 68 | name: flutter_lints 69 | sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "2.0.1" 73 | flutter_test: 74 | dependency: "direct dev" 75 | description: flutter 76 | source: sdk 77 | version: "0.0.0" 78 | flutter_web_plugins: 79 | dependency: transitive 80 | description: flutter 81 | source: sdk 82 | version: "0.0.0" 83 | leak_tracker: 84 | dependency: transitive 85 | description: 86 | name: leak_tracker 87 | sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" 88 | url: "https://pub.dev" 89 | source: hosted 90 | version: "10.0.0" 91 | leak_tracker_flutter_testing: 92 | dependency: transitive 93 | description: 94 | name: leak_tracker_flutter_testing 95 | sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 96 | url: "https://pub.dev" 97 | source: hosted 98 | version: "2.0.1" 99 | leak_tracker_testing: 100 | dependency: transitive 101 | description: 102 | name: leak_tracker_testing 103 | sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 104 | url: "https://pub.dev" 105 | source: hosted 106 | version: "2.0.1" 107 | lints: 108 | dependency: transitive 109 | description: 110 | name: lints 111 | sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" 112 | url: "https://pub.dev" 113 | source: hosted 114 | version: "2.0.1" 115 | matcher: 116 | dependency: transitive 117 | description: 118 | name: matcher 119 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 120 | url: "https://pub.dev" 121 | source: hosted 122 | version: "0.12.16+1" 123 | material_color_utilities: 124 | dependency: transitive 125 | description: 126 | name: material_color_utilities 127 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "0.8.0" 131 | meta: 132 | dependency: transitive 133 | description: 134 | name: meta 135 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.11.0" 139 | path: 140 | dependency: transitive 141 | description: 142 | name: path 143 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "1.9.0" 147 | plugin_platform_interface: 148 | dependency: transitive 149 | description: 150 | name: plugin_platform_interface 151 | sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "2.1.4" 155 | restart_app: 156 | dependency: "direct main" 157 | description: 158 | name: restart_app 159 | sha256: b37daeb1c02fcab30e19d9e30b6fdd215bd53577efd927042eb77cf6f09daadb 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.2.1" 163 | shorebird_code_push: 164 | dependency: "direct main" 165 | description: 166 | name: shorebird_code_push 167 | sha256: "872839ac1e9b86f97db99c6456c4886620246f15e1cff9d29d4101e2beb991eb" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "1.1.3" 171 | sky_engine: 172 | dependency: transitive 173 | description: flutter 174 | source: sdk 175 | version: "0.0.99" 176 | source_span: 177 | dependency: transitive 178 | description: 179 | name: source_span 180 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 181 | url: "https://pub.dev" 182 | source: hosted 183 | version: "1.10.0" 184 | stack_trace: 185 | dependency: transitive 186 | description: 187 | name: stack_trace 188 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 189 | url: "https://pub.dev" 190 | source: hosted 191 | version: "1.11.1" 192 | stream_channel: 193 | dependency: transitive 194 | description: 195 | name: stream_channel 196 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 197 | url: "https://pub.dev" 198 | source: hosted 199 | version: "2.1.2" 200 | string_scanner: 201 | dependency: transitive 202 | description: 203 | name: string_scanner 204 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 205 | url: "https://pub.dev" 206 | source: hosted 207 | version: "1.2.0" 208 | term_glyph: 209 | dependency: transitive 210 | description: 211 | name: term_glyph 212 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 213 | url: "https://pub.dev" 214 | source: hosted 215 | version: "1.2.1" 216 | test_api: 217 | dependency: transitive 218 | description: 219 | name: test_api 220 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 221 | url: "https://pub.dev" 222 | source: hosted 223 | version: "0.6.1" 224 | vector_math: 225 | dependency: transitive 226 | description: 227 | name: vector_math 228 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 229 | url: "https://pub.dev" 230 | source: hosted 231 | version: "2.1.4" 232 | vm_service: 233 | dependency: transitive 234 | description: 235 | name: vm_service 236 | sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 237 | url: "https://pub.dev" 238 | source: hosted 239 | version: "13.0.0" 240 | sdks: 241 | dart: ">=3.2.0-0 <4.0.0" 242 | flutter: ">=3.3.0" 243 | -------------------------------------------------------------------------------- /lib/clocks/generative_clock/generative_clock.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 'dart:math'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/scheduler.dart'; 9 | import 'package:flutter/services.dart'; 10 | 11 | import 'globals.dart'; 12 | import 'node.dart'; 13 | 14 | // An array of all the nodes which are animated 15 | // The fluidity of the animation is highly dependent on the size of this array 16 | // change for convenience 17 | List nodes = List.filled(2500, null); 18 | 19 | /// A digital clock. 20 | /// 21 | /// You can do better than this! 22 | class GenerativeClock extends StatefulWidget { 23 | const GenerativeClock({super.key}); 24 | 25 | @override 26 | State createState() => _GenerativeClockState(); 27 | } 28 | 29 | class _GenerativeClockState extends State { 30 | DateTime _dateTime = DateTime.now(); 31 | late Ticker _ticker; 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | 37 | SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft]); 38 | 39 | //setup Nodes: one half of the nodes are static and do not move, the other have does 40 | for (int i = 0; i < 600; i++) { 41 | nodes[i] = Node(true); 42 | } 43 | 44 | for (int i = 600; i < nodes.length; i++) { 45 | nodes[i] = Node(false); 46 | } 47 | 48 | for (int i = 0; i < quadrantDescriptors.length; i++) { 49 | quadrantDescriptors[i] = QuadrantDescriptor(); 50 | } 51 | 52 | //we use a ticker to regularly update the state, so that the clock is redrawn 53 | _ticker = Ticker(_updateTime); 54 | _ticker.start(); 55 | } 56 | 57 | ///the method invoked by the ticker, which just updates the current time 58 | void _updateTime(Duration d) { 59 | setState(() { 60 | _dateTime = DateTime.now(); 61 | }); 62 | } 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | int hour = _dateTime.hour; 67 | if (!MediaQuery.of(context).alwaysUse24HourFormat) { 68 | hour %= 12; 69 | } 70 | int minute = _dateTime.minute; 71 | 72 | bool inside = 73 | Theme.of(context).brightness == Brightness.light ? true : false; 74 | 75 | double percentageOfMinute = _dateTime.millisecond.toDouble() / 60000.0 + 76 | _dateTime.second.toDouble() / 60.0; 77 | 78 | return Container( 79 | color: Colors.black, 80 | child: CustomPaint( 81 | painter: Painter(hour, minute, percentageOfMinute, inside), 82 | // We give the CustomPaint a child, so that it can use the child's size 83 | // to determine its own size. If we don't do this, CustomPaint will 84 | // size itself wrong when inside a Scaffold. 85 | // https://github.com/flutter/flutter/issues/76230#issuecomment-781119751 86 | child: Container(), 87 | ), 88 | ); 89 | } 90 | } 91 | 92 | /// [QuadrantDescriptor] a helper class, which we use to keep track, how many nodes are in a quadrant 93 | /// the real information is stored in the indexs array, the QuadrantDescriptors, just describe the structural information of the array 94 | /// [size] how many nodes are in the quadrant 95 | /// [beginningIndex] in the 96 | /// [filled] how many nodes of the quadrant are already found 97 | /// in this way we define a slice of the indexs array, which only holds indexs of nodes, which belong to one quadrant 98 | class QuadrantDescriptor { 99 | int size = 0; 100 | int beginningIndex = 0; 101 | int filled = 0; 102 | 103 | void reset() { 104 | size = 0; 105 | beginningIndex = 0; 106 | filled = 0; 107 | } 108 | } 109 | 110 | // Maintain a QuadrantDescriptor for every quadrant 111 | List quadrantDescriptors = List.filled( 112 | quadrants(), 113 | QuadrantDescriptor(), 114 | ); 115 | 116 | // aspect ratio of the screen is 5/3 117 | double aspectRatio = 5 / 3; 118 | 119 | /// A custom painter, which is used to animate the clockface 120 | class Painter extends CustomPainter { 121 | final int _hour; 122 | final int _minute; 123 | final double _percentageOfMinute; 124 | final bool _drawInside; 125 | 126 | Painter( 127 | this._hour, 128 | this._minute, 129 | this._percentageOfMinute, 130 | this._drawInside, 131 | ); 132 | 133 | /// the paint function is used in a similar way to the main loop of a game: 134 | /// first the state of the world (in this case mainly the position of the nodes) is simulated 135 | /// second the world state is drawn, i.e. each node tests if it has close neightbors and draws a line to them 136 | @override 137 | void paint(Canvas canvas, Size size) { 138 | /// translate the point [p] from our local coordinate system into that of the canvas 139 | Point global(Point p) { 140 | return Point( 141 | p.x * size.height + size.width / 2, 142 | p.y * size.height + size.height / 2, 143 | ); 144 | } 145 | 146 | /// translates the point [p] into an equivalent offset 147 | Offset offset(Point p) { 148 | return Offset(p.x, p.y); 149 | } 150 | 151 | //reset the quadrantDescriptors 152 | for (var quadrantDescriptor in quadrantDescriptors) { 153 | quadrantDescriptor.reset(); 154 | } 155 | 156 | // update the positions of the node, we use the quadrant assoziations to count the amount of nodes in each quadrant 157 | for (var node in nodes) { 158 | node!.update(); 159 | quadrantDescriptors[node.quadrant].size++; 160 | } 161 | 162 | // fill out the beginningIndexs of the quadrantDescriptors, by summing over the sizes 163 | int counter = 0; 164 | for (var quadrantDescriptor in quadrantDescriptors) { 165 | quadrantDescriptor.beginningIndex = counter; 166 | counter += quadrantDescriptor.size; 167 | } 168 | 169 | { 170 | // This is a list of indexes of the nodes, it is ordered by the quadrant association of the nodes, 171 | // i.e. first all indexes of nodes in the first quadrant appear, then of the second and so forth 172 | List indexs = List.filled(nodes.length, 0); 173 | 174 | // fill out the indexs array, by iterating over nodes, and saving the node at appropriate index according to the quadrantDescriptor 175 | for (int i = 0; i < nodes.length; i++) { 176 | var node = nodes[i]; 177 | if (node == null) { 178 | continue; 179 | } 180 | 181 | var listDescriptor = quadrantDescriptors[node.quadrant]; 182 | int index = listDescriptor.beginningIndex + listDescriptor.filled; 183 | listDescriptor.filled++; 184 | indexs[index] = i; 185 | nodes[i]!.index = index; 186 | } 187 | 188 | //sort the nodes array, using the indexs array 189 | for (int i = 0; i < nodes.length; i++) { 190 | int index = indexs[i]; 191 | 192 | //swap 193 | Node temp = nodes[i]!; 194 | int indexOfSwap = temp.index; 195 | 196 | nodes[i] = nodes[index]; 197 | nodes[index] = temp; 198 | 199 | indexs[i] = i; 200 | indexs[indexOfSwap] = index; 201 | } 202 | } 203 | 204 | /// get the following, surrounding quadrants of [quadrant] are the return vakze: 205 | /// quadrant, right 206 | ///left under, under , right under 207 | List surroundingQuadrants(int quadrant) { 208 | List result = []; 209 | 210 | int x = quadrant % columns; 211 | 212 | int nQuadrants = quadrants(); 213 | 214 | if (quadrant < nQuadrants) { 215 | result.add(quadrant); 216 | } 217 | 218 | if (x + 1 < columns && quadrant + 1 < nQuadrants) { 219 | result.add(quadrant + 1); 220 | } 221 | 222 | if (x - 1 > 0 && quadrant + columns - 1 < nQuadrants) { 223 | result.add(quadrant + columns - 1); 224 | } 225 | 226 | if (quadrant + columns < nQuadrants) { 227 | result.add(quadrant + columns); 228 | } 229 | 230 | if (x + 1 < columns && quadrant + columns + 1 < nQuadrants) { 231 | result.add(quadrant + columns + 1); 232 | } 233 | return result; 234 | } 235 | 236 | // ----- Draw step ----- // 237 | 238 | //draw half the screen black 239 | Paint white = Paint() 240 | ..style = PaintingStyle.fill 241 | ..color = Colors.white70; 242 | 243 | // we compute a four sided polygon, which tints have the screen white (the other half is white) 244 | // it acts as a clockhand for the seconds 245 | Path path = Path(); 246 | double angle = _percentageOfMinute * 2 * pi; 247 | 248 | double x, y; 249 | double tangensAngle = atan((size.width / 2) / (size.height / 2)); 250 | if (angle < tangensAngle || angle > 2 * pi - tangensAngle) { 251 | x = tan(angle) * size.height / 2; 252 | path.moveTo(size.width / 2 + x, 0.0); 253 | path.lineTo(size.width, 0.0); 254 | path.lineTo(size.width, size.height); 255 | path.lineTo(size.width / 2 - x, size.height); 256 | } else if (angle < pi - tangensAngle) { 257 | y = tan(angle - pi / 2) * size.width / 2; 258 | path.moveTo(size.width, size.height / 2 + y); 259 | path.lineTo(size.width, size.height); 260 | path.lineTo(0.0, size.height); 261 | path.lineTo(0.0, size.height / 2 - y); 262 | } else if (angle < pi + tangensAngle) { 263 | x = tan(angle - pi) * size.height / 2; 264 | path.moveTo(size.width / 2 - x, size.height); 265 | path.lineTo(0.0, size.height); 266 | path.lineTo(0.0, 0.0); 267 | path.lineTo(size.width / 2 + x, 0.0); 268 | } else if (angle < 2 * pi - tangensAngle) { 269 | y = tan(angle - 3 * pi / 2) * size.width / 2; 270 | path.moveTo(0.0, size.height / 2 - y); 271 | path.lineTo(0.0, 0.0); 272 | path.lineTo(size.width, 0.0); 273 | path.lineTo(size.width, size.height / 2 + y); 274 | } 275 | path.close(); 276 | canvas.drawPath(path, white); 277 | 278 | // this is a function that computes if a point is in the white four sided figure drawn above or not 279 | bool isBelowAngle(Point p) { 280 | double pointAngle; 281 | if (p.x > 0 && p.y < 0) { 282 | pointAngle = atan(p.x / -p.y); 283 | } else if (p.x > 0 && p.y > 0) { 284 | pointAngle = pi / 2 + atan(p.y / p.x); 285 | } else if (p.x <= 0 && p.y > 0) { 286 | pointAngle = pi + atan(-p.x / p.y); 287 | } else { 288 | pointAngle = 3 * pi / 2 + atan(-p.y / -p.x); 289 | } 290 | 291 | if (angle > pi && (pointAngle > angle || pointAngle < angle - pi)) { 292 | return true; 293 | } else if (angle < pi && pointAngle > angle && pointAngle < angle + pi) { 294 | return true; 295 | } else { 296 | return false; 297 | } 298 | } 299 | 300 | // iterate over the nodes and check every other node if it is near 301 | // as is appearent, algorithmn scales quadratically with the numbers of nodes, 302 | // because of this we have the optimization with the quadrants 303 | for (var node in nodes) { 304 | if (node == null) { 305 | continue; 306 | } 307 | 308 | //do we have to draw things for this node? 309 | if (node.checkCoordinate(_hour, _minute, _drawInside)) { 310 | Point point1 = node.coordinate; 311 | Point globalPoint = global(point1); 312 | 313 | /* debug code: draws the nodes 314 | Offset off = Offset(globalPoint.x, globalPoint.y); 315 | Paint paint = Paint() 316 | ..color = Colors.red; 317 | canvas.drawCircle(off, 1, paint); 318 | */ 319 | 320 | var quadrantsToCheck = surroundingQuadrants(node.quadrant); 321 | 322 | for (var index in quadrantsToCheck) { 323 | QuadrantDescriptor quadrantDescriptor = quadrantDescriptors[index]; 324 | int startingIndex = 325 | max(quadrantDescriptor.beginningIndex, node.index); 326 | 327 | //iterate over the following nodes in the surrounding quadrant, which we can get by looking at the quadrantDescriptors 328 | for (int i = startingIndex; 329 | i < quadrantDescriptor.beginningIndex + quadrantDescriptor.size; 330 | i++) { 331 | // a node in the surrounding quadrants 332 | Node node2 = nodes[i]!; 333 | Point point2 = node2.coordinate; 334 | double distance = point1.distanceTo(point2); 335 | 336 | //do we have to draw things for this node 337 | if (node2.checkCoordinate(_hour, _minute, _drawInside)) { 338 | Point globalPoint2 = global(point2); 339 | 340 | //if the distance between nodes is small enough draw a line between them 341 | if (distance < maxDistanceBetweenNodes) { 342 | // we use a more pronaunced line, for close nodes 343 | // in technical terms: the alpha value of the linepaint is antiproportionally defined to the distance between the nodes 344 | double alpha = min(0.015 / (distance + 0.0001), 1.0); 345 | 346 | Paint paint; 347 | if (isBelowAngle(point1)) { 348 | paint = Paint() 349 | ..color = Colors.black.withAlpha((alpha * 255).floor()); 350 | } else { 351 | paint = Paint() 352 | ..color = Colors.white.withAlpha((alpha * 255).floor()); 353 | } 354 | 355 | var offset1 = offset(globalPoint); 356 | var offset2 = offset(globalPoint2); 357 | canvas.drawLine(offset1, offset2, paint); 358 | } 359 | } 360 | } 361 | } 362 | } 363 | } 364 | } 365 | 366 | //always repaint, we want an animation, which is as smooth as possible 367 | @override 368 | bool shouldRepaint(CustomPainter oldDelegate) { 369 | return true; 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 813818BFB9BB49F5BB60D6AE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5C3DE064D1A08F8E6A7DC11 /* Pods_Runner.framework */; }; 15 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 16 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 17 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 18 | 9CF2981888CBC82D40B8C39D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33EB2062AC918C21D323D4E6 /* Pods_RunnerTests.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 27 | remoteInfo = Runner; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXCopyFilesBuildPhase section */ 32 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 33 | isa = PBXCopyFilesBuildPhase; 34 | buildActionMask = 2147483647; 35 | dstPath = ""; 36 | dstSubfolderSpec = 10; 37 | files = ( 38 | ); 39 | name = "Embed Frameworks"; 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXCopyFilesBuildPhase section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 46 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 47 | 284DD404BFF3A339E0E6BBCF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 48 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 49 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 33EB2062AC918C21D323D4E6 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 52 | 68A8ECFC7D852E2C808E1C60 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 53 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 54 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 56 | 880A0F2F16AD4B996A80883C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 57 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 58 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 59 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 9ADA8FDFC9C6E3A6BC1FD046 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 65 | B5C3DE064D1A08F8E6A7DC11 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | DDE62DB222EE666E0CBCB768 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 67 | F6F27CCFAD36F2DC3F349CFC /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 12034162D0311F0302B82CE9 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 9CF2981888CBC82D40B8C39D /* Pods_RunnerTests.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 813818BFB9BB49F5BB60D6AE /* Pods_Runner.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 10A61520B39F1D545D584A8C /* Pods */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9ADA8FDFC9C6E3A6BC1FD046 /* Pods-Runner.debug.xcconfig */, 94 | 68A8ECFC7D852E2C808E1C60 /* Pods-Runner.release.xcconfig */, 95 | 880A0F2F16AD4B996A80883C /* Pods-Runner.profile.xcconfig */, 96 | 284DD404BFF3A339E0E6BBCF /* Pods-RunnerTests.debug.xcconfig */, 97 | DDE62DB222EE666E0CBCB768 /* Pods-RunnerTests.release.xcconfig */, 98 | F6F27CCFAD36F2DC3F349CFC /* Pods-RunnerTests.profile.xcconfig */, 99 | ); 100 | path = Pods; 101 | sourceTree = ""; 102 | }; 103 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 107 | ); 108 | path = RunnerTests; 109 | sourceTree = ""; 110 | }; 111 | 3DCE6269940FD62C12D73331 /* Frameworks */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | B5C3DE064D1A08F8E6A7DC11 /* Pods_Runner.framework */, 115 | 33EB2062AC918C21D323D4E6 /* Pods_RunnerTests.framework */, 116 | ); 117 | name = Frameworks; 118 | sourceTree = ""; 119 | }; 120 | 9740EEB11CF90186004384FC /* Flutter */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 124 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 125 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 126 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 127 | ); 128 | name = Flutter; 129 | sourceTree = ""; 130 | }; 131 | 97C146E51CF9000F007C117D = { 132 | isa = PBXGroup; 133 | children = ( 134 | 9740EEB11CF90186004384FC /* Flutter */, 135 | 97C146F01CF9000F007C117D /* Runner */, 136 | 97C146EF1CF9000F007C117D /* Products */, 137 | 331C8082294A63A400263BE5 /* RunnerTests */, 138 | 10A61520B39F1D545D584A8C /* Pods */, 139 | 3DCE6269940FD62C12D73331 /* Frameworks */, 140 | ); 141 | sourceTree = ""; 142 | }; 143 | 97C146EF1CF9000F007C117D /* Products */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 97C146EE1CF9000F007C117D /* Runner.app */, 147 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 148 | ); 149 | name = Products; 150 | sourceTree = ""; 151 | }; 152 | 97C146F01CF9000F007C117D /* Runner */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 156 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 157 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 158 | 97C147021CF9000F007C117D /* Info.plist */, 159 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 160 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 161 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 162 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 163 | ); 164 | path = Runner; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXNativeTarget section */ 170 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 173 | buildPhases = ( 174 | F154AAB717774527F1005029 /* [CP] Check Pods Manifest.lock */, 175 | 331C807D294A63A400263BE5 /* Sources */, 176 | 331C807F294A63A400263BE5 /* Resources */, 177 | 12034162D0311F0302B82CE9 /* Frameworks */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 183 | ); 184 | name = RunnerTests; 185 | productName = RunnerTests; 186 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 187 | productType = "com.apple.product-type.bundle.unit-test"; 188 | }; 189 | 97C146ED1CF9000F007C117D /* Runner */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 192 | buildPhases = ( 193 | 9F38765FC48E36B59996C5CB /* [CP] Check Pods Manifest.lock */, 194 | 9740EEB61CF901F6004384FC /* Run Script */, 195 | 97C146EA1CF9000F007C117D /* Sources */, 196 | 97C146EB1CF9000F007C117D /* Frameworks */, 197 | 97C146EC1CF9000F007C117D /* Resources */, 198 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 199 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 200 | 91FA61AD0A9F9F3BADE0FD10 /* [CP] Embed Pods Frameworks */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | ); 206 | name = Runner; 207 | productName = Runner; 208 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 209 | productType = "com.apple.product-type.application"; 210 | }; 211 | /* End PBXNativeTarget section */ 212 | 213 | /* Begin PBXProject section */ 214 | 97C146E61CF9000F007C117D /* Project object */ = { 215 | isa = PBXProject; 216 | attributes = { 217 | LastUpgradeCheck = 1430; 218 | ORGANIZATIONNAME = ""; 219 | TargetAttributes = { 220 | 331C8080294A63A400263BE5 = { 221 | CreatedOnToolsVersion = 14.0; 222 | TestTargetID = 97C146ED1CF9000F007C117D; 223 | }; 224 | 97C146ED1CF9000F007C117D = { 225 | CreatedOnToolsVersion = 7.3.1; 226 | LastSwiftMigration = 1100; 227 | }; 228 | }; 229 | }; 230 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 231 | compatibilityVersion = "Xcode 9.3"; 232 | developmentRegion = en; 233 | hasScannedForEncodings = 0; 234 | knownRegions = ( 235 | en, 236 | Base, 237 | ); 238 | mainGroup = 97C146E51CF9000F007C117D; 239 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 240 | projectDirPath = ""; 241 | projectRoot = ""; 242 | targets = ( 243 | 97C146ED1CF9000F007C117D /* Runner */, 244 | 331C8080294A63A400263BE5 /* RunnerTests */, 245 | ); 246 | }; 247 | /* End PBXProject section */ 248 | 249 | /* Begin PBXResourcesBuildPhase section */ 250 | 331C807F294A63A400263BE5 /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | 97C146EC1CF9000F007C117D /* Resources */ = { 258 | isa = PBXResourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 262 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 263 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 264 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXShellScriptBuildPhase section */ 271 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 272 | isa = PBXShellScriptBuildPhase; 273 | alwaysOutOfDate = 1; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | inputPaths = ( 278 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 279 | ); 280 | name = "Thin Binary"; 281 | outputPaths = ( 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | shellPath = /bin/sh; 285 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 286 | }; 287 | 91FA61AD0A9F9F3BADE0FD10 /* [CP] Embed Pods Frameworks */ = { 288 | isa = PBXShellScriptBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | inputFileListPaths = ( 293 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 294 | ); 295 | name = "[CP] Embed Pods Frameworks"; 296 | outputFileListPaths = ( 297 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | shellPath = /bin/sh; 301 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 302 | showEnvVarsInLog = 0; 303 | }; 304 | 9740EEB61CF901F6004384FC /* Run Script */ = { 305 | isa = PBXShellScriptBuildPhase; 306 | alwaysOutOfDate = 1; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | ); 310 | inputPaths = ( 311 | ); 312 | name = "Run Script"; 313 | outputPaths = ( 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | shellPath = /bin/sh; 317 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 318 | }; 319 | 9F38765FC48E36B59996C5CB /* [CP] Check Pods Manifest.lock */ = { 320 | isa = PBXShellScriptBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | ); 324 | inputFileListPaths = ( 325 | ); 326 | inputPaths = ( 327 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 328 | "${PODS_ROOT}/Manifest.lock", 329 | ); 330 | name = "[CP] Check Pods Manifest.lock"; 331 | outputFileListPaths = ( 332 | ); 333 | outputPaths = ( 334 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | shellPath = /bin/sh; 338 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 339 | showEnvVarsInLog = 0; 340 | }; 341 | F154AAB717774527F1005029 /* [CP] Check Pods Manifest.lock */ = { 342 | isa = PBXShellScriptBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | ); 346 | inputFileListPaths = ( 347 | ); 348 | inputPaths = ( 349 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 350 | "${PODS_ROOT}/Manifest.lock", 351 | ); 352 | name = "[CP] Check Pods Manifest.lock"; 353 | outputFileListPaths = ( 354 | ); 355 | outputPaths = ( 356 | "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | shellPath = /bin/sh; 360 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 361 | showEnvVarsInLog = 0; 362 | }; 363 | /* End PBXShellScriptBuildPhase section */ 364 | 365 | /* Begin PBXSourcesBuildPhase section */ 366 | 331C807D294A63A400263BE5 /* Sources */ = { 367 | isa = PBXSourcesBuildPhase; 368 | buildActionMask = 2147483647; 369 | files = ( 370 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | }; 374 | 97C146EA1CF9000F007C117D /* Sources */ = { 375 | isa = PBXSourcesBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 379 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 380 | ); 381 | runOnlyForDeploymentPostprocessing = 0; 382 | }; 383 | /* End PBXSourcesBuildPhase section */ 384 | 385 | /* Begin PBXTargetDependency section */ 386 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 387 | isa = PBXTargetDependency; 388 | target = 97C146ED1CF9000F007C117D /* Runner */; 389 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 390 | }; 391 | /* End PBXTargetDependency section */ 392 | 393 | /* Begin PBXVariantGroup section */ 394 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 395 | isa = PBXVariantGroup; 396 | children = ( 397 | 97C146FB1CF9000F007C117D /* Base */, 398 | ); 399 | name = Main.storyboard; 400 | sourceTree = ""; 401 | }; 402 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 403 | isa = PBXVariantGroup; 404 | children = ( 405 | 97C147001CF9000F007C117D /* Base */, 406 | ); 407 | name = LaunchScreen.storyboard; 408 | sourceTree = ""; 409 | }; 410 | /* End PBXVariantGroup section */ 411 | 412 | /* Begin XCBuildConfiguration section */ 413 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | ALWAYS_SEARCH_USER_PATHS = NO; 417 | CLANG_ANALYZER_NONNULL = YES; 418 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 419 | CLANG_CXX_LIBRARY = "libc++"; 420 | CLANG_ENABLE_MODULES = YES; 421 | CLANG_ENABLE_OBJC_ARC = YES; 422 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 423 | CLANG_WARN_BOOL_CONVERSION = YES; 424 | CLANG_WARN_COMMA = YES; 425 | CLANG_WARN_CONSTANT_CONVERSION = YES; 426 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 427 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 428 | CLANG_WARN_EMPTY_BODY = YES; 429 | CLANG_WARN_ENUM_CONVERSION = YES; 430 | CLANG_WARN_INFINITE_RECURSION = YES; 431 | CLANG_WARN_INT_CONVERSION = YES; 432 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 434 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 436 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 437 | CLANG_WARN_STRICT_PROTOTYPES = YES; 438 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 439 | CLANG_WARN_UNREACHABLE_CODE = YES; 440 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 441 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 442 | COPY_PHASE_STRIP = NO; 443 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 444 | ENABLE_NS_ASSERTIONS = NO; 445 | ENABLE_STRICT_OBJC_MSGSEND = YES; 446 | GCC_C_LANGUAGE_STANDARD = gnu99; 447 | GCC_NO_COMMON_BLOCKS = YES; 448 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 449 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 450 | GCC_WARN_UNDECLARED_SELECTOR = YES; 451 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 452 | GCC_WARN_UNUSED_FUNCTION = YES; 453 | GCC_WARN_UNUSED_VARIABLE = YES; 454 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 455 | MTL_ENABLE_DEBUG_INFO = NO; 456 | SDKROOT = iphoneos; 457 | SUPPORTED_PLATFORMS = iphoneos; 458 | TARGETED_DEVICE_FAMILY = "1,2"; 459 | VALIDATE_PRODUCT = YES; 460 | }; 461 | name = Profile; 462 | }; 463 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 464 | isa = XCBuildConfiguration; 465 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | CLANG_ENABLE_MODULES = YES; 469 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 470 | DEVELOPMENT_TEAM = 6V53YACS2W; 471 | ENABLE_BITCODE = NO; 472 | INFOPLIST_FILE = Runner/Info.plist; 473 | INFOPLIST_KEY_CFBundleDisplayName = "Shorebird Clock"; 474 | LD_RUNPATH_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "@executable_path/Frameworks", 477 | ); 478 | PRODUCT_BUNDLE_IDENTIFIER = com.shorebird.timeShift; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 481 | SWIFT_VERSION = 5.0; 482 | VERSIONING_SYSTEM = "apple-generic"; 483 | }; 484 | name = Profile; 485 | }; 486 | 331C8088294A63A400263BE5 /* Debug */ = { 487 | isa = XCBuildConfiguration; 488 | baseConfigurationReference = 284DD404BFF3A339E0E6BBCF /* Pods-RunnerTests.debug.xcconfig */; 489 | buildSettings = { 490 | BUNDLE_LOADER = "$(TEST_HOST)"; 491 | CODE_SIGN_STYLE = Automatic; 492 | CURRENT_PROJECT_VERSION = 1; 493 | GENERATE_INFOPLIST_FILE = YES; 494 | MARKETING_VERSION = 1.0; 495 | PRODUCT_BUNDLE_IDENTIFIER = dev.shorebird.timeShift.RunnerTests; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 498 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 499 | SWIFT_VERSION = 5.0; 500 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 501 | }; 502 | name = Debug; 503 | }; 504 | 331C8089294A63A400263BE5 /* Release */ = { 505 | isa = XCBuildConfiguration; 506 | baseConfigurationReference = DDE62DB222EE666E0CBCB768 /* Pods-RunnerTests.release.xcconfig */; 507 | buildSettings = { 508 | BUNDLE_LOADER = "$(TEST_HOST)"; 509 | CODE_SIGN_STYLE = Automatic; 510 | CURRENT_PROJECT_VERSION = 1; 511 | GENERATE_INFOPLIST_FILE = YES; 512 | MARKETING_VERSION = 1.0; 513 | PRODUCT_BUNDLE_IDENTIFIER = dev.shorebird.timeShift.RunnerTests; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | SWIFT_VERSION = 5.0; 516 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 517 | }; 518 | name = Release; 519 | }; 520 | 331C808A294A63A400263BE5 /* Profile */ = { 521 | isa = XCBuildConfiguration; 522 | baseConfigurationReference = F6F27CCFAD36F2DC3F349CFC /* Pods-RunnerTests.profile.xcconfig */; 523 | buildSettings = { 524 | BUNDLE_LOADER = "$(TEST_HOST)"; 525 | CODE_SIGN_STYLE = Automatic; 526 | CURRENT_PROJECT_VERSION = 1; 527 | GENERATE_INFOPLIST_FILE = YES; 528 | MARKETING_VERSION = 1.0; 529 | PRODUCT_BUNDLE_IDENTIFIER = dev.shorebird.timeShift.RunnerTests; 530 | PRODUCT_NAME = "$(TARGET_NAME)"; 531 | SWIFT_VERSION = 5.0; 532 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 533 | }; 534 | name = Profile; 535 | }; 536 | 97C147031CF9000F007C117D /* Debug */ = { 537 | isa = XCBuildConfiguration; 538 | buildSettings = { 539 | ALWAYS_SEARCH_USER_PATHS = NO; 540 | CLANG_ANALYZER_NONNULL = YES; 541 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 542 | CLANG_CXX_LIBRARY = "libc++"; 543 | CLANG_ENABLE_MODULES = YES; 544 | CLANG_ENABLE_OBJC_ARC = YES; 545 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 546 | CLANG_WARN_BOOL_CONVERSION = YES; 547 | CLANG_WARN_COMMA = YES; 548 | CLANG_WARN_CONSTANT_CONVERSION = YES; 549 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 550 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 551 | CLANG_WARN_EMPTY_BODY = YES; 552 | CLANG_WARN_ENUM_CONVERSION = YES; 553 | CLANG_WARN_INFINITE_RECURSION = YES; 554 | CLANG_WARN_INT_CONVERSION = YES; 555 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 556 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 557 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 558 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 559 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 560 | CLANG_WARN_STRICT_PROTOTYPES = YES; 561 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 562 | CLANG_WARN_UNREACHABLE_CODE = YES; 563 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 564 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 565 | COPY_PHASE_STRIP = NO; 566 | DEBUG_INFORMATION_FORMAT = dwarf; 567 | ENABLE_STRICT_OBJC_MSGSEND = YES; 568 | ENABLE_TESTABILITY = YES; 569 | GCC_C_LANGUAGE_STANDARD = gnu99; 570 | GCC_DYNAMIC_NO_PIC = NO; 571 | GCC_NO_COMMON_BLOCKS = YES; 572 | GCC_OPTIMIZATION_LEVEL = 0; 573 | GCC_PREPROCESSOR_DEFINITIONS = ( 574 | "DEBUG=1", 575 | "$(inherited)", 576 | ); 577 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 578 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 579 | GCC_WARN_UNDECLARED_SELECTOR = YES; 580 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 581 | GCC_WARN_UNUSED_FUNCTION = YES; 582 | GCC_WARN_UNUSED_VARIABLE = YES; 583 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 584 | MTL_ENABLE_DEBUG_INFO = YES; 585 | ONLY_ACTIVE_ARCH = YES; 586 | SDKROOT = iphoneos; 587 | TARGETED_DEVICE_FAMILY = "1,2"; 588 | }; 589 | name = Debug; 590 | }; 591 | 97C147041CF9000F007C117D /* Release */ = { 592 | isa = XCBuildConfiguration; 593 | buildSettings = { 594 | ALWAYS_SEARCH_USER_PATHS = NO; 595 | CLANG_ANALYZER_NONNULL = YES; 596 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 597 | CLANG_CXX_LIBRARY = "libc++"; 598 | CLANG_ENABLE_MODULES = YES; 599 | CLANG_ENABLE_OBJC_ARC = YES; 600 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 601 | CLANG_WARN_BOOL_CONVERSION = YES; 602 | CLANG_WARN_COMMA = YES; 603 | CLANG_WARN_CONSTANT_CONVERSION = YES; 604 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 605 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 606 | CLANG_WARN_EMPTY_BODY = YES; 607 | CLANG_WARN_ENUM_CONVERSION = YES; 608 | CLANG_WARN_INFINITE_RECURSION = YES; 609 | CLANG_WARN_INT_CONVERSION = YES; 610 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 611 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 612 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 613 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 614 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 615 | CLANG_WARN_STRICT_PROTOTYPES = YES; 616 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 617 | CLANG_WARN_UNREACHABLE_CODE = YES; 618 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 619 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 620 | COPY_PHASE_STRIP = NO; 621 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 622 | ENABLE_NS_ASSERTIONS = NO; 623 | ENABLE_STRICT_OBJC_MSGSEND = YES; 624 | GCC_C_LANGUAGE_STANDARD = gnu99; 625 | GCC_NO_COMMON_BLOCKS = YES; 626 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 627 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 628 | GCC_WARN_UNDECLARED_SELECTOR = YES; 629 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 630 | GCC_WARN_UNUSED_FUNCTION = YES; 631 | GCC_WARN_UNUSED_VARIABLE = YES; 632 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 633 | MTL_ENABLE_DEBUG_INFO = NO; 634 | SDKROOT = iphoneos; 635 | SUPPORTED_PLATFORMS = iphoneos; 636 | SWIFT_COMPILATION_MODE = wholemodule; 637 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 638 | TARGETED_DEVICE_FAMILY = "1,2"; 639 | VALIDATE_PRODUCT = YES; 640 | }; 641 | name = Release; 642 | }; 643 | 97C147061CF9000F007C117D /* Debug */ = { 644 | isa = XCBuildConfiguration; 645 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 646 | buildSettings = { 647 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 648 | CLANG_ENABLE_MODULES = YES; 649 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 650 | DEVELOPMENT_TEAM = 6V53YACS2W; 651 | ENABLE_BITCODE = NO; 652 | INFOPLIST_FILE = Runner/Info.plist; 653 | INFOPLIST_KEY_CFBundleDisplayName = "Shorebird Clock"; 654 | LD_RUNPATH_SEARCH_PATHS = ( 655 | "$(inherited)", 656 | "@executable_path/Frameworks", 657 | ); 658 | PRODUCT_BUNDLE_IDENTIFIER = com.shorebird.timeShift; 659 | PRODUCT_NAME = "$(TARGET_NAME)"; 660 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 661 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 662 | SWIFT_VERSION = 5.0; 663 | VERSIONING_SYSTEM = "apple-generic"; 664 | }; 665 | name = Debug; 666 | }; 667 | 97C147071CF9000F007C117D /* Release */ = { 668 | isa = XCBuildConfiguration; 669 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 670 | buildSettings = { 671 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 672 | CLANG_ENABLE_MODULES = YES; 673 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 674 | DEVELOPMENT_TEAM = 6V53YACS2W; 675 | ENABLE_BITCODE = NO; 676 | INFOPLIST_FILE = Runner/Info.plist; 677 | INFOPLIST_KEY_CFBundleDisplayName = "Shorebird Clock"; 678 | LD_RUNPATH_SEARCH_PATHS = ( 679 | "$(inherited)", 680 | "@executable_path/Frameworks", 681 | ); 682 | PRODUCT_BUNDLE_IDENTIFIER = com.shorebird.timeShift; 683 | PRODUCT_NAME = "$(TARGET_NAME)"; 684 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 685 | SWIFT_VERSION = 5.0; 686 | VERSIONING_SYSTEM = "apple-generic"; 687 | }; 688 | name = Release; 689 | }; 690 | /* End XCBuildConfiguration section */ 691 | 692 | /* Begin XCConfigurationList section */ 693 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 694 | isa = XCConfigurationList; 695 | buildConfigurations = ( 696 | 331C8088294A63A400263BE5 /* Debug */, 697 | 331C8089294A63A400263BE5 /* Release */, 698 | 331C808A294A63A400263BE5 /* Profile */, 699 | ); 700 | defaultConfigurationIsVisible = 0; 701 | defaultConfigurationName = Release; 702 | }; 703 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 704 | isa = XCConfigurationList; 705 | buildConfigurations = ( 706 | 97C147031CF9000F007C117D /* Debug */, 707 | 97C147041CF9000F007C117D /* Release */, 708 | 249021D3217E4FDB00AE95B9 /* Profile */, 709 | ); 710 | defaultConfigurationIsVisible = 0; 711 | defaultConfigurationName = Release; 712 | }; 713 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 714 | isa = XCConfigurationList; 715 | buildConfigurations = ( 716 | 97C147061CF9000F007C117D /* Debug */, 717 | 97C147071CF9000F007C117D /* Release */, 718 | 249021D4217E4FDB00AE95B9 /* Profile */, 719 | ); 720 | defaultConfigurationIsVisible = 0; 721 | defaultConfigurationName = Release; 722 | }; 723 | /* End XCConfigurationList section */ 724 | }; 725 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 726 | } 727 | --------------------------------------------------------------------------------