├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── WORK_LOG.md ├── analysis_options.yaml ├── assets └── .gitkeep ├── lib ├── app │ └── app.dart ├── components │ └── atom │ │ ├── blur_mask.dart │ │ ├── drag_handler.dart │ │ ├── expandable.dart │ │ ├── fixed_sized_box.dart │ │ ├── iphone │ │ ├── config │ │ │ ├── iphone_scales.dart │ │ │ ├── iphone_scales.freezed.dart │ │ │ └── iphone_scales_provider.dart │ │ └── iphone.dart │ │ └── shaker.dart ├── constants │ └── colors.dart ├── features │ └── spring_board │ │ ├── components │ │ ├── avatar_presenter.dart │ │ ├── context_menu │ │ │ ├── config │ │ │ │ └── context_menu_scales.dart │ │ │ ├── context_menu.dart │ │ │ ├── context_menu_presentational.dart │ │ │ └── enums │ │ │ │ ├── anchor_pattern.dart │ │ │ │ └── anchor_pattern_family.dart │ │ ├── debug │ │ │ └── spring_board_debug.dart │ │ ├── home_icon │ │ │ ├── components │ │ │ │ └── app_icon │ │ │ │ │ ├── app_icon.dart │ │ │ │ │ └── config │ │ │ │ │ ├── app_icon_scales.dart │ │ │ │ │ ├── app_icon_scales.freezed.dart │ │ │ │ │ └── app_icon_scales_provider.dart │ │ │ ├── config │ │ │ │ ├── home_icon_scales.dart │ │ │ │ ├── home_icon_scales.freezed.dart │ │ │ │ └── home_icon_scales_provider.dart │ │ │ ├── home_icon.dart │ │ │ ├── presentationals │ │ │ │ └── home_icon_presentational.dart │ │ │ └── state │ │ │ │ ├── compute_should_shake.dart │ │ │ │ └── icon_order_faimily.dart │ │ ├── home_icon_session_handler │ │ │ ├── callbacks.dart │ │ │ ├── home_icon_mode.dart │ │ │ ├── home_icon_session_handler.dart │ │ │ ├── states.dart │ │ │ └── states │ │ │ │ ├── drag_state.dart │ │ │ │ ├── drag_state.freezed.dart │ │ │ │ ├── drag_state_provider.dart │ │ │ │ ├── home_icon_session.dart │ │ │ │ └── home_icon_session.freezed.dart │ │ ├── slot_area.dart │ │ └── spring_board_scrollable_area.dart │ │ ├── config │ │ ├── slot_computed_props │ │ │ ├── slot_computed_props.dart │ │ │ ├── slot_computed_props.freezed.dart │ │ │ └── slot_computed_props_provider.dart │ │ └── spring_board_scales │ │ │ ├── spring_board_scales.dart │ │ │ ├── spring_board_scales.freezed.dart │ │ │ └── spring_board_scales_provider.dart │ │ ├── screen │ │ └── spring_board.dart │ │ ├── state │ │ ├── reorderer │ │ │ ├── reodered_history.dart │ │ │ ├── reodered_history.freezed.dart │ │ │ └── reorderer.dart │ │ └── spring_board_mode │ │ │ ├── spring_board_mode.dart │ │ │ └── spring_board_mode_provider.dart │ │ └── storage │ │ └── spring_board_registerer │ │ ├── mock_icon_data │ │ ├── mock_icon_data.dart │ │ ├── mock_icon_data.freezed.dart │ │ └── mock_icon_data_list.dart │ │ ├── spring_board_registerer.dart │ │ ├── spring_board_storage.dart │ │ └── spring_board_storage.freezed.dart ├── main.dart ├── providers │ ├── area_positions │ │ ├── portal_root_key.dart │ │ ├── portal_root_position_provider.dart │ │ ├── slot_area_key.dart │ │ └── slot_area_position_provider.dart │ ├── navigator_key_provider.dart │ ├── scale_rate_provider.dart │ └── slot_config │ │ ├── slot_config.dart │ │ ├── slot_config.freezed.dart │ │ └── slot_config_provider.dart ├── theme.dart └── utils │ └── sleep.dart ├── pubspec.lock ├── pubspec.yaml ├── scripts ├── build_runner_build.sh └── build_runner_watch.sh ├── test ├── .gitkeep └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cf4400006550b70f28e4b4af815151d1e74846c6 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ryunosuke Watanabe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ios_springboard 2 | Flutter で iOS のスプリングボードを再現するプロジェクトです。 3 | 4 | ## WORK_LOG 5 | 作業内容を [WORK_LOG.md](https://github.com/HeavenOSK/ios_springboard/blob/main/WORK_LOG.md) に記録しています。 6 | -------------------------------------------------------------------------------- /WORK_LOG.md: -------------------------------------------------------------------------------- 1 | # 2022/01/03 2 | - やること 3 | - グルーピングできるようにする 4 | - やったこと 5 | - 並び替えをできるようにした 6 | - プルプル震えるようにする 7 | - プルプルの Dismiss も実装した 8 | # 2022/01/02 Part2 9 | - やったこと 10 | - Anchored Widget を作成した 11 | - ContextMenu を Anchored を使って表示した 12 | - AnchorPattern を実装して適切なアンカー位置で ContextMenu を表示できるようにした 13 | - Anchored は筋が悪かった。 14 | - Animation を観察してどこがグループなのかを考えてたら、最短で実装できた 15 | - Portal を柔軟に使うのを考えるべきだった 16 | - グルーピングを考える時に、フェイズや表示レイヤではなくて座標系のグルーピングで考えるのが良さそう。 17 | - 次やること 18 | - 並び替えをできるようにする 19 | - グルーピングできるようにする 20 | - ボトムエリアを観察して設計を考える。 21 | # 2022/01/02 22 | - やること 23 | - コンテキストメニューの表示 24 | - コンテキストメニューを適切な位置に表示する 25 | - AnchorPositioned を自作するのもいいかも 26 | - SpringBoardState を作っていく 27 | - プルプルさせる動作とか 28 | - やったこと 29 | - ContextMenu の表示・非表示、アニメーションを実装する 30 | - 次やること 31 | - Anchored を実装する 32 | - StackParentData を使う感じ 33 | - child size から childAnchor を計算する 34 | - targetRect を引数でもらって targetAnchor を計算する 35 | - 課題 36 | - child size をどうやって取る? 37 | - ゴール 38 | - 色々計算した値から StackParenetData 用の Data を作れば OK 39 | # 2022/01/01 40 | - やったこと 41 | - mode の更新の修正 42 | - アイコンのアニメーションを AnimatedContainer を使って修正 43 | - Portal を使って Avatar に BackdropFilter を追加した 44 | - showContext mode で手放した時にロックするようにした 45 | - 静的な状態で表示してみる 46 | - 次やること 47 | - コンテキストメニューの表示 48 | - コンテキストメニューを適切な位置に表示する 49 | - アニメーションで表示する 50 | - TweenAnimationBuilder で Opacity Transform.scale を変化させる 51 | - ビジネスロジックと結合する 52 | - SpringBoardState を作っていく 53 | - プルプルさせる動作とか 54 | # 2021/12/31 Part2 55 | - やったこと 56 | - Better な設計 57 | - Container/Presentational で分割するリファクタリング 58 | - リファクタリング色々やった 59 | - HomeIconSessionHandler がかなり良さそう 60 | - 次にやること 61 | - HomeIconSessionHandler 内で mode の切り替えを行うようにする 62 | - HomeIconController を置き換えるような形 63 | - HomeIconSessionHandler の riverpod 周りのコードが増えてきたので part ファイルで分離する。 64 | 65 | # 2021/12/31 66 | ### やったこと 67 | - State 上の dragGlobalPosition を更新する 68 | - Avatar は dragGlobalPosition を監視して位置を補正するようにする 69 | - Slot の位置を globalPosition で計算するようにする 70 | - 手を離した時に AnimationController で所定の位置に戻す 71 | - Slot の位置を globalPosition で計算するようにする 72 | ### 次やること 73 | - Draggable から Avatar を分離する 74 | - Avatar には slot 位置を与えるようにする 75 | - コンポーネントに自分で自分の位置を計算させるか 76 | - 統括管理してコンポーネントに考えさせずに、位置を与えるか 77 | ### 設計手法 78 | - global => data storage 79 | - できるだけ管理をしない 80 | - local => container/presentational 81 | - dynamic/static のレイヤを分離する 82 | - computed な値は dynamic 側で計算する 83 | # 2021/12/30 84 | - やること 85 | - リファクタリング 86 | - Listener を Draggable ではなく、親 Widget に持っていって、State で現在位置を管理するようにする 87 | - ドラッグを担当するコンポーネントは、 ドラッグが完了後所定の位置に AnimationController で戻すことも担当する 88 | - Draggable と Avatar を分離するかは微妙な問題 89 | - blur 表示中に手を離しても表示が切り替わらないこと 90 | - background をタッチして Dismiss 91 | - コンテキストメニューを表示 92 | - https://github.com/HeavenOSK/flutter_ipad_ui_clone/tree/open_app_navigation/lib/components 93 | - やったこと 94 | - アイコンタップ時のフェイズ移行のコードを書く 95 | - フェイズ移行のバグ修正を行った 96 | - showContext mode でバックグラウンドに Blur をかけるようにした 97 | - mode が reorder になる前にドラッグを始めた場合、mode を強制的に reorder に移行する 98 | - Blur と拡大のアニメーションのタイミングを合わせた 99 | - 拡大時の位置調整を行った 100 | - 拡大時にアプリの名前を非表示にする 101 | - SpringBoardState の mode 切り替えのバグを修正する 102 | - 次やること 103 | - リファクタリング 104 | - Listener を Draggable ではなく、親 Widget に持っていって、State で現在位置を管理するようにする 105 | - ドラッグを担当するコンポーネントは、 ドラッグが完了後所定の位置に AnimationController で戻すことも担当する 106 | - Draggable と Avatar を分離するかは微妙な問題 107 | # 2021/12/29 part2 108 | - やること 109 | - コンテキストメニューの実装 110 | - 一旦、静的に実装する 111 | - ズームアウトしつつ 112 | - Blur をかける 113 | - SpringBoard をズームアウトする 114 | - ズームアウトしたら Blur をかける 115 | - やったこと 116 | - SpringBoard 全体に Blur をかけてみた。 117 | - すると、ズームアウトをせずともそれっぽくなった 118 | - 長押し時の SpringBoard の Phase 移行の疑似コードを書いた 119 | - 次にやること 120 | - アイコンの ShortTap 判定 121 | - アイコンの LongPress 判定をして ContextMenu を表示する 122 | - ContextMenu が非表示になったあと draggable に移行する 123 | - 上記がキャンセルされた際のハンドリングを実機を確認しながら再現する 124 | # 2021/12/29 125 | - 今日やること 126 | - Long press 怪しいので一旦消す 127 | - きたるべき時に対応する 128 | - 拡大表示する 129 | - 位置の調整 130 | - ドロップシャドウを入れる 131 | - 今日やったこと 132 | - Long press 怪しいので一旦消す 133 | - family の実装ミスを修正 134 | - 型を指定しないとバグが見つからない場合がある 135 | - [【Flutter】図解とサンプルでわかる連続アニメーション ⭐️💨 TweenSequence 136 | ](https://zenn.dev/inari_sushio/articles/98b1ee55aadd12) を読んだ。 137 | - Icon の拡大・コンテキストメニューを順番に表示するのに使えるかも 138 | - 少なくとも内部実装は使えそう 139 | - 明日やること 140 | - コンテキストメニューの実装 141 | - SpringBoard をズームアウトする 142 | - ズームアウトしたら Blur をかける 143 | # 2021/12/27 144 | - やったこと 145 | - ドラッグ時に名前を消して表示する 146 | - 次やること 147 | - 拡大表示する 148 | 149 | # 2021/12/26 150 | - やったこと 151 | - Draggable を個別に設定できるようにする 152 | - 並び替えのバグの修正 153 | - 移動先の index のチェックだけではなく、スロットの一致性のチェックも必要だった。 154 | - デバッガでは再現しなかったので、プリントデバッグを試す 155 | - 次やること 156 | - ドラッグ中に文字を消す 157 | - 拡大表示する 158 | - ドラッグを重くする 159 | - フレームの更新起因で位置を更新する 160 | - 位置やレイアウトに影響する要素を挙げて整理する 161 | - Size を変化するのは悪手 162 | - 長押しからのドラッグのハンドルが発生するのどうやんの? 163 | # 2021/12/25 164 | - やったこと 165 | - Draggable のコードを全消しする 166 | - Draggable を作り直す 167 | - Why 168 | - キャンセルのアニメーションが非常に作りづらく、できれば StatefulWidget の仕組みに乗っけたい 169 | - Portal を使って作る 170 | - とりあえずインターフェースはそのままにして、新しい Widget で置き換える。 171 | - 実装の順番 172 | - FlutterPortal を使えるようにする 173 | - GestureRecognizer を仕込む 174 | - ドラッグ中に子を透明にすること 175 | - ドラッグ中に avatar を表示する 176 | - ドラッグ中に avatar を動かす 177 | - onUpdate で現在位置を知らせること 178 | - ドラッグ終了時にアニメーションして所定の位置に戻ること 179 | - プルプルさせる 180 | - state に dragging を追加する 181 | - dragging を更新する処理を追加する 182 | - 動いてる途中のものはプルプルさせないようにする 183 | - 次やること 184 | - ドラッグを重くする 185 | - フレームの更新起因で位置を更新する 186 | - 拡大表示する 187 | - 位置やレイアウトに影響する要素を挙げて整理する 188 | - Size を変化するのは悪手  189 | - ドラッグ中に文字を消す 190 | - 長押しからのドラッグのハンドルが発生するのどうやんの? 191 | # 2021/12/21 192 | - やったこと 193 | - スロットの位置を色分けして表示した。 194 | - note 195 | - showDebugSlot 的なフラグを作ったけど、まだ使ってない 196 | - どういう用途? スロットを色分け表示できればデバッグしやすそうだ。 197 | - Opacity 薄めてもいいかも 198 | - padding.horizontal は合計じゃなくて片方のみの値 199 | - 並び替えできるようになった 200 | - アイコンを表示する 201 | - スロット丸ごと入れ替えられるようにする 202 | - Slot の計算できたから、それを使って DragTarget とか作る 203 | - DragTarget を Rect で作る 204 | - DragTarget を positions から分離して別のリストにした 205 | - 次やること 206 | - 手放した時にスムーズに戻る 207 | - 今後やりたいこと 208 | - PageView 化する 209 | - PageView に関わる特殊ケースを実装する 210 | - プルプルを実装する 211 | - グループ化を実装する 212 | - ディテイールに拘る 213 | - コンテキストメニューを実装する 214 | - ライブラリ化する 215 | - スマホ対応する 216 | - 自分のポートフォリオサイトに適用する 217 | - ScalableDouble を追加する 218 | # 2021/12/21 219 | - やったこと 220 | - デバッグ用に order の表示と shuffle button を表示した 221 | - movable の実装消した 222 | - 次やること 223 | - 画面サイズを見て preferred position を算出するようにして、AnimatedPositionedで宣言的に移動させる 224 | - Animated なしで Positioned を使って表示する 225 | - 画面サイズと縦横の最大アイコン数を使って距離などを求めるようにする 226 | - もしかしたらアイコンのサイズも動的に決定するようにした方がいいかも 227 | - どのデータが computed にできないのか考える。 228 | 229 | # 2021/12/20 230 | - やったこと 231 | - Scale の拡大縮小をクラス内に閉じ込める 232 | - MockIconData を作成 233 | - SpringBoardStorage に order と mockIconData を追加 234 | - 次やること 235 | - デバッグ用に order の表示と shuffle button を表示する 236 | - movable の実装消す 237 | - 画面サイズを見て preferred position を算出するようにして、AnimatedPositionedで宣言的に移動させる 238 | 239 | # 2021/12/15 240 | - やったこと 241 | - タッチ~ドラッグ間で、コンポーネント上のポインタの位置がずれないように、 242 | - ポインタのポジションに対してローカルポジションを引いた 243 | - 次やること 244 | - Scale の拡大縮小をクラス内に閉じ込める 245 | - クラスに倍率を注入して拡大縮小済みの値が取得できるようにしておく。 246 | - 理由: 長さを式の状態で扱うのロジックややこしくなる。 247 | 248 | # 2021/12/14 249 | - やったこと 250 | - SpringBoardDragAvatar を追加した 251 | - Draggable の実装をちゃんと読んだ 252 | - わかんねーーーーーーーー 253 | - 次やること 254 | - ドラッグした時にアイコンがついてくるようにする 255 | - ドラッグして手離した時に、スムーズに定位置に戻るようにする。 256 | # 2021/12/13 257 | - 作戦 258 | - インクリメンタルに実装する 259 | - まず一個のアイコンをドラッグできるようにする 260 | - やったこと 261 | - 拡大の倍率を Provider から提供するようにした 262 | - コードを消したりコメントアウトして、アイコン一つを画面左上に配置した 263 | - アイコンをタッチした時にアイコンを拡大表示するようにした 264 | - 次やること 265 | - ドラッグした時にアイコンがついてくるようにする 266 | - ドラッグして手離した時に、スムーズに定位置に戻るようにする。 267 | # 2021/12/11 268 | - やったこと 269 | - デバイスの各サイズを物差しで測って定数として書いた 270 | - SpringBoard にアイコンを並べた 271 | - そのコンポーネントがどのような役割か Component/Composable の観点で見直すのが大事 272 | - ディレクトリを整理した 273 | - とりあえずアイコン一個だけブルブルさせる 274 | - と思って始めたら普通に全部ブルブルさせれた (Shaker widget) 275 | - 次やること 276 | - Shaker の ON/OFF を実装する 277 | # 2021/12/10 278 | - やったこと 279 | - とりあえず iPhone の形を作った 280 | - 次やること 281 | - 静的に画面内にアイコンを並べる 282 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic_mono/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false 5 | implicit-dynamic: false 6 | exclude: 7 | - lib/l10n/messages_*.dart 8 | - lib/**/*.freezed.dart 9 | - lib/**/*.g.dart 10 | -------------------------------------------------------------------------------- /assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeavenOSK/ios_springboard/7f7b760ae2fecdb6c47f89b8e3d9026fe1f35adb/assets/.gitkeep -------------------------------------------------------------------------------- /lib/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/components/atom/iphone/iphone.dart'; 4 | import 'package:ios_springboard/features/spring_board/screen/spring_board.dart'; 5 | import 'package:ios_springboard/providers/navigator_key_provider.dart'; 6 | 7 | class App extends ConsumerWidget { 8 | const App({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context, WidgetRef ref) { 12 | return MaterialApp( 13 | navigatorKey: ref.watch(navigatorKey), 14 | title: 'Flutter Demo', 15 | theme: ThemeData(primarySwatch: Colors.blue), 16 | home: const _Canvas(), 17 | ); 18 | } 19 | } 20 | 21 | class _Canvas extends HookConsumerWidget { 22 | const _Canvas({Key? key}) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context, WidgetRef ref) { 26 | return Row( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: const [ 29 | IPhone( 30 | child: SpringBoard(), 31 | ), 32 | // SpringBoardDebug(), 33 | ], 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/components/atom/blur_mask.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 5 | 6 | class BlurMaskPresentational extends HookConsumerWidget { 7 | const BlurMaskPresentational({ 8 | required this.shouldBlur, 9 | Key? key, 10 | }) : super(key: key); 11 | 12 | final bool shouldBlur; 13 | 14 | @override 15 | Widget build(BuildContext context, WidgetRef ref) { 16 | return TweenAnimationBuilder( 17 | tween: Tween(begin: 0, end: shouldBlur ? 1 : 0), 18 | // Duration も変更できる 19 | duration: shouldBlur 20 | ? const Duration(milliseconds: 350) 21 | : const Duration(milliseconds: 180), 22 | builder: (context, value, child) { 23 | if (value <= 0) { 24 | return const SizedBox.shrink(); 25 | } 26 | final sigmaValue = value * 10; 27 | return ClipRect( 28 | child: BackdropFilter( 29 | filter: ui.ImageFilter.blur( 30 | sigmaX: sigmaValue, 31 | sigmaY: sigmaValue, 32 | ), 33 | child: Container( 34 | color: Colors.white.withOpacity(0.1), 35 | ), 36 | ), 37 | ); 38 | }, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/components/atom/drag_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | typedef OnDragStart = void Function( 4 | Offset globalPosition, 5 | Offset localPosition, 6 | ); 7 | typedef OnDragUpdate = void Function( 8 | Offset globalPosition, 9 | Offset localPosition, 10 | ); 11 | typedef OnDragEnd = void Function( 12 | Offset globalPosition, 13 | Offset localPosition, 14 | ); 15 | 16 | class DragHandler extends StatelessWidget { 17 | const DragHandler({ 18 | required this.canDragStart, 19 | this.onDragStart, 20 | this.onDragUpdate, 21 | this.onDragEnd, 22 | Key? key, 23 | required this.child, 24 | }) : super(key: key); 25 | 26 | final OnDragStart? onDragStart; 27 | final OnDragUpdate? onDragUpdate; 28 | final OnDragEnd? onDragEnd; 29 | final bool canDragStart; 30 | final Widget child; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return IgnorePointer( 35 | ignoring: !canDragStart, 36 | child: Listener( 37 | behavior: HitTestBehavior.translucent, 38 | onPointerDown: (event) { 39 | onDragStart?.call( 40 | event.position, 41 | event.localPosition, 42 | ); 43 | }, 44 | onPointerMove: (event) { 45 | onDragUpdate?.call( 46 | event.position, 47 | event.localPosition, 48 | ); 49 | }, 50 | onPointerUp: (event) { 51 | onDragEnd?.call( 52 | event.position, 53 | event.localPosition, 54 | ); 55 | }, 56 | onPointerCancel: (event) { 57 | onDragEnd?.call( 58 | event.position, 59 | event.localPosition, 60 | ); 61 | }, 62 | child: child, 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/components/atom/expandable.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Expandable extends StatelessWidget { 4 | const Expandable({ 5 | required this.size, 6 | required this.child, 7 | this.expanding = false, 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | final Size size; 12 | final Widget child; 13 | final bool expanding; 14 | 15 | double get scale => expanding ? 1.1 : 1.0; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return AnimatedContainer( 20 | transform: Matrix4.diagonal3Values(scale, scale, 1), 21 | transformAlignment: Alignment.center, 22 | duration: const Duration(milliseconds: 250), 23 | child: child, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/components/atom/fixed_sized_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FixedSizedBox extends StatelessWidget { 4 | const FixedSizedBox({ 5 | required this.size, 6 | required this.child, 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | final Size size; 11 | final Widget child; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SizedBox.fromSize( 16 | size: size, 17 | child: FittedBox( 18 | child: child, 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/components/atom/iphone/config/iphone_scales.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'iphone_scales.freezed.dart'; 5 | 6 | @freezed 7 | class IPhoneScales with _$IPhoneScales { 8 | factory IPhoneScales({ 9 | required double rate, 10 | }) = _IPhoneScales; 11 | 12 | IPhoneScales._(); 13 | 14 | late final caseSize = const Size( 15 | 67.3, 16 | 138.4, 17 | ) * 18 | rate; 19 | late final caseRadius = 8 * rate; 20 | 21 | late final padding = const EdgeInsets.symmetric( 22 | horizontal: 4, 23 | vertical: 17, 24 | ) * 25 | rate; 26 | 27 | late final screenSize = Size( 28 | caseSize.width - padding.horizontal, 29 | caseSize.height - padding.vertical, 30 | ); 31 | 32 | late final micHoleSize = (const Size( 33 | 12, 34 | 1.5, 35 | )) * 36 | rate; 37 | 38 | late final speakerHoleBottomMargin = 7 * rate; 39 | late final speakerHoleRightMargin = 4 * rate; 40 | 41 | late final frontCameraRadius = 0.75 * rate; 42 | late final homeButtonRadius = 5.0 * rate; 43 | } 44 | -------------------------------------------------------------------------------- /lib/components/atom/iphone/config/iphone_scales.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'iphone_scales.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$IPhoneScalesTearOff { 18 | const _$IPhoneScalesTearOff(); 19 | 20 | _IPhoneScales call({required double rate}) { 21 | return _IPhoneScales( 22 | rate: rate, 23 | ); 24 | } 25 | } 26 | 27 | /// @nodoc 28 | const $IPhoneScales = _$IPhoneScalesTearOff(); 29 | 30 | /// @nodoc 31 | mixin _$IPhoneScales { 32 | double get rate => throw _privateConstructorUsedError; 33 | 34 | @JsonKey(ignore: true) 35 | $IPhoneScalesCopyWith get copyWith => 36 | throw _privateConstructorUsedError; 37 | } 38 | 39 | /// @nodoc 40 | abstract class $IPhoneScalesCopyWith<$Res> { 41 | factory $IPhoneScalesCopyWith( 42 | IPhoneScales value, $Res Function(IPhoneScales) then) = 43 | _$IPhoneScalesCopyWithImpl<$Res>; 44 | $Res call({double rate}); 45 | } 46 | 47 | /// @nodoc 48 | class _$IPhoneScalesCopyWithImpl<$Res> implements $IPhoneScalesCopyWith<$Res> { 49 | _$IPhoneScalesCopyWithImpl(this._value, this._then); 50 | 51 | final IPhoneScales _value; 52 | // ignore: unused_field 53 | final $Res Function(IPhoneScales) _then; 54 | 55 | @override 56 | $Res call({ 57 | Object? rate = freezed, 58 | }) { 59 | return _then(_value.copyWith( 60 | rate: rate == freezed 61 | ? _value.rate 62 | : rate // ignore: cast_nullable_to_non_nullable 63 | as double, 64 | )); 65 | } 66 | } 67 | 68 | /// @nodoc 69 | abstract class _$IPhoneScalesCopyWith<$Res> 70 | implements $IPhoneScalesCopyWith<$Res> { 71 | factory _$IPhoneScalesCopyWith( 72 | _IPhoneScales value, $Res Function(_IPhoneScales) then) = 73 | __$IPhoneScalesCopyWithImpl<$Res>; 74 | @override 75 | $Res call({double rate}); 76 | } 77 | 78 | /// @nodoc 79 | class __$IPhoneScalesCopyWithImpl<$Res> extends _$IPhoneScalesCopyWithImpl<$Res> 80 | implements _$IPhoneScalesCopyWith<$Res> { 81 | __$IPhoneScalesCopyWithImpl( 82 | _IPhoneScales _value, $Res Function(_IPhoneScales) _then) 83 | : super(_value, (v) => _then(v as _IPhoneScales)); 84 | 85 | @override 86 | _IPhoneScales get _value => super._value as _IPhoneScales; 87 | 88 | @override 89 | $Res call({ 90 | Object? rate = freezed, 91 | }) { 92 | return _then(_IPhoneScales( 93 | rate: rate == freezed 94 | ? _value.rate 95 | : rate // ignore: cast_nullable_to_non_nullable 96 | as double, 97 | )); 98 | } 99 | } 100 | 101 | /// @nodoc 102 | 103 | class _$_IPhoneScales extends _IPhoneScales { 104 | _$_IPhoneScales({required this.rate}) : super._(); 105 | 106 | @override 107 | final double rate; 108 | 109 | @override 110 | String toString() { 111 | return 'IPhoneScales(rate: $rate)'; 112 | } 113 | 114 | @override 115 | bool operator ==(dynamic other) { 116 | return identical(this, other) || 117 | (other.runtimeType == runtimeType && 118 | other is _IPhoneScales && 119 | const DeepCollectionEquality().equals(other.rate, rate)); 120 | } 121 | 122 | @override 123 | int get hashCode => 124 | Object.hash(runtimeType, const DeepCollectionEquality().hash(rate)); 125 | 126 | @JsonKey(ignore: true) 127 | @override 128 | _$IPhoneScalesCopyWith<_IPhoneScales> get copyWith => 129 | __$IPhoneScalesCopyWithImpl<_IPhoneScales>(this, _$identity); 130 | } 131 | 132 | abstract class _IPhoneScales extends IPhoneScales { 133 | factory _IPhoneScales({required double rate}) = _$_IPhoneScales; 134 | _IPhoneScales._() : super._(); 135 | 136 | @override 137 | double get rate; 138 | @override 139 | @JsonKey(ignore: true) 140 | _$IPhoneScalesCopyWith<_IPhoneScales> get copyWith => 141 | throw _privateConstructorUsedError; 142 | } 143 | -------------------------------------------------------------------------------- /lib/components/atom/iphone/config/iphone_scales_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/components/atom/iphone/config/iphone_scales.dart'; 3 | import 'package:ios_springboard/providers/scale_rate_provider.dart'; 4 | 5 | final iPhoneScalesProvider = StateProvider( 6 | (ref) => IPhoneScales( 7 | rate: ref.watch(scaleRate), 8 | ), 9 | ); 10 | -------------------------------------------------------------------------------- /lib/components/atom/iphone/iphone.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/components/atom/iphone/config/iphone_scales_provider.dart'; 4 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode.dart'; 5 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode_provider.dart'; 6 | 7 | class IPhone extends HookConsumerWidget { 8 | const IPhone({ 9 | required this.child, 10 | Key? key, 11 | }) : super(key: key); 12 | 13 | final Widget child; 14 | 15 | @override 16 | Widget build(BuildContext context, WidgetRef ref) { 17 | final iPhoneScale = ref.watch(iPhoneScalesProvider); 18 | return Container( 19 | height: iPhoneScale.caseSize.height, 20 | width: iPhoneScale.caseSize.width, 21 | decoration: BoxDecoration( 22 | color: Colors.black, 23 | borderRadius: BorderRadius.circular(iPhoneScale.caseRadius), 24 | boxShadow: [ 25 | BoxShadow( 26 | offset: const Offset(0, 20), 27 | blurRadius: 25, 28 | spreadRadius: -5, 29 | color: Colors.black.withOpacity(0.1), 30 | ), 31 | BoxShadow( 32 | offset: const Offset(0, 8), 33 | blurRadius: 10, 34 | spreadRadius: -6, 35 | color: Colors.black.withOpacity(0.1), 36 | ), 37 | ], 38 | ), 39 | child: Padding( 40 | padding: EdgeInsets.symmetric( 41 | horizontal: iPhoneScale.padding.horizontal / 2, 42 | ), 43 | child: Column( 44 | children: [ 45 | const _TopArea(), 46 | Expanded( 47 | child: child, 48 | ), 49 | const _BottomArea(), 50 | ], 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | 57 | class _TopArea extends HookConsumerWidget { 58 | const _TopArea({Key? key}) : super(key: key); 59 | 60 | @override 61 | Widget build(BuildContext context, WidgetRef ref) { 62 | final iPhoneScale = ref.watch(iPhoneScalesProvider); 63 | 64 | return SizedBox( 65 | height: iPhoneScale.padding.top, 66 | child: Padding( 67 | padding: EdgeInsets.only( 68 | bottom: iPhoneScale.speakerHoleBottomMargin, 69 | ), 70 | child: Row( 71 | crossAxisAlignment: CrossAxisAlignment.end, 72 | children: [ 73 | Expanded( 74 | child: Align( 75 | alignment: Alignment.bottomRight, 76 | child: Container( 77 | height: iPhoneScale.frontCameraRadius * 2, 78 | width: iPhoneScale.frontCameraRadius * 2, 79 | margin: EdgeInsets.only( 80 | right: iPhoneScale.speakerHoleRightMargin, 81 | ), 82 | decoration: BoxDecoration( 83 | color: Colors.white.withOpacity(0.3), 84 | borderRadius: BorderRadius.circular( 85 | iPhoneScale.frontCameraRadius, 86 | ), 87 | ), 88 | ), 89 | ), 90 | ), 91 | Container( 92 | width: iPhoneScale.micHoleSize.width, 93 | height: iPhoneScale.micHoleSize.height, 94 | decoration: BoxDecoration( 95 | color: Colors.white.withOpacity(0.3), 96 | borderRadius: BorderRadius.circular( 97 | iPhoneScale.micHoleSize.height / 2, 98 | ), 99 | ), 100 | ), 101 | const Spacer(), 102 | ], 103 | ), 104 | ), 105 | ); 106 | } 107 | } 108 | 109 | class _BottomArea extends HookConsumerWidget { 110 | const _BottomArea({Key? key}) : super(key: key); 111 | 112 | @override 113 | Widget build(BuildContext context, WidgetRef ref) { 114 | final iPhoneScale = ref.watch(iPhoneScalesProvider); 115 | return GestureDetector( 116 | onTap: () { 117 | ref.read(springBoardMode.notifier).state = SpringBoardMode.waiting; 118 | }, 119 | child: SizedBox( 120 | height: iPhoneScale.padding.bottom, 121 | child: Center( 122 | child: Container( 123 | height: iPhoneScale.homeButtonRadius * 2, 124 | width: iPhoneScale.homeButtonRadius * 2, 125 | decoration: BoxDecoration( 126 | color: Colors.transparent, 127 | border: Border.all( 128 | color: Colors.white.withOpacity(0.7), 129 | width: 0.3, 130 | ), 131 | borderRadius: BorderRadius.circular( 132 | iPhoneScale.homeButtonRadius, 133 | ), 134 | ), 135 | ), 136 | ), 137 | ), 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/components/atom/shaker.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class Shaker extends StatefulWidget { 7 | const Shaker({ 8 | required this.child, 9 | this.shaking = false, 10 | this.maxDegree = 2.0, 11 | Key? key, 12 | }) : super(key: key); 13 | 14 | final Widget child; 15 | final bool shaking; 16 | final double maxDegree; 17 | 18 | @override 19 | State createState() => _ShakerState(); 20 | } 21 | 22 | class _ShakerState extends State with SingleTickerProviderStateMixin { 23 | late double _t = math.Random().nextDouble() * 2 - 1; 24 | late bool shaking = widget.shaking; 25 | late final _animationController = AnimationController( 26 | vsync: this, 27 | duration: const Duration(days: 1), 28 | ); 29 | 30 | Alignment get alignmentValue { 31 | if (!shaking) { 32 | return Alignment.center; 33 | } 34 | final currentT = math.pi * 2 * _t / 8; 35 | final x = math.cos(currentT); 36 | final y = math.sin(currentT); 37 | return Alignment(x, y); 38 | } 39 | 40 | double get rotationValue { 41 | if (!shaking) { 42 | return 0; 43 | } 44 | final currentT = math.pi * 2 * _t; 45 | return math.sin(currentT); 46 | } 47 | 48 | void _startShake() { 49 | _animationController.forward(from: 0); 50 | } 51 | 52 | void _stopShake() { 53 | _animationController.stop(); 54 | } 55 | 56 | @override 57 | void initState() { 58 | super.initState(); 59 | _animationController.addListener(() { 60 | setState(() { 61 | final nextValue = _t + 0.05; 62 | _t = (nextValue * 100 % 100) * 0.01; 63 | }); 64 | }); 65 | } 66 | 67 | @override 68 | void didUpdateWidget(covariant Shaker oldWidget) { 69 | super.didUpdateWidget(oldWidget); 70 | if (widget.shaking == oldWidget.shaking) { 71 | return; 72 | } 73 | shaking = widget.shaking; 74 | if (shaking) { 75 | _startShake(); 76 | } else { 77 | _stopShake(); 78 | } 79 | } 80 | 81 | @override 82 | void dispose() { 83 | _animationController.dispose(); 84 | super.dispose(); 85 | } 86 | 87 | @override 88 | Widget build(BuildContext context) { 89 | return Transform.rotate( 90 | angle: rotationValue * (math.pi / 180 * widget.maxDegree), 91 | alignment: alignmentValue * 0.2, 92 | child: widget.child, 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/constants/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const colors = [ 4 | ...Colors.primaries, 5 | ...Colors.accents, 6 | ]; 7 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/avatar_presenter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_portal/flutter_portal.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | import 'package:ios_springboard/components/atom/blur_mask.dart'; 5 | 6 | class AvatarPresenter extends ConsumerWidget { 7 | const AvatarPresenter({ 8 | required this.avatarPosition, 9 | required this.avatarVisible, 10 | required this.shouldBlur, 11 | required this.child, 12 | required this.onTapBlur, 13 | required this.id, 14 | Key? key, 15 | }) : super(key: key); 16 | 17 | final Offset avatarPosition; 18 | final bool avatarVisible; 19 | final bool shouldBlur; 20 | final Widget child; 21 | final VoidCallback onTapBlur; 22 | final int id; 23 | 24 | @override 25 | Widget build(BuildContext context, WidgetRef ref) { 26 | return PortalEntry( 27 | visible: avatarVisible, 28 | portal: Stack( 29 | children: [ 30 | Positioned.fill( 31 | child: GestureDetector( 32 | onTap: onTapBlur, 33 | child: BlurMaskPresentational( 34 | shouldBlur: shouldBlur, 35 | ), 36 | ), 37 | ), 38 | Positioned( 39 | left: avatarPosition.dx, 40 | top: avatarPosition.dy, 41 | child: IgnorePointer( 42 | child: child, 43 | ), 44 | ), 45 | ], 46 | ), 47 | child: IgnorePointer( 48 | ignoring: avatarVisible, 49 | child: Opacity( 50 | opacity: avatarVisible ? 0 : 1, 51 | child: child, 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/context_menu/config/context_menu_scales.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/config/slot_computed_props/slot_computed_props_provider.dart'; 3 | import 'package:ios_springboard/providers/scale_rate_provider.dart'; 4 | 5 | final contextMenuScalesProvider = StateProvider( 6 | (ref) { 7 | final rate = ref.watch(scaleRate); 8 | final computedWidth = ref.watch( 9 | slotComputedProps.select( 10 | (value) => value.slotSize.width / 1.1 * 3, 11 | ), 12 | ); 13 | return ContextMenuScales( 14 | rate: rate, 15 | computedWidth: computedWidth, 16 | ); 17 | }, 18 | ); 19 | 20 | class ContextMenuScales { 21 | ContextMenuScales({ 22 | required this.rate, 23 | required this.computedWidth, 24 | }); 25 | 26 | final double rate; 27 | final double computedWidth; 28 | 29 | double get itemHeight => 8.2 / 1.1 * rate; 30 | 31 | double get itemHorizontalPadding => 2.5 / 1.1 * rate; 32 | } 33 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/context_menu/context_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/features/spring_board/components/context_menu/config/context_menu_scales.dart'; 4 | import 'package:ios_springboard/features/spring_board/components/context_menu/enums/anchor_pattern.dart'; 5 | 6 | part 'context_menu_presentational.dart'; 7 | 8 | class ContextMenu extends StatelessWidget { 9 | const ContextMenu({ 10 | required this.visible, 11 | required this.anchorPattern, 12 | Key? key, 13 | }) : super(key: key); 14 | 15 | final bool visible; 16 | final AnchorPattern anchorPattern; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return _ContextMenuPresentational( 21 | visible: visible, 22 | animationAlignment: anchorPattern.animationAlignment, 23 | itemCount: 3, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/context_menu/context_menu_presentational.dart: -------------------------------------------------------------------------------- 1 | part of 'context_menu.dart'; 2 | 3 | class _ContextMenuPresentational extends ConsumerWidget { 4 | const _ContextMenuPresentational({ 5 | required this.visible, 6 | required this.itemCount, 7 | required this.animationAlignment, 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | final int itemCount; 12 | final bool visible; 13 | final Alignment animationAlignment; 14 | 15 | @override 16 | Widget build(BuildContext context, WidgetRef ref) { 17 | final contextMenuScales = ref.watch(contextMenuScalesProvider); 18 | 19 | return TweenAnimationBuilder( 20 | // t と同じもの 21 | tween: Tween(begin: 0, end: visible ? 1 : 0), 22 | duration: const Duration(milliseconds: 180), 23 | curve: Curves.easeInCubic, 24 | builder: (context, value, child) { 25 | // TODO(HeavenOSK): アニメーションを調整する 26 | // ref: https://api.flutter.dev/flutter/animation/Curves-class.html 27 | // ref: https://github.com/HeavenOSK/flutter_swipable_stack/blob/f92bb649b6e9ff789c3f93a5519f58521897db7e/lib/src/swipable_stack.dart#L104 28 | return Transform.scale( 29 | scale: value, 30 | alignment: animationAlignment, 31 | child: Opacity( 32 | opacity: value, 33 | child: ClipRRect( 34 | borderRadius: BorderRadius.circular( 35 | contextMenuScales.itemHeight / 4, 36 | ), 37 | child: SizedBox( 38 | width: contextMenuScales.computedWidth, 39 | child: Column( 40 | mainAxisSize: MainAxisSize.min, 41 | children: [ 42 | for (int i = 0; i < itemCount; i++) ...[ 43 | const _ContextMenuItem(), 44 | if (i < itemCount - 1) 45 | const Divider( 46 | height: 0, 47 | color: Color(0xFFb1b3b8), 48 | ), 49 | ], 50 | ], 51 | ), 52 | ), 53 | ), 54 | ), 55 | ); 56 | }, 57 | ); 58 | } 59 | } 60 | 61 | class _ContextMenuItem extends ConsumerWidget { 62 | const _ContextMenuItem({Key? key}) : super(key: key); 63 | 64 | @override 65 | Widget build(BuildContext context, WidgetRef ref) { 66 | final contextMenuScales = ref.watch( 67 | contextMenuScalesProvider, 68 | ); 69 | return Container( 70 | color: const Color(0xFFe4e7ed), 71 | height: contextMenuScales.itemHeight, 72 | width: double.infinity, 73 | padding: EdgeInsets.symmetric( 74 | horizontal: contextMenuScales.itemHorizontalPadding, 75 | ), 76 | alignment: Alignment.centerLeft, 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/context_menu/enums/anchor_pattern.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum AnchorPattern { 4 | underLeftArea, 5 | underRightArea, 6 | upLeftArea, 7 | upRightArea, 8 | } 9 | 10 | extension AnchorPatternX on AnchorPattern { 11 | Alignment get targetAnchor { 12 | switch (this) { 13 | case AnchorPattern.underLeftArea: 14 | return Alignment.bottomLeft; 15 | case AnchorPattern.underRightArea: 16 | return Alignment.bottomRight; 17 | case AnchorPattern.upLeftArea: 18 | return Alignment.topLeft; 19 | case AnchorPattern.upRightArea: 20 | return Alignment.topRight; 21 | } 22 | } 23 | 24 | Alignment get childAnchor { 25 | switch (this) { 26 | case AnchorPattern.underLeftArea: 27 | return Alignment.topLeft; 28 | case AnchorPattern.underRightArea: 29 | return Alignment.topRight; 30 | case AnchorPattern.upLeftArea: 31 | return Alignment.bottomLeft; 32 | case AnchorPattern.upRightArea: 33 | return Alignment.bottomRight; 34 | } 35 | } 36 | } 37 | 38 | extension AnimationAlignmentX on AnchorPattern { 39 | Alignment get animationAlignment { 40 | switch (this) { 41 | case AnchorPattern.underLeftArea: 42 | return (Alignment.topCenter + Alignment.topLeft) / 2; 43 | case AnchorPattern.underRightArea: 44 | return (Alignment.topCenter + Alignment.topRight) / 2; 45 | 46 | case AnchorPattern.upLeftArea: 47 | return (Alignment.bottomCenter + Alignment.bottomLeft) / 2; 48 | case AnchorPattern.upRightArea: 49 | return (Alignment.bottomCenter + Alignment.bottomRight) / 2; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/context_menu/enums/anchor_pattern_family.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/components/context_menu/enums/anchor_pattern.dart'; 3 | import 'package:ios_springboard/features/spring_board/components/home_icon/state/icon_order_faimily.dart'; 4 | import 'package:ios_springboard/providers/slot_config/slot_config_provider.dart'; 5 | 6 | final anchorPatternFamily = StateProvider.family( 7 | (ref, id) { 8 | final index = ref.watch(iconOrderIndexFamily(id)); 9 | final config = ref.watch(slotConfig); 10 | final itemCount = config.rowCount * config.columnCount; 11 | final lastIndexOfHalfItems = itemCount ~/ 2 - 1; 12 | final showUnder = index <= lastIndexOfHalfItems; 13 | final showLeft = index % 4 <= 1; 14 | if (showUnder) { 15 | if (showLeft) { 16 | return AnchorPattern.underLeftArea; 17 | } 18 | return AnchorPattern.underRightArea; 19 | } 20 | if (showLeft) { 21 | return AnchorPattern.upLeftArea; 22 | } 23 | return AnchorPattern.upRightArea; 24 | }, 25 | ); 26 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/debug/spring_board_debug.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/spring_board_registerer.dart'; 5 | 6 | class SpringBoardDebug extends HookConsumerWidget { 7 | const SpringBoardDebug({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context, WidgetRef ref) { 11 | final order = ref.watch( 12 | springBoardRegisterer.select((value) => value.order), 13 | ); 14 | return Padding( 15 | padding: const EdgeInsets.only(left: 32), 16 | child: Material( 17 | type: MaterialType.transparency, 18 | child: Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | SizedBox( 22 | width: 128, 23 | child: Text( 24 | order.toString(), 25 | ), 26 | ), 27 | const Gap(8), 28 | ElevatedButton( 29 | onPressed: () { 30 | ref.read(springBoardRegisterer.notifier).shuffle(); 31 | }, 32 | child: const Text('shuffle'), 33 | ), 34 | const Gap(8), 35 | ElevatedButton( 36 | onPressed: () { 37 | ref.read(springBoardRegisterer.notifier).reset(); 38 | }, 39 | child: const Text('reset'), 40 | ), 41 | ], 42 | ), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/components/app_icon/app_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/features/spring_board/components/home_icon/components/app_icon/config/app_icon_scales_provider.dart'; 4 | 5 | class AppIcon extends HookConsumerWidget { 6 | const AppIcon({ 7 | this.color, 8 | this.icon, 9 | Key? key, 10 | }) : super(key: key); 11 | 12 | final Color? color; 13 | final String? icon; 14 | 15 | @override 16 | Widget build(BuildContext context, WidgetRef ref) { 17 | final appIconScales = ref.watch(appIconScalesProvider); 18 | final color = this.color ?? Colors.white; 19 | return MouseRegion( 20 | cursor: SystemMouseCursors.click, 21 | child: Material( 22 | type: MaterialType.transparency, 23 | child: Container( 24 | height: appIconScales.iconSize, 25 | width: appIconScales.iconSize, 26 | decoration: BoxDecoration( 27 | color: color, 28 | borderRadius: BorderRadius.circular( 29 | appIconScales.borderRadius, 30 | ), 31 | ), 32 | child: icon != null 33 | ? Center( 34 | child: Text( 35 | icon!, 36 | style: TextStyle( 37 | height: 1, 38 | fontSize: 12, 39 | color: color.computeLuminance() < 0.5 40 | ? Colors.white 41 | : Colors.black38, 42 | fontWeight: FontWeight.bold, 43 | ), 44 | ), 45 | ) 46 | : null, 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/components/app_icon/config/app_icon_scales.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'app_icon_scales.freezed.dart'; 4 | 5 | @freezed 6 | class AppIconScales with _$AppIconScales { 7 | factory AppIconScales({ 8 | required double rate, 9 | }) = _AppIconScales; 10 | 11 | AppIconScales._(); 12 | 13 | late final double iconSize = 9.2 * rate; 14 | late final double borderRadius = 1.9 * rate; 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/components/app_icon/config/app_icon_scales.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'app_icon_scales.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$AppIconScalesTearOff { 18 | const _$AppIconScalesTearOff(); 19 | 20 | _AppIconScales call({required double rate}) { 21 | return _AppIconScales( 22 | rate: rate, 23 | ); 24 | } 25 | } 26 | 27 | /// @nodoc 28 | const $AppIconScales = _$AppIconScalesTearOff(); 29 | 30 | /// @nodoc 31 | mixin _$AppIconScales { 32 | double get rate => throw _privateConstructorUsedError; 33 | 34 | @JsonKey(ignore: true) 35 | $AppIconScalesCopyWith get copyWith => 36 | throw _privateConstructorUsedError; 37 | } 38 | 39 | /// @nodoc 40 | abstract class $AppIconScalesCopyWith<$Res> { 41 | factory $AppIconScalesCopyWith( 42 | AppIconScales value, $Res Function(AppIconScales) then) = 43 | _$AppIconScalesCopyWithImpl<$Res>; 44 | $Res call({double rate}); 45 | } 46 | 47 | /// @nodoc 48 | class _$AppIconScalesCopyWithImpl<$Res> 49 | implements $AppIconScalesCopyWith<$Res> { 50 | _$AppIconScalesCopyWithImpl(this._value, this._then); 51 | 52 | final AppIconScales _value; 53 | // ignore: unused_field 54 | final $Res Function(AppIconScales) _then; 55 | 56 | @override 57 | $Res call({ 58 | Object? rate = freezed, 59 | }) { 60 | return _then(_value.copyWith( 61 | rate: rate == freezed 62 | ? _value.rate 63 | : rate // ignore: cast_nullable_to_non_nullable 64 | as double, 65 | )); 66 | } 67 | } 68 | 69 | /// @nodoc 70 | abstract class _$AppIconScalesCopyWith<$Res> 71 | implements $AppIconScalesCopyWith<$Res> { 72 | factory _$AppIconScalesCopyWith( 73 | _AppIconScales value, $Res Function(_AppIconScales) then) = 74 | __$AppIconScalesCopyWithImpl<$Res>; 75 | @override 76 | $Res call({double rate}); 77 | } 78 | 79 | /// @nodoc 80 | class __$AppIconScalesCopyWithImpl<$Res> 81 | extends _$AppIconScalesCopyWithImpl<$Res> 82 | implements _$AppIconScalesCopyWith<$Res> { 83 | __$AppIconScalesCopyWithImpl( 84 | _AppIconScales _value, $Res Function(_AppIconScales) _then) 85 | : super(_value, (v) => _then(v as _AppIconScales)); 86 | 87 | @override 88 | _AppIconScales get _value => super._value as _AppIconScales; 89 | 90 | @override 91 | $Res call({ 92 | Object? rate = freezed, 93 | }) { 94 | return _then(_AppIconScales( 95 | rate: rate == freezed 96 | ? _value.rate 97 | : rate // ignore: cast_nullable_to_non_nullable 98 | as double, 99 | )); 100 | } 101 | } 102 | 103 | /// @nodoc 104 | 105 | class _$_AppIconScales extends _AppIconScales { 106 | _$_AppIconScales({required this.rate}) : super._(); 107 | 108 | @override 109 | final double rate; 110 | 111 | @override 112 | String toString() { 113 | return 'AppIconScales(rate: $rate)'; 114 | } 115 | 116 | @override 117 | bool operator ==(dynamic other) { 118 | return identical(this, other) || 119 | (other.runtimeType == runtimeType && 120 | other is _AppIconScales && 121 | const DeepCollectionEquality().equals(other.rate, rate)); 122 | } 123 | 124 | @override 125 | int get hashCode => 126 | Object.hash(runtimeType, const DeepCollectionEquality().hash(rate)); 127 | 128 | @JsonKey(ignore: true) 129 | @override 130 | _$AppIconScalesCopyWith<_AppIconScales> get copyWith => 131 | __$AppIconScalesCopyWithImpl<_AppIconScales>(this, _$identity); 132 | } 133 | 134 | abstract class _AppIconScales extends AppIconScales { 135 | factory _AppIconScales({required double rate}) = _$_AppIconScales; 136 | _AppIconScales._() : super._(); 137 | 138 | @override 139 | double get rate; 140 | @override 141 | @JsonKey(ignore: true) 142 | _$AppIconScalesCopyWith<_AppIconScales> get copyWith => 143 | throw _privateConstructorUsedError; 144 | } 145 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/components/app_icon/config/app_icon_scales_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/components/home_icon/components/app_icon/config/app_icon_scales.dart'; 3 | import 'package:ios_springboard/providers/scale_rate_provider.dart'; 4 | 5 | final appIconScalesProvider = StateProvider( 6 | (ref) => AppIconScales( 7 | rate: ref.watch(scaleRate), 8 | ), 9 | ); 10 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/config/home_icon_scales.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:ios_springboard/features/spring_board/components/home_icon/components/app_icon/config/app_icon_scales.dart'; 4 | 5 | part 'home_icon_scales.freezed.dart'; 6 | 7 | @freezed 8 | class HomeIconScales with _$HomeIconScales { 9 | factory HomeIconScales({ 10 | required AppIconScales appIconScales, 11 | required double rate, 12 | }) = _HomeIconScales; 13 | 14 | HomeIconScales._(); 15 | 16 | late final areaSize = Size( 17 | appIconScales.iconSize, 18 | appIconScales.iconSize + textAreaHeight, 19 | ); 20 | 21 | late final double textAreaHeight = 3.0 * rate; 22 | } 23 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/config/home_icon_scales.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'home_icon_scales.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$HomeIconScalesTearOff { 18 | const _$HomeIconScalesTearOff(); 19 | 20 | _HomeIconScales call( 21 | {required AppIconScales appIconScales, required double rate}) { 22 | return _HomeIconScales( 23 | appIconScales: appIconScales, 24 | rate: rate, 25 | ); 26 | } 27 | } 28 | 29 | /// @nodoc 30 | const $HomeIconScales = _$HomeIconScalesTearOff(); 31 | 32 | /// @nodoc 33 | mixin _$HomeIconScales { 34 | AppIconScales get appIconScales => throw _privateConstructorUsedError; 35 | double get rate => throw _privateConstructorUsedError; 36 | 37 | @JsonKey(ignore: true) 38 | $HomeIconScalesCopyWith get copyWith => 39 | throw _privateConstructorUsedError; 40 | } 41 | 42 | /// @nodoc 43 | abstract class $HomeIconScalesCopyWith<$Res> { 44 | factory $HomeIconScalesCopyWith( 45 | HomeIconScales value, $Res Function(HomeIconScales) then) = 46 | _$HomeIconScalesCopyWithImpl<$Res>; 47 | $Res call({AppIconScales appIconScales, double rate}); 48 | 49 | $AppIconScalesCopyWith<$Res> get appIconScales; 50 | } 51 | 52 | /// @nodoc 53 | class _$HomeIconScalesCopyWithImpl<$Res> 54 | implements $HomeIconScalesCopyWith<$Res> { 55 | _$HomeIconScalesCopyWithImpl(this._value, this._then); 56 | 57 | final HomeIconScales _value; 58 | // ignore: unused_field 59 | final $Res Function(HomeIconScales) _then; 60 | 61 | @override 62 | $Res call({ 63 | Object? appIconScales = freezed, 64 | Object? rate = freezed, 65 | }) { 66 | return _then(_value.copyWith( 67 | appIconScales: appIconScales == freezed 68 | ? _value.appIconScales 69 | : appIconScales // ignore: cast_nullable_to_non_nullable 70 | as AppIconScales, 71 | rate: rate == freezed 72 | ? _value.rate 73 | : rate // ignore: cast_nullable_to_non_nullable 74 | as double, 75 | )); 76 | } 77 | 78 | @override 79 | $AppIconScalesCopyWith<$Res> get appIconScales { 80 | return $AppIconScalesCopyWith<$Res>(_value.appIconScales, (value) { 81 | return _then(_value.copyWith(appIconScales: value)); 82 | }); 83 | } 84 | } 85 | 86 | /// @nodoc 87 | abstract class _$HomeIconScalesCopyWith<$Res> 88 | implements $HomeIconScalesCopyWith<$Res> { 89 | factory _$HomeIconScalesCopyWith( 90 | _HomeIconScales value, $Res Function(_HomeIconScales) then) = 91 | __$HomeIconScalesCopyWithImpl<$Res>; 92 | @override 93 | $Res call({AppIconScales appIconScales, double rate}); 94 | 95 | @override 96 | $AppIconScalesCopyWith<$Res> get appIconScales; 97 | } 98 | 99 | /// @nodoc 100 | class __$HomeIconScalesCopyWithImpl<$Res> 101 | extends _$HomeIconScalesCopyWithImpl<$Res> 102 | implements _$HomeIconScalesCopyWith<$Res> { 103 | __$HomeIconScalesCopyWithImpl( 104 | _HomeIconScales _value, $Res Function(_HomeIconScales) _then) 105 | : super(_value, (v) => _then(v as _HomeIconScales)); 106 | 107 | @override 108 | _HomeIconScales get _value => super._value as _HomeIconScales; 109 | 110 | @override 111 | $Res call({ 112 | Object? appIconScales = freezed, 113 | Object? rate = freezed, 114 | }) { 115 | return _then(_HomeIconScales( 116 | appIconScales: appIconScales == freezed 117 | ? _value.appIconScales 118 | : appIconScales // ignore: cast_nullable_to_non_nullable 119 | as AppIconScales, 120 | rate: rate == freezed 121 | ? _value.rate 122 | : rate // ignore: cast_nullable_to_non_nullable 123 | as double, 124 | )); 125 | } 126 | } 127 | 128 | /// @nodoc 129 | 130 | class _$_HomeIconScales extends _HomeIconScales { 131 | _$_HomeIconScales({required this.appIconScales, required this.rate}) 132 | : super._(); 133 | 134 | @override 135 | final AppIconScales appIconScales; 136 | @override 137 | final double rate; 138 | 139 | @override 140 | String toString() { 141 | return 'HomeIconScales(appIconScales: $appIconScales, rate: $rate)'; 142 | } 143 | 144 | @override 145 | bool operator ==(dynamic other) { 146 | return identical(this, other) || 147 | (other.runtimeType == runtimeType && 148 | other is _HomeIconScales && 149 | const DeepCollectionEquality() 150 | .equals(other.appIconScales, appIconScales) && 151 | const DeepCollectionEquality().equals(other.rate, rate)); 152 | } 153 | 154 | @override 155 | int get hashCode => Object.hash( 156 | runtimeType, 157 | const DeepCollectionEquality().hash(appIconScales), 158 | const DeepCollectionEquality().hash(rate)); 159 | 160 | @JsonKey(ignore: true) 161 | @override 162 | _$HomeIconScalesCopyWith<_HomeIconScales> get copyWith => 163 | __$HomeIconScalesCopyWithImpl<_HomeIconScales>(this, _$identity); 164 | } 165 | 166 | abstract class _HomeIconScales extends HomeIconScales { 167 | factory _HomeIconScales( 168 | {required AppIconScales appIconScales, 169 | required double rate}) = _$_HomeIconScales; 170 | _HomeIconScales._() : super._(); 171 | 172 | @override 173 | AppIconScales get appIconScales; 174 | @override 175 | double get rate; 176 | @override 177 | @JsonKey(ignore: true) 178 | _$HomeIconScalesCopyWith<_HomeIconScales> get copyWith => 179 | throw _privateConstructorUsedError; 180 | } 181 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/config/home_icon_scales_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/components/home_icon/components/app_icon/config/app_icon_scales_provider.dart'; 3 | import 'package:ios_springboard/features/spring_board/components/home_icon/config/home_icon_scales.dart'; 4 | import 'package:ios_springboard/providers/scale_rate_provider.dart'; 5 | 6 | final homeIconScales = StateProvider( 7 | (ref) => HomeIconScales( 8 | appIconScales: ref.watch(appIconScalesProvider), 9 | rate: ref.watch(scaleRate), 10 | ), 11 | ); 12 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/home_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | import 'package:ios_springboard/components/atom/expandable.dart'; 5 | import 'package:ios_springboard/components/atom/shaker.dart'; 6 | import 'package:ios_springboard/features/spring_board/components/avatar_presenter.dart'; 7 | import 'package:ios_springboard/features/spring_board/components/context_menu/enums/anchor_pattern_family.dart'; 8 | import 'package:ios_springboard/features/spring_board/components/home_icon/presentationals/home_icon_presentational.dart'; 9 | import 'package:ios_springboard/features/spring_board/components/home_icon/state/compute_should_shake.dart'; 10 | import 'package:ios_springboard/features/spring_board/components/home_icon/state/icon_order_faimily.dart'; 11 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/home_icon_mode.dart'; 12 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/home_icon_session_handler.dart'; 13 | import 'package:ios_springboard/features/spring_board/config/slot_computed_props/slot_computed_props_provider.dart'; 14 | import 'package:ios_springboard/features/spring_board/state/reorderer/reorderer.dart'; 15 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data.dart'; 16 | 17 | class HomeIcon extends HookConsumerWidget { 18 | const HomeIcon({ 19 | required this.mockIconData, 20 | Key? key, 21 | }) : super(key: key); 22 | 23 | final MockIconData mockIconData; 24 | 25 | @override 26 | Widget build(BuildContext context, WidgetRef ref) { 27 | final slotLayerComputed = ref.watch(slotComputedProps); 28 | final shouldShake = ref.watch(computeShouldShake); 29 | final _reorderer = ref.watch(reorderer); 30 | final index = ref.watch( 31 | iconOrderIndexFamily(mockIconData.id), 32 | ); 33 | final position = slotLayerComputed.slotPositions[index]; 34 | final anchorPattern = ref.watch( 35 | anchorPatternFamily(mockIconData.id), 36 | ); 37 | return AnimatedPositioned( 38 | curve: Curves.easeOutCubic, 39 | duration: const Duration(milliseconds: 500), 40 | top: position.dy, 41 | left: position.dx, 42 | child: HomeIconSessionHandler( 43 | onDragUpdate: (globalPosition) { 44 | _reorderer.updatePosition( 45 | id: mockIconData.id, 46 | dragGlobalPosition: globalPosition, 47 | ); 48 | }, 49 | id: mockIconData.id, 50 | canDragStart: true, 51 | onTap: () {}, 52 | builder: ( 53 | context, 54 | mode, 55 | avatarPosition, 56 | dismissCallback, 57 | ) { 58 | final shouldExpand = mode.shouldExpand; 59 | return AvatarPresenter( 60 | id: mockIconData.id, 61 | shouldBlur: mode.shouldBlur, 62 | onTapBlur: dismissCallback, 63 | avatarVisible: mode.avatarVisible, 64 | avatarPosition: avatarPosition, 65 | child: SizedBox.fromSize( 66 | size: slotLayerComputed.slotSize, 67 | child: Shaker( 68 | shaking: shouldShake(mode), 69 | child: Expandable( 70 | expanding: shouldExpand, 71 | size: slotLayerComputed.slotSize, 72 | child: SizedBox.fromSize( 73 | size: slotLayerComputed.slotSize, 74 | child: Center( 75 | child: HomeIconPresentational( 76 | anchorPattern: anchorPattern, 77 | showContextMenu: mode.isShowContextMenu, 78 | shouldExpand: mode != HomeIconMode.waiting, 79 | shouldHideName: mode != HomeIconMode.waiting, 80 | mockIconData: mockIconData, 81 | ), 82 | ), 83 | ), 84 | ), 85 | ), 86 | ), 87 | ); 88 | }, 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/presentationals/home_icon_presentational.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_portal/flutter_portal.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | import 'package:ios_springboard/features/spring_board/components/context_menu/context_menu.dart'; 5 | import 'package:ios_springboard/features/spring_board/components/context_menu/enums/anchor_pattern.dart'; 6 | import 'package:ios_springboard/features/spring_board/components/context_menu/enums/anchor_pattern_family.dart'; 7 | import 'package:ios_springboard/features/spring_board/components/home_icon/components/app_icon/app_icon.dart'; 8 | import 'package:ios_springboard/features/spring_board/config/slot_computed_props/slot_computed_props_provider.dart'; 9 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data.dart'; 10 | 11 | class HomeIconPresentational extends HookConsumerWidget { 12 | const HomeIconPresentational({ 13 | required this.shouldExpand, 14 | required this.shouldHideName, 15 | required this.mockIconData, 16 | required this.showContextMenu, 17 | required this.anchorPattern, 18 | Key? key, 19 | }) : super(key: key); 20 | 21 | final MockIconData mockIconData; 22 | final bool shouldHideName; 23 | final bool shouldExpand; 24 | final bool showContextMenu; 25 | final AnchorPattern anchorPattern; 26 | 27 | @override 28 | Widget build(BuildContext context, WidgetRef ref) { 29 | final slotLayerComputed = ref.watch( 30 | slotComputedProps, 31 | ); 32 | final anchorPattern = ref.watch( 33 | anchorPatternFamily(mockIconData.id), 34 | ); 35 | return SizedBox.fromSize( 36 | size: slotLayerComputed.slotSize, 37 | child: Column( 38 | children: [ 39 | PortalEntry( 40 | portal: Padding( 41 | padding: const EdgeInsets.symmetric(vertical: 8), 42 | child: ContextMenu( 43 | visible: showContextMenu, 44 | anchorPattern: anchorPattern, 45 | ), 46 | ), 47 | childAnchor: anchorPattern.targetAnchor, 48 | portalAnchor: anchorPattern.childAnchor, 49 | child: AppIcon( 50 | color: mockIconData.color, 51 | icon: mockIconData.id.toString(), 52 | ), 53 | ), 54 | Expanded( 55 | child: Center( 56 | child: Material( 57 | type: MaterialType.transparency, 58 | child: Padding( 59 | padding: const EdgeInsets.symmetric( 60 | vertical: 1.7, 61 | ), 62 | child: FittedBox( 63 | child: AnimatedOpacity( 64 | opacity: shouldExpand || shouldHideName ? 0.0 : 1.0, 65 | duration: const Duration(milliseconds: 250), 66 | child: Text( 67 | mockIconData.name, 68 | style: const TextStyle( 69 | fontSize: 10, 70 | height: 1, 71 | letterSpacing: 1, 72 | color: Colors.white, 73 | fontWeight: FontWeight.w100, 74 | ), 75 | ), 76 | ), 77 | ), 78 | ), 79 | ), 80 | ), 81 | ), 82 | ], 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/state/compute_should_shake.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/home_icon_mode.dart'; 3 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode.dart'; 4 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode_provider.dart'; 5 | 6 | typedef ShouldShake = bool Function( 7 | HomeIconMode mode, 8 | ); 9 | 10 | final computeShouldShake = StateProvider( 11 | (ref) { 12 | final isReorderableMode = ref.watch( 13 | springBoardMode.select( 14 | (value) => value.isReorderableMode, 15 | ), 16 | ); 17 | return (mode) { 18 | return isReorderableMode && mode.isWaiting; 19 | }; 20 | }, 21 | ); 22 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon/state/icon_order_faimily.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/spring_board_registerer.dart'; 3 | 4 | final iconOrderIndexFamily = StateProvider.family((ref, id) { 5 | final order = ref.watch( 6 | springBoardRegisterer.select( 7 | (value) => value.order, 8 | ), 9 | ); 10 | return order.indexOf(id); 11 | }); 12 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/callbacks.dart: -------------------------------------------------------------------------------- 1 | part of 'home_icon_session_handler.dart'; 2 | 3 | typedef HomeIconBuilder = Widget Function( 4 | BuildContext context, 5 | HomeIconMode mode, 6 | Offset avatarPosition, 7 | VoidCallback dissmissCallback, 8 | ); 9 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/home_icon_mode.dart: -------------------------------------------------------------------------------- 1 | enum HomeIconMode { 2 | waiting, 3 | tapped, 4 | showContextMenu, 5 | dragging, 6 | endDragging, 7 | } 8 | 9 | extension HomeIconModeX on HomeIconMode { 10 | bool get isWaiting => this == HomeIconMode.waiting; 11 | 12 | bool get isTapped => this == HomeIconMode.tapped; 13 | 14 | bool get isShowContextMenu => this == HomeIconMode.showContextMenu; 15 | 16 | bool get isDragging => this == HomeIconMode.dragging; 17 | 18 | bool get isEndDragging => this == HomeIconMode.endDragging; 19 | } 20 | 21 | extension HomeIconModeBehaviors on HomeIconMode { 22 | bool get avatarVisible { 23 | switch (this) { 24 | case HomeIconMode.waiting: 25 | case HomeIconMode.tapped: 26 | return false; 27 | case HomeIconMode.showContextMenu: 28 | case HomeIconMode.dragging: 29 | case HomeIconMode.endDragging: 30 | return true; 31 | } 32 | } 33 | 34 | bool get shouldExpand { 35 | switch (this) { 36 | case HomeIconMode.waiting: 37 | case HomeIconMode.tapped: 38 | return false; 39 | case HomeIconMode.showContextMenu: 40 | case HomeIconMode.dragging: 41 | return true; 42 | case HomeIconMode.endDragging: 43 | return false; 44 | } 45 | } 46 | 47 | bool get shouldBlur => this == HomeIconMode.showContextMenu; 48 | } 49 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/home_icon_session_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/components/atom/drag_handler.dart'; 4 | import 'package:ios_springboard/features/spring_board/components/home_icon/state/icon_order_faimily.dart'; 5 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/home_icon_mode.dart'; 6 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/states/drag_state.dart'; 7 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/states/drag_state_provider.dart'; 8 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/states/home_icon_session.dart'; 9 | import 'package:ios_springboard/features/spring_board/config/slot_computed_props/slot_computed_props_provider.dart'; 10 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode.dart'; 11 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode_provider.dart'; 12 | import 'package:ios_springboard/providers/area_positions/portal_root_position_provider.dart'; 13 | import 'package:ios_springboard/providers/area_positions/slot_area_position_provider.dart'; 14 | import 'package:ios_springboard/utils/sleep.dart'; 15 | 16 | part 'callbacks.dart'; 17 | part 'states.dart'; 18 | 19 | class HomeIconSessionHandler extends ConsumerStatefulWidget { 20 | const HomeIconSessionHandler({ 21 | required this.canDragStart, 22 | required this.id, 23 | required this.builder, 24 | required this.onTap, 25 | required this.onDragUpdate, 26 | Key? key, 27 | }) : super(key: key); 28 | 29 | final VoidCallback onTap; 30 | final HomeIconBuilder builder; 31 | final int id; 32 | final bool canDragStart; 33 | final ValueChanged onDragUpdate; 34 | 35 | @override 36 | ConsumerState createState() => 37 | _HomeIconSessionHandlerContainerState(); 38 | } 39 | 40 | class _HomeIconSessionHandlerContainerState 41 | extends ConsumerState 42 | with SingleTickerProviderStateMixin { 43 | late final AnimationController _cancelAnimationController = 44 | AnimationController( 45 | vsync: this, 46 | duration: const Duration(milliseconds: 250), 47 | ); 48 | 49 | @override 50 | void dispose() { 51 | _cancelAnimationController.dispose(); 52 | super.dispose(); 53 | } 54 | 55 | Animation _getCancelAnimation({ 56 | required Offset currentPosition, 57 | required Offset finishPosition, 58 | }) { 59 | return Tween( 60 | begin: currentPosition, 61 | end: finishPosition, 62 | ).animate( 63 | CurvedAnimation( 64 | parent: _cancelAnimationController, 65 | curve: Curves.easeOutCubic, 66 | ), 67 | ); 68 | } 69 | 70 | void _animatePosition(Animation positionAnimation) { 71 | ref.read(dragState(widget.id).notifier).state = 72 | ref.read(dragState(widget.id)).copyWith( 73 | globalPosition: positionAnimation.value, 74 | ); 75 | } 76 | 77 | Future onDragStart({ 78 | required Offset globalPosition, 79 | required Offset localPosition, 80 | }) async { 81 | // TODO(HeavenOSK): ロックしてる場合はアプリを開いてあげる必要がある 82 | ref.read(_homeIconSession(widget.id).notifier).state = 83 | ref.read(_homeIconSession(widget.id)).copyWith( 84 | mode: HomeIconMode.tapped, 85 | locked: false, 86 | ); 87 | 88 | ref.read(dragState(widget.id).notifier).state = DragState( 89 | id: widget.id, 90 | globalPosition: globalPosition, 91 | localPosition: localPosition, 92 | isDragging: false, 93 | ); 94 | await sleep(milliseconds: 500); 95 | if (!ref 96 | .read(_homeIconSession(widget.id)) 97 | .canMoveFor(HomeIconMode.showContextMenu)) { 98 | return; 99 | } 100 | ref.read(_homeIconSession(widget.id).notifier).state = 101 | ref.read(_homeIconSession(widget.id)).copyWith( 102 | mode: HomeIconMode.showContextMenu, 103 | ); 104 | await sleep(milliseconds: 1500); 105 | if (!ref 106 | .read(_homeIconSession(widget.id)) 107 | .canMoveFor(HomeIconMode.dragging)) { 108 | return; 109 | } 110 | ref.read(_homeIconSession(widget.id).notifier).state = 111 | ref.read(_homeIconSession(widget.id)).copyWith( 112 | mode: HomeIconMode.dragging, 113 | ); 114 | ref.read(springBoardMode.notifier).state = SpringBoardMode.reorderable; 115 | } 116 | 117 | void onDragUpdate({ 118 | required Offset globalPosition, 119 | required Offset localPosition, 120 | }) { 121 | ref.read(_homeIconSession(widget.id).notifier).state = 122 | ref.read(_homeIconSession(widget.id)).copyWith( 123 | mode: HomeIconMode.dragging, 124 | ); 125 | ref.read(dragState(widget.id).notifier).state = 126 | ref.read(dragState(widget.id)).copyWith( 127 | globalPosition: globalPosition, 128 | // localPosition: localPosition, 129 | isDragging: true, 130 | ); 131 | ref.read(springBoardMode.notifier).state = SpringBoardMode.reorderable; 132 | widget.onDragUpdate(globalPosition); 133 | } 134 | 135 | void _finishSession() { 136 | switch (ref.read(_homeIconSession(widget.id)).mode) { 137 | case HomeIconMode.waiting: 138 | return; 139 | case HomeIconMode.tapped: 140 | case HomeIconMode.showContextMenu: 141 | ref.read(_homeIconSession(widget.id).notifier).state = 142 | ref.read(_homeIconSession(widget.id)).copyWith( 143 | mode: HomeIconMode.waiting, 144 | ); 145 | // TODO(HeavenOSK): アプリを開く処理を発火させる必要がある 146 | return; 147 | case HomeIconMode.dragging: 148 | case HomeIconMode.endDragging: 149 | // SpringBoard は並び替え状態なのでプルプル震える状態になる。 150 | 151 | ref.read(_homeIconSession(widget.id).notifier).state = 152 | ref.read(_homeIconSession(widget.id)).copyWith( 153 | mode: HomeIconMode.waiting, 154 | ); 155 | return; 156 | } 157 | } 158 | 159 | void onDragEnd({ 160 | required Offset globalPosition, 161 | required Offset localPosition, 162 | }) { 163 | if (ref.read(_homeIconSession(widget.id)).mode.isShowContextMenu) { 164 | ref.read(_homeIconSession(widget.id).notifier).state = 165 | ref.read(_homeIconSession(widget.id)).copyWith( 166 | locked: true, 167 | ); 168 | return; 169 | } 170 | if (ref.read(_homeIconSession(widget.id)).mode.isDragging) { 171 | ref.read(springBoardMode.notifier).state = SpringBoardMode.reorderable; 172 | } 173 | ref.read(_homeIconSession(widget.id).notifier).state = 174 | ref.read(_homeIconSession(widget.id)).copyWith( 175 | mode: HomeIconMode.endDragging, 176 | ); 177 | final slotLayerComputed = ref.read(slotComputedProps); 178 | final index = ref.read( 179 | iconOrderIndexFamily(widget.id), 180 | ); 181 | final slotPosition = slotLayerComputed.slotPositions[index]; 182 | final cancelAnimation = _getCancelAnimation( 183 | currentPosition: globalPosition, 184 | finishPosition: slotPosition + 185 | (ref.read(dragState(widget.id)).localPosition ?? Offset.zero) + 186 | ref.read(slotAreaPosition), 187 | ); 188 | void _animate() { 189 | _animatePosition(cancelAnimation); 190 | } 191 | 192 | cancelAnimation.addListener(_animate); 193 | _cancelAnimationController.forward(from: 0).then( 194 | (_) { 195 | cancelAnimation.removeListener(_animate); 196 | if (!ref.read(_homeIconSession(widget.id)).mode.isEndDragging) { 197 | return; 198 | } 199 | ref.read(dragState(widget.id).notifier).state = 200 | ref.read(dragState(widget.id)).copyWith( 201 | globalPosition: null, 202 | localPosition: null, 203 | isDragging: false, 204 | ); 205 | _finishSession(); 206 | }, 207 | ); 208 | } 209 | 210 | @override 211 | Widget build(BuildContext context) { 212 | final mode = ref.watch( 213 | _homeIconSession(widget.id).select( 214 | (s) => s.mode, 215 | ), 216 | ); 217 | final avatarPosition = ref.watch( 218 | _avatarPosition(widget.id), 219 | ); 220 | return DragHandler( 221 | canDragStart: widget.canDragStart, 222 | onDragStart: (globalPosition, localPosition) { 223 | onDragStart( 224 | globalPosition: globalPosition, 225 | localPosition: localPosition, 226 | ); 227 | }, 228 | onDragUpdate: (globalPosition, localPosition) { 229 | onDragUpdate( 230 | globalPosition: globalPosition, 231 | localPosition: localPosition, 232 | ); 233 | }, 234 | onDragEnd: (globalPosition, localPosition) { 235 | onDragEnd( 236 | globalPosition: globalPosition, 237 | localPosition: localPosition, 238 | ); 239 | }, 240 | child: widget.builder( 241 | context, 242 | mode, 243 | avatarPosition, 244 | _finishSession, 245 | ), 246 | ); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/states.dart: -------------------------------------------------------------------------------- 1 | part of 'home_icon_session_handler.dart'; 2 | 3 | final _homeIconSession = StateProvider.family( 4 | (ref, id) => const HomeIconSession(), 5 | ); 6 | 7 | final _avatarPosition = StateProvider.family( 8 | (ref, id) { 9 | final draggingState = ref.watch(dragState(id)); 10 | final globalPosition = draggingState.globalPosition; 11 | final localPosition = draggingState.localPosition; 12 | if (globalPosition == null || localPosition == null) { 13 | return Offset.zero; 14 | } 15 | final portalRootOffset = ref.watch( 16 | portalRootPosition, 17 | ); 18 | final position = globalPosition - localPosition - portalRootOffset; 19 | return position; 20 | }, 21 | ); 22 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/states/drag_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'drag_state.freezed.dart'; 5 | 6 | @freezed 7 | class DragState with _$DragState { 8 | const factory DragState({ 9 | required int id, 10 | Offset? globalPosition, 11 | Offset? localPosition, 12 | @Default(false) bool isDragging, 13 | }) = _DragState; 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/states/drag_state.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'drag_state.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$DragStateTearOff { 18 | const _$DragStateTearOff(); 19 | 20 | _DragState call( 21 | {required int id, 22 | Offset? globalPosition, 23 | Offset? localPosition, 24 | bool isDragging = false}) { 25 | return _DragState( 26 | id: id, 27 | globalPosition: globalPosition, 28 | localPosition: localPosition, 29 | isDragging: isDragging, 30 | ); 31 | } 32 | } 33 | 34 | /// @nodoc 35 | const $DragState = _$DragStateTearOff(); 36 | 37 | /// @nodoc 38 | mixin _$DragState { 39 | int get id => throw _privateConstructorUsedError; 40 | Offset? get globalPosition => throw _privateConstructorUsedError; 41 | Offset? get localPosition => throw _privateConstructorUsedError; 42 | bool get isDragging => throw _privateConstructorUsedError; 43 | 44 | @JsonKey(ignore: true) 45 | $DragStateCopyWith get copyWith => 46 | throw _privateConstructorUsedError; 47 | } 48 | 49 | /// @nodoc 50 | abstract class $DragStateCopyWith<$Res> { 51 | factory $DragStateCopyWith(DragState value, $Res Function(DragState) then) = 52 | _$DragStateCopyWithImpl<$Res>; 53 | $Res call( 54 | {int id, Offset? globalPosition, Offset? localPosition, bool isDragging}); 55 | } 56 | 57 | /// @nodoc 58 | class _$DragStateCopyWithImpl<$Res> implements $DragStateCopyWith<$Res> { 59 | _$DragStateCopyWithImpl(this._value, this._then); 60 | 61 | final DragState _value; 62 | // ignore: unused_field 63 | final $Res Function(DragState) _then; 64 | 65 | @override 66 | $Res call({ 67 | Object? id = freezed, 68 | Object? globalPosition = freezed, 69 | Object? localPosition = freezed, 70 | Object? isDragging = freezed, 71 | }) { 72 | return _then(_value.copyWith( 73 | id: id == freezed 74 | ? _value.id 75 | : id // ignore: cast_nullable_to_non_nullable 76 | as int, 77 | globalPosition: globalPosition == freezed 78 | ? _value.globalPosition 79 | : globalPosition // ignore: cast_nullable_to_non_nullable 80 | as Offset?, 81 | localPosition: localPosition == freezed 82 | ? _value.localPosition 83 | : localPosition // ignore: cast_nullable_to_non_nullable 84 | as Offset?, 85 | isDragging: isDragging == freezed 86 | ? _value.isDragging 87 | : isDragging // ignore: cast_nullable_to_non_nullable 88 | as bool, 89 | )); 90 | } 91 | } 92 | 93 | /// @nodoc 94 | abstract class _$DragStateCopyWith<$Res> implements $DragStateCopyWith<$Res> { 95 | factory _$DragStateCopyWith( 96 | _DragState value, $Res Function(_DragState) then) = 97 | __$DragStateCopyWithImpl<$Res>; 98 | @override 99 | $Res call( 100 | {int id, Offset? globalPosition, Offset? localPosition, bool isDragging}); 101 | } 102 | 103 | /// @nodoc 104 | class __$DragStateCopyWithImpl<$Res> extends _$DragStateCopyWithImpl<$Res> 105 | implements _$DragStateCopyWith<$Res> { 106 | __$DragStateCopyWithImpl(_DragState _value, $Res Function(_DragState) _then) 107 | : super(_value, (v) => _then(v as _DragState)); 108 | 109 | @override 110 | _DragState get _value => super._value as _DragState; 111 | 112 | @override 113 | $Res call({ 114 | Object? id = freezed, 115 | Object? globalPosition = freezed, 116 | Object? localPosition = freezed, 117 | Object? isDragging = freezed, 118 | }) { 119 | return _then(_DragState( 120 | id: id == freezed 121 | ? _value.id 122 | : id // ignore: cast_nullable_to_non_nullable 123 | as int, 124 | globalPosition: globalPosition == freezed 125 | ? _value.globalPosition 126 | : globalPosition // ignore: cast_nullable_to_non_nullable 127 | as Offset?, 128 | localPosition: localPosition == freezed 129 | ? _value.localPosition 130 | : localPosition // ignore: cast_nullable_to_non_nullable 131 | as Offset?, 132 | isDragging: isDragging == freezed 133 | ? _value.isDragging 134 | : isDragging // ignore: cast_nullable_to_non_nullable 135 | as bool, 136 | )); 137 | } 138 | } 139 | 140 | /// @nodoc 141 | 142 | class _$_DragState implements _DragState { 143 | const _$_DragState( 144 | {required this.id, 145 | this.globalPosition, 146 | this.localPosition, 147 | this.isDragging = false}); 148 | 149 | @override 150 | final int id; 151 | @override 152 | final Offset? globalPosition; 153 | @override 154 | final Offset? localPosition; 155 | @JsonKey(defaultValue: false) 156 | @override 157 | final bool isDragging; 158 | 159 | @override 160 | String toString() { 161 | return 'DragState(id: $id, globalPosition: $globalPosition, localPosition: $localPosition, isDragging: $isDragging)'; 162 | } 163 | 164 | @override 165 | bool operator ==(dynamic other) { 166 | return identical(this, other) || 167 | (other.runtimeType == runtimeType && 168 | other is _DragState && 169 | const DeepCollectionEquality().equals(other.id, id) && 170 | const DeepCollectionEquality() 171 | .equals(other.globalPosition, globalPosition) && 172 | const DeepCollectionEquality() 173 | .equals(other.localPosition, localPosition) && 174 | const DeepCollectionEquality() 175 | .equals(other.isDragging, isDragging)); 176 | } 177 | 178 | @override 179 | int get hashCode => Object.hash( 180 | runtimeType, 181 | const DeepCollectionEquality().hash(id), 182 | const DeepCollectionEquality().hash(globalPosition), 183 | const DeepCollectionEquality().hash(localPosition), 184 | const DeepCollectionEquality().hash(isDragging)); 185 | 186 | @JsonKey(ignore: true) 187 | @override 188 | _$DragStateCopyWith<_DragState> get copyWith => 189 | __$DragStateCopyWithImpl<_DragState>(this, _$identity); 190 | } 191 | 192 | abstract class _DragState implements DragState { 193 | const factory _DragState( 194 | {required int id, 195 | Offset? globalPosition, 196 | Offset? localPosition, 197 | bool isDragging}) = _$_DragState; 198 | 199 | @override 200 | int get id; 201 | @override 202 | Offset? get globalPosition; 203 | @override 204 | Offset? get localPosition; 205 | @override 206 | bool get isDragging; 207 | @override 208 | @JsonKey(ignore: true) 209 | _$DragStateCopyWith<_DragState> get copyWith => 210 | throw _privateConstructorUsedError; 211 | } 212 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/states/drag_state_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/states/drag_state.dart'; 3 | 4 | final dragState = StateProvider.family( 5 | (ref, id) => DragState(id: id), 6 | ); 7 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/states/home_icon_session.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:ios_springboard/features/spring_board/components/home_icon_session_handler/home_icon_mode.dart'; 3 | 4 | part 'home_icon_session.freezed.dart'; 5 | 6 | @freezed 7 | class HomeIconSession with _$HomeIconSession { 8 | const factory HomeIconSession({ 9 | @Default(HomeIconMode.waiting) HomeIconMode mode, 10 | @Default(false) bool locked, 11 | }) = _HomeIconSession; 12 | } 13 | 14 | extension HomeIconSessionX on HomeIconSession { 15 | bool canMoveFor(HomeIconMode targetMode) { 16 | if (locked) { 17 | return false; 18 | } 19 | switch (targetMode) { 20 | case HomeIconMode.waiting: 21 | case HomeIconMode.tapped: 22 | return true; 23 | case HomeIconMode.showContextMenu: 24 | return mode.isTapped && !locked; 25 | case HomeIconMode.dragging: 26 | return mode.isShowContextMenu && !locked; 27 | case HomeIconMode.endDragging: 28 | return mode.isDragging && !locked; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/home_icon_session_handler/states/home_icon_session.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'home_icon_session.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$HomeIconSessionTearOff { 18 | const _$HomeIconSessionTearOff(); 19 | 20 | _HomeIconSession call( 21 | {HomeIconMode mode = HomeIconMode.waiting, bool locked = false}) { 22 | return _HomeIconSession( 23 | mode: mode, 24 | locked: locked, 25 | ); 26 | } 27 | } 28 | 29 | /// @nodoc 30 | const $HomeIconSession = _$HomeIconSessionTearOff(); 31 | 32 | /// @nodoc 33 | mixin _$HomeIconSession { 34 | HomeIconMode get mode => throw _privateConstructorUsedError; 35 | bool get locked => throw _privateConstructorUsedError; 36 | 37 | @JsonKey(ignore: true) 38 | $HomeIconSessionCopyWith get copyWith => 39 | throw _privateConstructorUsedError; 40 | } 41 | 42 | /// @nodoc 43 | abstract class $HomeIconSessionCopyWith<$Res> { 44 | factory $HomeIconSessionCopyWith( 45 | HomeIconSession value, $Res Function(HomeIconSession) then) = 46 | _$HomeIconSessionCopyWithImpl<$Res>; 47 | $Res call({HomeIconMode mode, bool locked}); 48 | } 49 | 50 | /// @nodoc 51 | class _$HomeIconSessionCopyWithImpl<$Res> 52 | implements $HomeIconSessionCopyWith<$Res> { 53 | _$HomeIconSessionCopyWithImpl(this._value, this._then); 54 | 55 | final HomeIconSession _value; 56 | // ignore: unused_field 57 | final $Res Function(HomeIconSession) _then; 58 | 59 | @override 60 | $Res call({ 61 | Object? mode = freezed, 62 | Object? locked = freezed, 63 | }) { 64 | return _then(_value.copyWith( 65 | mode: mode == freezed 66 | ? _value.mode 67 | : mode // ignore: cast_nullable_to_non_nullable 68 | as HomeIconMode, 69 | locked: locked == freezed 70 | ? _value.locked 71 | : locked // ignore: cast_nullable_to_non_nullable 72 | as bool, 73 | )); 74 | } 75 | } 76 | 77 | /// @nodoc 78 | abstract class _$HomeIconSessionCopyWith<$Res> 79 | implements $HomeIconSessionCopyWith<$Res> { 80 | factory _$HomeIconSessionCopyWith( 81 | _HomeIconSession value, $Res Function(_HomeIconSession) then) = 82 | __$HomeIconSessionCopyWithImpl<$Res>; 83 | @override 84 | $Res call({HomeIconMode mode, bool locked}); 85 | } 86 | 87 | /// @nodoc 88 | class __$HomeIconSessionCopyWithImpl<$Res> 89 | extends _$HomeIconSessionCopyWithImpl<$Res> 90 | implements _$HomeIconSessionCopyWith<$Res> { 91 | __$HomeIconSessionCopyWithImpl( 92 | _HomeIconSession _value, $Res Function(_HomeIconSession) _then) 93 | : super(_value, (v) => _then(v as _HomeIconSession)); 94 | 95 | @override 96 | _HomeIconSession get _value => super._value as _HomeIconSession; 97 | 98 | @override 99 | $Res call({ 100 | Object? mode = freezed, 101 | Object? locked = freezed, 102 | }) { 103 | return _then(_HomeIconSession( 104 | mode: mode == freezed 105 | ? _value.mode 106 | : mode // ignore: cast_nullable_to_non_nullable 107 | as HomeIconMode, 108 | locked: locked == freezed 109 | ? _value.locked 110 | : locked // ignore: cast_nullable_to_non_nullable 111 | as bool, 112 | )); 113 | } 114 | } 115 | 116 | /// @nodoc 117 | 118 | class _$_HomeIconSession implements _HomeIconSession { 119 | const _$_HomeIconSession( 120 | {this.mode = HomeIconMode.waiting, this.locked = false}); 121 | 122 | @JsonKey(defaultValue: HomeIconMode.waiting) 123 | @override 124 | final HomeIconMode mode; 125 | @JsonKey(defaultValue: false) 126 | @override 127 | final bool locked; 128 | 129 | @override 130 | String toString() { 131 | return 'HomeIconSession(mode: $mode, locked: $locked)'; 132 | } 133 | 134 | @override 135 | bool operator ==(dynamic other) { 136 | return identical(this, other) || 137 | (other.runtimeType == runtimeType && 138 | other is _HomeIconSession && 139 | const DeepCollectionEquality().equals(other.mode, mode) && 140 | const DeepCollectionEquality().equals(other.locked, locked)); 141 | } 142 | 143 | @override 144 | int get hashCode => Object.hash( 145 | runtimeType, 146 | const DeepCollectionEquality().hash(mode), 147 | const DeepCollectionEquality().hash(locked)); 148 | 149 | @JsonKey(ignore: true) 150 | @override 151 | _$HomeIconSessionCopyWith<_HomeIconSession> get copyWith => 152 | __$HomeIconSessionCopyWithImpl<_HomeIconSession>(this, _$identity); 153 | } 154 | 155 | abstract class _HomeIconSession implements HomeIconSession { 156 | const factory _HomeIconSession({HomeIconMode mode, bool locked}) = 157 | _$_HomeIconSession; 158 | 159 | @override 160 | HomeIconMode get mode; 161 | @override 162 | bool get locked; 163 | @override 164 | @JsonKey(ignore: true) 165 | _$HomeIconSessionCopyWith<_HomeIconSession> get copyWith => 166 | throw _privateConstructorUsedError; 167 | } 168 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/slot_area.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/features/spring_board/components/home_icon/home_icon.dart'; 4 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/spring_board_registerer.dart'; 5 | import 'package:ios_springboard/providers/area_positions/slot_area_key.dart'; 6 | 7 | class SlotArea extends HookConsumerWidget { 8 | const SlotArea({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context, WidgetRef ref) { 12 | final mockDataList = ref.watch( 13 | springBoardRegisterer.select((value) => value.mockDataList), 14 | ); 15 | 16 | return Stack( 17 | key: slotAreaKey, 18 | children: mockDataList 19 | .map( 20 | (data) => HomeIcon( 21 | key: ValueKey(data.id), 22 | mockIconData: data, 23 | ), 24 | ) 25 | .toList(), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/features/spring_board/components/spring_board_scrollable_area.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/features/spring_board/components/slot_area.dart'; 4 | import 'package:ios_springboard/features/spring_board/config/spring_board_scales/spring_board_scales_provider.dart'; 5 | 6 | class ScrollableArea extends HookConsumerWidget { 7 | const ScrollableArea({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context, WidgetRef ref) { 11 | final sbScales = ref.watch(springBoardScales); 12 | 13 | return Container( 14 | height: sbScales.bottomAreaHeight, 15 | width: double.infinity, 16 | padding: EdgeInsets.symmetric( 17 | horizontal: sbScales.horizontalPadding, 18 | ).copyWith( 19 | top: sbScales.topPadding, 20 | ), 21 | child: Stack( 22 | children: const [ 23 | Positioned.fill( 24 | child: SlotArea(), 25 | ), 26 | ], 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/features/spring_board/config/slot_computed_props/slot_computed_props.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'slot_computed_props.freezed.dart'; 5 | 6 | @freezed 7 | class SlotComputedProps with _$SlotComputedProps { 8 | const factory SlotComputedProps({ 9 | required Size slotSize, 10 | required List slotPositions, 11 | required List dragTargets, 12 | }) = _SlotComputedProps; 13 | } 14 | -------------------------------------------------------------------------------- /lib/features/spring_board/config/slot_computed_props/slot_computed_props.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'slot_computed_props.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$SlotComputedPropsTearOff { 18 | const _$SlotComputedPropsTearOff(); 19 | 20 | _SlotComputedProps call( 21 | {required Size slotSize, 22 | required List slotPositions, 23 | required List dragTargets}) { 24 | return _SlotComputedProps( 25 | slotSize: slotSize, 26 | slotPositions: slotPositions, 27 | dragTargets: dragTargets, 28 | ); 29 | } 30 | } 31 | 32 | /// @nodoc 33 | const $SlotComputedProps = _$SlotComputedPropsTearOff(); 34 | 35 | /// @nodoc 36 | mixin _$SlotComputedProps { 37 | Size get slotSize => throw _privateConstructorUsedError; 38 | List get slotPositions => throw _privateConstructorUsedError; 39 | List get dragTargets => throw _privateConstructorUsedError; 40 | 41 | @JsonKey(ignore: true) 42 | $SlotComputedPropsCopyWith get copyWith => 43 | throw _privateConstructorUsedError; 44 | } 45 | 46 | /// @nodoc 47 | abstract class $SlotComputedPropsCopyWith<$Res> { 48 | factory $SlotComputedPropsCopyWith( 49 | SlotComputedProps value, $Res Function(SlotComputedProps) then) = 50 | _$SlotComputedPropsCopyWithImpl<$Res>; 51 | $Res call( 52 | {Size slotSize, List slotPositions, List dragTargets}); 53 | } 54 | 55 | /// @nodoc 56 | class _$SlotComputedPropsCopyWithImpl<$Res> 57 | implements $SlotComputedPropsCopyWith<$Res> { 58 | _$SlotComputedPropsCopyWithImpl(this._value, this._then); 59 | 60 | final SlotComputedProps _value; 61 | // ignore: unused_field 62 | final $Res Function(SlotComputedProps) _then; 63 | 64 | @override 65 | $Res call({ 66 | Object? slotSize = freezed, 67 | Object? slotPositions = freezed, 68 | Object? dragTargets = freezed, 69 | }) { 70 | return _then(_value.copyWith( 71 | slotSize: slotSize == freezed 72 | ? _value.slotSize 73 | : slotSize // ignore: cast_nullable_to_non_nullable 74 | as Size, 75 | slotPositions: slotPositions == freezed 76 | ? _value.slotPositions 77 | : slotPositions // ignore: cast_nullable_to_non_nullable 78 | as List, 79 | dragTargets: dragTargets == freezed 80 | ? _value.dragTargets 81 | : dragTargets // ignore: cast_nullable_to_non_nullable 82 | as List, 83 | )); 84 | } 85 | } 86 | 87 | /// @nodoc 88 | abstract class _$SlotComputedPropsCopyWith<$Res> 89 | implements $SlotComputedPropsCopyWith<$Res> { 90 | factory _$SlotComputedPropsCopyWith( 91 | _SlotComputedProps value, $Res Function(_SlotComputedProps) then) = 92 | __$SlotComputedPropsCopyWithImpl<$Res>; 93 | @override 94 | $Res call( 95 | {Size slotSize, List slotPositions, List dragTargets}); 96 | } 97 | 98 | /// @nodoc 99 | class __$SlotComputedPropsCopyWithImpl<$Res> 100 | extends _$SlotComputedPropsCopyWithImpl<$Res> 101 | implements _$SlotComputedPropsCopyWith<$Res> { 102 | __$SlotComputedPropsCopyWithImpl( 103 | _SlotComputedProps _value, $Res Function(_SlotComputedProps) _then) 104 | : super(_value, (v) => _then(v as _SlotComputedProps)); 105 | 106 | @override 107 | _SlotComputedProps get _value => super._value as _SlotComputedProps; 108 | 109 | @override 110 | $Res call({ 111 | Object? slotSize = freezed, 112 | Object? slotPositions = freezed, 113 | Object? dragTargets = freezed, 114 | }) { 115 | return _then(_SlotComputedProps( 116 | slotSize: slotSize == freezed 117 | ? _value.slotSize 118 | : slotSize // ignore: cast_nullable_to_non_nullable 119 | as Size, 120 | slotPositions: slotPositions == freezed 121 | ? _value.slotPositions 122 | : slotPositions // ignore: cast_nullable_to_non_nullable 123 | as List, 124 | dragTargets: dragTargets == freezed 125 | ? _value.dragTargets 126 | : dragTargets // ignore: cast_nullable_to_non_nullable 127 | as List, 128 | )); 129 | } 130 | } 131 | 132 | /// @nodoc 133 | 134 | class _$_SlotComputedProps implements _SlotComputedProps { 135 | const _$_SlotComputedProps( 136 | {required this.slotSize, 137 | required this.slotPositions, 138 | required this.dragTargets}); 139 | 140 | @override 141 | final Size slotSize; 142 | @override 143 | final List slotPositions; 144 | @override 145 | final List dragTargets; 146 | 147 | @override 148 | String toString() { 149 | return 'SlotComputedProps(slotSize: $slotSize, slotPositions: $slotPositions, dragTargets: $dragTargets)'; 150 | } 151 | 152 | @override 153 | bool operator ==(dynamic other) { 154 | return identical(this, other) || 155 | (other.runtimeType == runtimeType && 156 | other is _SlotComputedProps && 157 | const DeepCollectionEquality().equals(other.slotSize, slotSize) && 158 | const DeepCollectionEquality() 159 | .equals(other.slotPositions, slotPositions) && 160 | const DeepCollectionEquality() 161 | .equals(other.dragTargets, dragTargets)); 162 | } 163 | 164 | @override 165 | int get hashCode => Object.hash( 166 | runtimeType, 167 | const DeepCollectionEquality().hash(slotSize), 168 | const DeepCollectionEquality().hash(slotPositions), 169 | const DeepCollectionEquality().hash(dragTargets)); 170 | 171 | @JsonKey(ignore: true) 172 | @override 173 | _$SlotComputedPropsCopyWith<_SlotComputedProps> get copyWith => 174 | __$SlotComputedPropsCopyWithImpl<_SlotComputedProps>(this, _$identity); 175 | } 176 | 177 | abstract class _SlotComputedProps implements SlotComputedProps { 178 | const factory _SlotComputedProps( 179 | {required Size slotSize, 180 | required List slotPositions, 181 | required List dragTargets}) = _$_SlotComputedProps; 182 | 183 | @override 184 | Size get slotSize; 185 | @override 186 | List get slotPositions; 187 | @override 188 | List get dragTargets; 189 | @override 190 | @JsonKey(ignore: true) 191 | _$SlotComputedPropsCopyWith<_SlotComputedProps> get copyWith => 192 | throw _privateConstructorUsedError; 193 | } 194 | -------------------------------------------------------------------------------- /lib/features/spring_board/config/slot_computed_props/slot_computed_props_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/components/atom/iphone/config/iphone_scales_provider.dart'; 4 | import 'package:ios_springboard/features/spring_board/config/slot_computed_props/slot_computed_props.dart'; 5 | import 'package:ios_springboard/features/spring_board/config/spring_board_scales/spring_board_scales.dart'; 6 | import 'package:ios_springboard/features/spring_board/config/spring_board_scales/spring_board_scales_provider.dart'; 7 | import 'package:ios_springboard/providers/slot_config/slot_config_provider.dart'; 8 | 9 | final slotComputedProps = StateProvider( 10 | (ref) { 11 | final config = ref.watch(slotConfig); 12 | final iPhoneScales = ref.watch(iPhoneScalesProvider); 13 | final slotArea = ref.watch(springBoardScales).slotArea( 14 | iPhoneScales: iPhoneScales, 15 | ); 16 | final slotSize = Size( 17 | slotArea.width / config.rowCount, 18 | slotArea.height / config.columnCount, 19 | ); 20 | final slotItems = []; 21 | final dragTargets = []; 22 | for (var cIndex = 0; cIndex < config.columnCount; cIndex++) { 23 | for (var rIndex = 0; rIndex < config.rowCount; rIndex++) { 24 | final position = Offset( 25 | rIndex * slotSize.width, 26 | cIndex * slotSize.height, 27 | ); 28 | slotItems.add(position); 29 | } 30 | } 31 | for (var cIndex = 0; cIndex < config.columnCount; cIndex++) { 32 | for (var rIndex = 0; rIndex <= config.rowCount; rIndex++) { 33 | const targetWidth = 40.0; 34 | final position = Offset( 35 | rIndex * slotSize.width, 36 | cIndex * slotSize.height, 37 | ); 38 | final dragTarget = Rect.fromLTWH( 39 | position.dx - targetWidth / 2, 40 | position.dy + (slotSize.height / 4), 41 | targetWidth, 42 | slotSize.height / 2, 43 | ); 44 | dragTargets.add(dragTarget); 45 | } 46 | } 47 | return SlotComputedProps( 48 | slotSize: slotSize, 49 | slotPositions: slotItems, 50 | dragTargets: dragTargets, 51 | ); 52 | }, 53 | ); 54 | -------------------------------------------------------------------------------- /lib/features/spring_board/config/spring_board_scales/spring_board_scales.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:ios_springboard/components/atom/iphone/config/iphone_scales.dart'; 4 | 5 | part 'spring_board_scales.freezed.dart'; 6 | 7 | @freezed 8 | class SpringBoardScales with _$SpringBoardScales { 9 | factory SpringBoardScales({ 10 | required double rate, 11 | }) = _SpringBoardScales; 12 | 13 | SpringBoardScales._(); 14 | 15 | late final bottomAreaHeight = 14.0 * rate; 16 | late final verticalSpacing = 1.0 * rate; 17 | late final horizontalSpacing = 4.1 * rate; 18 | late final horizontalPadding = 4.0 * rate; 19 | late final topPadding = 5.0 * rate; 20 | } 21 | 22 | extension SpringBoardScalesX on SpringBoardScales { 23 | Size slotArea({ 24 | required IPhoneScales iPhoneScales, 25 | }) { 26 | return Size( 27 | iPhoneScales.screenSize.width - horizontalPadding * 2, 28 | iPhoneScales.screenSize.height - topPadding - bottomAreaHeight, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/features/spring_board/config/spring_board_scales/spring_board_scales.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'spring_board_scales.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$SpringBoardScalesTearOff { 18 | const _$SpringBoardScalesTearOff(); 19 | 20 | _SpringBoardScales call({required double rate}) { 21 | return _SpringBoardScales( 22 | rate: rate, 23 | ); 24 | } 25 | } 26 | 27 | /// @nodoc 28 | const $SpringBoardScales = _$SpringBoardScalesTearOff(); 29 | 30 | /// @nodoc 31 | mixin _$SpringBoardScales { 32 | double get rate => throw _privateConstructorUsedError; 33 | 34 | @JsonKey(ignore: true) 35 | $SpringBoardScalesCopyWith get copyWith => 36 | throw _privateConstructorUsedError; 37 | } 38 | 39 | /// @nodoc 40 | abstract class $SpringBoardScalesCopyWith<$Res> { 41 | factory $SpringBoardScalesCopyWith( 42 | SpringBoardScales value, $Res Function(SpringBoardScales) then) = 43 | _$SpringBoardScalesCopyWithImpl<$Res>; 44 | $Res call({double rate}); 45 | } 46 | 47 | /// @nodoc 48 | class _$SpringBoardScalesCopyWithImpl<$Res> 49 | implements $SpringBoardScalesCopyWith<$Res> { 50 | _$SpringBoardScalesCopyWithImpl(this._value, this._then); 51 | 52 | final SpringBoardScales _value; 53 | // ignore: unused_field 54 | final $Res Function(SpringBoardScales) _then; 55 | 56 | @override 57 | $Res call({ 58 | Object? rate = freezed, 59 | }) { 60 | return _then(_value.copyWith( 61 | rate: rate == freezed 62 | ? _value.rate 63 | : rate // ignore: cast_nullable_to_non_nullable 64 | as double, 65 | )); 66 | } 67 | } 68 | 69 | /// @nodoc 70 | abstract class _$SpringBoardScalesCopyWith<$Res> 71 | implements $SpringBoardScalesCopyWith<$Res> { 72 | factory _$SpringBoardScalesCopyWith( 73 | _SpringBoardScales value, $Res Function(_SpringBoardScales) then) = 74 | __$SpringBoardScalesCopyWithImpl<$Res>; 75 | @override 76 | $Res call({double rate}); 77 | } 78 | 79 | /// @nodoc 80 | class __$SpringBoardScalesCopyWithImpl<$Res> 81 | extends _$SpringBoardScalesCopyWithImpl<$Res> 82 | implements _$SpringBoardScalesCopyWith<$Res> { 83 | __$SpringBoardScalesCopyWithImpl( 84 | _SpringBoardScales _value, $Res Function(_SpringBoardScales) _then) 85 | : super(_value, (v) => _then(v as _SpringBoardScales)); 86 | 87 | @override 88 | _SpringBoardScales get _value => super._value as _SpringBoardScales; 89 | 90 | @override 91 | $Res call({ 92 | Object? rate = freezed, 93 | }) { 94 | return _then(_SpringBoardScales( 95 | rate: rate == freezed 96 | ? _value.rate 97 | : rate // ignore: cast_nullable_to_non_nullable 98 | as double, 99 | )); 100 | } 101 | } 102 | 103 | /// @nodoc 104 | 105 | class _$_SpringBoardScales extends _SpringBoardScales { 106 | _$_SpringBoardScales({required this.rate}) : super._(); 107 | 108 | @override 109 | final double rate; 110 | 111 | @override 112 | String toString() { 113 | return 'SpringBoardScales(rate: $rate)'; 114 | } 115 | 116 | @override 117 | bool operator ==(dynamic other) { 118 | return identical(this, other) || 119 | (other.runtimeType == runtimeType && 120 | other is _SpringBoardScales && 121 | const DeepCollectionEquality().equals(other.rate, rate)); 122 | } 123 | 124 | @override 125 | int get hashCode => 126 | Object.hash(runtimeType, const DeepCollectionEquality().hash(rate)); 127 | 128 | @JsonKey(ignore: true) 129 | @override 130 | _$SpringBoardScalesCopyWith<_SpringBoardScales> get copyWith => 131 | __$SpringBoardScalesCopyWithImpl<_SpringBoardScales>(this, _$identity); 132 | } 133 | 134 | abstract class _SpringBoardScales extends SpringBoardScales { 135 | factory _SpringBoardScales({required double rate}) = _$_SpringBoardScales; 136 | _SpringBoardScales._() : super._(); 137 | 138 | @override 139 | double get rate; 140 | @override 141 | @JsonKey(ignore: true) 142 | _$SpringBoardScalesCopyWith<_SpringBoardScales> get copyWith => 143 | throw _privateConstructorUsedError; 144 | } 145 | -------------------------------------------------------------------------------- /lib/features/spring_board/config/spring_board_scales/spring_board_scales_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/config/spring_board_scales/spring_board_scales.dart'; 3 | import 'package:ios_springboard/providers/scale_rate_provider.dart'; 4 | 5 | final springBoardScales = StateProvider( 6 | (ref) => SpringBoardScales( 7 | rate: ref.watch(scaleRate), 8 | ), 9 | ); 10 | -------------------------------------------------------------------------------- /lib/features/spring_board/screen/spring_board.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_portal/flutter_portal.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | import 'package:ios_springboard/features/spring_board/components/spring_board_scrollable_area.dart'; 7 | import 'package:ios_springboard/features/spring_board/config/spring_board_scales/spring_board_scales_provider.dart'; 8 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode.dart'; 9 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode_provider.dart'; 10 | import 'package:ios_springboard/providers/area_positions/portal_root_key.dart'; 11 | 12 | class SpringBoard extends HookConsumerWidget { 13 | const SpringBoard({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context, WidgetRef ref) { 17 | return MaterialApp( 18 | key: portalRootKey, 19 | debugShowCheckedModeBanner: false, 20 | builder: (context, child) => Portal( 21 | child: child!, 22 | ), 23 | home: Stack( 24 | children: [ 25 | Positioned.fill( 26 | child: GestureDetector( 27 | onTap: () { 28 | ref.read(springBoardMode.notifier).state = 29 | SpringBoardMode.waiting; 30 | }, 31 | child: const ColoredBox( 32 | color: Colors.blue, 33 | ), 34 | ), 35 | ), 36 | const Positioned.fill( 37 | child: ScrollableArea(), 38 | ), 39 | const Align( 40 | alignment: Alignment.bottomCenter, 41 | child: _BottomArea(), 42 | ), 43 | ], 44 | ), 45 | ); 46 | } 47 | } 48 | 49 | class _BottomArea extends HookConsumerWidget { 50 | const _BottomArea({Key? key}) : super(key: key); 51 | 52 | @override 53 | Widget build(BuildContext context, WidgetRef ref) { 54 | final sbScales = ref.watch(springBoardScales); 55 | return SizedBox( 56 | height: sbScales.bottomAreaHeight, 57 | width: double.infinity, 58 | child: Stack( 59 | children: [ 60 | Positioned.fill( 61 | child: ClipRect( 62 | child: ImageFiltered( 63 | imageFilter: ImageFilter.blur( 64 | sigmaX: 15, 65 | sigmaY: 15, 66 | ), 67 | child: Container( 68 | color: Colors.blue.shade50, 69 | ), 70 | ), 71 | ), 72 | ), 73 | ], 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/features/spring_board/state/reorderer/reodered_history.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'reodered_history.freezed.dart'; 4 | 5 | @freezed 6 | class ReorderedHistory with _$ReorderedHistory { 7 | const factory ReorderedHistory({ 8 | required int id, 9 | required int dragTargetIndex, 10 | }) = _ReorderedHistory; 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/spring_board/state/reorderer/reodered_history.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'reodered_history.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$ReorderedHistoryTearOff { 18 | const _$ReorderedHistoryTearOff(); 19 | 20 | _ReorderedHistory call({required int id, required int dragTargetIndex}) { 21 | return _ReorderedHistory( 22 | id: id, 23 | dragTargetIndex: dragTargetIndex, 24 | ); 25 | } 26 | } 27 | 28 | /// @nodoc 29 | const $ReorderedHistory = _$ReorderedHistoryTearOff(); 30 | 31 | /// @nodoc 32 | mixin _$ReorderedHistory { 33 | int get id => throw _privateConstructorUsedError; 34 | int get dragTargetIndex => throw _privateConstructorUsedError; 35 | 36 | @JsonKey(ignore: true) 37 | $ReorderedHistoryCopyWith get copyWith => 38 | throw _privateConstructorUsedError; 39 | } 40 | 41 | /// @nodoc 42 | abstract class $ReorderedHistoryCopyWith<$Res> { 43 | factory $ReorderedHistoryCopyWith( 44 | ReorderedHistory value, $Res Function(ReorderedHistory) then) = 45 | _$ReorderedHistoryCopyWithImpl<$Res>; 46 | $Res call({int id, int dragTargetIndex}); 47 | } 48 | 49 | /// @nodoc 50 | class _$ReorderedHistoryCopyWithImpl<$Res> 51 | implements $ReorderedHistoryCopyWith<$Res> { 52 | _$ReorderedHistoryCopyWithImpl(this._value, this._then); 53 | 54 | final ReorderedHistory _value; 55 | // ignore: unused_field 56 | final $Res Function(ReorderedHistory) _then; 57 | 58 | @override 59 | $Res call({ 60 | Object? id = freezed, 61 | Object? dragTargetIndex = freezed, 62 | }) { 63 | return _then(_value.copyWith( 64 | id: id == freezed 65 | ? _value.id 66 | : id // ignore: cast_nullable_to_non_nullable 67 | as int, 68 | dragTargetIndex: dragTargetIndex == freezed 69 | ? _value.dragTargetIndex 70 | : dragTargetIndex // ignore: cast_nullable_to_non_nullable 71 | as int, 72 | )); 73 | } 74 | } 75 | 76 | /// @nodoc 77 | abstract class _$ReorderedHistoryCopyWith<$Res> 78 | implements $ReorderedHistoryCopyWith<$Res> { 79 | factory _$ReorderedHistoryCopyWith( 80 | _ReorderedHistory value, $Res Function(_ReorderedHistory) then) = 81 | __$ReorderedHistoryCopyWithImpl<$Res>; 82 | @override 83 | $Res call({int id, int dragTargetIndex}); 84 | } 85 | 86 | /// @nodoc 87 | class __$ReorderedHistoryCopyWithImpl<$Res> 88 | extends _$ReorderedHistoryCopyWithImpl<$Res> 89 | implements _$ReorderedHistoryCopyWith<$Res> { 90 | __$ReorderedHistoryCopyWithImpl( 91 | _ReorderedHistory _value, $Res Function(_ReorderedHistory) _then) 92 | : super(_value, (v) => _then(v as _ReorderedHistory)); 93 | 94 | @override 95 | _ReorderedHistory get _value => super._value as _ReorderedHistory; 96 | 97 | @override 98 | $Res call({ 99 | Object? id = freezed, 100 | Object? dragTargetIndex = freezed, 101 | }) { 102 | return _then(_ReorderedHistory( 103 | id: id == freezed 104 | ? _value.id 105 | : id // ignore: cast_nullable_to_non_nullable 106 | as int, 107 | dragTargetIndex: dragTargetIndex == freezed 108 | ? _value.dragTargetIndex 109 | : dragTargetIndex // ignore: cast_nullable_to_non_nullable 110 | as int, 111 | )); 112 | } 113 | } 114 | 115 | /// @nodoc 116 | 117 | class _$_ReorderedHistory implements _ReorderedHistory { 118 | const _$_ReorderedHistory({required this.id, required this.dragTargetIndex}); 119 | 120 | @override 121 | final int id; 122 | @override 123 | final int dragTargetIndex; 124 | 125 | @override 126 | String toString() { 127 | return 'ReorderedHistory(id: $id, dragTargetIndex: $dragTargetIndex)'; 128 | } 129 | 130 | @override 131 | bool operator ==(dynamic other) { 132 | return identical(this, other) || 133 | (other.runtimeType == runtimeType && 134 | other is _ReorderedHistory && 135 | const DeepCollectionEquality().equals(other.id, id) && 136 | const DeepCollectionEquality() 137 | .equals(other.dragTargetIndex, dragTargetIndex)); 138 | } 139 | 140 | @override 141 | int get hashCode => Object.hash( 142 | runtimeType, 143 | const DeepCollectionEquality().hash(id), 144 | const DeepCollectionEquality().hash(dragTargetIndex)); 145 | 146 | @JsonKey(ignore: true) 147 | @override 148 | _$ReorderedHistoryCopyWith<_ReorderedHistory> get copyWith => 149 | __$ReorderedHistoryCopyWithImpl<_ReorderedHistory>(this, _$identity); 150 | } 151 | 152 | abstract class _ReorderedHistory implements ReorderedHistory { 153 | const factory _ReorderedHistory( 154 | {required int id, required int dragTargetIndex}) = _$_ReorderedHistory; 155 | 156 | @override 157 | int get id; 158 | @override 159 | int get dragTargetIndex; 160 | @override 161 | @JsonKey(ignore: true) 162 | _$ReorderedHistoryCopyWith<_ReorderedHistory> get copyWith => 163 | throw _privateConstructorUsedError; 164 | } 165 | -------------------------------------------------------------------------------- /lib/features/spring_board/state/reorderer/reorderer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/rendering.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | import 'package:ios_springboard/features/spring_board/components/home_icon/state/icon_order_faimily.dart'; 7 | import 'package:ios_springboard/features/spring_board/config/slot_computed_props/slot_computed_props_provider.dart'; 8 | import 'package:ios_springboard/features/spring_board/state/reorderer/reodered_history.dart'; 9 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/spring_board_registerer.dart'; 10 | import 'package:ios_springboard/providers/area_positions/slot_area_position_provider.dart'; 11 | import 'package:ios_springboard/providers/slot_config/slot_config_provider.dart'; 12 | 13 | final reorderer = Provider( 14 | (ref) => Reorderer(ref.read), 15 | ); 16 | 17 | class Reorderer { 18 | Reorderer(this._read); 19 | 20 | final Reader _read; 21 | ReorderedHistory? currentReorderedHistory; 22 | 23 | int? _checkInTarget(Offset currentPosition) { 24 | final slotProps = _read(slotComputedProps); 25 | for (var index = 0; index < slotProps.dragTargets.length; index++) { 26 | final dragTarget = slotProps.dragTargets[index]; 27 | final inTarget = dragTarget.contains(currentPosition); 28 | if (inTarget) { 29 | return index; 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | void updatePosition({ 36 | required int id, 37 | required Offset dragGlobalPosition, 38 | }) { 39 | final slotAreaOffset = _read(slotAreaPosition); 40 | final dragTargetIndex = _checkInTarget( 41 | dragGlobalPosition - slotAreaOffset, 42 | ); 43 | if (dragTargetIndex == null) { 44 | return; 45 | } 46 | final newHistory = ReorderedHistory( 47 | id: id, 48 | dragTargetIndex: dragTargetIndex, 49 | ); 50 | if (newHistory == currentReorderedHistory) { 51 | return; 52 | } 53 | currentReorderedHistory = newHistory; 54 | final currentIndex = _read(iconOrderIndexFamily(id)); 55 | final columnNumberForCurrentIndex = currentIndex ~/ 4; 56 | final config = _read(slotConfig); 57 | final rowNumber = dragTargetIndex % (config.rowCount + 1); 58 | final columnNumber = dragTargetIndex ~/ 5; 59 | final targetRowNumber = columnNumberForCurrentIndex <= columnNumber 60 | ? max(rowNumber - 1, 0) 61 | : min(rowNumber, 3); 62 | 63 | _read(springBoardRegisterer.notifier).moveItem( 64 | id: id, 65 | targetIndex: columnNumber * config.rowCount + targetRowNumber, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/features/spring_board/state/spring_board_mode/spring_board_mode.dart: -------------------------------------------------------------------------------- 1 | enum SpringBoardMode { 2 | waiting, 3 | reorderable, 4 | } 5 | 6 | extension SpringBoardModeX on SpringBoardMode { 7 | bool get isReorderableMode => this == SpringBoardMode.reorderable; 8 | } 9 | -------------------------------------------------------------------------------- /lib/features/spring_board/state/spring_board_mode/spring_board_mode_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/state/spring_board_mode/spring_board_mode.dart'; 3 | 4 | final springBoardMode = StateProvider( 5 | (ref) => SpringBoardMode.waiting, 6 | ); 7 | -------------------------------------------------------------------------------- /lib/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'mock_icon_data.freezed.dart'; 5 | 6 | @freezed 7 | class MockIconData with _$MockIconData { 8 | factory MockIconData({ 9 | required int id, 10 | required Color color, 11 | }) = _MockIconData; 12 | 13 | MockIconData._(); 14 | 15 | late final name = 'name-$id'; 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'mock_icon_data.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$MockIconDataTearOff { 18 | const _$MockIconDataTearOff(); 19 | 20 | _MockIconData call({required int id, required Color color}) { 21 | return _MockIconData( 22 | id: id, 23 | color: color, 24 | ); 25 | } 26 | } 27 | 28 | /// @nodoc 29 | const $MockIconData = _$MockIconDataTearOff(); 30 | 31 | /// @nodoc 32 | mixin _$MockIconData { 33 | int get id => throw _privateConstructorUsedError; 34 | Color get color => throw _privateConstructorUsedError; 35 | 36 | @JsonKey(ignore: true) 37 | $MockIconDataCopyWith get copyWith => 38 | throw _privateConstructorUsedError; 39 | } 40 | 41 | /// @nodoc 42 | abstract class $MockIconDataCopyWith<$Res> { 43 | factory $MockIconDataCopyWith( 44 | MockIconData value, $Res Function(MockIconData) then) = 45 | _$MockIconDataCopyWithImpl<$Res>; 46 | $Res call({int id, Color color}); 47 | } 48 | 49 | /// @nodoc 50 | class _$MockIconDataCopyWithImpl<$Res> implements $MockIconDataCopyWith<$Res> { 51 | _$MockIconDataCopyWithImpl(this._value, this._then); 52 | 53 | final MockIconData _value; 54 | // ignore: unused_field 55 | final $Res Function(MockIconData) _then; 56 | 57 | @override 58 | $Res call({ 59 | Object? id = freezed, 60 | Object? color = freezed, 61 | }) { 62 | return _then(_value.copyWith( 63 | id: id == freezed 64 | ? _value.id 65 | : id // ignore: cast_nullable_to_non_nullable 66 | as int, 67 | color: color == freezed 68 | ? _value.color 69 | : color // ignore: cast_nullable_to_non_nullable 70 | as Color, 71 | )); 72 | } 73 | } 74 | 75 | /// @nodoc 76 | abstract class _$MockIconDataCopyWith<$Res> 77 | implements $MockIconDataCopyWith<$Res> { 78 | factory _$MockIconDataCopyWith( 79 | _MockIconData value, $Res Function(_MockIconData) then) = 80 | __$MockIconDataCopyWithImpl<$Res>; 81 | @override 82 | $Res call({int id, Color color}); 83 | } 84 | 85 | /// @nodoc 86 | class __$MockIconDataCopyWithImpl<$Res> extends _$MockIconDataCopyWithImpl<$Res> 87 | implements _$MockIconDataCopyWith<$Res> { 88 | __$MockIconDataCopyWithImpl( 89 | _MockIconData _value, $Res Function(_MockIconData) _then) 90 | : super(_value, (v) => _then(v as _MockIconData)); 91 | 92 | @override 93 | _MockIconData get _value => super._value as _MockIconData; 94 | 95 | @override 96 | $Res call({ 97 | Object? id = freezed, 98 | Object? color = freezed, 99 | }) { 100 | return _then(_MockIconData( 101 | id: id == freezed 102 | ? _value.id 103 | : id // ignore: cast_nullable_to_non_nullable 104 | as int, 105 | color: color == freezed 106 | ? _value.color 107 | : color // ignore: cast_nullable_to_non_nullable 108 | as Color, 109 | )); 110 | } 111 | } 112 | 113 | /// @nodoc 114 | 115 | class _$_MockIconData extends _MockIconData { 116 | _$_MockIconData({required this.id, required this.color}) : super._(); 117 | 118 | @override 119 | final int id; 120 | @override 121 | final Color color; 122 | 123 | @override 124 | String toString() { 125 | return 'MockIconData(id: $id, color: $color)'; 126 | } 127 | 128 | @override 129 | bool operator ==(dynamic other) { 130 | return identical(this, other) || 131 | (other.runtimeType == runtimeType && 132 | other is _MockIconData && 133 | const DeepCollectionEquality().equals(other.id, id) && 134 | const DeepCollectionEquality().equals(other.color, color)); 135 | } 136 | 137 | @override 138 | int get hashCode => Object.hash( 139 | runtimeType, 140 | const DeepCollectionEquality().hash(id), 141 | const DeepCollectionEquality().hash(color)); 142 | 143 | @JsonKey(ignore: true) 144 | @override 145 | _$MockIconDataCopyWith<_MockIconData> get copyWith => 146 | __$MockIconDataCopyWithImpl<_MockIconData>(this, _$identity); 147 | } 148 | 149 | abstract class _MockIconData extends MockIconData { 150 | factory _MockIconData({required int id, required Color color}) = 151 | _$_MockIconData; 152 | _MockIconData._() : super._(); 153 | 154 | @override 155 | int get id; 156 | @override 157 | Color get color; 158 | @override 159 | @JsonKey(ignore: true) 160 | _$MockIconDataCopyWith<_MockIconData> get copyWith => 161 | throw _privateConstructorUsedError; 162 | } 163 | -------------------------------------------------------------------------------- /lib/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data.dart'; 3 | 4 | final _colors = [ 5 | Colors.yellowAccent, 6 | Colors.blueAccent, 7 | Colors.cyanAccent, 8 | Colors.deepOrangeAccent, 9 | Colors.deepPurpleAccent, 10 | Colors.orangeAccent, 11 | Colors.limeAccent, 12 | Colors.tealAccent, 13 | ]; 14 | 15 | MockIconData generateMockIconData(int index) => MockIconData( 16 | id: index, 17 | color: _colors[index % _colors.length], 18 | ); 19 | -------------------------------------------------------------------------------- /lib/features/spring_board/storage/spring_board_registerer/spring_board_registerer.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/spring_board_storage.dart'; 3 | 4 | final springBoardRegisterer = 5 | StateNotifierProvider( 6 | (ref) => SpringBoardRegisterer(), 7 | ); 8 | 9 | class SpringBoardRegisterer extends StateNotifier { 10 | SpringBoardRegisterer() 11 | : super( 12 | SpringBoardStorageX.getInitialStorage( 13 | itemCount: 24, 14 | ), 15 | ); 16 | 17 | void shuffle() { 18 | final copied = [...state.order]..shuffle(); 19 | state = state.copyWith( 20 | order: copied, 21 | ); 22 | } 23 | 24 | void reset() { 25 | state = state.copyWith( 26 | order: List.generate(state.mockDataList.length, (index) => index), 27 | ); 28 | } 29 | 30 | void moveItem({ 31 | required int id, 32 | required int targetIndex, 33 | }) { 34 | final originalIndex = state.order.indexOf(id); 35 | if (originalIndex == targetIndex) { 36 | return; 37 | } 38 | final cloned = [ 39 | ...state.order, 40 | ] 41 | ..remove(id) 42 | ..insert(targetIndex, id); 43 | state = state.copyWith( 44 | order: cloned, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/features/spring_board/storage/spring_board_registerer/spring_board_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data.dart'; 3 | import 'package:ios_springboard/features/spring_board/storage/spring_board_registerer/mock_icon_data/mock_icon_data_list.dart'; 4 | 5 | part 'spring_board_storage.freezed.dart'; 6 | 7 | @freezed 8 | class SpringBoardStorage with _$SpringBoardStorage { 9 | const factory SpringBoardStorage({ 10 | required List mockDataList, 11 | required List order, 12 | }) = _SpringBoardStorage; 13 | } 14 | 15 | extension SpringBoardStorageX on SpringBoardStorage { 16 | static SpringBoardStorage getInitialStorage({ 17 | required int itemCount, 18 | }) { 19 | final mockDataList = List.generate( 20 | itemCount, 21 | generateMockIconData, 22 | ); 23 | final order = List.generate(itemCount, (index) => index); 24 | return SpringBoardStorage( 25 | mockDataList: mockDataList, 26 | order: order, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/features/spring_board/storage/spring_board_registerer/spring_board_storage.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'spring_board_storage.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$SpringBoardStorageTearOff { 18 | const _$SpringBoardStorageTearOff(); 19 | 20 | _SpringBoardStorage call( 21 | {required List mockDataList, required List order}) { 22 | return _SpringBoardStorage( 23 | mockDataList: mockDataList, 24 | order: order, 25 | ); 26 | } 27 | } 28 | 29 | /// @nodoc 30 | const $SpringBoardStorage = _$SpringBoardStorageTearOff(); 31 | 32 | /// @nodoc 33 | mixin _$SpringBoardStorage { 34 | List get mockDataList => throw _privateConstructorUsedError; 35 | List get order => throw _privateConstructorUsedError; 36 | 37 | @JsonKey(ignore: true) 38 | $SpringBoardStorageCopyWith get copyWith => 39 | throw _privateConstructorUsedError; 40 | } 41 | 42 | /// @nodoc 43 | abstract class $SpringBoardStorageCopyWith<$Res> { 44 | factory $SpringBoardStorageCopyWith( 45 | SpringBoardStorage value, $Res Function(SpringBoardStorage) then) = 46 | _$SpringBoardStorageCopyWithImpl<$Res>; 47 | $Res call({List mockDataList, List order}); 48 | } 49 | 50 | /// @nodoc 51 | class _$SpringBoardStorageCopyWithImpl<$Res> 52 | implements $SpringBoardStorageCopyWith<$Res> { 53 | _$SpringBoardStorageCopyWithImpl(this._value, this._then); 54 | 55 | final SpringBoardStorage _value; 56 | // ignore: unused_field 57 | final $Res Function(SpringBoardStorage) _then; 58 | 59 | @override 60 | $Res call({ 61 | Object? mockDataList = freezed, 62 | Object? order = freezed, 63 | }) { 64 | return _then(_value.copyWith( 65 | mockDataList: mockDataList == freezed 66 | ? _value.mockDataList 67 | : mockDataList // ignore: cast_nullable_to_non_nullable 68 | as List, 69 | order: order == freezed 70 | ? _value.order 71 | : order // ignore: cast_nullable_to_non_nullable 72 | as List, 73 | )); 74 | } 75 | } 76 | 77 | /// @nodoc 78 | abstract class _$SpringBoardStorageCopyWith<$Res> 79 | implements $SpringBoardStorageCopyWith<$Res> { 80 | factory _$SpringBoardStorageCopyWith( 81 | _SpringBoardStorage value, $Res Function(_SpringBoardStorage) then) = 82 | __$SpringBoardStorageCopyWithImpl<$Res>; 83 | @override 84 | $Res call({List mockDataList, List order}); 85 | } 86 | 87 | /// @nodoc 88 | class __$SpringBoardStorageCopyWithImpl<$Res> 89 | extends _$SpringBoardStorageCopyWithImpl<$Res> 90 | implements _$SpringBoardStorageCopyWith<$Res> { 91 | __$SpringBoardStorageCopyWithImpl( 92 | _SpringBoardStorage _value, $Res Function(_SpringBoardStorage) _then) 93 | : super(_value, (v) => _then(v as _SpringBoardStorage)); 94 | 95 | @override 96 | _SpringBoardStorage get _value => super._value as _SpringBoardStorage; 97 | 98 | @override 99 | $Res call({ 100 | Object? mockDataList = freezed, 101 | Object? order = freezed, 102 | }) { 103 | return _then(_SpringBoardStorage( 104 | mockDataList: mockDataList == freezed 105 | ? _value.mockDataList 106 | : mockDataList // ignore: cast_nullable_to_non_nullable 107 | as List, 108 | order: order == freezed 109 | ? _value.order 110 | : order // ignore: cast_nullable_to_non_nullable 111 | as List, 112 | )); 113 | } 114 | } 115 | 116 | /// @nodoc 117 | 118 | class _$_SpringBoardStorage implements _SpringBoardStorage { 119 | const _$_SpringBoardStorage( 120 | {required this.mockDataList, required this.order}); 121 | 122 | @override 123 | final List mockDataList; 124 | @override 125 | final List order; 126 | 127 | @override 128 | String toString() { 129 | return 'SpringBoardStorage(mockDataList: $mockDataList, order: $order)'; 130 | } 131 | 132 | @override 133 | bool operator ==(dynamic other) { 134 | return identical(this, other) || 135 | (other.runtimeType == runtimeType && 136 | other is _SpringBoardStorage && 137 | const DeepCollectionEquality() 138 | .equals(other.mockDataList, mockDataList) && 139 | const DeepCollectionEquality().equals(other.order, order)); 140 | } 141 | 142 | @override 143 | int get hashCode => Object.hash( 144 | runtimeType, 145 | const DeepCollectionEquality().hash(mockDataList), 146 | const DeepCollectionEquality().hash(order)); 147 | 148 | @JsonKey(ignore: true) 149 | @override 150 | _$SpringBoardStorageCopyWith<_SpringBoardStorage> get copyWith => 151 | __$SpringBoardStorageCopyWithImpl<_SpringBoardStorage>(this, _$identity); 152 | } 153 | 154 | abstract class _SpringBoardStorage implements SpringBoardStorage { 155 | const factory _SpringBoardStorage( 156 | {required List mockDataList, 157 | required List order}) = _$_SpringBoardStorage; 158 | 159 | @override 160 | List get mockDataList; 161 | @override 162 | List get order; 163 | @override 164 | @JsonKey(ignore: true) 165 | _$SpringBoardStorageCopyWith<_SpringBoardStorage> get copyWith => 166 | throw _privateConstructorUsedError; 167 | } 168 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | 4 | import 'app/app.dart'; 5 | 6 | void main() { 7 | runApp( 8 | const ProviderScope( 9 | child: App(), 10 | ), 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /lib/providers/area_positions/portal_root_key.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | final portalRootKey = GlobalKey(); 4 | -------------------------------------------------------------------------------- /lib/providers/area_positions/portal_root_position_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/providers/area_positions/portal_root_key.dart'; 4 | 5 | final portalRootPosition = StateProvider( 6 | (_) { 7 | final box = portalRootKey.currentContext!.findRenderObject()! as RenderBox; 8 | return box.localToGlobal(Offset.zero); 9 | }, 10 | ); 11 | -------------------------------------------------------------------------------- /lib/providers/area_positions/slot_area_key.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | final slotAreaKey = GlobalKey(); 4 | -------------------------------------------------------------------------------- /lib/providers/area_positions/slot_area_position_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:ios_springboard/providers/area_positions/slot_area_key.dart'; 4 | 5 | final slotAreaPosition = StateProvider( 6 | (_) { 7 | final box = slotAreaKey.currentContext!.findRenderObject()! as RenderBox; 8 | return box.localToGlobal(Offset.zero); 9 | }, 10 | ); 11 | -------------------------------------------------------------------------------- /lib/providers/navigator_key_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | 4 | final navigatorKey = Provider( 5 | (_) => GlobalKey(), 6 | ); 7 | -------------------------------------------------------------------------------- /lib/providers/scale_rate_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | 3 | final scaleRate = StateProvider( 4 | (_) => 5, 5 | ); 6 | -------------------------------------------------------------------------------- /lib/providers/slot_config/slot_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'slot_config.freezed.dart'; 4 | 5 | @freezed 6 | class SlotConfig with _$SlotConfig { 7 | const factory SlotConfig({ 8 | @Default(4) int rowCount, 9 | @Default(6) int columnCount, 10 | }) = _SlotConfig; 11 | } 12 | -------------------------------------------------------------------------------- /lib/providers/slot_config/slot_config.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target 4 | 5 | part of 'slot_config.dart'; 6 | 7 | // ************************************************************************** 8 | // FreezedGenerator 9 | // ************************************************************************** 10 | 11 | T _$identity(T value) => value; 12 | 13 | final _privateConstructorUsedError = UnsupportedError( 14 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 15 | 16 | /// @nodoc 17 | class _$SlotConfigTearOff { 18 | const _$SlotConfigTearOff(); 19 | 20 | _SlotConfig call({int rowCount = 4, int columnCount = 6}) { 21 | return _SlotConfig( 22 | rowCount: rowCount, 23 | columnCount: columnCount, 24 | ); 25 | } 26 | } 27 | 28 | /// @nodoc 29 | const $SlotConfig = _$SlotConfigTearOff(); 30 | 31 | /// @nodoc 32 | mixin _$SlotConfig { 33 | int get rowCount => throw _privateConstructorUsedError; 34 | int get columnCount => throw _privateConstructorUsedError; 35 | 36 | @JsonKey(ignore: true) 37 | $SlotConfigCopyWith get copyWith => 38 | throw _privateConstructorUsedError; 39 | } 40 | 41 | /// @nodoc 42 | abstract class $SlotConfigCopyWith<$Res> { 43 | factory $SlotConfigCopyWith( 44 | SlotConfig value, $Res Function(SlotConfig) then) = 45 | _$SlotConfigCopyWithImpl<$Res>; 46 | $Res call({int rowCount, int columnCount}); 47 | } 48 | 49 | /// @nodoc 50 | class _$SlotConfigCopyWithImpl<$Res> implements $SlotConfigCopyWith<$Res> { 51 | _$SlotConfigCopyWithImpl(this._value, this._then); 52 | 53 | final SlotConfig _value; 54 | // ignore: unused_field 55 | final $Res Function(SlotConfig) _then; 56 | 57 | @override 58 | $Res call({ 59 | Object? rowCount = freezed, 60 | Object? columnCount = freezed, 61 | }) { 62 | return _then(_value.copyWith( 63 | rowCount: rowCount == freezed 64 | ? _value.rowCount 65 | : rowCount // ignore: cast_nullable_to_non_nullable 66 | as int, 67 | columnCount: columnCount == freezed 68 | ? _value.columnCount 69 | : columnCount // ignore: cast_nullable_to_non_nullable 70 | as int, 71 | )); 72 | } 73 | } 74 | 75 | /// @nodoc 76 | abstract class _$SlotConfigCopyWith<$Res> implements $SlotConfigCopyWith<$Res> { 77 | factory _$SlotConfigCopyWith( 78 | _SlotConfig value, $Res Function(_SlotConfig) then) = 79 | __$SlotConfigCopyWithImpl<$Res>; 80 | @override 81 | $Res call({int rowCount, int columnCount}); 82 | } 83 | 84 | /// @nodoc 85 | class __$SlotConfigCopyWithImpl<$Res> extends _$SlotConfigCopyWithImpl<$Res> 86 | implements _$SlotConfigCopyWith<$Res> { 87 | __$SlotConfigCopyWithImpl( 88 | _SlotConfig _value, $Res Function(_SlotConfig) _then) 89 | : super(_value, (v) => _then(v as _SlotConfig)); 90 | 91 | @override 92 | _SlotConfig get _value => super._value as _SlotConfig; 93 | 94 | @override 95 | $Res call({ 96 | Object? rowCount = freezed, 97 | Object? columnCount = freezed, 98 | }) { 99 | return _then(_SlotConfig( 100 | rowCount: rowCount == freezed 101 | ? _value.rowCount 102 | : rowCount // ignore: cast_nullable_to_non_nullable 103 | as int, 104 | columnCount: columnCount == freezed 105 | ? _value.columnCount 106 | : columnCount // ignore: cast_nullable_to_non_nullable 107 | as int, 108 | )); 109 | } 110 | } 111 | 112 | /// @nodoc 113 | 114 | class _$_SlotConfig implements _SlotConfig { 115 | const _$_SlotConfig({this.rowCount = 4, this.columnCount = 6}); 116 | 117 | @JsonKey(defaultValue: 4) 118 | @override 119 | final int rowCount; 120 | @JsonKey(defaultValue: 6) 121 | @override 122 | final int columnCount; 123 | 124 | @override 125 | String toString() { 126 | return 'SlotConfig(rowCount: $rowCount, columnCount: $columnCount)'; 127 | } 128 | 129 | @override 130 | bool operator ==(dynamic other) { 131 | return identical(this, other) || 132 | (other.runtimeType == runtimeType && 133 | other is _SlotConfig && 134 | const DeepCollectionEquality().equals(other.rowCount, rowCount) && 135 | const DeepCollectionEquality() 136 | .equals(other.columnCount, columnCount)); 137 | } 138 | 139 | @override 140 | int get hashCode => Object.hash( 141 | runtimeType, 142 | const DeepCollectionEquality().hash(rowCount), 143 | const DeepCollectionEquality().hash(columnCount)); 144 | 145 | @JsonKey(ignore: true) 146 | @override 147 | _$SlotConfigCopyWith<_SlotConfig> get copyWith => 148 | __$SlotConfigCopyWithImpl<_SlotConfig>(this, _$identity); 149 | } 150 | 151 | abstract class _SlotConfig implements SlotConfig { 152 | const factory _SlotConfig({int rowCount, int columnCount}) = _$_SlotConfig; 153 | 154 | @override 155 | int get rowCount; 156 | @override 157 | int get columnCount; 158 | @override 159 | @JsonKey(ignore: true) 160 | _$SlotConfigCopyWith<_SlotConfig> get copyWith => 161 | throw _privateConstructorUsedError; 162 | } 163 | -------------------------------------------------------------------------------- /lib/providers/slot_config/slot_config_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | import 'package:ios_springboard/providers/slot_config/slot_config.dart'; 3 | 4 | final slotConfig = StateProvider( 5 | (_) => const SlotConfig(), 6 | ); 7 | -------------------------------------------------------------------------------- /lib/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ThemeData buildTheme() { 4 | final base = ThemeData.light(); 5 | return base; 6 | } 7 | -------------------------------------------------------------------------------- /lib/utils/sleep.dart: -------------------------------------------------------------------------------- 1 | Future sleep({ 2 | required int milliseconds, 3 | }) => 4 | Future.delayed( 5 | Duration( 6 | milliseconds: milliseconds, 7 | ), 8 | ); 9 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "31.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.8.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.3.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.8.2" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.1.0" 39 | build: 40 | dependency: transitive 41 | description: 42 | name: build 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.1.1" 46 | build_config: 47 | dependency: transitive 48 | description: 49 | name: build_config 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.0" 53 | build_daemon: 54 | dependency: transitive 55 | description: 56 | name: build_daemon 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.1" 60 | build_resolvers: 61 | dependency: transitive 62 | description: 63 | name: build_resolvers 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.0.5" 67 | build_runner: 68 | dependency: "direct dev" 69 | description: 70 | name: build_runner 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.1.5" 74 | build_runner_core: 75 | dependency: transitive 76 | description: 77 | name: build_runner_core 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "7.2.2" 81 | built_collection: 82 | dependency: transitive 83 | description: 84 | name: built_collection 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "5.1.1" 88 | built_value: 89 | dependency: transitive 90 | description: 91 | name: built_value 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "8.1.3" 95 | characters: 96 | dependency: transitive 97 | description: 98 | name: characters 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.2.0" 102 | charcode: 103 | dependency: transitive 104 | description: 105 | name: charcode 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "1.3.1" 109 | checked_yaml: 110 | dependency: transitive 111 | description: 112 | name: checked_yaml 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "2.0.1" 116 | cli_util: 117 | dependency: transitive 118 | description: 119 | name: cli_util 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.3.5" 123 | clock: 124 | dependency: transitive 125 | description: 126 | name: clock 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.1.0" 130 | code_builder: 131 | dependency: transitive 132 | description: 133 | name: code_builder 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "4.1.0" 137 | collection: 138 | dependency: transitive 139 | description: 140 | name: collection 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.15.0" 144 | convert: 145 | dependency: transitive 146 | description: 147 | name: convert 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.0.1" 151 | crypto: 152 | dependency: transitive 153 | description: 154 | name: crypto 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "3.0.1" 158 | dart_style: 159 | dependency: transitive 160 | description: 161 | name: dart_style 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "2.2.0" 165 | fake_async: 166 | dependency: transitive 167 | description: 168 | name: fake_async 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.2.0" 172 | file: 173 | dependency: transitive 174 | description: 175 | name: file 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "6.1.2" 179 | fixnum: 180 | dependency: transitive 181 | description: 182 | name: fixnum 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "1.0.0" 186 | flutter: 187 | dependency: "direct main" 188 | description: flutter 189 | source: sdk 190 | version: "0.0.0" 191 | flutter_hooks: 192 | dependency: "direct main" 193 | description: 194 | name: flutter_hooks 195 | url: "https://pub.dartlang.org" 196 | source: hosted 197 | version: "0.18.1" 198 | flutter_lints: 199 | dependency: transitive 200 | description: 201 | name: flutter_lints 202 | url: "https://pub.dartlang.org" 203 | source: hosted 204 | version: "1.0.4" 205 | flutter_portal: 206 | dependency: "direct main" 207 | description: 208 | name: flutter_portal 209 | url: "https://pub.dartlang.org" 210 | source: hosted 211 | version: "0.4.0" 212 | flutter_riverpod: 213 | dependency: transitive 214 | description: 215 | name: flutter_riverpod 216 | url: "https://pub.dartlang.org" 217 | source: hosted 218 | version: "1.0.2" 219 | flutter_test: 220 | dependency: "direct dev" 221 | description: flutter 222 | source: sdk 223 | version: "0.0.0" 224 | freezed: 225 | dependency: "direct dev" 226 | description: 227 | name: freezed 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "1.0.2+1" 231 | freezed_annotation: 232 | dependency: "direct main" 233 | description: 234 | name: freezed_annotation 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "1.0.0" 238 | frontend_server_client: 239 | dependency: transitive 240 | description: 241 | name: frontend_server_client 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "2.1.2" 245 | gap: 246 | dependency: "direct main" 247 | description: 248 | name: gap 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "2.0.0" 252 | glob: 253 | dependency: transitive 254 | description: 255 | name: glob 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "2.0.2" 259 | graphs: 260 | dependency: transitive 261 | description: 262 | name: graphs 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "2.1.0" 266 | hooks_riverpod: 267 | dependency: "direct main" 268 | description: 269 | name: hooks_riverpod 270 | url: "https://pub.dartlang.org" 271 | source: hosted 272 | version: "1.0.2" 273 | http_multi_server: 274 | dependency: transitive 275 | description: 276 | name: http_multi_server 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "3.0.1" 280 | http_parser: 281 | dependency: transitive 282 | description: 283 | name: http_parser 284 | url: "https://pub.dartlang.org" 285 | source: hosted 286 | version: "4.0.0" 287 | io: 288 | dependency: transitive 289 | description: 290 | name: io 291 | url: "https://pub.dartlang.org" 292 | source: hosted 293 | version: "1.0.3" 294 | js: 295 | dependency: transitive 296 | description: 297 | name: js 298 | url: "https://pub.dartlang.org" 299 | source: hosted 300 | version: "0.6.3" 301 | json_annotation: 302 | dependency: transitive 303 | description: 304 | name: json_annotation 305 | url: "https://pub.dartlang.org" 306 | source: hosted 307 | version: "4.4.0" 308 | json_serializable: 309 | dependency: "direct dev" 310 | description: 311 | name: json_serializable 312 | url: "https://pub.dartlang.org" 313 | source: hosted 314 | version: "6.1.1" 315 | lints: 316 | dependency: transitive 317 | description: 318 | name: lints 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "1.0.1" 322 | logging: 323 | dependency: transitive 324 | description: 325 | name: logging 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "1.0.2" 329 | matcher: 330 | dependency: transitive 331 | description: 332 | name: matcher 333 | url: "https://pub.dartlang.org" 334 | source: hosted 335 | version: "0.12.11" 336 | meta: 337 | dependency: transitive 338 | description: 339 | name: meta 340 | url: "https://pub.dartlang.org" 341 | source: hosted 342 | version: "1.7.0" 343 | mime: 344 | dependency: transitive 345 | description: 346 | name: mime 347 | url: "https://pub.dartlang.org" 348 | source: hosted 349 | version: "1.0.1" 350 | package_config: 351 | dependency: transitive 352 | description: 353 | name: package_config 354 | url: "https://pub.dartlang.org" 355 | source: hosted 356 | version: "2.0.2" 357 | path: 358 | dependency: transitive 359 | description: 360 | name: path 361 | url: "https://pub.dartlang.org" 362 | source: hosted 363 | version: "1.8.0" 364 | pedantic_mono: 365 | dependency: "direct dev" 366 | description: 367 | name: pedantic_mono 368 | url: "https://pub.dartlang.org" 369 | source: hosted 370 | version: "1.15.0" 371 | pool: 372 | dependency: transitive 373 | description: 374 | name: pool 375 | url: "https://pub.dartlang.org" 376 | source: hosted 377 | version: "1.5.0" 378 | pub_semver: 379 | dependency: transitive 380 | description: 381 | name: pub_semver 382 | url: "https://pub.dartlang.org" 383 | source: hosted 384 | version: "2.1.0" 385 | pubspec_parse: 386 | dependency: transitive 387 | description: 388 | name: pubspec_parse 389 | url: "https://pub.dartlang.org" 390 | source: hosted 391 | version: "1.2.0" 392 | riverpod: 393 | dependency: transitive 394 | description: 395 | name: riverpod 396 | url: "https://pub.dartlang.org" 397 | source: hosted 398 | version: "1.0.2" 399 | shelf: 400 | dependency: transitive 401 | description: 402 | name: shelf 403 | url: "https://pub.dartlang.org" 404 | source: hosted 405 | version: "1.2.0" 406 | shelf_web_socket: 407 | dependency: transitive 408 | description: 409 | name: shelf_web_socket 410 | url: "https://pub.dartlang.org" 411 | source: hosted 412 | version: "1.0.1" 413 | sky_engine: 414 | dependency: transitive 415 | description: flutter 416 | source: sdk 417 | version: "0.0.99" 418 | source_gen: 419 | dependency: transitive 420 | description: 421 | name: source_gen 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "1.2.0" 425 | source_helper: 426 | dependency: transitive 427 | description: 428 | name: source_helper 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "1.3.0" 432 | source_span: 433 | dependency: transitive 434 | description: 435 | name: source_span 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "1.8.1" 439 | sprung: 440 | dependency: "direct main" 441 | description: 442 | name: sprung 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "3.0.0" 446 | stack_trace: 447 | dependency: transitive 448 | description: 449 | name: stack_trace 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "1.10.0" 453 | state_notifier: 454 | dependency: "direct main" 455 | description: 456 | name: state_notifier 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "0.7.1" 460 | stream_channel: 461 | dependency: transitive 462 | description: 463 | name: stream_channel 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "2.1.0" 467 | stream_transform: 468 | dependency: transitive 469 | description: 470 | name: stream_transform 471 | url: "https://pub.dartlang.org" 472 | source: hosted 473 | version: "2.0.0" 474 | string_scanner: 475 | dependency: transitive 476 | description: 477 | name: string_scanner 478 | url: "https://pub.dartlang.org" 479 | source: hosted 480 | version: "1.1.0" 481 | term_glyph: 482 | dependency: transitive 483 | description: 484 | name: term_glyph 485 | url: "https://pub.dartlang.org" 486 | source: hosted 487 | version: "1.2.0" 488 | test_api: 489 | dependency: transitive 490 | description: 491 | name: test_api 492 | url: "https://pub.dartlang.org" 493 | source: hosted 494 | version: "0.4.3" 495 | timing: 496 | dependency: transitive 497 | description: 498 | name: timing 499 | url: "https://pub.dartlang.org" 500 | source: hosted 501 | version: "1.0.0" 502 | typed_data: 503 | dependency: transitive 504 | description: 505 | name: typed_data 506 | url: "https://pub.dartlang.org" 507 | source: hosted 508 | version: "1.3.0" 509 | vector_math: 510 | dependency: transitive 511 | description: 512 | name: vector_math 513 | url: "https://pub.dartlang.org" 514 | source: hosted 515 | version: "2.1.1" 516 | watcher: 517 | dependency: transitive 518 | description: 519 | name: watcher 520 | url: "https://pub.dartlang.org" 521 | source: hosted 522 | version: "1.0.1" 523 | web_socket_channel: 524 | dependency: transitive 525 | description: 526 | name: web_socket_channel 527 | url: "https://pub.dartlang.org" 528 | source: hosted 529 | version: "2.1.0" 530 | yaml: 531 | dependency: transitive 532 | description: 533 | name: yaml 534 | url: "https://pub.dartlang.org" 535 | source: hosted 536 | version: "3.1.0" 537 | sdks: 538 | dart: ">=2.15.0 <3.0.0" 539 | flutter: ">=1.21.0" 540 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ios_springboard 2 | description: A new Flutter application. 3 | version: 1.0.0+1 4 | environment: 5 | sdk: ">=2.15.0 <3.0.0" 6 | dependencies: 7 | flutter: 8 | sdk: flutter 9 | flutter_hooks: 10 | flutter_portal: 11 | freezed_annotation: 12 | gap: 13 | hooks_riverpod: 14 | sprung: 15 | state_notifier: 16 | dev_dependencies: 17 | build_runner: 18 | flutter_test: 19 | sdk: flutter 20 | freezed: 21 | json_serializable: 22 | pedantic_mono: 23 | flutter: 24 | uses-material-design: true 25 | -------------------------------------------------------------------------------- /scripts/build_runner_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | flutter pub get 3 | flutter pub run build_runner build --delete-conflicting-outputs 4 | -------------------------------------------------------------------------------- /scripts/build_runner_watch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | flutter pub get 3 | flutter pub run build_runner watch --delete-conflicting-outputs -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeavenOSK/ios_springboard/7f7b760ae2fecdb6c47f89b8e3d9026fe1f35adb/test/.gitkeep -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeavenOSK/ios_springboard/7f7b760ae2fecdb6c47f89b8e3d9026fe1f35adb/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeavenOSK/ios_springboard/7f7b760ae2fecdb6c47f89b8e3d9026fe1f35adb/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeavenOSK/ios_springboard/7f7b760ae2fecdb6c47f89b8e3d9026fe1f35adb/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeavenOSK/ios_springboard/7f7b760ae2fecdb6c47f89b8e3d9026fe1f35adb/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeavenOSK/ios_springboard/7f7b760ae2fecdb6c47f89b8e3d9026fe1f35adb/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ios_springboard 33 | 34 | 35 | 36 | 39 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ios_springboard", 3 | "short_name": "ios_springboard", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | --------------------------------------------------------------------------------