diff --git a/.firebase/hosting.YnVpbGQvd2Vi.cache b/.firebase/hosting.YnVpbGQvd2Vi.cache
index f4d83c9f..8f1c2cb5 100644
--- a/.firebase/hosting.YnVpbGQvd2Vi.cache
+++ b/.firebase/hosting.YnVpbGQvd2Vi.cache
@@ -1,49 +1,78 @@
sqlite3.wasm,1741271571761,8e9753999ba85942d1a9a6c04be82ee46302f3a1f0aeba058a7bebae30841625
-manifest.json,1742052616326,d140e9b4ee89585110795b5694c8810100fa767bcaae0ccd87eff4c913d13ad5
-main.dart.js,1742190209674,a3b8a795e9b34e847e39638e72a5560f5fa0c01c45de09238d9d02cf7de4e2a1
-functions.js,1742052626416,c1c285b00096d7fef7dd8c06d7613cb917b93a3038a83f9be2783cc251fdd25f
-flutter.js,1738903988000,8d47a0ef2a70705bf7e18df9c5148574acdff9bc412554b0784f1f46ba5918a4
-firebase-messaging-sw.js,1742052626416,eedb577a90ce796089ee56fbe936247b155b7de5f3ba19d4717b71b4bad8c426
+manifest.json,1752490781207,a757159d5701bd44c02424059e28c99edfd7fd9f176033bca5241c5d22032a6d
+main.dart.js,1752490908489,65489c1b2e9f2acf767affd71d44842340037ac34d6abfbb5b05225d5e46e8fa
+functions.js,1750668902266,9d3411e395dd7b9aa7362e74a6d9a89e16afc0bd71d8dcfc0ca9e256b0ba07de
+flutter.js,1751939930000,053adb95c9fd7e93f05fde87a56d68d3269388c2678826834ffd3ed7d292bf26
+firebase-messaging-sw.js,1751874063698,7f16d7008b5751ac8a85c1135b85c0d1f65bd9122d79bc50ad318b3e507eca4c
+favicon.ico,1750668902266,f9a33e18c8eda1a025b8bc80736ac4cb93ff348dc42f1dd19d34eb30080e4c73
drift_worker.dart.js,1741271571758,06ac27c648f4e1b3d5f75980fd2ffd502e43ab8a36baafa6a85f2fd0f95b1367
-icons/android-chrome-512x512.png,1742163336000,ecea49f2fa9a08b5245e9f8e6d0a45fd5388508fc280fad141e53d1e9acefff4
+icons/android-chrome-512x512.png,1750668902267,ecea49f2fa9a08b5245e9f8e6d0a45fd5388508fc280fad141e53d1e9acefff4
+icons/android-chrome-192x192.png,1750668902266,6d7ee72aa1c5ac35e5dd1ab12f907f6d8cecce25ff518884cea0a275fb090167
icons/Icon-maskable-512.png,1738903832255,e7983524dc70254adc61764657d7e03d19284de8da586b5818d737bc08c6d14e
icons/Icon-maskable-192.png,1738903832254,dd96c123fdf6817cdf7e63d9693bcc246bac2e3782a41a6952fa41c0617c5573
icons/Icon-512.png,1738903832254,7a31ce91e554f1941158ca46f31c7f3f2b7c8c129229ea74a8fae1affe335033
-icons/Icon-192.png,1742163336000,6d7ee72aa1c5ac35e5dd1ab12f907f6d8cecce25ff518884cea0a275fb090167
-canvaskit/skwasm_st.wasm,1738904552000,669c860c596b623d7c98a6b4c60d2bcadc92d13ee78b7e565612eb7d2b96b04c
-canvaskit/skwasm_st.js.symbols,1738904552000,dbf1ecd2987907700e49fee8eef71967eaf756e205fb2e97cd9c4b239ab4c2e2
-canvaskit/skwasm_st.js,1738904552000,5099d82713c753802530620e167e0f11fb72010175acb07527e54e55b725612d
-canvaskit/skwasm.wasm,1738904476000,c9ad2544aaa0ce1958582d30599bef88b49be0df5c0c2b83c096b07232bca473
-canvaskit/skwasm.js.symbols,1738904476000,c063e47b2ede178417c06d899f69de3545140120712c2bca349b35209b1aeec5
-canvaskit/skwasm.js,1738904476000,378f5e2d7b590c1c638f783163e05c8df438118d9b9908f673bf15669fb014a5
-canvaskit/canvaskit.wasm,1738904372000,f8d74a0d0fa6b9d9bd9a040b6fff8425d629e9b28e840da9a8ab87ababcac878
-canvaskit/canvaskit.js.symbols,1738904372000,6262f5b2f99aabf760dbb4c592e02365ad32acb14252cb23ad9cbe42b39b5ae6
-canvaskit/canvaskit.js,1738904372000,b6506e44bdf978464c0d0c09ce64a9963df9477ae5198b38df82b83f380bfd25
-canvaskit/chromium/canvaskit.wasm,1738904398000,cfcbb6f12d5f5846a8a53873c929949b31eac8013d73fa34eaba28a7b6679a56
-canvaskit/chromium/canvaskit.js.symbols,1738904398000,02c8ca3407599c63e420fc8b9a5e5b5864fa29af85d7a33915273d7ec0d446bd
-canvaskit/chromium/canvaskit.js,1738904398000,4321eb032e18194de43ed6955c201db6bec385f36c5d282da35dc62950d01e8f
-assets/packages/assets/pubspec.yaml,1740755364270,97f7c5dd9c68dd4d7f2f8f9d70164b968cef244070ee02a85f5b217ca53b41ce
+icons/Icon-192.png,1742367193073,6d7ee72aa1c5ac35e5dd1ab12f907f6d8cecce25ff518884cea0a275fb090167
+canvaskit/skwasm.wasm,1751940262000,bfcbd4b6e44b3118380e714f97dbab1b685b95bcd4a9c007bfa79c43b60c4b4d
+canvaskit/skwasm.js.symbols,1751940262000,3bef5f28baf386a699e4b98893c94c0dd42ad82f1f93c1cdea532a75210aca11
+canvaskit/skwasm.js,1751940264000,28b1c6f000b61bd2f141dbc2d6dd2d4e8d78295593baea48ad52a9f8b0eca76b
+canvaskit/canvaskit.wasm,1751940100000,c6fe626af107c4e97739d6c3062cfcb1f8e2eb75d8f1f657fdf77ac78b4dd934
+canvaskit/canvaskit.js.symbols,1751940100000,c58c818ceea58799cc5cfd951fa78f7607db6756342bd7a1aaeb1e431063894a
+canvaskit/canvaskit.js,1751940100000,9b87a0d6c67ac68cbce3c8018ecc18f7d77811460b772cd0c51bba45d120136c
+canvaskit/chromium/canvaskit.wasm,1751940174000,3b6125ac9e23794cf7f39985a7f21d8270fbdf5af7b30d1fa67a2b70061a1513
+canvaskit/chromium/canvaskit.js.symbols,1751940174000,6e864727463607c5ed93d9197e943eee281b93d814115f14e114defd31359f65
+canvaskit/chromium/canvaskit.js,1751940174000,98eed9e4311de4bc5e460d73ab0e034be11a0c0aded58733e94bf9971c8d1ecb
+assets/packages/widgetbook/assets/logo.png,1741256900199,da3c6819025b53cf2a3bc0eff512193269452c0213a292a3a0dffc31bbe22bd7
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-ThinItalic.ttf,1741256900190,d3cc26ce5d1acad07fcd1a5810175ef93f05037fd501f70d92958f0e07d0c0b4
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-Thin.ttf,1741256900194,e53363da55b8bc62157e3aa4def2cf68e9968b78712f848dd2798154e438d9c3
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-SemiBoldItalic.ttf,1741256900195,6752603a5b29c70341ad377199d483cf73f5da20f1efe73526d25a0c1985232e
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-SemiBold.ttf,1741256900185,9ff2d627cf726339d98799c8593117646254a33a4384092eac7df61912e02d12
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-Regular.ttf,1741256900178,f615176f3a3b7edf54daa48faef435de8b016238911dc6824d31e93442799dd5
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-MediumItalic.ttf,1741256900177,b2a28a314091fd9e3e962d7cb59e54f9479623c60cd055a0a225d83e19ce71d2
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-Medium.ttf,1741256900182,317336af08dd76b61c0408fd9acb437d1fc0980c93b254e5cd6a5cd333b50a1b
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-LightItalic.ttf,1741256900193,bd7dbc534b976b66917f3a613d945181c3d889872e1a8f00bd860e54481ccab6
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-Light.ttf,1741256900189,0489dfd0e53f494827c309ab4cfc4b9de4ac441bb2816f19f2889850924e4963
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-Italic.ttf,1741256900198,93b9b71d19dd58bc9fe7f008778b4573248eddfb8dd550d5b55201e85997be80
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-ExtraLightItalic.ttf,1741256900174,b4dbe9ae34525bc4a2fd45fe951101261f1ebd11469d19c69496c78fe7f31360
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-ExtraLight.ttf,1741256900187,ccea34684b8b652c1471f6c6e5ce0170a3875c3433df790188d2d72121123d0d
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-ExtraBoldItalic.ttf,1741256900173,65be628568c2509c234d128daf299456b3e7de432498a50969d5456555051410
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-ExtraBold.ttf,1741256900183,f487ec93d1694fd0b33ccdbd97205c64089031db444ef1ec4a804f07ee77b749
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-BoldItalic.ttf,1741256900184,2b629c288425dcadc1b6f3dd6f6d336b3896cd74bc2846a7220efe46b3599452
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-Bold.ttf,1741256900192,2b25219c282dc2b511dc717e6d2cc2dbd602a15786b7acf159701f17f169709d
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-BlackItalic.ttf,1741256900180,adf4afa9d526d683eae20cf3f4dc7d41b294d3c880b491c8a306527e01a76cff
+assets/packages/widgetbook/assets/fonts/Poppins/Poppins-Black.ttf,1741256900197,7e6988176defbd8f12b2d2f419d57a37492413e17efaa36da9d3e92613563895
+assets/packages/assets/trash_can.svg,1751873154521,bfc4219e19a26cd0e89d0d1f46a0426bdddd20652ee3c8c9dc0d1d284b1c8a15
+assets/packages/assets/pubspec.yaml,1750668902247,dc38080715b81cfac02ffca13bf517064b171b31965a221d5324c70b9a6c3cc2
+assets/packages/assets/pubspec.lock,1750668902247,b180867dcd6eb5e27b92f5131f5f6ab630a047328ce3cfebcb169e9553762e5b
assets/packages/assets/meatballs.svg,1740755364270,32314300150c2bbf11964bb161c1b56376cc720a347d0a282fe95bbb1ad6ea1a
+assets/packages/assets/map_pin_fill.svg,1751873154521,82636f6ff1bec87b1c6d21cc3057fb17782dc3816cdcd633dcb2ba1b0aa0e8c2
assets/packages/assets/google_icon.svg,1740755612751,e08acff98ce88a250447229cee21ecdae27480ac800b1b4da44eecb48b88da3b
assets/packages/assets/friends.svg,1738903832234,1ee30373884f25838c31f838cebc5380b6126831e920cf5bc7fbac2aa57db3df
+assets/packages/assets/edit_pencil.svg,1751873154521,5fb0fa7d8b7affc52fba9ed4dfd7828d1478b5d1aad4f16a279281ce2d1ff19e
assets/packages/assets/drag_indicator.svg,1738903832234,7f6340c5647dad5a0c507e6f1168a20907c47b7d5275d8496de67f0fd63716de
assets/packages/assets/chevron_left.svg,1740755612751,8802af56fdfee2ecbe35180eadcba4f11b7569abc2c2574e41b7ff180e0c56f5
+assets/packages/assets/chevron_down.svg,1751873154521,beb5bba9c8beb3b0875bf5654d595a0d00c3c0354ef3d3d12bda863c0c49f7ca
assets/packages/assets/check.svg,1738903832234,e676f27ee117041dedb876a9c543488d7cb67b631dd548d4aa1c4dd48c6e795a
assets/packages/assets/character_greeting.svg,1740755612751,903498d2af556692923ea804f8da368a3d0e791ee66a8056a8ad1f5b42302187
-assets/packages/assets/character.png,1738903832234,35286d084ea575ff5850e8bbcf3d05ebe92fadf294461e207dbac059ebb16975
assets/packages/assets/bell.svg,1738903832233,49a4b438c7ea87905fe3ded57d50eef2f1d1cd4bfc211a3c3ad99203859be4eb
+assets/packages/assets/bell-ringing.svg,1751874063686,0e894d7cbfed735c201d89eddd73431210f2e50dc98a0fd95e7f623912e06cd5
assets/packages/assets/arrow_right.svg,1738903832233,040f26c73d8bd55b2907501108164918edbe1d5fc161f5400b9b2eeeb502df0b
-assets/assets/pubspec.yaml,1740755364270,97f7c5dd9c68dd4d7f2f8f9d70164b968cef244070ee02a85f5b217ca53b41ce
-assets/assets/meatballs.svg,1740755364270,32314300150c2bbf11964bb161c1b56376cc720a347d0a282fe95bbb1ad6ea1a
-assets/assets/google_icon.svg,1740755612751,e08acff98ce88a250447229cee21ecdae27480ac800b1b4da44eecb48b88da3b
-assets/assets/friends.svg,1738903832234,1ee30373884f25838c31f838cebc5380b6126831e920cf5bc7fbac2aa57db3df
-assets/assets/drag_indicator.svg,1738903832234,7f6340c5647dad5a0c507e6f1168a20907c47b7d5275d8496de67f0fd63716de
-assets/assets/chevron_left.svg,1740755612751,8802af56fdfee2ecbe35180eadcba4f11b7569abc2c2574e41b7ff180e0c56f5
-assets/assets/check.svg,1738903832234,e676f27ee117041dedb876a9c543488d7cb67b631dd548d4aa1c4dd48c6e795a
-assets/assets/character_greeting.svg,1740755612751,903498d2af556692923ea804f8da368a3d0e791ee66a8056a8ad1f5b42302187
-assets/assets/character.png,1738903832234,35286d084ea575ff5850e8bbcf3d05ebe92fadf294461e207dbac059ebb16975
-assets/assets/bell.svg,1738903832233,49a4b438c7ea87905fe3ded57d50eef2f1d1cd4bfc211a3c3ad99203859be4eb
-assets/assets/arrow_right.svg,1738903832233,040f26c73d8bd55b2907501108164918edbe1d5fc161f5400b9b2eeeb502df0b
+assets/packages/assets/Plus.svg,1750668902245,b6ac76bebf49483c61a15f030ce9b42c41b559a83f926f282a5998034fc7a2ef
+assets/packages/assets/My.svg,1750668902245,c7e4ee520bbe25b85ceb14240bfec2e9df7d62970a7a3706b371c5decf69d968
+assets/packages/assets/Home.svg,1750668902245,ec7eca5316f5d7d03e38f35f238f5ac9083bf71a9988f658461e4e022798bcf6
+assets/packages/assets/fonts/Pretendard-Thin.ttf,1741271571730,08f0d2582e2d57a69251416419835211228f008ad5c5030e556d073e944a1163
+assets/packages/assets/fonts/Pretendard-SemiBold.ttf,1741271571725,4dd3c854bad5348ba2beb8da553aeafe1d3d47076cd967854a528731acdda82a
+assets/packages/assets/fonts/Pretendard-Regular.ttf,1741271571719,5f0cf1bf0eb351550179a82dc7a9367ebab30004a353f7de0d681e171fe0c3d3
+assets/packages/assets/fonts/Pretendard-Medium.ttf,1741271571714,ab65081a45a0401fe472fe73e3d3c65c506371804b1f066739f7ecde276104b4
+assets/packages/assets/fonts/Pretendard-Light.ttf,1741271571708,d2c0b5af96e96d8063c08837230ea14b258c9b89b18c5efefe21658aedc01259
+assets/packages/assets/fonts/Pretendard-ExtraLight.ttf,1741271571702,bf25b1053065297c0cacfd770844f6b53a4564bbe2f5778e6553423712f06d87
+assets/packages/assets/fonts/Pretendard-ExtraBold.ttf,1741271571695,374f1cdec1c9872bfdabfc5049437dd6334dee93700538243ef573f3b735de04
+assets/packages/assets/fonts/Pretendard-Bold.ttf,1741271571690,2e40f7edb945eec3271f1e5cd938fe774adade44641e0715a89330354b8b5262
+assets/packages/assets/fonts/Pretendard-Black.ttf,1741271571685,d54873770d0faf69253bb90d4318993b837fd7b584833d375fb53fddf2182a57
+assets/packages/assets/characters/onboarding_character.svg,1743489251941,d7423b05fd5c5682f721ee5e366f0092ea8fd13f1794770c47b53c4f33bfd86c
+assets/packages/assets/characters/character_whistle.svg,1751874063687,7feddbb3fbd19eea850ecc2c6b23bd0e58efaa7b397d651bb0c7c97923d62419
+assets/packages/assets/characters/character_late_earth.svg,1751874063687,946472332dff00e8d28859c050f00901dc6fce59708ef6e99bfadc9e52ae723b
+assets/packages/assets/characters/character_headphone.svg,1751874063686,8820769698f302305ff1c159cfa1fdebb9a4d1b12e193ecb2cea836c61c17d93
+assets/packages/assets/characters/character.svg,1750668902246,464b1f16cb53180e813193883d6ab84af066193b746da7e468e57c8d6db1016c
assets/assets/fonts/Pretendard-Thin.ttf,1741271571730,08f0d2582e2d57a69251416419835211228f008ad5c5030e556d073e944a1163
assets/assets/fonts/Pretendard-SemiBold.ttf,1741271571725,4dd3c854bad5348ba2beb8da553aeafe1d3d47076cd967854a528731acdda82a
assets/assets/fonts/Pretendard-Regular.ttf,1741271571719,5f0cf1bf0eb351550179a82dc7a9367ebab30004a353f7de0d681e171fe0c3d3
@@ -53,16 +82,15 @@ assets/assets/fonts/Pretendard-ExtraLight.ttf,1741271571702,bf25b1053065297c0cac
assets/assets/fonts/Pretendard-ExtraBold.ttf,1741271571695,374f1cdec1c9872bfdabfc5049437dd6334dee93700538243ef573f3b735de04
assets/assets/fonts/Pretendard-Bold.ttf,1741271571690,2e40f7edb945eec3271f1e5cd938fe774adade44641e0715a89330354b8b5262
assets/assets/fonts/Pretendard-Black.ttf,1741271571685,d54873770d0faf69253bb90d4318993b837fd7b584833d375fb53fddf2182a57
-version.json,1742196629388,3fe66f58c7b0080dde159d39cc063691b0918161056f5a917bf3094372849e2f
-flutter_service_worker.js,1742196631024,702f2111014a58322d9348ae1347dc422ca892fb10869084a933b44abe544abd
-flutter_bootstrap.js,1742196629054,4bf591df5ab527696b57c069974925265ec05303160fb0cf04391d21433af517
-index.html,1742196629058,8e245a51242bf7295809eb2acfe4d385ac43314cda8f950a671efc732f2389dd
-assets/AssetManifest.json,1742196629484,047af0ade8f084f8d59e40e9dd3c5a2d3e1ffa3de23d50f9d4c03c4ed3b77b09
-assets/FontManifest.json,1742196629484,0c968932010df9e5202500a7acdd4f8448d421d4017ca6afaf4335c16dc951aa
-favicon.ico,1742163336000,f9a33e18c8eda1a025b8bc80736ac4cb93ff348dc42f1dd19d34eb30080e4c73
-assets/AssetManifest.bin.json,1742196629484,964cf7d6b415bb1842182a8a79ab9d021ad14962a7a139633bd47a1a872677c1
-assets/AssetManifest.bin,1742196629484,010317c335c67cd05018616bd643931078fc91a96de7ed76e5544fe7ccdfb195
-assets/shaders/ink_sparkle.frag,1742196629588,80c6e65c75f1de434b1b22dba61e96ad82dba0f2fc5e8b3b59c2def46d794354
-assets/fonts/MaterialIcons-Regular.otf,1742196630435,ca9df6f14a20e3b4e1ae7b7857f56b0563ab90564b04633477e4242f948dce70
-assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1742196630435,53de5ef48304000a5bae3a08f9499510850cbb032372063e82f1f1dcd938f395
-assets/NOTICES,1742196629484,efb01c5cd600fe3ae72b5c7b44733ac570e80f528e4c1d207b16684e33e5f51d
+version.json,1752492631935,3fe66f58c7b0080dde159d39cc063691b0918161056f5a917bf3094372849e2f
+index.html,1752492631464,6b105ca8a67fd3db8921be5bfe26a8a75eb147dad4015c8a7af161f6e648a85e
+flutter_bootstrap.js,1752492631450,17b8adfc6ec198ff3766c8fc0e9836dc18104c6af2ccc0754c41215963224389
+flutter_service_worker.js,1752492633737,ec3bdaa13bca1f5c07324b9773d41b4305a626ad037eeeb650c77f5c84dfe45e
+assets/FontManifest.json,1752492632023,54c5688728a24562e288e535588428f46cae8ae083620677b3623e61776b4f3f
+assets/AssetManifest.bin.json,1752492632023,6e0d2359eab69b0e75e7c7a03438add2ed0dcdebe2897f3a3e31befe05cb76cc
+assets/AssetManifest.json,1752492632023,2c74d88153ec9425f4b2097ccd3446ee6c35e84d6925332415dced51bf0afada
+assets/AssetManifest.bin,1752492632023,254e0919a1241f26abd472d313843cb1f28be084fde2b507405498bec49cdbd9
+assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1752492632919,12bc6bf55aad4657f62747b6c1c9b5c120a594ed3540db21729b6db3c847340d
+assets/shaders/ink_sparkle.frag,1752492632104,80c6e65c75f1de434b1b22dba61e96ad82dba0f2fc5e8b3b59c2def46d794354
+assets/fonts/MaterialIcons-Regular.otf,1752492632919,9fd6dfb402eb47d98919d5fc0b5ff7fdcca2d7e8c4b66fa69ac9cabcb6f18af7
+assets/NOTICES,1752492632025,d73d4f8b973e02b39ee6db02af01aa00137f53ce4b1da8bf035fe67354ee7679
diff --git a/assets/characters/half_character.svg b/assets/characters/half_character.svg
new file mode 100644
index 00000000..61869d73
--- /dev/null
+++ b/assets/characters/half_character.svg
@@ -0,0 +1,61 @@
+
diff --git a/assets/profile.png b/assets/profile.png
new file mode 100644
index 00000000..9cbf1e12
Binary files /dev/null and b/assets/profile.png differ
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index fc27324a..0663e871 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -44,6 +44,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
_requestNotificationPermission();
-
-@JS()
-external JSBoolean _isInStandaloneMode();
-
-class JsInteropService {
- static Future requestNotificationPermission() {
- return _requestNotificationPermission()
- .toDart
- .then((jsString) => jsString.toString());
- }
-
- static bool isInStandaloneMode() {
- return _isInStandaloneMode().toDart;
- }
-}
+export 'js_interop_service_stub.dart'
+ if (dart.library.js_interop) 'js_interop_service_web.dart';
diff --git a/lib/core/services/js_interop_service_stub.dart b/lib/core/services/js_interop_service_stub.dart
new file mode 100644
index 00000000..56605c65
--- /dev/null
+++ b/lib/core/services/js_interop_service_stub.dart
@@ -0,0 +1,9 @@
+class JsInteropService {
+ static Future requestNotificationPermission() async {
+ throw UnsupportedError('JsInteropService is only available on web');
+ }
+
+ static bool isInStandaloneMode() {
+ throw UnsupportedError('JsInteropService is only available on web');
+ }
+}
diff --git a/lib/core/services/js_interop_service_web.dart b/lib/core/services/js_interop_service_web.dart
new file mode 100644
index 00000000..1d9a136e
--- /dev/null
+++ b/lib/core/services/js_interop_service_web.dart
@@ -0,0 +1,20 @@
+import 'dart:js_interop';
+import 'package:js/js.dart';
+
+@JS()
+external JSPromise _requestNotificationPermission();
+
+@JS()
+external JSBoolean _isInStandaloneMode();
+
+class JsInteropService {
+ static Future requestNotificationPermission() {
+ return _requestNotificationPermission()
+ .toDart
+ .then((jsString) => jsString.toString());
+ }
+
+ static bool isInStandaloneMode() {
+ return _isInStandaloneMode().toDart;
+ }
+}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index b3f91256..f41bd8c0 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -274,5 +274,37 @@
"type": "int"
}
}
+ },
+ "myPageTitle": "My Page",
+ "@myPageTitle": {
+ "description": "Title for the my page screen"
+ },
+ "myAccount": "My Account",
+ "@myAccount": {
+ "description": "Title for the my account section"
+ },
+ "appSettings": "App Settings",
+ "@appSettings": {
+ "description": "Title for the app settings section"
+ },
+ "editDefaultPreparation": "Edit Default Preparation / Spare Time",
+ "@editDefaultPreparation": {
+ "description": "Setting tile for editing default preparation and spare time"
+ },
+ "allowAppNotifications": "Allow App Notifications",
+ "@allowAppNotifications": {
+ "description": "Setting tile for allowing app notifications"
+ },
+ "editSpareTime": "Edit spare time",
+ "@editSpareTime": {
+ "description": "Section title for editing spare time"
+ },
+ "editPreparationTime": "Edit preparation time",
+ "@editPreparationTime": {
+ "description": "Section title for editing preparation time"
+ },
+ "totalTime": "Total time: ",
+ "@totalTime": {
+ "description": "Label for total preparation time"
}
}
\ No newline at end of file
diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb
index 3cd05922..6e94fc09 100644
--- a/lib/l10n/app_ko.arb
+++ b/lib/l10n/app_ko.arb
@@ -62,5 +62,14 @@
"plus": "플러스",
"schedule": "일정",
"hourFormatted": "{count}시간",
- "minuteFormatted": "{count}분"
-}
\ No newline at end of file
+ "minuteFormatted": "{count}분",
+ "myPageTitle": "마이페이지",
+ "myAccount": "내 계정",
+ "appSettings": "앱 설정",
+ "editDefaultPreparation": "기본 준비과정 / 여유시간 수정",
+ "allowAppNotifications": "앱 알림 허용"
+,
+ "editSpareTime": "여유시간 수정",
+ "editPreparationTime": "준비시간 수정",
+ "totalTime": "총 시간: "
+}
\ No newline at end of file
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index bcae4261..0270853f 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -481,6 +481,54 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'{count,plural, =1{{count} minute} other{{count} minutes}}'**
String minuteFormatted(int count);
+
+ /// Title for the my page screen
+ ///
+ /// In en, this message translates to:
+ /// **'My Page'**
+ String get myPageTitle;
+
+ /// Title for the my account section
+ ///
+ /// In en, this message translates to:
+ /// **'My Account'**
+ String get myAccount;
+
+ /// Title for the app settings section
+ ///
+ /// In en, this message translates to:
+ /// **'App Settings'**
+ String get appSettings;
+
+ /// Setting tile for editing default preparation and spare time
+ ///
+ /// In en, this message translates to:
+ /// **'Edit Default Preparation / Spare Time'**
+ String get editDefaultPreparation;
+
+ /// Setting tile for allowing app notifications
+ ///
+ /// In en, this message translates to:
+ /// **'Allow App Notifications'**
+ String get allowAppNotifications;
+
+ /// Section title for editing spare time
+ ///
+ /// In en, this message translates to:
+ /// **'Edit spare time'**
+ String get editSpareTime;
+
+ /// Section title for editing preparation time
+ ///
+ /// In en, this message translates to:
+ /// **'Edit preparation time'**
+ String get editPreparationTime;
+
+ /// Label for total preparation time
+ ///
+ /// In en, this message translates to:
+ /// **'Total time: '**
+ String get totalTime;
}
class _AppLocalizationsDelegate
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index fe4d5e47..0bb8846a 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -229,4 +229,28 @@ class AppLocalizationsEn extends AppLocalizations {
);
return '$_temp0';
}
+
+ @override
+ String get myPageTitle => 'My Page';
+
+ @override
+ String get myAccount => 'My Account';
+
+ @override
+ String get appSettings => 'App Settings';
+
+ @override
+ String get editDefaultPreparation => 'Edit Default Preparation / Spare Time';
+
+ @override
+ String get allowAppNotifications => 'Allow App Notifications';
+
+ @override
+ String get editSpareTime => 'Edit spare time';
+
+ @override
+ String get editPreparationTime => 'Edit preparation time';
+
+ @override
+ String get totalTime => 'Total time: ';
}
diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart
index ead61b7e..c39ccdb3 100644
--- a/lib/l10n/app_localizations_ko.dart
+++ b/lib/l10n/app_localizations_ko.dart
@@ -210,4 +210,28 @@ class AppLocalizationsKo extends AppLocalizations {
String minuteFormatted(int count) {
return '$count분';
}
+
+ @override
+ String get myPageTitle => '마이페이지';
+
+ @override
+ String get myAccount => '내 계정';
+
+ @override
+ String get appSettings => '앱 설정';
+
+ @override
+ String get editDefaultPreparation => '기본 준비과정 / 여유시간 수정';
+
+ @override
+ String get allowAppNotifications => '앱 알림 허용';
+
+ @override
+ String get editSpareTime => '여유시간 수정';
+
+ @override
+ String get editPreparationTime => '준비시간 수정';
+
+ @override
+ String get totalTime => '총 시간: ';
}
diff --git a/lib/presentation/home/components/todays_schedule_tile.dart b/lib/presentation/home/components/todays_schedule_tile.dart
index e8c8087a..47e5a237 100644
--- a/lib/presentation/home/components/todays_schedule_tile.dart
+++ b/lib/presentation/home/components/todays_schedule_tile.dart
@@ -12,7 +12,8 @@ class TodaysScheduleTile extends StatelessWidget {
Widget _noSchedule(BuildContext context) {
final theme = Theme.of(context);
- return Center(
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 11.0, vertical: 16.0),
child: Text(
AppLocalizations.of(context)!.noAppointments,
style: theme.textTheme.bodyLarge?.copyWith(
@@ -63,14 +64,10 @@ class TodaysScheduleTile extends StatelessWidget {
color: schedule == null
? theme.colorScheme.surfaceContainerLow
: theme.colorScheme.primaryContainer,
- borderRadius: BorderRadius.circular(16),
+ borderRadius: BorderRadius.circular(8),
),
width: double.infinity,
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 6.5),
- child:
- schedule == null ? _noSchedule(context) : _scheduleExists(context),
- ),
+ child: schedule == null ? _noSchedule(context) : _scheduleExists(context),
);
}
}
diff --git a/lib/presentation/home/screens/home_screen_tmp.dart b/lib/presentation/home/screens/home_screen_tmp.dart
index 51346e27..3d73acb8 100644
--- a/lib/presentation/home/screens/home_screen_tmp.dart
+++ b/lib/presentation/home/screens/home_screen_tmp.dart
@@ -46,8 +46,7 @@ class _HomeScreenTmpState extends State {
Container(
color: colorScheme.primary,
padding: const EdgeInsets.only(top: 58.0),
- child: Stack(
- alignment: Alignment.bottomCenter,
+ child: Column(
children: [
_CharacterSection(score: score),
todaysScheduleOverlayBuilder(state),
@@ -105,7 +104,8 @@ class _HomeScreenTmpState extends State {
borderRadius: BorderRadius.circular(20),
),
color: theme.colorScheme.surface,
- elevation: 2,
+ elevation: 6,
+ shadowColor: Colors.black.withOpacity(0.4),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
@@ -113,7 +113,7 @@ class _HomeScreenTmpState extends State {
children: [
Text(
AppLocalizations.of(context)!.todaysAppointments,
- style: theme.textTheme.titleLarge,
+ style: theme.textTheme.titleMedium,
),
SizedBox(height: 21.0),
TodaysScheduleTile(
@@ -307,8 +307,7 @@ class _CharacterSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
- padding: const EdgeInsets.only(bottom: 40.0) +
- EdgeInsets.symmetric(horizontal: 17.0),
+ padding: const EdgeInsets.symmetric(horizontal: 17.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -380,9 +379,9 @@ class _Character extends StatelessWidget {
Widget build(BuildContext context) {
return SizedBox(
width: 176,
- height: 247.57,
+ height: 130,
child: SvgPicture.asset(
- 'characters/character.svg',
+ 'characters/half_character.svg',
package: 'assets',
),
);
diff --git a/lib/presentation/login/screens/sign_in_main_screen.dart b/lib/presentation/login/screens/sign_in_main_screen.dart
index 8780f5fb..d514ce3f 100644
--- a/lib/presentation/login/screens/sign_in_main_screen.dart
+++ b/lib/presentation/login/screens/sign_in_main_screen.dart
@@ -37,7 +37,7 @@ class _SignInMainScreenState extends State {
mainAxisSize: MainAxisSize.min,
children: [
_Title(),
- SizedBox(height: 24),
+ SizedBox(height: 48),
_CharacterImage(),
SizedBox(height: 24),
GoogleSignInButton(),
@@ -55,6 +55,7 @@ class _Title extends StatelessWidget {
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column(
+ spacing: 8,
children: [
Text(AppLocalizations.of(context)!.appName,
style: TextStyle(
@@ -78,7 +79,7 @@ class _CharacterImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
- height: 280,
+ height: 241,
child: SvgPicture.asset(
'characters/character.svg',
package: 'assets',
diff --git a/lib/presentation/my_page/my_page_screen.dart b/lib/presentation/my_page/my_page_screen.dart
new file mode 100644
index 00000000..8927c150
--- /dev/null
+++ b/lib/presentation/my_page/my_page_screen.dart
@@ -0,0 +1,158 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+import 'package:on_time_front/domain/entities/preparation_entity.dart';
+import 'package:on_time_front/l10n/app_localizations.dart';
+import 'package:on_time_front/presentation/app/bloc/app_bloc.dart';
+
+class MyPageScreen extends StatelessWidget {
+ const MyPageScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
+ appBar: AppBar(
+ title: Text(AppLocalizations.of(context)!.myPageTitle,
+ style: Theme.of(context).textTheme.titleLarge),
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ ),
+ body: Column(
+ spacing: 12,
+ children: [
+ _FrameView(
+ title: AppLocalizations.of(context)!.myAccount,
+ child: _MyAccountView()),
+ _FrameView(
+ title: AppLocalizations.of(context)!.appSettings,
+ child: Column(
+ spacing: 25,
+ children: [
+ _SettingTile(
+ title: AppLocalizations.of(context)!.editDefaultPreparation,
+ onTap: () async {
+ final PreparationEntity? updatedPreparation =
+ await context.push('/defaultPreparationSpareTimeEdit');
+ if (updatedPreparation != null) {}
+ },
+ ),
+ _SettingTile(
+ title: AppLocalizations.of(context)!.allowAppNotifications,
+ onTap: () {},
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class _MyAccountView extends StatelessWidget {
+ const _MyAccountView();
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = Theme.of(context).textTheme;
+ final colorScheme = Theme.of(context).colorScheme;
+ return BlocBuilder(
+ builder: (context, state) {
+ if (state.status == AppStatus.authenticated) {
+ final user = state.user.mapOrNull(
+ (user) => user,
+ empty: (_) => null,
+ );
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10.0) +
+ EdgeInsets.only(bottom: 9),
+ child: Row(
+ spacing: 20,
+ children: [
+ CircleAvatar(
+ radius: 30,
+ backgroundImage: Image.asset(
+ 'profile.png',
+ package: 'assets',
+ ).image,
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(user!.name, style: textTheme.titleMedium),
+ Text(user.email,
+ style: textTheme.bodyMedium!.copyWith(
+ color: colorScheme.outline,
+ )),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+ return const SizedBox.shrink();
+ },
+ );
+ }
+}
+
+class _FrameView extends StatelessWidget {
+ const _FrameView({
+ required this.title,
+ required this.child,
+ });
+
+ final String title;
+ final Widget child;
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = Theme.of(context).textTheme;
+ final colorScheme = Theme.of(context).colorScheme;
+ return Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 19),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 25,
+ children: [
+ Text(title,
+ style:
+ textTheme.bodyMedium!.copyWith(color: colorScheme.outline)),
+ child,
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class _SettingTile extends StatelessWidget {
+ const _SettingTile({
+ required this.title,
+ required this.onTap,
+ });
+
+ final String title;
+ final VoidCallback onTap;
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = Theme.of(context).textTheme;
+ final colorScheme = Theme.of(context).colorScheme;
+ return InkWell(
+ onTap: onTap,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(title, style: textTheme.bodyLarge),
+ Icon(Icons.arrow_forward_ios,
+ size: 16, color: colorScheme.outlineVariant),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart b/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart
new file mode 100644
index 00000000..a6237649
--- /dev/null
+++ b/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart
@@ -0,0 +1,96 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:equatable/equatable.dart';
+import 'package:injectable/injectable.dart';
+import 'package:on_time_front/domain/entities/preparation_entity.dart';
+import 'package:on_time_front/domain/use-cases/get_default_preparation_use_case.dart';
+import 'package:on_time_front/domain/use-cases/onboard_use_case.dart';
+
+part 'default_preparation_spare_time_form_event.dart';
+part 'default_preparation_spare_time_form_state.dart';
+
+@injectable
+class DefaultPreparationSpareTimeFormBloc extends Bloc<
+ DefaultPreparationSpareTimeFormEvent,
+ DefaultPreparationSpareTimeFormState> {
+ DefaultPreparationSpareTimeFormBloc(
+ this._getDefaultPreparationUseCase,
+ this._onboardUseCase,
+ ) : super(DefaultPreparationSpareTimeFormState()) {
+ on(_onFormEditRequested);
+ on(_onSpareTimeIncreased);
+ on(_onSpareTimeDecreased);
+ on(_onFormSubmitted);
+ }
+
+ final GetDefaultPreparationUseCase _getDefaultPreparationUseCase;
+ final OnboardUseCase _onboardUseCase;
+ final Duration lowerBound = Duration(minutes: 10);
+ final Duration stepSize = Duration(minutes: 5);
+
+ Future _onFormEditRequested(FormEditRequested event,
+ Emitter emit) async {
+ emit(state.copyWith(
+ status: DefaultPreparationSpareTimeStatus.loading,
+ ));
+
+ final preparation = await _getDefaultPreparationUseCase();
+
+ emit(state.copyWith(
+ status: DefaultPreparationSpareTimeStatus.success,
+ preparation: preparation,
+ spareTime: event.spareTime,
+ ));
+ }
+
+ void _onSpareTimeIncreased(SpareTimeIncreased event,
+ Emitter emit) {
+ final currentSpareTime = state.spareTime ?? Duration.zero;
+ final newSpareTime = currentSpareTime + stepSize;
+
+ emit(state.copyWith(
+ spareTime: newSpareTime,
+ ));
+ }
+
+ void _onSpareTimeDecreased(SpareTimeDecreased event,
+ Emitter emit) {
+ final currentSpareTime = state.spareTime ?? Duration.zero;
+ final newSpareTime = currentSpareTime - stepSize;
+
+ if (newSpareTime >= lowerBound) {
+ emit(state.copyWith(
+ spareTime: newSpareTime,
+ ));
+ }
+ }
+
+ Future _onFormSubmitted(FormSubmitted event,
+ Emitter emit) async {
+ if (state.spareTime == null) {
+ emit(state.copyWith(
+ status: DefaultPreparationSpareTimeStatus.error,
+ ));
+ return;
+ }
+
+ emit(state.copyWith(
+ status: DefaultPreparationSpareTimeStatus.loading,
+ ));
+
+ try {
+ await _onboardUseCase(
+ preparationEntity: event.preparation,
+ spareTime: state.spareTime!,
+ note: event.note,
+ );
+
+ emit(state.copyWith(
+ status: DefaultPreparationSpareTimeStatus.success,
+ ));
+ } catch (e) {
+ emit(state.copyWith(
+ status: DefaultPreparationSpareTimeStatus.error,
+ ));
+ }
+ }
+}
diff --git a/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_event.dart b/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_event.dart
new file mode 100644
index 00000000..949b3fac
--- /dev/null
+++ b/lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_event.dart
@@ -0,0 +1,39 @@
+part of 'default_preparation_spare_time_form_bloc.dart';
+
+class DefaultPreparationSpareTimeFormEvent extends Equatable {
+ const DefaultPreparationSpareTimeFormEvent();
+
+ @override
+ List