diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index 544e19577b1b4058559504bf3f00ec977e05c480..5e5207a3f66c1f0551d9faa1cd2ac95986fc4e69 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -10,8 +10,8 @@ + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_app_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_app_round.xml new file mode 100644 index 0000000000000000000000000000000000000000..73954bb824f5bebc19423d89cf937e78924ba0fe --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_app_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 345888d26e662baa7c5ae78589829dca8cf5b4f0..0000000000000000000000000000000000000000 --- a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_app.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_app.webp new file mode 100644 index 0000000000000000000000000000000000000000..1061688f23606db677772c7419b09f81314adffd Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_app.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_app_foreground.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_app_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..c1db2a8392deefa62bf4bf34d93731bc76b5d220 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_app_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_app_round.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_app_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..be6c20a21f769bd07dad13f944f76890c44c6521 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_app_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 49fc8aa0e960157edc7bfca0bf079cf797c8fb4b..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_background.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_background.png deleted file mode 100644 index 60520782710b6d6756d9314f7ad683114dcd89bb..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_background.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 9419f9e9251d031b1206346416078f0459aa7f79..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.png deleted file mode 100644 index 6f6fb0c502201dc4081622545a9b7fc8bed1394d..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_app.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_app.webp new file mode 100644 index 0000000000000000000000000000000000000000..b7daf331ad6563549f959a6cfb2ba520cb9f8959 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_app.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_app_foreground.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_app_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..9fd6b1e346612088716d5ccf47de049470e692fc Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_app_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_app_round.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_app_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..a2364af696d366eb7ddfb2d5555cf492c25c0e0e Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_app_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c9373378eb611ecaf2dcf4b65cb3ffd0648b0abb..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_background.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_background.png deleted file mode 100644 index fd555be0afa8c827d998241da90d099bc3d68ece..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_background.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index c9a322dd6d922cc6d350f8aeff70e764730cc8e3..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.png deleted file mode 100644 index c0fe4bac39405ac6f933531247772ae687d516fd..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app.webp new file mode 100644 index 0000000000000000000000000000000000000000..7423cc41b8a3f34771587ef02452298df0644bcc Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app_foreground.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..e9d0b389c59ef6849f1df596b056041c40781fc1 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app_round.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..622ff49060401aa5e3c38f2c8089e11189921509 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_app_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a515227cb6172de58ee04f3c07fa995432c0b4ca..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_background.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_background.png deleted file mode 100644 index 4e952b39bbba6b175e0da36fe991c801f8778317..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 8e8555bee2bc130cce518cd0313b07b612a0f3ce..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.png deleted file mode 100644 index db97a776af2b9b85a70074c5fa31ca2ac03802c6..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app.webp new file mode 100644 index 0000000000000000000000000000000000000000..b88da76a9453ac11dc098fd0e0b70387b074730f Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..f4e1e15b7b3eda421073046034a5f40aa1ecaf7c Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app_round.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..d9203f001b574c65d3e8e6c514b8ba803c9b2c6e Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_app_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 41c48344d8894902e6480bb2a7c75547354a46b3..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_background.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_background.png deleted file mode 100644 index cb2622d1309f3baf90b61bb66acf38ab277de700..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 7e541e9b66729330af6bc737b882789abaa6c18f..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 3382bd6a62a96eaf2c47ea30b0e8d3fb98fab424..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app.webp new file mode 100644 index 0000000000000000000000000000000000000000..e7867ae281fb3ae9a69f4de795b1af90a47f4986 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app_foreground.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..40a55f0ff1bee0b3f7f3e9d65b457e069890be93 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app_foreground.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app_round.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..a0305517beca33dd7f4cebfe745eaa45164c9859 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_app_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5bafe328db38174ef986e9f5e1510e2b59c92ee6..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_background.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_background.png deleted file mode 100644 index 19cde18cbcac681118349813a6ada0378ca7dca3..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 78a05b88755113b37d1f22e8068e7923356ab598..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 4a2202d103165b60a5a96a91e8b9b389308cb413..0000000000000000000000000000000000000000 Binary files a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/composeApp/src/androidMain/res/values/ic_app_background.xml b/composeApp/src/androidMain/res/values/ic_app_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..18b34e3c877f9fdc39d1be25cfde6f3e66093ed8 --- /dev/null +++ b/composeApp/src/androidMain/res/values/ic_app_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Color.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Color.kt index 2e71c08a373eb5c9b87aef41630ca635ac563235..ebb6ae310692091b6e4b1edf88f7182121a48d24 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Color.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Color.kt @@ -2,8 +2,57 @@ package band.effective.office.elevator import androidx.compose.ui.graphics.Color -//generated by https://m3.material.io/theme-builder#/custom -//Color palette was taken here: https://colorhunt.co/palettes/popular +//neutral colors +internal val neutral100 = Color(0xFF0D0D0D) +internal val neutral95 = Color(0xFF1A1A1A) +internal val neutral90 = Color(0xFF262626) +internal val neutral85 = Color(0xFF333333) +internal val neutral80 = Color(0xFF404040) +internal val neutral75 = Color(0xFF4D4D4D) +internal val neutral70 = Color(0xFF595959) +internal val neutral65 = Color(0xFF666666) +internal val neutral60 = Color(0xFF737373) +internal val neutral55 = Color(0xFF808080) +internal val neutral50 = Color(0xFF8C8C8C) +internal val neutral45 = Color(0xFF999999) +internal val neutral40 = Color(0xFFA6A6A6A) +internal val neutral35 = Color(0xFFB3B3B3) +internal val neutral30 = Color(0xFFBFBFBF) +internal val neutral25 = Color(0xFFCCCCCC) +internal val neutral20 = Color(0xFFE6E6E6) +internal val neutral15 = Color(0xFFF9F9F9) +internal val neutral10 = Color(0xFFFDFDFD) +internal val neutral5 = Color(0xFFF2F2F2) +//orange colors +internal val orange100 = Color(0xFF260F02) +internal val orange90 = Color(0xFF562206) +internal val orange80 = Color(0xFF863509) +internal val orange70 = Color(0xFFB6470C) +internal val orange60 = Color(0xFFFF6C1A) +internal val orange50 = Color(0xFFEF7234) +internal val orange40 = Color(0xFFF68E56) +internal val orange30 = Color(0xFFF8AE86) +internal val orange20 = Color(0xFFFBCEB6) +internal val orange10 = Color(0xFFFEEFE7) +//purple colors +internal val purple100 = Color(0xFF17052E) +internal val purple90 = Color(0xFF2E0B5B) +internal val purple80 = Color(0xFF451089) +internal val purple70 = Color(0xFF5C16B6) +internal val purple60 = Color(0xFF731BE4) +internal val purple50 = Color(0xFF8F49E9) +internal val purple40 = Color(0xFFAC78EF) +internal val purple30 = Color(0xFFB98DF1) +internal val purple20 = Color(0xFFD5BBF7) +internal val purple10 = Color(0xFFF1E8FC) +//green colors +internal val green50 = Color(0xFF74BB00) +internal val green40 = Color(0xFF9ECF4E) +//red colors +internal val red50 = Color(0xFFEB5454) +internal val red40 = Color(0xFFED6868) +internal val red30 = Color(0xFFFB6A6A) +internal val red20 = Color(0xFFF87C7C) //region::Old light colors internal val md_theme_light_primary = Color(0xFFFFFFFF) @@ -176,7 +225,7 @@ internal val successGreen = Color(0xFF4BB543) internal val borderGreen = Color(0xFF34C759) internal val textGrayColor = Color(0x80000000) // TODO(Maksim Mishenko) i don't know how fix text color in OutlinedTextField, because i chenge textGrayColor on black. True value is 0x80000000 -internal val companyColor = Color(0xFF323E48) +internal val companyTitleColor = Color(0xFF323E48) internal val calendarColor = Color(0x14ACACAC) -internal val radioButtonColor = Color(0xFF6750A4) \ No newline at end of file +internal val radioButtonColor = Color(0xFF6750A4) diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveDarkColors.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveDarkColors.kt new file mode 100644 index 0000000000000000000000000000000000000000..b9286e419a968bd76bf6fa74ca0b4eb6375bf191 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveDarkColors.kt @@ -0,0 +1,114 @@ +package band.effective.office.elevator + +val EffectiveDarkColors = EffectiveThemeColors( + background = EffectiveThemeColors.Background( + primary = neutral95, + secondary = neutral90, + tertiary = neutral95.copy(alpha = 0.8f), + additional = neutral80, + ), + + text = EffectiveThemeColors.Text( + primary = neutral10, + secondary = neutral50, + tertiary = orange30, + accent = orange50, + primaryEvent = neutral10, + error = red40, + additional = neutral40, + caption = neutral20, + optional = purple30, + ), + + icon = EffectiveThemeColors.Icon( + primary = neutral10, + secondary = neutral25, + tertiary = neutral35, + accent = orange50, + success = green40, + error = red40, + primaryEvent = neutral10, + optional = purple30, + ), + + stroke = EffectiveThemeColors.Stroke( + primary = neutral85, + error = red40, + accent = orange50, + ), + + divider = EffectiveThemeColors.Divider( + primary = neutral85, + ), + + graph = EffectiveThemeColors.Graph( + orange = orange40, + violet = purple50, + ), + + button = EffectiveThemeColors.Button( + primaryNormal = orange50, + primaryPress = orange40, + primaryDisable = orange90, + iconNormal = neutral95, + iconPress = neutral90, + iconDisable = neutral95, + tertiaryNormal = neutral95, + tertiaryPress = neutral90, + actionNormalNormal = purple30, + actionEditPress = purple20, + actionDelete = red20, + timeNormal = neutral90, + timePress = neutral95, + timeActive = neutral90, + fullDayNormal = neutral90, + fullDayPress = neutral95, + repeatNormal = neutral90, + repeatPress = neutral95, + toggleOn = green40, + toggleOff = neutral80, + toggleNormal = neutral5, + ), + + calendar = EffectiveThemeColors.Calendar( + date = purple30, + ), + + item = EffectiveThemeColors.Item( + colleagueNormal = neutral95, + colleagueRepeat = neutral90, + repeatNormal = neutral90, + repeatPress = neutral85, + ), + + avatar = EffectiveThemeColors.Avatar( + default = neutral90, + ), + + card = EffectiveThemeColors.Card( + normal = neutral95, + active = neutral95, + press = neutral90, + ), + + input = EffectiveThemeColors.Input( + freeNormal = neutral95, + freePress = neutral90, + freeTyping = neutral95, + freeFill = neutral95, + lockNormal = neutral90, + lockPress = neutral95, + lockTyping = neutral90, + lockFill = neutral90, + lockDisable = neutral95, + searchNormal = neutral95, + searchPress = neutral90, + searchTyping = neutral95, + searchFill = neutral95, + ), + + table = EffectiveThemeColors.Table( + tableAvailable = neutral85, + tableSelect = purple40, + ) +) diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveLightColors.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveLightColors.kt new file mode 100644 index 0000000000000000000000000000000000000000..28f3356502726e3ba2ade9caea3142924a77888b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveLightColors.kt @@ -0,0 +1,116 @@ +package band.effective.office.elevator + +import androidx.compose.ui.graphics.Color + +val EffectiveLightColors = EffectiveThemeColors( + background = EffectiveThemeColors.Background( + primary = neutral10, + secondary = neutral20, + tertiary = neutral15.copy(alpha = 0.8f), + additional = neutral25, + ), + + text = EffectiveThemeColors.Text( + primary = neutral90, + secondary = neutral45, + tertiary = orange40, + accent = orange60, + primaryEvent = neutral10, + error = red50, + additional = neutral30, + caption = neutral20, + optional = purple40, + ), + + icon = EffectiveThemeColors.Icon( + primary = neutral90, + secondary = neutral30, + tertiary = neutral20, + accent = orange60, + success = green50, + error = red50, + primaryEvent = neutral10, + optional = purple40, + ), + + stroke = EffectiveThemeColors.Stroke( + primary = neutral20, + error = red50, + accent = orange60, + ), + + divider = EffectiveThemeColors.Divider( + primary = neutral20, + ), + + graph = EffectiveThemeColors.Graph( + orange = orange50, + violet = purple30, + ), + + button = EffectiveThemeColors.Button( + primaryNormal = orange60, + primaryPress = orange50, + primaryDisable = orange20, + iconNormal = neutral10, + iconPress = neutral15, + iconDisable = neutral10, + tertiaryNormal = neutral10, + tertiaryPress = neutral15, + actionNormalNormal = purple40, + actionEditPress = purple30, + actionDelete = red30, + timeNormal = neutral5, + timePress = neutral10, + timeActive = neutral5, + fullDayNormal = neutral5, + fullDayPress = neutral10, + repeatNormal = neutral5, + repeatPress = neutral10, + toggleOn = green50, + toggleOff = neutral5, + toggleNormal = neutral10, + ), + + calendar = EffectiveThemeColors.Calendar( + date = purple40, + ), + + item = EffectiveThemeColors.Item( + colleagueNormal = neutral10, + colleagueRepeat = neutral5, + repeatNormal = neutral5, + repeatPress = neutral15, + ), + + avatar = EffectiveThemeColors.Avatar( + default = neutral15, + ), + + card = EffectiveThemeColors.Card( + normal = neutral10, + active = neutral10, + press = neutral15, + ), + + input = EffectiveThemeColors.Input( + freeNormal = neutral10, + freePress = neutral15, + freeTyping = neutral10, + freeFill = neutral10, + lockNormal = neutral15, + lockPress = neutral10, + lockTyping = neutral15, + lockFill = neutral15, + lockDisable = neutral10, + searchNormal = neutral10, + searchPress = neutral15, + searchTyping = neutral10, + searchFill = neutral10, + ), + + table = EffectiveThemeColors.Table( + tableAvailable = purple10, + tableSelect = purple50, + ), +) diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveTheme.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveTheme.kt new file mode 100644 index 0000000000000000000000000000000000000000..aaaee3a27eb40f6af0bcb7dd5ba635869329f9f0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveTheme.kt @@ -0,0 +1,185 @@ +package band.effective.office.elevator + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle + +data class EffectiveThemeColors( + val background: Background, + val text: Text, + val icon: Icon, + val stroke: Stroke, + val divider: Divider, + val graph: Graph, + val button: Button, + val calendar: Calendar, + val item: Item, + val avatar: Avatar, + val card: Card, + val input: Input, + val table: Table, +) { + + data class Background( + val primary: Color, + val secondary: Color, + val tertiary: Color, + val additional: Color, + ) + + data class Text( + val primary: Color, + val secondary: Color, + val tertiary: Color, + val accent: Color, + val primaryEvent: Color, + val error: Color, + val additional: Color, + val caption: Color, + val optional: Color, + ) + + data class Icon( + val primary: Color, + val secondary: Color, + val tertiary: Color, + val accent: Color, + val success: Color, + val error: Color, + val primaryEvent: Color, + val optional: Color, + ) + + data class Stroke( + val primary: Color, + val error: Color, + val accent: Color, + ) + + data class Divider( + val primary: Color, + ) + + data class Graph( + val orange: Color, + val violet: Color, + ) + + data class Button( + val primaryNormal: Color, + val primaryPress: Color, + val primaryDisable: Color, + val iconNormal: Color, + val iconPress: Color, + val iconDisable: Color, + val tertiaryNormal: Color, + val tertiaryPress: Color, + val actionNormalNormal: Color, + val actionEditPress: Color, + val actionDelete: Color, + val timeNormal: Color, + val timePress: Color, + val timeActive: Color, + val fullDayNormal: Color, + val fullDayPress: Color, + val repeatNormal: Color, + val repeatPress: Color, + val toggleOff: Color, + val toggleOn: Color, + val toggleNormal: Color, + ) + + data class Calendar( + val date: Color, + ) + + data class Item( + val colleagueNormal: Color, + val colleagueRepeat: Color, + val repeatNormal: Color, + val repeatPress: Color, + ) + + data class Avatar( + val default: Color, + ) + + data class Card( + val normal: Color, + val active: Color, + val press: Color, + ) + + data class Input( + val freeNormal: Color, + val freePress: Color, + val freeTyping: Color, + val freeFill: Color, + val lockNormal: Color, + val lockPress: Color, + val lockTyping: Color, + val lockFill: Color, + val lockDisable: Color, + val searchNormal: Color, + val searchPress: Color, + val searchTyping: Color, + val searchFill: Color, + ) + + data class Table( + val tableAvailable: Color, + val tableSelect: Color, + ) +} + +data class EffectiveThemeTypography( + val xlMedium: TextStyle, + val lMedium: TextStyle, + val mMedium: TextStyle, + val sMedium: TextStyle, + val xsMedium: TextStyle, + val xsRegular: TextStyle, +) + +@Composable +internal fun EffectiveTheme( + useDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val colors = if (useDarkTheme) { + EffectiveDarkColors + } else { + EffectiveLightColors + } + CompositionLocalProvider( + LocalEffectiveThemeColors provides colors, + LocalEffectiveThemeTypography provides effectiveTypography, + ) { + content() + } +} + +object EffectiveTheme { + + val colors: EffectiveThemeColors + @Composable + @ReadOnlyComposable + get() = LocalEffectiveThemeColors.current + + val typography: EffectiveThemeTypography + @Composable + @ReadOnlyComposable + get() = LocalEffectiveThemeTypography.current +} + +val LocalEffectiveThemeColors = staticCompositionLocalOf { + throw IllegalStateException("EffectiveTheme is not set properly") +} + +val LocalEffectiveThemeTypography = staticCompositionLocalOf { + throw IllegalStateException("EffectiveTheme is not set properly") +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveTypography.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveTypography.kt new file mode 100644 index 0000000000000000000000000000000000000000..2536b06ec1ba6ee237f74c7c389d66c1c3450743 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/EffectiveTypography.kt @@ -0,0 +1,32 @@ +package band.effective.office.elevator + +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +val effectiveTypography = EffectiveThemeTypography( + xlMedium = TextStyle( + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + ), + lMedium = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + ), + mMedium = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + ), + sMedium = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + ), + xsMedium = TextStyle( + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + ), + xsRegular = TextStyle( + fontSize = 10.sp, + fontWeight = FontWeight.Normal, + ) +) diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Theme.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Theme.kt index 72e94a78b288c8af1bbdf180371c1d9eac11f613..c5dcf10074392be1dcdc5634ef1caeae4a2b8984 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Theme.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/Theme.kt @@ -7,8 +7,6 @@ import androidx.compose.material.Typography import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -128,9 +126,9 @@ internal fun AppTheme( ) val colors = if (!useDarkTheme) { - LightColors + EffectiveLightColors } else { - DarkColors + EffectiveDarkColors } ExtendedTheme( diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EffectiveButton.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EffectiveButton.kt index 4b9df4503e4af18189f0bf3adbd1663eff9bcd29..acf6ab264a3b1aac26ca938dd1e52b4635e71d85 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EffectiveButton.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EffectiveButton.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme @Composable @@ -22,23 +23,23 @@ fun EffectiveButton( contentPadding: Dp = 14.dp, roundedCorner: Dp = 40.dp, buttonColors: ButtonColors = ButtonDefaults.buttonColors( - backgroundColor = MaterialTheme.colors.primary, - contentColor = MaterialTheme.colors.background + backgroundColor = EffectiveTheme.colors.button.primaryNormal, + contentColor = EffectiveTheme.colors.text.caption ), border: BorderStroke? = null ) { Button( + modifier = modifier, onClick = onClick, colors = buttonColors, contentPadding = PaddingValues(contentPadding), elevation = Elevation(), shape = RoundedCornerShape(roundedCorner), - modifier = modifier, border = border ) { Text( text = buttonText, - style = MaterialTheme.typography.button, + style = EffectiveTheme.typography.mMedium, ) } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EffectiveGradient.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EffectiveGradient.kt new file mode 100644 index 0000000000000000000000000000000000000000..89bb49b1acaf8ef07fe2eb0b3094729b5961690d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EffectiveGradient.kt @@ -0,0 +1,39 @@ +package band.effective.office.elevator.components + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme + +@Composable +fun EffectiveGradient(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxSize() + .blur(radius = 200.dp) + .background(EffectiveTheme.colors.divider.primary) + ) { + val colors = EffectiveTheme.colors + + Canvas(modifier = Modifier.fillMaxSize()) { + val radius = size.minDimension / 4 + val topMargin = size.height / 5 + drawCircle( + color = colors.graph.violet, + radius = radius, + center = Offset(x = size.width / 2, y = topMargin) + ) + drawCircle( + color = colors.graph.orange, + radius = radius, + center = Offset(x = size.width / 2, y = topMargin + radius * 2) + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EmployeeBlock.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EmployeeBlock.kt new file mode 100644 index 0000000000000000000000000000000000000000..a4a437e84fdde23ba8bdcc15f9781549887393fc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/EmployeeBlock.kt @@ -0,0 +1,108 @@ +package band.effective.office.elevator.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.MainRes +import band.effective.office.elevator.ui.employee.aboutEmployee.components.EmployeeInfo +import band.effective.office.elevator.utils.prettifyPhoneNumber +import com.seiko.imageloader.model.ImageRequest +import com.seiko.imageloader.rememberImagePainter +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +fun EmployeeBlock( + modifier: Modifier = Modifier, + imageUrl: String?, + userName: String?, + post: String?, + telegram: String?, + email: String?, + phoneNumber: String?, + onCopyText: (value: String, label: String) -> Unit, + onOpenUri: (uri: String) -> Unit, +) { + val imageLoader = remember { generateImageLoader() } + val request = remember(imageUrl) { + ImageRequest { + data(imageUrl) + } + } + val painter = rememberImagePainter( + request = request, + imageLoader = imageLoader, + placeholderPainter = { painterResource(MainRes.images.logo_default) }, + errorPainter = { painterResource(MainRes.images.logo_default) } + ) + + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .size(100.dp) + .border( + BorderStroke( + width = 2.dp, + color = EffectiveTheme.colors.stroke.accent, + ), + shape = CircleShape, + ) + .padding(2.dp) + .clip(CircleShape) + ) + Spacer(modifier = Modifier.padding(12.dp)) + UserDetails(userName = userName, post = post) + Spacer(modifier = Modifier.padding(24.dp)) + telegram?.let { + val iconTitle = stringResource(MainRes.strings.telegram) + EmployeeInfo( + icon = MainRes.images.ic_telegram, + value = telegram, + iconTitle = iconTitle, + onClick = { onOpenUri("https://t.me/$telegram") }, + onLongClick = { onCopyText(telegram, iconTitle) }, + ) + Spacer(modifier = Modifier.height(8.dp)) + } + email?.let { + val iconTitle = stringResource(MainRes.strings.email) + EmployeeInfo( + icon = MainRes.images.ic_email, + value = email, + iconTitle = iconTitle, + onClick = { onOpenUri("mailto:$email") }, + onLongClick = { onCopyText(email, iconTitle) }, + ) + Spacer(modifier = Modifier.height(8.dp)) + } + phoneNumber?.let { + val iconTitle = stringResource(MainRes.strings.phone_number) + EmployeeInfo( + icon = MainRes.images.ic_phone, + value = prettifyPhoneNumber(phoneNumber) ?: phoneNumber, + iconTitle = iconTitle, + onClick = { onOpenUri("tel:$phoneNumber") }, + onLongClick = { onCopyText(phoneNumber, iconTitle) }, + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/GoogleSignInButton.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/GoogleSignInButton.kt index d520504734546323ed58db506bf8d7efe5e33c95..638123e16ae40ba76d5cc99741ee20da9b0203c3 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/GoogleSignInButton.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/GoogleSignInButton.kt @@ -1,47 +1,56 @@ package band.effective.office.elevator.components import androidx.compose.foundation.Image -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable -internal fun GoogleSignInButton(modifier: Modifier, onClick: () -> Unit) { - Button( - onClick = onClick, - modifier = modifier.fillMaxWidth() - .border(2.dp, MaterialTheme.colors.secondary, RoundedCornerShape(40.dp)), +internal fun GoogleSignInButton( + modifier: Modifier = Modifier, + isEnabled: Boolean, + onClick: () -> Unit +) { + TextButton( + modifier = modifier.fillMaxWidth(), + onClick = { if (isEnabled) onClick() }, shape = RoundedCornerShape(40.dp), colors = ButtonDefaults.buttonColors( - backgroundColor = MaterialTheme.colors.background, - contentColor = MaterialTheme.colors.secondary + backgroundColor = Color.Transparent, + contentColor = EffectiveTheme.colors.text.accent ), - elevation = Elevation() + elevation = Elevation(), ) { - Image( - painterResource(MainRes.images.google_icon), - contentDescription = null, - modifier = Modifier.size(32.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(MainRes.strings.sign_in_google), - modifier = Modifier.padding(6.dp), - style = MaterialTheme.typography.button - ) + if (isEnabled) { + Image( + painter = painterResource(MainRes.images.ic_google), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + modifier = Modifier.padding(6.dp), + text = stringResource(MainRes.strings.sign_in_google), + style = EffectiveTheme.typography.mMedium, + color = EffectiveTheme.colors.text.accent + ) + } else { + CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp) + } } } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/InfoAboutUserUIComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/InfoAboutUserUIComponent.kt index 0b68b74bd7ad0d8fd95d69cb9442cbd031814bbf..b378ede32cf747b121b491e3c556a44ee4ac526b 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/InfoAboutUserUIComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/InfoAboutUserUIComponent.kt @@ -2,31 +2,44 @@ package band.effective.office.elevator.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme @Composable -fun InfoAboutUserUIComponent(userName:String?, post:String?){ - Column{ - userName?.let{ +fun UserDetails( + modifier: Modifier = Modifier, + userName: String?, + post: String?, +) { + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + userName?.let { Text( text = it, - style = MaterialTheme.typography.subtitle1, - color = Color.Black + style = EffectiveTheme.typography.mMedium, + color = EffectiveTheme.colors.text.primary, + fontWeight = FontWeight(500), + textAlign = TextAlign.Center, ) } - Spacer(modifier = Modifier.height(8.dp)) - post?.let{ + post?.let { + Spacer(modifier = Modifier.height(4.dp)) Text( text = it, - style = MaterialTheme.typography.subtitle1, - color = Color.Black + style = EffectiveTheme.typography.sMedium, + color = EffectiveTheme.colors.text.secondary, + textAlign = TextAlign.Center, ) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendar.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendar.kt index dfbcf7b5e4a05d744de2f3a1f166b77b9ba8d7bb..2998d4e73bb07d7c5ca9b96ab99000c0cfe56006 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendar.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendar.kt @@ -51,7 +51,7 @@ fun ModalCalendar( modifier = modifier .background(Color.White, shape = RoundedCornerShape(16.dp)) .padding(horizontal = 16.dp, vertical = 24.dp) - ){ + ) { Calendar(state = state) Spacer(modifier = Modifier.height(32.dp)) Row( @@ -67,12 +67,12 @@ fun ModalCalendar( ) Spacer(modifier = Modifier.width(16.dp)) EffectiveButton( - buttonText = stringResource(MainRes.strings.ok), - modifier = Modifier.weight(.1f), - onClick = { onClickOk(state.selectedDates.firstOrNull())}, + buttonText = stringResource(MainRes.strings.ok), + modifier = Modifier.weight(.1f), + onClick = { onClickOk(state.selectedDates.firstOrNull()) }, roundedCorner = 8.dp, contentPadding = 12.dp ) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendarDateRange.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendarDateRange.kt index 783ccf66a6f01b4062b5cb9160170ff1e1df86a4..34ec1d1c548cacba8cf9d8d1a64c6952d5649e4f 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendarDateRange.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/ModalCalendarDateRange.kt @@ -13,10 +13,10 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material3.Switch import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DateRange +import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -71,7 +71,7 @@ fun ModalCalendarDateRange( modifier = modifier .background(Color.White, shape = RoundedCornerShape(16.dp)) .padding(horizontal = 16.dp, vertical = 24.dp) - ){ + ) { Calendar(state = state) Spacer(modifier = Modifier.height(16.dp)) SwitchedDateRange( @@ -92,15 +92,16 @@ fun ModalCalendarDateRange( ) Spacer(modifier = Modifier.width(16.dp)) EffectiveButton( - buttonText = stringResource(MainRes.strings.ok), - modifier = Modifier.weight(.1f), - onClick = { onClickOk(state.selectedDates)}, + buttonText = stringResource(MainRes.strings.ok), + modifier = Modifier.weight(.1f), + onClick = { onClickOk(state.selectedDates) }, roundedCorner = 8.dp, contentPadding = 12.dp ) } } } + @Composable private fun SwitchedDateRange( isSelectRange: Boolean, @@ -138,4 +139,4 @@ private fun SwitchedDateRange( ) ) } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/OutlinedTextFieldColorsSetup.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/OutlinedTextFieldColorsSetup.kt index f09a99787c01ef6e7f973ab4c9c1d3b8f617a2fe..ac6aab292731bbf33995e7412ec18a3e5d1f4726 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/OutlinedTextFieldColorsSetup.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/OutlinedTextFieldColorsSetup.kt @@ -3,16 +3,17 @@ package band.effective.office.elevator.components import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.ExtendedThemeColors import band.effective.office.elevator.textGrayColor @Composable fun OutlinedTextColorsSetup() = TextFieldDefaults.outlinedTextFieldColors( // region::Border - focusedBorderColor = ExtendedThemeColors.colors.trinidad_400, - unfocusedBorderColor = textGrayColor, - disabledBorderColor = textGrayColor, - errorBorderColor = ExtendedThemeColors.colors.error, + focusedBorderColor = EffectiveTheme.colors.stroke.accent, + unfocusedBorderColor = EffectiveTheme.colors.stroke.primary, + disabledBorderColor = EffectiveTheme.colors.stroke.primary, + errorBorderColor = EffectiveTheme.colors.stroke.error, // endregion // region::Trailing icon @@ -22,13 +23,13 @@ fun OutlinedTextColorsSetup() = TextFieldDefaults.outlinedTextFieldColors( // endregion // region::Leading icon - disabledLeadingIconColor = textGrayColor, + disabledLeadingIconColor = EffectiveTheme.colors.text.additional, // endregion // region::Cursor colors - cursorColor = ExtendedThemeColors.colors.trinidad_400, - errorCursorColor = ExtendedThemeColors.colors.error, + cursorColor = EffectiveTheme.colors.text.primary, + errorCursorColor = EffectiveTheme.colors.text.error, // endregion - textColor = Color.Black, - disabledTextColor = Color.Black -) \ No newline at end of file + textColor = EffectiveTheme.colors.text.primary, + disabledTextColor = EffectiveTheme.colors.text.additional, +) diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/PopupMessage.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/PopupMessage.kt new file mode 100644 index 0000000000000000000000000000000000000000..a276edbdcf888e121ec39a5db4fae68b8f3c27b0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/PopupMessage.kt @@ -0,0 +1,87 @@ +package band.effective.office.elevator.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.MainRes +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +data class PopupMessageInfo( + val messageResource: StringResource, + val type: Type, +) { + + enum class Type { + SUCCESS, + ERROR, + } +} + +@Composable +fun PopupMessage( + modifier: Modifier = Modifier, + info: PopupMessageInfo, + onCloseClick: () -> Unit, +) { + Row( + modifier = modifier + .fillMaxWidth() + .background( + color = EffectiveTheme.colors.background.primary, + shape = RoundedCornerShape(16.dp), + ) + .border( + border = BorderStroke( + width = 1.dp, + color = EffectiveTheme.colors.stroke.primary, + ), + shape = RoundedCornerShape(16.dp), + ) + .padding(vertical = 12.dp, horizontal = 20.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + if (info.type == PopupMessageInfo.Type.SUCCESS) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(MainRes.images.ic_success), + tint = EffectiveTheme.colors.icon.success, + contentDescription = null, + ) + } + Text( + modifier = Modifier.weight(1f), + text = stringResource(info.messageResource), + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.mMedium, + textAlign = TextAlign.Start, + ) + IconButton( + onClick = onCloseClick, + ) { + Icon( + modifier = Modifier.size(14.dp), + painter = painterResource(MainRes.images.ic_close), + tint = EffectiveTheme.colors.icon.secondary, + contentDescription = stringResource(MainRes.strings.close) + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/PrimaryButton.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/PrimaryButton.kt new file mode 100644 index 0000000000000000000000000000000000000000..9c85b738abd95cac54518167004c3e59ad2c7453 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/PrimaryButton.kt @@ -0,0 +1,36 @@ +package band.effective.office.elevator.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme + +@Composable +fun PrimaryButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + buttonText: String, +) { + Button( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + onClick = onClick, + shape = RoundedCornerShape(32.dp), + colors = ButtonDefaults.buttonColors( + containerColor = EffectiveTheme.colors.button.primaryNormal, + ), + ) { + Text( + text = buttonText, + style = EffectiveTheme.typography.mMedium, + color = EffectiveTheme.colors.text.primaryEvent, + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TabNavigationItem.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TabNavigationItem.kt index 63b8555c7230ee2bc4df6573f73fb1f26158800f..c48927914ce4e030de315d9929da39930277eef3 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TabNavigationItem.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TabNavigationItem.kt @@ -10,14 +10,14 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.BottomNavigationItem import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.navigation.Tab -import band.effective.office.elevator.textGrayColor +import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable @@ -26,8 +26,8 @@ internal fun RowScope.TabNavigationItem( selected: Boolean, onSelect: () -> Unit, ) { - val selectedColor = MaterialTheme.colors.primary - val unselectedColor = textGrayColor + val selectedColor = EffectiveTheme.colors.icon.accent + val unselectedColor = EffectiveTheme.colors.icon.primary BottomNavigationItem( modifier = Modifier.fillMaxWidth().wrapContentHeight(), selected = selected, @@ -40,16 +40,15 @@ internal fun RowScope.TabNavigationItem( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(height = 8.dp)) Icon( modifier = Modifier.size(size = 24.dp), - imageVector = tab.icon, + painter = painterResource(tab.icon), contentDescription = stringResource(tab.title) ) Spacer(modifier = Modifier.height(height = 4.dp)) Text( text = stringResource(tab.title), - style = MaterialTheme.typography.caption.copy( + style = EffectiveTheme.typography.xsRegular.copy( color = if (selected) selectedColor else unselectedColor ), maxLines = 1 diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TimePickerModal.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TimePickerModal.kt index be4a4d669509987d6f8c945bc8dfe9ac28979c7e..68a86904e447e3b9a7ccfe7dcc18ea1d0058b2ac 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TimePickerModal.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TimePickerModal.kt @@ -16,7 +16,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import band.effective.office.elevator.ExtendedThemeColors @@ -72,7 +71,7 @@ fun TimePickerModal( horizontalArrangement = Arrangement.SpaceBetween, ) { OutlinedPrimaryButton( - modifier =Modifier.weight(.1f), + modifier = Modifier.weight(.1f), title = MainRes.strings.cansel, onClick = onClickCansel, padding = 12.dp @@ -87,4 +86,4 @@ fun TimePickerModal( } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TitlePage.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TitlePage.kt index 597697fcb5287c7ea095971023a1ca87356abd77..4d5a40e4945a3b88fbb6d68aa90777e44f083a5f 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TitlePage.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/TitlePage.kt @@ -1,17 +1,26 @@ package band.effective.office.elevator.components -import androidx.compose.material.MaterialTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import band.effective.office.elevator.EffectiveTheme @Composable -fun TitlePage(title: String, modifier: Modifier = Modifier) { - Text( - text = title, - modifier = modifier, - style = MaterialTheme.typography.h6, - color = Color.Black // TODO(Gruzdev) поменять на цвет из темы - ) -} \ No newline at end of file +fun TitlePage( + modifier: Modifier = Modifier, + title: String, + textAlign: TextAlign = TextAlign.Center, +) { + Row(modifier = modifier) { + Text( + text = title, + style = EffectiveTheme.typography.mMedium, + color = EffectiveTheme.colors.text.primary, + textAlign = textAlign, + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/UserInfoTextField.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/UserInfoTextField.kt index 1f3733df681e98bb2909bb23efbf1417c494303a..0e8ca7cca3e02f41ed05c63e214ede0aa372e993 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/UserInfoTextField.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/components/UserInfoTextField.kt @@ -1,19 +1,9 @@ package band.effective.office.elevator.components -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -21,19 +11,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import band.effective.office.elevator.ExtendedThemeColors -import band.effective.office.elevator.MainRes +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.ui.models.UserDataTextFieldType -import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable @@ -49,52 +33,27 @@ fun UserInfoTextField( var textValue by remember { mutableStateOf(text) } OutlinedTextField( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), value = textValue, - modifier = modifier.fillMaxWidth(), onValueChange = { textValue = it onValueChange(it) }, shape = RoundedCornerShape(12.dp), singleLine = true, - textStyle = TextStyle(fontSize = 16.sp), - leadingIcon = { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start, - modifier = Modifier.padding(horizontal = 16.dp) - ) { - Icon( - painter = painterResource(item.icon), - contentDescription = null, - ) - Spacer(modifier = Modifier.width(16.dp)) - Divider( - modifier = Modifier.height(28.dp).width(2.dp).clip(RoundedCornerShape(4.dp)) - ) - } - }, - trailingIcon = { - IconButton( - onClick = { - textValue = "" - onValueChange("") - } - ) { - Icon( - painter = painterResource(MainRes.images.clear_icon), - contentDescription = null, - ) - } - }, + textStyle = EffectiveTheme.typography.sMedium, + colors = OutlinedTextColorsSetup(), isError = error, visualTransformation = visualTransformation, placeholder = { - Text( - text = stringResource(item.placeholder), - style = MaterialTheme.typography.button - ) + Text( + text = stringResource(item.placeholder), + color = EffectiveTheme.colors.text.additional, + style = EffectiveTheme.typography.sMedium, + ) }, keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = ImeAction.Done) ) diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/BookingRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/BookingRepositoryImpl.kt index d4dbf40092a1e16c1516992a73e5ec7e88691c02..fa6185bf6596ae55d439b5a8d7242a68a2c39713 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/BookingRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/BookingRepositoryImpl.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.atTime @@ -42,9 +41,8 @@ class BookingRepositoryImpl( init { CoroutineScope(Dispatchers.IO).launch { - val currentUserResponse = profileRepository.getUser() - - currentUserResponse.collect { response -> + profileRepository.refresh() + profileRepository.user.collect { response -> user = when (response) { is Either.Error -> null is Either.Success -> response.data @@ -99,18 +97,15 @@ class BookingRepositoryImpl( book(user = user!!, creatingBookModel = creatingBookModel) ) } else { - profileRepository.getUser().collect { userResponse -> - when (userResponse) { - is Either.Success -> { - user = userResponse.data - emit( - book(user = user!!, creatingBookModel = creatingBookModel) - ) - } - - is Either.Error -> { - emit(Either.Error(userResponse.error.error)) - } + when (val userResponse = profileRepository.refresh()) { + is Either.Success -> { + user = userResponse.data + emit( + book(user = user!!, creatingBookModel = creatingBookModel) + ) + } + is Either.Error -> { + emit(Either.Error(userResponse.error.error)) } } } @@ -144,27 +139,24 @@ class BookingRepositoryImpl( bookingsFilter: BookingsFilter ): Flow>, List>> = flow { if (ownerId == null) { - val currentUserResponse = profileRepository.getUser() // TODO (Artem Gruzdev) use saved user params - currentUserResponse.collect { userResponse -> - when (userResponse) { - is Either.Success -> { - val bookings = api - .getBookingsByUser( - userId = userResponse.data.id, - beginDate = localDateTimeToUnix(beginDate)!!, - endDate = localDateTimeToUnix(endDate)!! - ) - .convert( - filter = bookingsFilter, - oldValue = lastResponse.value - ) - lastResponse.update { bookings } - emit(bookings) - } + when (val userResponse = profileRepository.refresh()) { + is Either.Success -> { + val bookings = api + .getBookingsByUser( + userId = userResponse.data.id, + beginDate = localDateTimeToUnix(beginDate)!!, + endDate = localDateTimeToUnix(endDate)!! + ) + .convert( + filter = bookingsFilter, + oldValue = lastResponse.value + ) + lastResponse.update { bookings } + emit(bookings) + } - is Either.Error -> { - // TODO add implemetation for error - } + is Either.Error -> { + // TODO add implemetation for error } } } else { @@ -218,29 +210,6 @@ class BookingRepositoryImpl( } } - private fun Either>.convertWithDateFilter( - filter: BookingsFilter, - oldValue: Either>, List>, - dateFilter: LocalDate - ) = - map(errorMapper = { error -> - ErrorWithData( - error = error, saveData = when (oldValue) { - is Either.Error -> oldValue.error.saveData - is Either.Success -> oldValue.data - } - ) - }, - successMapper = { bookingDTOS -> - placeFilter(filter = filter, list = bookingDTOS) - .toDomainZone() - .filter { - println("repository: ${it.dateOfStart.date} == ${dateFilter}") - it.dateOfStart.date == dateFilter - } - } - ) - private fun getRecurrenceModal( bookingPeriod: BookingPeriod?, typeEndPeriod: TypeEndPeriodBooking? @@ -275,4 +244,4 @@ class BookingRepositoryImpl( } ) } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/ProfileRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/ProfileRepositoryImpl.kt index 13fbb3aa6eb5fa6ce9b0a6d942a76b29267f72c3..0688d4fcb165eb0b833aa2b2e12797a45baff877 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/ProfileRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/data/repository/ProfileRepositoryImpl.kt @@ -13,8 +13,14 @@ import band.effective.office.network.api.Api import band.effective.office.network.dto.UserDTO import band.effective.office.network.model.Either import band.effective.office.network.model.ErrorResponse +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update import org.koin.core.component.KoinComponent @@ -28,6 +34,13 @@ class ProfileRepositoryImpl( private val idPhoneNumber = OfficeElevatorConfig.integrationPhoneId private val idTelegram = OfficeElevatorConfig.integrationTelegramId + private val updateUserFlow: MutableSharedFlow = + MutableSharedFlow(replay = 1, extraBufferCapacity = 1) + + private val mutableUser: MutableStateFlow, User>?> = + MutableStateFlow(null) + override val user: Flow, User>> = mutableUser.filterNotNull() + private val lastResponse: MutableStateFlow, User>> = MutableStateFlow( Either.Error( @@ -37,11 +50,19 @@ class ProfileRepositoryImpl( ) ) + init { + updateUserFlow.tryEmit(Unit) + } + override suspend fun updateUser(user: User): Flow, User>> = flow { println("User for auth: ${user}") val requestResult = - api.updateUser(user.toUserDTO(idPhoneNumber = idPhoneNumber, - idTelegram = idTelegram )).convert(this@ProfileRepositoryImpl.lastResponse.value) + api.updateUser( + user.toUserDTO( + idPhoneNumber = idPhoneNumber, + idTelegram = idTelegram, + ) + ).convert(this@ProfileRepositoryImpl.lastResponse.value) val newUser = requestResult.getData() val cashedUser = bdSource.getCurrentUserInfo() if (newUser != null && newUser != cashedUser) { @@ -49,20 +70,20 @@ class ProfileRepositoryImpl( lastResponse.update { requestResult } } val dateForEmit = bdSource.getCurrentUserInfo().packageEither(requestResult) + updateUserFlow.tryEmit(Unit) emit(dateForEmit) } - //TODO(Artem Gruzdev) maybe can easier this method - override suspend fun getUser(): Flow, User>> = flow { - + override suspend fun refresh(): Either, User> { val cashedUser = bdSource.getCurrentUserInfo() - if (cashedUser == null) { - emit(Either.Error(ErrorWithData( - error = ErrorResponse(code = 404, description = "you don`t login"), - saveData = null - ))) - } - else { + return if (cashedUser == null) { + Either.Error( + ErrorWithData( + error = ErrorResponse(code = 404, description = "you don`t login"), + saveData = null + ) + ) + } else { val requestResult = api.getUser(cashedUser.id).convert(lastResponse.value) val userFromApi = requestResult.getData() if (userFromApi != null && userFromApi != cashedUser) { @@ -70,8 +91,7 @@ class ProfileRepositoryImpl( lastResponse.update { requestResult } } - val dateForEmit = bdSource.getCurrentUserInfo().packageEither(requestResult) - emit(dateForEmit) + bdSource.getCurrentUserInfo().packageEither(requestResult) } } @@ -83,7 +103,7 @@ class ProfileRepositoryImpl( private fun User?.packageEither(apiResponse: Either, User>) = when (apiResponse) { - is Either.Success -> Either.Success(this?:apiResponse.data) + is Either.Success -> Either.Success(this ?: apiResponse.data) is Either.Error -> Either.Error( ErrorWithData( error = apiResponse.error.error, @@ -106,4 +126,4 @@ class ProfileRepositoryImpl( successMapper = { userDTO -> userDTO.toUser() }) -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/di/ProfileDomainModuleDI.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/di/ProfileDomainModuleDI.kt index 4f72b39cf46cdda4bffa7947fac44be281363fdd..90099233ae774f97319ec2ddbcc149d90772e355 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/di/ProfileDomainModuleDI.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/di/ProfileDomainModuleDI.kt @@ -1,13 +1,12 @@ package band.effective.office.elevator.domain.di import band.effective.office.elevator.data.repository.ProfileRepositoryImpl -import band.effective.office.elevator.data.repository.UserRepositoryImpl import band.effective.office.elevator.domain.repository.ProfileRepository -import band.effective.office.elevator.domain.repository.UserRepository -import band.effective.office.elevator.domain.useCase.GetLastUserIdUseCase import band.effective.office.elevator.domain.useCase.GetUserByIdUseCase import band.effective.office.elevator.domain.useCase.GetUserUseCase import band.effective.office.elevator.domain.useCase.UpdateUserUseCase +import band.effective.office.elevator.domain.validator.ExtendedUserInfoValidator +import band.effective.office.elevator.domain.validator.ExtendedUserInfoValidatorImpl import org.koin.dsl.module internal val profileDomainModuleDI = module { @@ -18,8 +17,12 @@ internal val profileDomainModuleDI = module { } single { GetUserByIdUseCase(get()) } single { UpdateUserUseCase(get()) } - single {GetUserUseCase( - profileRepository = get() - ) + single { + GetUserUseCase( + profileRepository = get() + ) + } + factory { + ExtendedUserInfoValidatorImpl() } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/repository/ProfileRepository.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/repository/ProfileRepository.kt index 310474b273942521c811a210f0f0f5af2d7f68c7..63a093a163504f8410de951080cecbeff47c51a7 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/repository/ProfileRepository.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/repository/ProfileRepository.kt @@ -4,8 +4,11 @@ import band.effective.office.elevator.domain.models.ErrorWithData import band.effective.office.elevator.domain.models.User import band.effective.office.network.model.Either import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow interface ProfileRepository { - suspend fun getUser(): Flow, User>> + val user: Flow, User>> + + suspend fun refresh(): Either, User> suspend fun updateUser(user: User): Flow, User>> -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/GetUserUseCase.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/GetUserUseCase.kt index 30f46c2ad75303a335a33e93ff0c6a3402980b9f..4198aa290833725cd228f2ce118acf57107b202e 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/GetUserUseCase.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/GetUserUseCase.kt @@ -4,24 +4,29 @@ import band.effective.office.elevator.domain.models.ErrorWithData import band.effective.office.elevator.domain.models.User import band.effective.office.elevator.domain.repository.ProfileRepository import band.effective.office.network.model.Either +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map class GetUserUseCase( private val profileRepository: ProfileRepository ) { - suspend fun execute() = profileRepository.getUser() - suspend fun executeInFormat() = - profileRepository.getUser().map { user -> - when (user) { - is Either.Success -> Either.Success(user.data.formatToUI()) - is Either.Error -> Either.Error( - ErrorWithData( - error = user.error.error, - saveData = user.error.saveData?.formatToUI() - ) + fun execute() = flow { + profileRepository.refresh() + emitAll(profileRepository.user) + } + + fun executeInFormat() = execute().map { user -> + when (user) { + is Either.Success -> Either.Success(user.data.formatToUI()) + is Either.Error -> Either.Error( + ErrorWithData( + error = user.error.error, + saveData = user.error.saveData?.formatToUI() ) - } + ) } + } private fun User.formatToUI() = User( @@ -33,4 +38,4 @@ class GetUserUseCase( telegram = telegram, email = email ) -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/UpdateUserUseCase.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/UpdateUserUseCase.kt index c8a4eaea7fce7810550c53c1ce6a652ed234410c..4eb17ee1e41d44d18900abab13aebf8d00bba3ec 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/UpdateUserUseCase.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/usecase/UpdateUserUseCase.kt @@ -12,7 +12,7 @@ class UpdateUserUseCase (private val profileRepository: ProfileRepository) { imageUrl = user.imageUrl, userName = user.userName, post = user.post, - phoneNumber = "+7" + user.phoneNumber.replace("-", ""), + phoneNumber = user.phoneNumber, telegram = user.telegram, email = user.email, ) @@ -25,4 +25,4 @@ class UpdateUserUseCase (private val profileRepository: ProfileRepository) { } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ExtendedUserInfoValidator.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ExtendedUserInfoValidator.kt new file mode 100644 index 0000000000000000000000000000000000000000..db843465968f490b2ebc9f0939f2ad3abe6bea92 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ExtendedUserInfoValidator.kt @@ -0,0 +1,18 @@ +package band.effective.office.elevator.domain.validator + +import dev.icerock.moko.resources.StringResource + +interface ExtendedUserInfoValidator { + + fun checkPhone(phoneNumber: String): Result + fun checkName(name: String): Result + fun checkPost(post: String): Result + fun checkTelegramNick(telegram: String): Result + + + sealed interface Result { + + data object Valid : Result + data class Invalid(val message: StringResource) : Result + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ExtendedUserInfoValidatorImpl.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ExtendedUserInfoValidatorImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..0aaabaedace00c2b26a0ba8ec4cb87faa8dbff17 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ExtendedUserInfoValidatorImpl.kt @@ -0,0 +1,50 @@ +package band.effective.office.elevator.domain.validator + +import band.effective.office.elevator.MainRes +import band.effective.office.elevator.domain.validator.ExtendedUserInfoValidator.Result.Invalid +import band.effective.office.elevator.domain.validator.ExtendedUserInfoValidator.Result.Valid + +class ExtendedUserInfoValidatorImpl : ExtendedUserInfoValidator { + + override fun checkPhone(phoneNumber: String): ExtendedUserInfoValidator.Result { + return when { + phoneNumber.isEmpty() -> Invalid(MainRes.strings.error_empty_field) + phoneNumber.length != PHONE_NUMBER_SIZE -> Invalid(MainRes.strings.error_phone_format) + else -> Valid + } + } + + override fun checkName(name: String): ExtendedUserInfoValidator.Result { + return when { + name.isEmpty() -> + Invalid(MainRes.strings.error_empty_field) + name.any { it !in 'A'..'Z' && it !in 'a'..'z' && it != ' ' } -> + Invalid(MainRes.strings.error_name_must_consist_of_latin_letters) + else -> + Valid + } + } + + override fun checkPost(post: String): ExtendedUserInfoValidator.Result { + return when { + post.isEmpty() -> Invalid(MainRes.strings.error_empty_field) + else -> Valid + } + } + + override fun checkTelegramNick(telegram: String): ExtendedUserInfoValidator.Result { + return when { + telegram.isEmpty() -> + Invalid(MainRes.strings.error_empty_field) + !telegram.startsWith(char = '@', ignoreCase = true) -> + Invalid(MainRes.strings.error_telegram_must_contain_at_symbol) + else -> + Valid + } + } + + companion object { + + const val PHONE_NUMBER_SIZE = 12 + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/models/validator/UserInfoValidator.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/UserInfoValidator.kt similarity index 83% rename from composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/models/validator/UserInfoValidator.kt rename to composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/UserInfoValidator.kt index 3304d4dd38b0f1f507eba2196ba3ccb125d1ef68..1bd40e38b5db63ba6217230a0ba2c4d4f1ba7298 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/models/validator/UserInfoValidator.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/UserInfoValidator.kt @@ -1,4 +1,4 @@ -package band.effective.office.elevator.ui.models.validator +package band.effective.office.elevator.domain.validator open class UserInfoValidator : ValidatorMethods { @@ -12,6 +12,6 @@ open class UserInfoValidator : ValidatorMethods { telegramNick.isNotEmpty() && !telegramNick.contains(char = '@', ignoreCase = true) companion object { - const val phoneNumberSize = 10 + const val phoneNumberSize = 10 } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/models/validator/ValidatorMethods.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ValidatorMethods.kt similarity index 79% rename from composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/models/validator/ValidatorMethods.kt rename to composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ValidatorMethods.kt index 93975342c9d73e5f37d4f8c1dcb03ecb4b8c6b5d..d5fdbd39f8d2a52ecb9c6449da5cf818be8e3d14 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/models/validator/ValidatorMethods.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/domain/validator/ValidatorMethods.kt @@ -1,8 +1,8 @@ -package band.effective.office.elevator.ui.models.validator +package band.effective.office.elevator.domain.validator interface ValidatorMethods { fun checkPhone(phoneNumber : String) : Boolean fun checkName(name : String) : Boolean fun checkPost(post : String) : Boolean fun checkTelegramNick(telegramNick : String) : Boolean -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.kt index a291c039d0b215d118675c3567eb402ca762b812..348334718fb8245b8689dacb84c6c80bebbe8c25 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.kt @@ -18,4 +18,4 @@ internal expect fun ComponentRegistryBuilder.setupDefaultComponents() internal expect fun getImageCacheDirectoryPath(): Path -expect fun setClipboardText(text: String, label: String, toastMessage: StringResource) \ No newline at end of file +expect fun setClipboardText(text: String, label: String) diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/BookingTab.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/BookingTab.kt index c5868d9d9ea369660b056bd645928c2085ea1a7a..2fd9c65a29142b8f40558b9ea00a0ad1286de944 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/BookingTab.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/BookingTab.kt @@ -1,14 +1,8 @@ package band.effective.office.elevator.navigation -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DateRange -import androidx.compose.ui.graphics.vector.ImageVector import band.effective.office.elevator.MainRes -import dev.icerock.moko.resources.StringResource object BookingTab: Tab { - override val title: StringResource - get() = MainRes.strings.booking - override val icon: ImageVector - get() = Icons.Default.DateRange -} \ No newline at end of file + override val title = MainRes.strings.booking + override val icon = MainRes.images.ic_booking +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/EmployeesTab.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/EmployeesTab.kt index 55bc943ed8ed9f024b76099a711eb188367b68d4..5986da308ccd273963d2a14f77a5289c2bc76a43 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/EmployeesTab.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/EmployeesTab.kt @@ -1,14 +1,8 @@ package band.effective.office.elevator.navigation -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Group -import androidx.compose.ui.graphics.vector.ImageVector import band.effective.office.elevator.MainRes -import dev.icerock.moko.resources.StringResource object EmployeesTab: Tab { - override val title: StringResource - get() = MainRes.strings.employees - override val icon: ImageVector - get() = Icons.Default.Group -} \ No newline at end of file + override val title = MainRes.strings.employees + override val icon = MainRes.images.ic_employee +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/MainTab.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/MainTab.kt index e976129227b38e9d2a37d9c008b700d16b022ecc..930807d1178597ca843991c3a11f65545deb8704 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/MainTab.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/MainTab.kt @@ -6,5 +6,5 @@ import band.effective.office.elevator.MainRes object MainTab : Tab { override val title = MainRes.strings.main - override val icon = Icons.Default.Home + override val icon = MainRes.images.ic_profile // delete later } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/ProfileTab.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/ProfileTab.kt index 48c4f9d32f47a0b73ebb4685af5dec000d8463e7..1431fa87e67b9a56678d0308a5f10d7380dcf295 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/ProfileTab.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/ProfileTab.kt @@ -1,11 +1,8 @@ package band.effective.office.elevator.navigation -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Person import band.effective.office.elevator.MainRes -import dev.icerock.moko.resources.desc.StringDesc object ProfileTab : Tab { override val title = MainRes.strings.profile - override val icon = Icons.Default.Person + override val icon = MainRes.images.ic_profile } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/Tab.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/Tab.kt index 2a9a708a90f53eb1a33c4dabdcddf8610cf98b62..13b477e7dc888eedcc997424071aa5c5e87937ec 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/Tab.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/navigation/Tab.kt @@ -1,9 +1,9 @@ package band.effective.office.elevator.navigation -import androidx.compose.ui.graphics.vector.ImageVector +import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource interface Tab { val title: StringResource - val icon: ImageVector + val icon: ImageResource } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/Authorization.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/Authorization.kt index 0d6f328fa274197db310238ab6e97e36980bbdb3..7d04f013457af48c1bcd0cab9ab00d5ff314f8ae 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/Authorization.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/Authorization.kt @@ -2,10 +2,12 @@ package band.effective.office.elevator.ui.authorization import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import band.effective.office.elevator.ui.authorization.authorization_finish.AuthorizationFinishScreen import band.effective.office.elevator.ui.authorization.authorization_google.AuthorizationGoogleScreen import band.effective.office.elevator.ui.authorization.authorization_phone.AuthorizationPhoneScreen import band.effective.office.elevator.ui.authorization.authorization_profile.AuthorizationProfileScreen import band.effective.office.elevator.ui.authorization.authorization_telegram.AuthorizationTelegramScreen +import band.effective.office.elevator.ui.authorization.no_booking.NoBookingContent import band.effective.office.elevator.ui.authorization.store.AuthorizationStore import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade @@ -33,6 +35,8 @@ fun AuthorizationScreen(component: AuthorizationComponent) { is AuthorizationComponent.Child.PhoneAuthChild -> AuthorizationPhoneScreen(child.component) is AuthorizationComponent.Child.ProfileAuthChild -> AuthorizationProfileScreen(child.component) is AuthorizationComponent.Child.TelegramAuthChild -> AuthorizationTelegramScreen(child.component) + is AuthorizationComponent.Child.FinishAuthChild -> AuthorizationFinishScreen(child.component) + is AuthorizationComponent.Child.NoBookingChild -> NoBookingContent(child.component) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/AuthorizationComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/AuthorizationComponent.kt index c679def4a3780b87ced754fddae57bdb824ca6d3..3cd8b9cc07a989111d1b8ec325d5857483802114 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/AuthorizationComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/AuthorizationComponent.kt @@ -2,13 +2,15 @@ package band.effective.office.elevator.ui.authorization import band.effective.office.elevator.domain.models.User import band.effective.office.elevator.domain.useCase.UpdateUserInfoUseCase +import band.effective.office.elevator.domain.validator.UserInfoValidator +import band.effective.office.elevator.ui.authorization.authorization_finish.AuthorizationFinishComponent import band.effective.office.elevator.ui.authorization.authorization_google.AuthorizationGoogleComponent import band.effective.office.elevator.ui.authorization.authorization_phone.AuthorizationPhoneComponent import band.effective.office.elevator.ui.authorization.authorization_profile.AuthorizationProfileComponent import band.effective.office.elevator.ui.authorization.authorization_telegram.AuthorizationTelegramComponent +import band.effective.office.elevator.ui.authorization.no_booking.NoBookingComponent import band.effective.office.elevator.ui.authorization.store.AuthorizationStore import band.effective.office.elevator.ui.authorization.store.AuthorizationStoreFactory -import band.effective.office.elevator.ui.models.validator.UserInfoValidator import band.effective.office.network.model.Either import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.router.stack.ChildStack @@ -45,8 +47,6 @@ class AuthorizationComponent( private val validator: UserInfoValidator = UserInfoValidator() private val navigation = StackNavigation() private val updateUserInfoUseCase: UpdateUserInfoUseCase by inject() - // TODO (Artem Gruzdev) replace this.This is a temporary crutch. It is necessary to synchronize the state somehow - private var userData: User = User.defaultUser private val authorizationStore = instanceKeeper.getStore { @@ -88,8 +88,8 @@ class AuthorizationComponent( private fun child( config: AuthorizationComponent.Config, componentContext: ComponentContext - ): AuthorizationComponent.Child = - when (config) { + ): AuthorizationComponent.Child { + return when (config) { is Config.GoogleAuth -> Child.GoogleAuthChild( AuthorizationGoogleComponent( componentContext, @@ -103,7 +103,7 @@ class AuthorizationComponent( componentContext, storeFactory, validator, - userData.phoneNumber, + state.value.userData.phoneNumber, ::phoneAuthOutput, ::changePhoneNumber ) @@ -114,8 +114,8 @@ class AuthorizationComponent( componentContext, storeFactory, validator, - userData.userName, - userData.post, + state.value.userData.userName, + state.value.userData.post, ::profileAuthOutput, ::changeName, ::changePost @@ -127,18 +127,33 @@ class AuthorizationComponent( componentContext, storeFactory, validator, - userData.telegram, + state.value.userData.telegram, ::telegramAuthOutput, ::changeTelegramNick ) ) + + is Config.FinishAuth -> Child.FinishAuthChild( + AuthorizationFinishComponent( + componentContext, + storeFactory, + state.value.userData.userName, + state.value.userData.post, + state.value.userData.imageUrl, + ::finishAuthOutput + ) + ) + + is Config.NoBooking -> Child.NoBookingChild( + NoBookingComponent(componentContext, ::noBookingOutput) + ) } + } private fun googleAuthOutput(output: AuthorizationGoogleComponent.Output) { when (output) { is AuthorizationGoogleComponent.Output.OpenAuthorizationPhoneScreen -> { authorizationStore.accept(AuthorizationStore.Intent.UpdateUserInfo(output.userData)) - userData = output.userData navigation.replaceAll( Config.PhoneAuth ) @@ -177,9 +192,9 @@ class AuthorizationComponent( ) // TODO (Artem Gruzdev) @Slivka you should replace this logic to storeFactory - is AuthorizationTelegramComponent.Output.OpenContentFlow -> { + is AuthorizationTelegramComponent.Output.OpenFinishScreen -> { CoroutineScope(Dispatchers.IO).launch { - Napier.d{ + Napier.d { "telegram: ${authorizationStore.state.userData.telegram}" } val response = updateUserInfoUseCase.execute(authorizationStore.state.userData) @@ -187,12 +202,12 @@ class AuthorizationComponent( withContext(Dispatchers.Main) { when (result) { is Either.Success -> { - openContentFlow() + navigation.bringToFront(Config.FinishAuth) } is Either.Error -> { println("error show content: ${result.error.error} ") - openContentFlow() + navigation.bringToFront(Config.FinishAuth) //TODO show error } } @@ -203,11 +218,26 @@ class AuthorizationComponent( } } + private fun finishAuthOutput(output: AuthorizationFinishComponent.Output) { + when (output) { + AuthorizationFinishComponent.Output.OpenNoBookingScreen -> + navigation.bringToFront(Config.NoBooking) + } + } + + private fun noBookingOutput(output: NoBookingComponent.Output) { + when (output) { + NoBookingComponent.Output.OpenContentScreen -> openContentFlow() + } + } + sealed class Child { class GoogleAuthChild(val component: AuthorizationGoogleComponent) : Child() class PhoneAuthChild(val component: AuthorizationPhoneComponent) : Child() class ProfileAuthChild(val component: AuthorizationProfileComponent) : Child() class TelegramAuthChild(val component: AuthorizationTelegramComponent) : Child() + class FinishAuthChild(val component: AuthorizationFinishComponent) : Child() + class NoBookingChild(val component: NoBookingComponent): Child() } sealed class Config : Parcelable { @@ -222,5 +252,11 @@ class AuthorizationComponent( @Parcelize object TelegramAuth : Config() + + @Parcelize + object FinishAuth : Config() + + @Parcelize + object NoBooking: Config() } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/AuthorizationFinishComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/AuthorizationFinishComponent.kt new file mode 100644 index 0000000000000000000000000000000000000000..5e719a5824cdc1c9096d85c97d9be92fee13d612 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/AuthorizationFinishComponent.kt @@ -0,0 +1,41 @@ +package band.effective.office.elevator.ui.authorization.authorization_finish + +import band.effective.office.elevator.ui.authorization.authorization_finish.store.AuthorizationFinishStore +import band.effective.office.elevator.ui.authorization.authorization_finish.store.AuthorizationFinishStoreFactory +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.mvikotlin.core.instancekeeper.getStore +import com.arkivanov.mvikotlin.core.store.StoreFactory +import com.arkivanov.mvikotlin.extensions.coroutines.stateFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.StateFlow + +class AuthorizationFinishComponent( + componentContext: ComponentContext, + private val storeFactory: StoreFactory, + private val name: String, + private val post: String, + private val avatarUrl: String, + private val output: (Output) -> Unit, +) : ComponentContext by componentContext { + + private val authorizationProfileStore = + instanceKeeper.getStore { + AuthorizationFinishStoreFactory( + storeFactory = storeFactory, + name = name, + post = post, + avatarUrl = avatarUrl, + ).create() + } + + @OptIn(ExperimentalCoroutinesApi::class) + val user: StateFlow = authorizationProfileStore.stateFlow + + fun onOutput(output: Output) { + output(output) + } + + sealed interface Output { + data object OpenNoBookingScreen: Output + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/AuthorizationFinishScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/AuthorizationFinishScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..e0cee4b0e6c3442e64a0621b6bed5514a64568f3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/AuthorizationFinishScreen.kt @@ -0,0 +1,156 @@ +package band.effective.office.elevator.ui.authorization.authorization_finish + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.MainRes +import band.effective.office.elevator.components.EffectiveGradient +import band.effective.office.elevator.components.PrimaryButton +import band.effective.office.elevator.components.generateImageLoader +import com.seiko.imageloader.model.ImageRequest +import com.seiko.imageloader.rememberImagePainter +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +fun AuthorizationFinishScreen(component: AuthorizationFinishComponent) { + val state by component.user.collectAsState() + + AuthorizationFinishContent( + modifier = Modifier + .fillMaxSize(), + name = state.name, + post = state.post, + avatarUrl = state.avatarUrl, + onButtonClick = { component.onOutput(AuthorizationFinishComponent.Output.OpenNoBookingScreen) } + ) +} + +@Composable +private fun AuthorizationFinishContent( + modifier: Modifier = Modifier, + name: String, + post: String, + avatarUrl: String?, + onButtonClick: () -> Unit, +) { + val imageLoader = remember { generateImageLoader() } + val painter = avatarUrl?.let { url -> + rememberImagePainter( + request = remember(url) { + ImageRequest { + data(url) + } + }, + imageLoader = imageLoader, + placeholderPainter = { painterResource(MainRes.images.logo_default) }, + errorPainter = { painterResource(MainRes.images.logo_default) } + ) + } + EffectiveGradient() + Box( + modifier = modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center, + ) { + EffectiveGradient() + Column( + modifier = Modifier + .fillMaxSize() + .align(Alignment.TopCenter), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.height(32.dp)) + Text( + text = stringResource(MainRes.strings.finish_registration_title), + color = EffectiveTheme.colors.text.accent, + style = EffectiveTheme.typography.mMedium, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(MainRes.strings.finish_registration_description), + color = EffectiveTheme.colors.text.secondary, + style = EffectiveTheme.typography.sMedium, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.weight(0.5f)) + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = EffectiveTheme.colors.background.tertiary, + shape = RoundedCornerShape(32.dp) + ) + .border( + width = 1.dp, + color = EffectiveTheme.colors.stroke.primary, + shape = RoundedCornerShape(32.dp), + ) + .padding(40.dp) + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Image( + painter = painter ?: painterResource(MainRes.images.logo_default), + contentDescription = null, + modifier = Modifier + .size(100.dp) + .border( + BorderStroke( + width = 2.dp, + color = EffectiveTheme.colors.stroke.accent, + ), + shape = CircleShape, + ) + .padding(2.dp) + .clip(CircleShape), + ) + Spacer(modifier = Modifier.padding(16.dp)) + Text( + text = name, + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.mMedium, + ) + Text( + text = post, + color = EffectiveTheme.colors.text.secondary, + style = EffectiveTheme.typography.sMedium, + ) + } + } + Spacer(modifier = Modifier.weight(1f)) + PrimaryButton( + onClick = onButtonClick, + buttonText = stringResource(MainRes.strings.go_book), + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/store/AuthorizationFinishStore.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/store/AuthorizationFinishStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..f1e31ca560de798df29a733452a92bcd209e134b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/store/AuthorizationFinishStore.kt @@ -0,0 +1,13 @@ +package band.effective.office.elevator.ui.authorization.authorization_finish.store + +import com.arkivanov.mvikotlin.core.store.Store + +interface AuthorizationFinishStore : + Store { + + data class State( + val name: String, + val post: String, + val avatarUrl: String, + ) +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/store/AuthorizationFinishStoreFactory.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/store/AuthorizationFinishStoreFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..87183d83670789ff02f883481adb115c4e8e0f28 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_finish/store/AuthorizationFinishStoreFactory.kt @@ -0,0 +1,42 @@ +package band.effective.office.elevator.ui.authorization.authorization_finish.store + +import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper +import com.arkivanov.mvikotlin.core.store.Store +import com.arkivanov.mvikotlin.core.store.StoreFactory +import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor + +class AuthorizationFinishStoreFactory( + private val storeFactory: StoreFactory, + private val name: String, + private val post: String, + private val avatarUrl: String, +) { + + fun create(): AuthorizationFinishStore = + object : AuthorizationFinishStore, + Store by storeFactory.create( + name = "Authorization finish", + initialState = AuthorizationFinishStore.State(name, post, avatarUrl), + bootstrapper = SimpleBootstrapper(Action.InitUser), + executorFactory = { ExecutorImpl() }, + ) { } + + private sealed interface Action { + data object InitUser : Action + } + + private inner class ExecutorImpl : + CoroutineExecutor() { + + override fun executeIntent( + intent: Nothing, + getState: () -> AuthorizationFinishStore.State, + ) = Unit + + + override fun executeAction( + action: Action, + getState: () -> AuthorizationFinishStore.State + ) = Unit + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleComponent.kt index 92d8d14c2ee7ee725c14bc987617a1061f5e3fc7..4a2775793876bd076790eafbb7c507792fa6f930 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleComponent.kt @@ -8,9 +8,15 @@ import com.arkivanov.decompose.ComponentContext import com.arkivanov.mvikotlin.core.instancekeeper.getStore import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.extensions.coroutines.labels +import com.arkivanov.mvikotlin.extensions.coroutines.stateFlow +import com.arkivanov.mvikotlin.extensions.coroutines.states +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn import org.koin.core.component.KoinComponent +@OptIn(ExperimentalCoroutinesApi::class) class AuthorizationGoogleComponent( componentContext: ComponentContext, storeFactory: StoreFactory, @@ -24,6 +30,8 @@ class AuthorizationGoogleComponent( ).create() } + val state: StateFlow = authorizationStore.stateFlow + val label: Flow = authorizationStore.labels diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleScreen.kt index d6fe9e71554e3019ace910fe3b6af8eba9f1abbe..3dff258f947046bbd7af01a47f1259fe5dbc0283 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/AuthorizationGoogleScreen.kt @@ -1,33 +1,34 @@ package band.effective.office.elevator.ui.authorization.authorization_google import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.material.MaterialTheme +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes -import band.effective.office.elevator.companyColor +import band.effective.office.elevator.components.EffectiveGradient import band.effective.office.elevator.components.GoogleSignInButton import band.effective.office.elevator.expects.showToast import band.effective.office.elevator.ui.authorization.authorization_google.store.AuthorizationGoogleStore import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource - @Composable fun AuthorizationGoogleScreen(component: AuthorizationGoogleComponent) { @@ -43,41 +44,69 @@ fun AuthorizationGoogleScreen(component: AuthorizationGoogleComponent) { } } } + val state by component.state.collectAsState() - AuthorizationGoogleScreenContent(onEvent = component::onEvent) + AuthorizationGoogleScreenContent( + isAuthInProgress = state.isAuthInProgress, + onEvent = component::onEvent, + ) } @Composable -private fun AuthorizationGoogleScreenContent(onEvent: (AuthorizationGoogleStore.Intent) -> Unit) { +private fun AuthorizationGoogleScreenContent( + isAuthInProgress: Boolean, + onEvent: (AuthorizationGoogleStore.Intent) -> Unit, +) { + EffectiveGradient() Box(modifier = Modifier.fillMaxSize().padding(16.dp)) { Column( + modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceAround, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() ) { - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - Image( - painterResource(MainRes.images.effective_logo), - contentDescription = "Effective logo", - modifier = Modifier.size(80.dp) + Box( + modifier = Modifier + .background( + color = EffectiveTheme.colors.background.primary.copy(alpha = 0.6f), + shape = RoundedCornerShape(20.dp) + ) + .border( + width = 1.dp, + shape = RoundedCornerShape(20.dp), + color = EffectiveTheme.colors.stroke.primary + ) + .padding(20.dp) + ) { + Image( + painter = painterResource(MainRes.images.ic_logo), + contentDescription = null, + modifier = Modifier.size(90.dp) + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(MainRes.strings.company_title_name), + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.xlMedium ) - Spacer(modifier = Modifier.width(16.dp)) Text( - text = stringResource(MainRes.strings.company_name), - color = companyColor, - style = MaterialTheme.typography.h4 + text = stringResource(MainRes.strings.company_subtitle_name), + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.sMedium ) } GoogleSignInButton( modifier = Modifier, - onClick = { onEvent(AuthorizationGoogleStore.Intent.SignInButtonClicked) }) + isEnabled = !isAuthInProgress, + onClick = { onEvent(AuthorizationGoogleStore.Intent.SignInButtonClicked) } + ) } } } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStore.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStore.kt index 40517cf5ef109baa3f2f0431472ad825309c382d..6e204a2923af76ac72a90dd4935df50c12939d70 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStore.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStore.kt @@ -11,11 +11,11 @@ interface AuthorizationGoogleStore : object SignInButtonClicked : Intent } - class State + data class State(val isAuthInProgress: Boolean) sealed interface Label { data class AuthorizationSuccess(val user: User) : Label data class AuthorizationFailure(val message: String) : Label } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStoreImpl.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStoreImpl.kt index 99a575bb50b9eb6d1ebd1d086e06110c40603b90..7759b1817e01441c8c92a252513b2bcbcc7a65e3 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStoreImpl.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_google/store/AuthorizationGoogleStoreImpl.kt @@ -1,23 +1,14 @@ package band.effective.office.elevator.ui.authorization.authorization_google.store -import band.effective.office.elevator.data.ApiResponse -import band.effective.office.elevator.domain.GoogleSignIn -import band.effective.office.elevator.domain.SignInResultCallback import band.effective.office.elevator.domain.useCase.AuthorizationUseCase import band.effective.office.elevator.ui.authorization.authorization_google.store.AuthorizationGoogleStore.Intent import band.effective.office.elevator.ui.authorization.authorization_google.store.AuthorizationGoogleStore.Label import band.effective.office.elevator.ui.authorization.authorization_google.store.AuthorizationGoogleStore.State -import band.effective.office.elevator.ui.authorization.authorization_phone.store.AuthorizationPhoneStoreFactory -import band.effective.office.network.model.Either +import com.arkivanov.mvikotlin.core.store.Reducer import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.StoreFactory -import com.arkivanov.mvikotlin.core.utils.ExperimentalMviKotlinApi import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor -import com.arkivanov.mvikotlin.extensions.coroutines.coroutineBootstrapper -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -25,31 +16,51 @@ internal class AuthorizationGoogleStoreFactory( private val storeFactory: StoreFactory ) : KoinComponent { - private val signInClient: GoogleSignIn by inject() private val authorizationUseCase: AuthorizationUseCase by inject() - @OptIn(ExperimentalMviKotlinApi::class) fun create(): AuthorizationGoogleStore = object : AuthorizationGoogleStore, Store by storeFactory.create( name = "AuthorizationStore", - initialState = State(), + initialState = State(isAuthInProgress = false), executorFactory = ::ExecutorImpl, + reducer = ReducerImpl, ) {} + private sealed interface Msg { + data class UpdateInProgress(val isInProgress: Boolean) : Msg + } + + private object ReducerImpl : Reducer { + + override fun State.reduce(msg: Msg): State = + when (msg) { + is Msg.UpdateInProgress -> copy(isAuthInProgress = msg.isInProgress) + } + } + private inner class ExecutorImpl : - CoroutineExecutor() { + CoroutineExecutor() { override fun executeIntent(intent: Intent, getState: () -> State) { when (intent) { - Intent.SignInButtonClicked -> startAuthorization() + Intent.SignInButtonClicked -> if (!getState().isAuthInProgress) { + startAuthorization() + } } } private fun startAuthorization() { + dispatch(Msg.UpdateInProgress(true)) scope.launch { authorizationUseCase.authorize( scope = scope, - successCallBack = { publish(Label.AuthorizationSuccess(it)) }, - failureCallBack = { publish(Label.AuthorizationFailure(it)) } + successCallBack = { + publish(Label.AuthorizationSuccess(it)) + dispatch(Msg.UpdateInProgress(false)) + }, + failureCallBack = { + publish(Label.AuthorizationFailure(it)) + dispatch(Msg.UpdateInProgress(false)) + } ) } } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneComponent.kt index 71631804e20eb754e9efda862605d79414ab52e4..4f811f46adb3b6db27b2cd3bf596c532224cece8 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneComponent.kt @@ -2,7 +2,7 @@ package band.effective.office.elevator.ui.authorization.authorization_phone import band.effective.office.elevator.ui.authorization.authorization_phone.store.AuthorizationPhoneStore import band.effective.office.elevator.ui.authorization.authorization_phone.store.AuthorizationPhoneStoreFactory -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import band.effective.office.elevator.domain.validator.UserInfoValidator import com.arkivanov.decompose.ComponentContext import com.arkivanov.mvikotlin.core.instancekeeper.getStore import com.arkivanov.mvikotlin.core.store.StoreFactory diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneScreen.kt index 8ba987c2f3a8f3c5b643a1ef15bce11d8ba704f0..b907673b44e71594e935c9925d841b09c4015faa 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/AuthorizationPhoneScreen.kt @@ -2,27 +2,15 @@ package band.effective.office.elevator.ui.authorization.authorization_phone import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material3.Divider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -32,26 +20,22 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes import band.effective.office.elevator.components.EffectiveButton -import band.effective.office.elevator.components.OutlinedTextColorsSetup import band.effective.office.elevator.components.UserInfoTextField +import band.effective.office.elevator.domain.validator.UserInfoValidator import band.effective.office.elevator.expects.showToast import band.effective.office.elevator.textGrayColor import band.effective.office.elevator.ui.authorization.authorization_phone.store.AuthorizationPhoneStore import band.effective.office.elevator.ui.authorization.components.AuthSubTitle -import band.effective.office.elevator.ui.authorization.components.AuthTabRow import band.effective.office.elevator.ui.authorization.components.AuthTitle import band.effective.office.elevator.ui.models.PhoneMaskTransformation import band.effective.office.elevator.ui.models.UserDataTextFieldType -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable @@ -95,100 +79,86 @@ private fun AuthorizationPhoneComponent( val leadingColor = remember { mutableStateOf(textGrayColor) } val phoneState = if (state.phoneNumber.length > UserInfoValidator.phoneNumberSize) - state.phoneNumber.substring( - startIndex = state.phoneNumber.length % UserInfoValidator.phoneNumberSize, - ) - else state.phoneNumber + state.phoneNumber.substring( + startIndex = state.phoneNumber.length % UserInfoValidator.phoneNumberSize, + ) + else state.phoneNumber var phoneNumber by remember { mutableStateOf(phoneState) } - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Top, + Box( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding( - horizontal = 16.dp, - vertical = 48.dp - ), + .fillMaxSize() + .background(EffectiveTheme.colors.background.primary), ) { IconButton( modifier = Modifier.size(size = 48.dp), onClick = { onEvent(AuthorizationPhoneStore.Intent.BackButtonClicked) - }) { + } + ) { Icon( - imageVector = Icons.Rounded.ArrowBack, - tint = ExtendedThemeColors.colors.blackColor, - contentDescription = "back screen arrow" + painter = painterResource(MainRes.images.back_button), + contentDescription = stringResource(MainRes.strings.back), + modifier = Modifier.size(size = 24.dp), + tint = EffectiveTheme.colors.icon.secondary ) } - - Spacer(modifier = Modifier.height(8.dp)) - - AuthTabRow(0) - Column( - verticalArrangement = Arrangement.Center, modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() + .fillMaxSize(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally ) { AuthTitle( + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp), text = stringResource(MainRes.strings.input_number), - modifier = Modifier.padding(bottom = 7.dp), - textAlign = TextAlign.Start + textAlign = TextAlign.Center ) - AuthSubTitle( - text = stringResource(MainRes.strings.select_number), modifier = Modifier.padding(bottom = 24.dp), - textAlign = TextAlign.Start + text = stringResource(MainRes.strings.select_number), + textAlign = TextAlign.Center, ) UserInfoTextField( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), item = UserDataTextFieldType.Phone, error = state.isErrorPhoneNumber, visualTransformation = PhoneMaskTransformation, text = phoneNumber, keyboardType = KeyboardType.Phone, onValueChange = { - if (it.isNotEmpty()) { - closeIcon.value = true - leadingColor.value = Color.Black - borderColor.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor.value = textGrayColor - closeIcon.value = false - leadingColor.value = textGrayColor - } phoneNumber = it onEvent( AuthorizationPhoneStore.Intent.PhoneNumberChanged(phoneNumber = it) ) - }, + } + ) + + Column( modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .onFocusChanged { - if (it.isFocused) { - borderColor.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor.value = textGrayColor - leadingColor.value = textGrayColor - } + .fillMaxSize() + .padding(bottom = 56.dp), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + AuthSubTitle( + modifier = Modifier.padding(bottom = 20.dp), + text = stringResource(MainRes.strings.button_title), + textAlign = TextAlign.Center + ) + EffectiveButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + buttonText = stringResource(MainRes.strings._continue), + onClick = { + onEvent(AuthorizationPhoneStore.Intent.ContinueButtonClicked) } ) - - Spacer(modifier = Modifier.height(16.dp)) - - EffectiveButton( - buttonText = stringResource(MainRes.strings._continue), - onClick = { - onEvent(AuthorizationPhoneStore.Intent.ContinueButtonClicked) - }, - modifier = Modifier.fillMaxWidth() - ) + } } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/store/AuthorizationPhoneStoreImpl.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/store/AuthorizationPhoneStoreImpl.kt index 4d6dc74d539cea8f8ee5596187cafc5ae3b2a257..cc9782b118af0eaa69ff910ba51beee603df7305 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/store/AuthorizationPhoneStoreImpl.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_phone/store/AuthorizationPhoneStoreImpl.kt @@ -1,7 +1,7 @@ package band.effective.office.elevator.ui.authorization.authorization_phone.store import band.effective.office.elevator.ui.authorization.authorization_phone.store.AuthorizationPhoneStore.* -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import band.effective.office.elevator.domain.validator.UserInfoValidator import com.arkivanov.mvikotlin.core.store.Bootstrapper import com.arkivanov.mvikotlin.core.store.Reducer import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper @@ -114,4 +114,4 @@ internal class AuthorizationPhoneStoreFactory( } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileComponent.kt index 69e06a285efde6c79aeaebe6868283944921455a..667e5412c9bc6b4b72c76934efa4da8e2fc90f48 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileComponent.kt @@ -1,8 +1,8 @@ package band.effective.office.elevator.ui.authorization.authorization_profile import band.effective.office.elevator.ui.authorization.authorization_profile.store.AuthorizationProfileStore +import band.effective.office.elevator.domain.validator.UserInfoValidator import band.effective.office.elevator.ui.authorization.authorization_profile.store.AuthorizationProfileStoreFactory -import band.effective.office.elevator.ui.models.validator.UserInfoValidator import com.arkivanov.decompose.ComponentContext import com.arkivanov.mvikotlin.core.instancekeeper.getStore import com.arkivanov.mvikotlin.core.store.StoreFactory @@ -54,4 +54,4 @@ class AuthorizationProfileComponent( object OpenPhoneScreen : Output() } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileScreen.kt index 23968ee365b8fdff44cf43418f33700b444c50d2..c20d5869c8af8c7b2ee41113e3e330449e91e170 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/AuthorizationProfileScreen.kt @@ -2,28 +2,17 @@ package band.effective.office.elevator.ui.authorization.authorization_profile import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material.icons.rounded.Person -import androidx.compose.material.icons.rounded.Work -import androidx.compose.material3.Divider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -33,23 +22,20 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes import band.effective.office.elevator.components.EffectiveButton -import band.effective.office.elevator.components.OutlinedTextColorsSetup import band.effective.office.elevator.components.UserInfoTextField import band.effective.office.elevator.expects.showToast import band.effective.office.elevator.textGrayColor import band.effective.office.elevator.ui.authorization.authorization_profile.store.AuthorizationProfileStore -import band.effective.office.elevator.ui.authorization.components.AuthTabRow +import band.effective.office.elevator.ui.authorization.components.AuthSubTitle import band.effective.office.elevator.ui.authorization.components.AuthTitle import band.effective.office.elevator.ui.models.UserDataTextFieldType +import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable @@ -102,122 +88,94 @@ fun AuthorizationProfileComponent( var personName by remember { mutableStateOf(state.name) } var personPost by remember { mutableStateOf(state.post) } - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Top, + Box( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding( - horizontal = 16.dp, - vertical = 48.dp - ), + .fillMaxSize() + .background(EffectiveTheme.colors.background.primary), ) { IconButton( modifier = Modifier.size(size = 48.dp), onClick = { onEvent(AuthorizationProfileStore.Intent.BackButtonClicked) - }) { + } + ) { Icon( - imageVector = Icons.Rounded.ArrowBack, - tint = Color.Black, - contentDescription = "image_back" + painter = painterResource(MainRes.images.back_button), + contentDescription = stringResource(MainRes.strings.back), + modifier = Modifier.size(size = 24.dp), + tint = EffectiveTheme.colors.icon.secondary ) } - - Spacer(modifier = Modifier.height(8.dp)) - - AuthTabRow(1) - Column( - verticalArrangement = Arrangement.Center, modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() + .fillMaxSize(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally ) { - AuthTitle( + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp), text = stringResource(MainRes.strings.input_profile), - modifier = Modifier.padding(bottom = 7.dp), - textAlign = TextAlign.Start + textAlign = TextAlign.Center + ) + AuthSubTitle( + modifier = Modifier.padding(bottom = 24.dp), + text = stringResource(MainRes.strings.select_number), + textAlign = TextAlign.Center ) - // Person name UserInfoTextField( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), text = personName, item = UserDataTextFieldType.Person, error = state.isErrorName, keyboardType = KeyboardType.Text, onValueChange = { - if (it.isNotEmpty()) { - closeIcon1.value = true - leadingColor1.value = Color.Black - borderColor1.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor1.value = textGrayColor - closeIcon1.value = false - leadingColor1.value = textGrayColor - } personName = it onEvent(AuthorizationProfileStore.Intent.NameChanged(name = it)) - }, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .onFocusChanged { - if (it.isFocused) { - borderColor1.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor1.value = textGrayColor - leadingColor1.value = textGrayColor - } - } + } ) Spacer(modifier = Modifier.height(16.dp)) // POST UserInfoTextField( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), text = personPost, item = UserDataTextFieldType.Post, error = state.isErrorPost, keyboardType = KeyboardType.Text, onValueChange = { - if (it.isNotEmpty()) { - closeIcon2.value = true - leadingColor2.value = ExtendedThemeColors.colors.blackColor - borderColor2.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor2.value = textGrayColor - closeIcon2.value = false - leadingColor2.value = textGrayColor - personPost = it - } - + personPost = it onEvent(AuthorizationProfileStore.Intent.PostChanged(post = it)) - }, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .onFocusChanged { - if (it.isFocused) { - borderColor2.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor2.value = textGrayColor - leadingColor2.value = textGrayColor - } - } + } ) - Spacer(modifier = Modifier.height(16.dp)) - - EffectiveButton( - buttonText = stringResource(MainRes.strings._continue), - onClick = { - onEvent(AuthorizationProfileStore.Intent.ContinueButtonClicked) - }, - modifier = Modifier.fillMaxWidth() - ) + Column( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 56.dp), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + AuthSubTitle( + modifier = Modifier.padding(bottom = 20.dp), + text = stringResource(MainRes.strings.button_title), + textAlign = TextAlign.Center + ) + EffectiveButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + buttonText = stringResource(MainRes.strings._continue), + onClick = { + onEvent(AuthorizationProfileStore.Intent.ContinueButtonClicked) + } + ) + } } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/store/AuthorizationProfileStoreImpl.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/store/AuthorizationProfileStoreImpl.kt index 044bad5c8ed032a00f5e2358ba94ba0d1a5ea7bd..959d394df9b624f74e6468f9cc46cdcd1a64d184 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/store/AuthorizationProfileStoreImpl.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_profile/store/AuthorizationProfileStoreImpl.kt @@ -1,14 +1,12 @@ package band.effective.office.elevator.ui.authorization.authorization_profile.store -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import band.effective.office.elevator.domain.validator.UserInfoValidator import com.arkivanov.mvikotlin.core.store.Reducer import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.utils.ExperimentalMviKotlinApi import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor -import com.arkivanov.mvikotlin.extensions.coroutines.coroutineBootstrapper -import kotlinx.coroutines.launch class AuthorizationProfileStoreFactory( private val storeFactory: StoreFactory, @@ -141,4 +139,4 @@ class AuthorizationProfileStoreFactory( publish(AuthorizationProfileStore.Label.ReturnInPhoneAuthorization) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramComponent.kt index e5792346752c1814947318a8edcf5aba4fd8ff64..90c46e74d97cfe3ad326041a35f3bb51a51c8970 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramComponent.kt @@ -2,7 +2,7 @@ package band.effective.office.elevator.ui.authorization.authorization_telegram import band.effective.office.elevator.ui.authorization.authorization_telegram.store.AuthorizationTelegramStore import band.effective.office.elevator.ui.authorization.authorization_telegram.store.AuthorizationTelegramStoreFactory -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import band.effective.office.elevator.domain.validator.UserInfoValidator import com.arkivanov.decompose.ComponentContext import com.arkivanov.mvikotlin.core.instancekeeper.getStore import com.arkivanov.mvikotlin.core.store.StoreFactory @@ -46,8 +46,8 @@ class AuthorizationTelegramComponent( fun changeTG(telegramNick: String) = changeTelegramNick(telegramNick) sealed class Output { - object OpenContentFlow : Output() + object OpenFinishScreen : Output() object OpenProfileScreen : Output() } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramScreen.kt index d679487d03bd9c86a1f7bbb2311bd68342005908..2313ffcb791f10b12652a165d47a342f0c110e8f 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/AuthorizationTelegramScreen.kt @@ -2,27 +2,17 @@ package band.effective.office.elevator.ui.authorization.authorization_telegram import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material3.Divider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -32,24 +22,19 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes import band.effective.office.elevator.components.EffectiveButton -import band.effective.office.elevator.components.OutlinedTextColorsSetup import band.effective.office.elevator.components.UserInfoTextField import band.effective.office.elevator.expects.showToast -import band.effective.office.elevator.textGrayColor -import band.effective.office.elevator.ui.authorization.authorization_profile.store.AuthorizationProfileStore import band.effective.office.elevator.ui.authorization.authorization_telegram.store.AuthorizationTelegramStore -import band.effective.office.elevator.ui.authorization.components.AuthTabRow +import band.effective.office.elevator.ui.authorization.components.AuthSubTitle import band.effective.office.elevator.ui.authorization.components.AuthTitle import band.effective.office.elevator.ui.models.UserDataTextFieldType +import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -69,7 +54,7 @@ fun AuthorizationTelegramScreen(component: AuthorizationTelegramComponent) { is AuthorizationTelegramStore.Label.AuthorizationTelegramSuccess -> { component.changeTG(state.nick) component.onOutput( - AuthorizationTelegramComponent.Output.OpenContentFlow + AuthorizationTelegramComponent.Output.OpenFinishScreen ) } @@ -88,21 +73,16 @@ private fun AuthorizationTelegramComponent( onEvent: (AuthorizationTelegramStore.Intent) -> Unit, state: AuthorizationTelegramStore.State ) { + val colors = EffectiveTheme.colors val closeIcon = remember { mutableStateOf(false) } - val borderColor = remember { mutableStateOf(textGrayColor) } - val leadingColor = remember { mutableStateOf(textGrayColor) } + val borderColor = remember { mutableStateOf(colors.input.freeNormal) } + val leadingColor = remember { mutableStateOf(colors.text.additional) } var telegram by remember { mutableStateOf(state.nick) } - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Top, + Box( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding( - horizontal = 16.dp, - vertical = 48.dp - ), + .fillMaxSize() + .background(EffectiveTheme.colors.background.primary) ) { IconButton( modifier = Modifier.size(size = 48.dp), @@ -110,68 +90,66 @@ private fun AuthorizationTelegramComponent( onEvent(AuthorizationTelegramStore.Intent.BackButtonClicked) }) { Icon( - imageVector = Icons.Rounded.ArrowBack, - tint = ExtendedThemeColors.colors.blackColor, - contentDescription = "back screen arrow" + painter = painterResource(MainRes.images.back_button), + contentDescription = stringResource(MainRes.strings.back), + modifier = Modifier.size(size = 24.dp), + tint = EffectiveTheme.colors.icon.secondary ) } - - Spacer(modifier = Modifier.height(8.dp)) - - AuthTabRow(2) - Column( - verticalArrangement = Arrangement.Center, modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() + .fillMaxSize(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally ) { AuthTitle( + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp), text = stringResource(MainRes.strings.input_employee), - modifier = Modifier.padding(bottom = 7.dp), - textAlign = TextAlign.Start + textAlign = TextAlign.Center + ) + AuthSubTitle( + modifier = Modifier.padding(bottom = 24.dp), + text = stringResource(MainRes.strings.select_number), + textAlign = TextAlign.Center ) - UserInfoTextField( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), text = telegram, item = UserDataTextFieldType.Telegram, error = state.isErrorNick, keyboardType = KeyboardType.Text, onValueChange = { - if (it.isNotEmpty()) { - closeIcon.value = true - leadingColor.value = Color.Black - borderColor.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor.value = textGrayColor - closeIcon.value = false - leadingColor.value = textGrayColor - } telegram = it onEvent(AuthorizationTelegramStore.Intent.NickChanged(name = it)) }, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .onFocusChanged { - if (it.isFocused) { - borderColor.value = ExtendedThemeColors.colors.trinidad_400 - } else { - borderColor.value = textGrayColor - leadingColor.value = textGrayColor - } - } ) Spacer(modifier = Modifier.height(16.dp)) - EffectiveButton( - buttonText = stringResource(MainRes.strings._continue), - onClick = { - onEvent(AuthorizationTelegramStore.Intent.ContinueButtonClicked) - }, - modifier = Modifier.fillMaxWidth() - ) + Column( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 56.dp), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + AuthSubTitle( + modifier = Modifier.padding(bottom = 20.dp), + text = stringResource(MainRes.strings.button_title), + textAlign = TextAlign.Center + ) + EffectiveButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + buttonText = stringResource(MainRes.strings._continue), + onClick = { + onEvent(AuthorizationTelegramStore.Intent.ContinueButtonClicked) + } + ) + } } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/store/AuthorizationTelegramStoreImpl.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/store/AuthorizationTelegramStoreImpl.kt index 37840d08646e2e289b45db6f29a1c502381e6ea2..3a14b0b495f02b4150c8eeb02bab7e6588682587 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/store/AuthorizationTelegramStoreImpl.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/authorization_telegram/store/AuthorizationTelegramStoreImpl.kt @@ -1,6 +1,6 @@ package band.effective.office.elevator.ui.authorization.authorization_telegram.store -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import band.effective.office.elevator.domain.validator.UserInfoValidator import com.arkivanov.mvikotlin.core.store.Reducer import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper import com.arkivanov.mvikotlin.core.store.Store @@ -104,4 +104,4 @@ class AuthorizationTelegramStoreFactory( } } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthSubTitile.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthSubTitile.kt index 9aa63a8467d8759997a0d8a2120f068dbc177a5f..2251b56707e437d8d0bb7f634f32002b81c7db19 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthSubTitile.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthSubTitile.kt @@ -1,7 +1,6 @@ package band.effective.office.elevator.ui.authorization.components import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -9,8 +8,9 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.getDefaultFont -import band.effective.office.elevator.textGrayColor + @Composable fun AuthSubTitle( text: String, @@ -23,11 +23,11 @@ fun AuthSubTitle( .wrapContentSize() .then(modifier), style = TextStyle( - fontSize = 16.sp, - lineHeight = 20.8.sp, + fontSize = 12.sp, + lineHeight = 16.8.sp, fontFamily = getDefaultFont(), fontWeight = FontWeight(500), - color = textGrayColor, + color = EffectiveTheme.colors.text.secondary, letterSpacing = 0.1.sp, ), textAlign = textAlign diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthTitile.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthTitile.kt index 909bff8003f5386f80fe19b76dc702ce890264af..41853ddcab98afae78005dd286cdb250de88c7a3 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthTitile.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/components/AuthTitile.kt @@ -1,12 +1,11 @@ package band.effective.office.elevator.ui.authorization.components import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme @Composable fun AuthTitle( @@ -19,8 +18,8 @@ fun AuthTitle( modifier = Modifier .wrapContentSize() .then(modifier), - style = MaterialTheme.typography.h5, - color = ExtendedThemeColors.colors.purple_heart_800, + style = EffectiveTheme.typography.lMedium, + color = EffectiveTheme.colors.text.accent, textAlign = textAlign, ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/NoBookingComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/NoBookingComponent.kt new file mode 100644 index 0000000000000000000000000000000000000000..a1408edc2fb1f98a81b5bebc142109e68bd7c6c0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/NoBookingComponent.kt @@ -0,0 +1,17 @@ +package band.effective.office.elevator.ui.authorization.no_booking + +import com.arkivanov.decompose.ComponentContext + +class NoBookingComponent( + componentContext: ComponentContext, + private val output: (Output) -> Unit, +) : ComponentContext by componentContext { + + fun onOutput(output: Output) { + output(output) + } + + sealed interface Output { + data object OpenContentScreen: Output + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/NoBookingScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/NoBookingScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..1d896ebd45f8952c93d382129311de42fea2bbf9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/NoBookingScreen.kt @@ -0,0 +1,102 @@ +package band.effective.office.elevator.ui.authorization.no_booking + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.MainRes +import band.effective.office.elevator.components.EffectiveGradient +import band.effective.office.elevator.components.PrimaryButton +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +fun NoBookingContent(component: NoBookingComponent) { + NoBookingContent( + onButtonClick = { component.onOutput(NoBookingComponent.Output.OpenContentScreen) }, + ) +} + +@Composable +private fun NoBookingContent( + modifier: Modifier = Modifier, + onButtonClick: () -> Unit, +) { + Box( + modifier = modifier + .fillMaxSize() + .background(EffectiveTheme.colors.background.primary), + contentAlignment = Alignment.Center, + ) { + EffectiveGradient() + Column( + modifier = Modifier.padding(16.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = EffectiveTheme.colors.background.tertiary, + shape = RoundedCornerShape(32.dp), + ) + .border( + width = 1.dp, + color = EffectiveTheme.colors.stroke.primary, + shape = RoundedCornerShape(32.dp), + ) + .padding(40.dp) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) + { + Image( + painter = painterResource(MainRes.images.ic_effective_triangle), + contentDescription = null, + modifier = Modifier.size(width = 110.dp, height = 80.dp) + ) + Spacer(modifier = Modifier.height(40.dp)) + Text( + text = stringResource(MainRes.strings.no_occupied_tables_title), + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.lMedium, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(MainRes.strings.no_occupied_tables_description), + color = EffectiveTheme.colors.text.secondary, + style = EffectiveTheme.typography.mMedium, + textAlign = TextAlign.Center, + ) + } + } + } + + + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter), + onClick = onButtonClick, + buttonText = stringResource(MainRes.strings.book_table), + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/store/NoBookingStore.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/store/NoBookingStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..93d84f7c34541287b06e00b563b27d95fa23f74c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/authorization/no_booking/store/NoBookingStore.kt @@ -0,0 +1,5 @@ +package band.effective.office.elevator.ui.authorization.no_booking.store + +import com.arkivanov.mvikotlin.core.store.Store + +interface NoBookingStore: Store diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/BookingComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/BookingComponent.kt index f45cb43b649c61b473a17dfe3530977b389be968..8821912e36e21093f313d8596648469c2f87683e 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/BookingComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/BookingComponent.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.StateFlow class BookingComponent( componentContext: ComponentContext, storeFactory: StoreFactory, - private val output: (Output) -> Unit ) : ComponentContext by componentContext { @@ -44,7 +43,6 @@ class BookingComponent( componentContext = componentContext, initState = state.value.toBookAcceptState(), close = { closeSheet() }, - onMainScreen = { onOutput(Output.OpenMainTab) } ) SheetConfig.BookPeriod -> BookPeriodSheetComponent( @@ -90,14 +88,6 @@ class BookingComponent( bookingStore.accept(event) } - fun onOutput(output: Output) { - output(output) - } - - sealed class Output { - data object OpenMainTab : Output() - } - fun openSheet(config: SheetConfig) { navigation.activate(config) } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookAccept.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookAccept.kt index d3c0428a99f9d56ee1bcee2fe970e0c421ec58c6..e676f6b8c2003ab102fbac9579848d34bbba72e2 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookAccept.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookAccept.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import band.effective.office.elevator.ExtendedThemeColors @@ -35,9 +34,7 @@ import band.effective.office.elevator.ui.booking.models.Frequency import band.effective.office.elevator.utils.stringFromBookPeriod import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime -import kotlinx.datetime.LocalTime @Composable fun BookAccept( @@ -171,4 +168,4 @@ fun noPeriodReserve(bookingInfo: BookingInfo, frequency: Frequency): String{ }else "" } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingPeriod.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingPeriod.kt index da5147784ac5be609a14e67b76bceb8a3bb90d89..7b4f0c798e96df20562ac96fd9cf5470558313a5 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingPeriod.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingPeriod.kt @@ -36,7 +36,6 @@ import band.effective.office.elevator.MainRes import band.effective.office.elevator.components.EffectiveButton import band.effective.office.elevator.components.Elevation import band.effective.office.elevator.textGrayColor -import band.effective.office.elevator.ui.booking.models.Frequency import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.datetime.LocalDate @@ -226,4 +225,4 @@ fun BookingPeriod( ) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingRepeat.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingRepeat.kt index 7df7c62e74916705a17623d3636097a55cd1510d..402f1878a3d8d02bb7c0db79859f63980cedc8eb 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingRepeat.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/BookingRepeat.kt @@ -644,4 +644,4 @@ private fun getDays(dayOfWeek: List): List { ) return dayOfWeek.map { list[it] } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/DeleteBooking.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/DeleteBooking.kt index 9d79339fa932f26af0d9e0479931d0175e5f195e..3465d9ee41e589956df7533c6e30c98e2f1b7c7d 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/DeleteBooking.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/DeleteBooking.kt @@ -94,4 +94,4 @@ fun DeleteBooking(place: String, fullDate: String, onCanselCLick: () -> Unit, on ) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/EditBooking.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/EditBooking.kt index 7c53b1aae2e0a99fa20ed0a88cf53a0f91b37bfd..2d8f6a40d83bca95c2008281ffaaccdd8e494a7b 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/EditBooking.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/booking/components/modals/EditBooking.kt @@ -39,7 +39,6 @@ import band.effective.office.elevator.MainRes import band.effective.office.elevator.components.EffectiveButton import band.effective.office.elevator.components.Elevation import band.effective.office.elevator.domain.models.DayOfWeek -import band.effective.office.elevator.ui.booking.models.Frequency import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -241,4 +240,4 @@ fun EditBooking( ) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/BookAcceptSheetComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/BookAcceptSheetComponent.kt index a4ee821f777cfda686a93344ed9760edd49b6001..a844117d0957314081e98c69404ffe5b57b8ec68 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/BookAcceptSheetComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/BookAcceptSheetComponent.kt @@ -10,13 +10,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import band.effective.office.elevator.domain.models.BookingInfo import band.effective.office.elevator.ui.booking.components.modals.BookAccept import band.effective.office.elevator.ui.booking.components.modals.BookingResult import band.effective.office.elevator.ui.bottomSheets.BottomSheet import band.effective.office.elevator.ui.bottomSheets.bookingSheet.bookAccept.store.BookAcceptStore import band.effective.office.elevator.ui.bottomSheets.bookingSheet.bookAccept.store.BookAcceptStoreFactory -import band.effective.office.elevator.ui.bottomSheets.bookingSheet.bookPeriod.BookPeriodSheetComponent import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children import com.arkivanov.decompose.router.stack.StackNavigation @@ -33,14 +31,12 @@ class BookAcceptSheetComponent( componentContext: ComponentContext, initState: BookAcceptStore.State, close: () -> Unit, - onMainScreen: () -> Unit ) : BottomSheet, ComponentContext by componentContext { private val store = BookAcceptStoreFactory( storeFactory = DefaultStoreFactory(), initState = initState, close = close, - onMainScreen = onMainScreen ).create() private val navigation = StackNavigation() @@ -85,7 +81,6 @@ class BookAcceptSheetComponent( BookingResult( onMain = { store.accept(BookAcceptStore.Intent.OnClose) - store.accept(BookAcceptStore.Intent.SwitchOnMain) }, close = { store.accept( diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStore.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStore.kt index f60278d0ca486638c9230a83361169eaed4c76cb..cfd38a4ec9205a5ed77d1c623d39c241baddbab9 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStore.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStore.kt @@ -11,7 +11,6 @@ interface BookAcceptStore : sealed interface Intent { object OnClose : Intent object OnAccept : Intent - object SwitchOnMain : Intent data class CloseModal(val withSheet: Boolean) : Intent } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStoreFactory.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStoreFactory.kt index d44a079bfba09f1a6f13eaa871d0ad96496af80a..c256373092e8ea9ddcabea7f03aa4873b3189c37 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStoreFactory.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/bottomSheets/bookingSheet/bookAccept/store/BookAcceptStoreFactory.kt @@ -18,7 +18,6 @@ class BookAcceptStoreFactory( private val storeFactory: StoreFactory, private val initState: BookAcceptStore.State, private val close: () -> Unit, - private val onMainScreen: () -> Unit ) : KoinComponent { private val bookingInteract: BookingInteract by inject() @@ -54,11 +53,6 @@ class BookAcceptStoreFactory( dispatch(Message.CloseModal) if (intent.withSheet) close() } - - BookAcceptStore.Intent.SwitchOnMain -> { - dispatch(Message.CloseModal) - onMainScreen() - } } } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/Content.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/Content.kt index 1b6968db3d123247f0e6206fe0b96cc46d05115f..f842143aebf09dd9129c140c628d25cbe0c2bff6 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/Content.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/Content.kt @@ -1,28 +1,15 @@ package band.effective.office.elevator.ui.content -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.BottomNavigation -import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import band.effective.office.elevator.components.TabNavigationItem -import band.effective.office.elevator.navigation.BookingTab -import band.effective.office.elevator.navigation.EmployeesTab -import band.effective.office.elevator.navigation.MainTab -import band.effective.office.elevator.navigation.ProfileTab import band.effective.office.elevator.ui.booking.BookingScreen import band.effective.office.elevator.ui.employee.Employee -import band.effective.office.elevator.ui.main.MainScreen import band.effective.office.elevator.ui.profile.Profile +import band.effective.office.elevator.ui.main.components.EffectiveBottomNavigation import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.Direction import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation @@ -38,66 +25,32 @@ fun Content(component: ContentComponent) { val activeComponent = childStack.active.instance Scaffold( modifier = Modifier, - content = { innerPadding -> - Box(modifier = Modifier.padding(innerPadding)) { - Children( - stack = component.childStack, - modifier = Modifier, - animation = tabAnimation() - ) { - when (val child = it.instance) { - is ContentComponent.Child.Main -> MainScreen(child.component) - is ContentComponent.Child.Profile -> Profile(child.component) - is ContentComponent.Child.Booking -> BookingScreen(child.component) - is ContentComponent.Child.Employee -> Employee(child.component) - } - } - } - }, bottomBar = { - BottomNavigation( - modifier = Modifier - .background(Color.White) - .fillMaxWidth() - .fillMaxHeight(fraction = 0.107f), - backgroundColor = Color.White + EffectiveBottomNavigation(activeComponent, component::onOutput) + }) + { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + Children( + modifier = Modifier, + stack = component.childStack, + animation = tabAnimation() ) { - TabNavigationItem( - tab = MainTab, - selected = activeComponent is ContentComponent.Child.Main - ) { - component.onOutput(ContentComponent.Output.OpenMainTab) - } - TabNavigationItem( - tab = BookingTab, - selected = activeComponent is ContentComponent.Child.Booking - ) { - component.onOutput(ContentComponent.Output.OpenBookingTab) - } - TabNavigationItem( - tab = EmployeesTab, - selected = activeComponent is ContentComponent.Child.Employee - ) { - component.onOutput(ContentComponent.Output.OpenEmployeeTab) - } - TabNavigationItem( - tab = ProfileTab, - selected = activeComponent is ContentComponent.Child.Profile - ) { - component.onOutput(ContentComponent.Output.OpenProfileTab) + when (val child = it.instance) { + is ContentComponent.Child.Profile -> Profile(child.component) + is ContentComponent.Child.Booking -> BookingScreen(child.component) + is ContentComponent.Child.Employee -> Employee(child.component) } } } - ) + } } private val ContentComponent.Child.index: Int get() = when (this) { - is ContentComponent.Child.Main -> 0 - is ContentComponent.Child.Booking -> 1 - is ContentComponent.Child.Employee -> 2 - is ContentComponent.Child.Profile -> 3 + is ContentComponent.Child.Booking -> 0 + is ContentComponent.Child.Employee -> 1 + is ContentComponent.Child.Profile -> 2 } @Composable @@ -127,4 +80,3 @@ private fun Direction.flipSide(): Direction = Direction.ENTER_BACK -> Direction.ENTER_FRONT Direction.EXIT_BACK -> Direction.EXIT_FRONT } - diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/ContentComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/ContentComponent.kt index 4d3878a53944e05fd5a06f81252192c7f5fee4ed..bf0ae76bc3b47f9d6ae0170c7cd3a0135698b98d 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/ContentComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/content/ContentComponent.kt @@ -1,9 +1,7 @@ package band.effective.office.elevator.ui.content -import band.effective.office.elevator.domain.entity.BookingInteract import band.effective.office.elevator.ui.booking.BookingComponent import band.effective.office.elevator.ui.employee.FullEmployeeComponent -import band.effective.office.elevator.ui.main.MainComponent import band.effective.office.elevator.ui.profile.ProfileComponent import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.router.stack.ChildStack @@ -14,12 +12,7 @@ import com.arkivanov.decompose.value.Value import com.arkivanov.essenty.parcelable.Parcelable import com.arkivanov.essenty.parcelable.Parcelize import com.arkivanov.mvikotlin.core.store.StoreFactory -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent -import org.koin.core.component.inject class ContentComponent( componentContext: ComponentContext, @@ -30,24 +23,15 @@ class ContentComponent( private val navigation = StackNavigation() private val stack = childStack( - initialStack = { listOf(Config.MainScreen) }, + initialStack = { listOf(Config.Booking) }, handleBackButton = true, source = navigation, childFactory = ::child, ) - val bookingInteract by inject() val childStack: Value> = stack private fun child(config: Config, componentContext: ComponentContext): Child = when (config) { - is Config.MainScreen -> Child.Main( - MainComponent( - componentContext, - storeFactory, - ::mainOutput - ) - ) - is Config.Profile -> Child.Profile( ProfileComponent( componentContext, @@ -60,39 +44,14 @@ class ContentComponent( BookingComponent( componentContext, storeFactory, - ::bookingOutput ) ) is Config.Employee -> Child.Employee(FullEmployeeComponent(componentContext, storeFactory)) } - private fun bookingOutput(output: BookingComponent.Output) { - when (output) { - BookingComponent.Output.OpenMainTab -> navigation.bringToFront(Config.MainScreen) - } - } - - private fun mainOutput(output: MainComponent.Output) { - when (output) { - is MainComponent.Output.DeleteBooking -> { - CoroutineScope(Dispatchers.IO).launch { - bookingInteract.deleteBooking( - bookingId = output.id - ) - } - } - - MainComponent.Output.OpenBookingScreen -> navigation.bringToFront(Config.Booking) - MainComponent.Output.OpenMap -> { - navigation.bringToFront(Config.Booking) - } - } - } - fun onOutput(output: Output) { when (output) { - Output.OpenMainTab -> navigation.bringToFront(Config.MainScreen) Output.OpenProfileTab -> navigation.bringToFront(Config.Profile) Output.OpenBookingTab -> navigation.bringToFront(Config.Booking) Output.OpenEmployeeTab -> navigation.bringToFront(Config.Employee) @@ -101,8 +60,6 @@ class ContentComponent( sealed class Child { - class Main(val component: MainComponent) : Child() - class Profile(val component: ProfileComponent) : Child() class Booking(val component: BookingComponent) : Child() @@ -112,9 +69,6 @@ class ContentComponent( private sealed interface Config : Parcelable { - @Parcelize - object MainScreen : Config - @Parcelize object Booking : Config @@ -127,7 +81,6 @@ class ContentComponent( sealed class Output { object OpenProfileTab : Output() - object OpenMainTab : Output() object OpenEmployeeTab : Output() object OpenBookingTab : Output() } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/Employee.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/Employee.kt index 2c02cf751d67325060cddda96e1febf84f0a16fb..2137a28621afda7d1d14950585f4839e67d46194 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/Employee.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/Employee.kt @@ -1,7 +1,7 @@ package band.effective.office.elevator.ui.employee import androidx.compose.runtime.Composable -import band.effective.office.elevator.ui.employee.aboutEmployee.AboutEmployee +import band.effective.office.elevator.ui.employee.aboutEmployee.AboutEmployeeScreen import band.effective.office.elevator.ui.employee.allEmployee.EmployeeScreen import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade @@ -10,14 +10,14 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.scal import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation @Composable -fun Employee (component: FullEmployeeComponent){ +fun Employee(component: FullEmployeeComponent) { Children( stack = component.childStack, animation = stackAnimation(fade() + scale()), - ){ - when(val child = it.instance){ + ) { + when (val child = it.instance) { is FullEmployeeComponent.Child.AllEmployeeChild -> EmployeeScreen(child.component) - is FullEmployeeComponent.Child.AboutEmployeeChild -> AboutEmployee(child.component) + is FullEmployeeComponent.Child.AboutEmployeeChild -> AboutEmployeeScreen(child.component) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployee.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployee.kt index 2cd08cd28dc5ae474dfad00b285d1701e8e60682..73219423d8aa41b493419392a856b234aa0284db 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployee.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployee.kt @@ -1,8 +1,6 @@ package band.effective.office.elevator.ui.employee.aboutEmployee -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,16 +12,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -35,40 +30,27 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes -import band.effective.office.elevator.components.EffectiveOutlinedButton -import band.effective.office.elevator.components.InfoAboutUserUIComponent +import band.effective.office.elevator.components.EmployeeBlock import band.effective.office.elevator.components.LoadingIndicator -import band.effective.office.elevator.components.ModalCalendarDateRange import band.effective.office.elevator.components.TitlePage -import band.effective.office.elevator.components.generateImageLoader -import band.effective.office.elevator.textGrayColor +import band.effective.office.elevator.expects.setClipboardText import band.effective.office.elevator.ui.employee.aboutEmployee.components.BookingCardUser -import band.effective.office.elevator.ui.employee.aboutEmployee.components.ContactUserUIComponent import band.effective.office.elevator.ui.employee.aboutEmployee.models.BookingsFilter import band.effective.office.elevator.ui.employee.aboutEmployee.store.AboutEmployeeStore import band.effective.office.elevator.ui.main.components.BottomDialog -import band.effective.office.elevator.ui.main.components.CalendarTitle -import band.effective.office.elevator.ui.main.components.FilterButton import band.effective.office.elevator.ui.models.ReservedSeat -import com.seiko.imageloader.model.ImageRequest -import com.seiko.imageloader.rememberImagePainter import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.datetime.LocalDate @OptIn(ExperimentalMaterialApi::class) @Composable -fun AboutEmployee(component: AboutEmployeeComponent) { +fun AboutEmployeeScreen(component: AboutEmployeeComponent) { val state by component.state.collectAsState() var showModalCalendar by remember { mutableStateOf(false) } var bottomSheetState = @@ -82,34 +64,22 @@ fun AboutEmployee(component: AboutEmployeeComponent) { AboutEmployeeStore.Label.OpenBottomDialog -> bottomSheetState.show() AboutEmployeeStore.Label.CloseBottomDialog -> bottomSheetState.hide() } - } } Box( modifier = Modifier - .background(Color.White) + .background(EffectiveTheme.colors.background.primary) .fillMaxSize() ) { AboutEmployeeContent( - isLoading = state.isLoading, imageUrl = state.user.imageUrl, userName = state.user.userName, post = state.user.post, telegram = state.user.telegram, email = state.user.email, - reservedSeatsList = state.reservedSeatsList, - dateFiltrationOnReserves = state.dateFiltrationOnReserves, - filtrationOnReserves = state.filtrationOnReserves, - beginDate = state.beginDate, - endDate = state.endDate, - isLoadingBooking = state.isLoadingBookings, + phoneNumber = state.user.phoneNumber, bottomSheetState = bottomSheetState, - onClickOpenPhone = { component.onEvent(AboutEmployeeStore.Intent.TelephoneClicked) }, - onClickOpenTelegram = { component.onEvent(AboutEmployeeStore.Intent.TelegramClicked) }, - onClickOpenSpb = { component.onEvent(AboutEmployeeStore.Intent.TransferMoneyClicked) }, onClickBack = { component.onOutput(AboutEmployeeComponent.Output.OpenAllEmployee) }, - onClickOpenCalendar = { component.onEvent(AboutEmployeeStore.Intent.OpenCalendarClicked) }, - onClickOpenBottomDialog = { component.onEvent(AboutEmployeeStore.Intent.OpenBottomDialog) }, onClickCloseBottomDialog = { component.onEvent( AboutEmployeeStore.Intent.CloseBottomDialog( @@ -117,26 +87,12 @@ fun AboutEmployee(component: AboutEmployeeComponent) { ) ) }, - onClickCopyEmail = { component.onEvent(AboutEmployeeStore.Intent.OnClickCopyEmail) }, - onClickCopyPhone = { component.onEvent(AboutEmployeeStore.Intent.OnClickCopyPhone) }, - onClickCopyTelegram = { component.onEvent(AboutEmployeeStore.Intent.OnClickCopyTelegram) } + isLoading = state.isLoading, + isLoadingBooking = state.isLoadingBookings, + reservedSeatsList = state.reservedSeatsList, + dateFiltrationOnReserves = state.dateFiltrationOnReserves, + filtrationOnReserves = state.filtrationOnReserves ) - - if (showModalCalendar) { - Dialog( - onDismissRequest = { component.onEvent(AboutEmployeeStore.Intent.CloseCalendarClicked) }, - properties = DialogProperties(usePlatformDefaultWidth = false) - ) { - ModalCalendarDateRange( - modifier = Modifier - .padding(horizontal = 16.dp) - .align(Alignment.Center), - onClickCansel = { component.onEvent(AboutEmployeeStore.Intent.CloseCalendarClicked) }, - onClickOk = { component.onEvent(AboutEmployeeStore.Intent.OnClickApplyDate(it)) }, - currentDate = state.beginDate - ) - } - } } } @@ -148,27 +104,16 @@ private fun AboutEmployeeContent( post: String?, telegram: String?, email: String?, - reservedSeatsList: List, - dateFiltrationOnReserves: Boolean, - filtrationOnReserves: Boolean, + phoneNumber: String?, bottomSheetState: ModalBottomSheetState, - beginDate: LocalDate, - endDate: LocalDate?, onClickBack: () -> Unit, - onClickOpenPhone: () -> Unit, - onClickOpenTelegram: () -> Unit, - onClickOpenSpb: () -> Unit, - onClickOpenCalendar: () -> Unit, - onClickOpenBottomDialog: () -> Unit, onClickCloseBottomDialog: (BookingsFilter) -> Unit, - onClickCopyPhone: () -> Unit, - onClickCopyEmail: () -> Unit, - onClickCopyTelegram: () -> Unit, isLoading: Boolean, - isLoadingBooking: Boolean + isLoadingBooking: Boolean, + reservedSeatsList: List, + dateFiltrationOnReserves: Boolean, + filtrationOnReserves: Boolean, ) { - val imageLoader = generateImageLoader() - ModalBottomSheetLayout( sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), sheetState = bottomSheetState, @@ -182,164 +127,110 @@ private fun AboutEmployeeContent( ) { Column(modifier = Modifier.fillMaxSize()) { Column( - modifier = Modifier.background(Color.White).padding(top = 40.dp, bottom = 24.dp) + modifier = Modifier + .background(EffectiveTheme.colors.background.tertiary) + .padding(top = 40.dp, bottom = 24.dp) .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top + verticalArrangement = Arrangement.Top, ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start, + Box( modifier = Modifier.fillMaxWidth() ) { - IconButton(onClick = onClickBack) { + IconButton( + modifier = Modifier.align(Alignment.CenterStart), + onClick = onClickBack, + ) { Icon( painter = painterResource(MainRes.images.back_button), - contentDescription = null, - tint = Color.Black + contentDescription = stringResource(MainRes.strings.back), + tint = EffectiveTheme.colors.icon.secondary, + modifier = Modifier.size(32.dp), ) } TitlePage( title = stringResource(MainRes.strings.about_the_employee), - modifier = Modifier.align(Alignment.CenterVertically).padding(start = 16.dp) + modifier = Modifier.align(Alignment.Center) ) } - when (isLoading) { - true -> { - LoadingIndicator() - } - - false -> { - val request = remember(imageUrl) { - ImageRequest { - data(imageUrl) - } - } - val painter = rememberImagePainter( - request = request, - imageLoader = imageLoader, - placeholderPainter = { painterResource(MainRes.images.logo_default) }, - errorPainter = { painterResource(MainRes.images.logo_default) } - ) - Row(modifier = Modifier.padding(top = 24.dp)) { - Column { - InfoAboutUserUIComponent(userName, post) - ContactUserUIComponent( - MainRes.images.icon_telegram, - telegram, - modifier = Modifier - .clickable(onClick = onClickCopyTelegram) - .padding(top = 18.dp) - ) - ContactUserUIComponent( - MainRes.images.mail, - email, - modifier = Modifier - .clickable(onClick = onClickCopyEmail) - .padding(top = 10.dp) - ) - } - Spacer(modifier = Modifier.weight(.1f)) - Surface( - modifier = Modifier.size(88.dp), - shape = CircleShape, - color = ExtendedThemeColors.colors.purple_heart_100 - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painter, - contentScale = ContentScale.Crop, - contentDescription = null, - ) - } - } - } - } - - Row( - modifier = Modifier.padding(top = 16.dp).fillMaxWidth(), - horizontalArrangement = Arrangement.Start - ) { - EffectiveOutlinedButton( - MainRes.images.icon_call, - text = null, - onClick = onClickOpenPhone, - modifier = Modifier.padding(end = 8.dp) - ) - EffectiveOutlinedButton( - MainRes.images.icon_telegram, - text = null, - onClick = onClickOpenTelegram, - modifier = Modifier.padding(end = 8.dp) - ) - EffectiveOutlinedButton( - MainRes.images.spb_icon, - text = MainRes.strings.transfer, - onClick = onClickCopyPhone, - modifier = Modifier.padding(end = 8.dp) + Spacer(modifier = Modifier.padding(24.dp)) + val localUriHandler = LocalUriHandler.current + if (isLoading) { + LoadingIndicator() + } else { + EmployeeBlock( + imageUrl = imageUrl, + userName = userName, + post = post, + telegram = telegram, + email = email, + phoneNumber = phoneNumber, + onOpenUri = { localUriHandler.openUri(it) }, + onCopyText = { value, label -> setClipboardText(value, label) } ) } } - when (isLoadingBooking) { - true -> LoadingIndicator() - false -> { - Column( - modifier = Modifier.background(MaterialTheme.colors.onBackground), - ) { - Row( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(MainRes.strings.upcoming_bookings), - color = ExtendedThemeColors.colors.blackColor, - style = MaterialTheme.typography.body1, - modifier = Modifier.padding(vertical = 24.dp).padding(end = 16.dp), - fontWeight = FontWeight(500) - ) - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.End), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - CalendarTitle( - onClickOpenCalendar = onClickOpenCalendar, - fromMainScreen = false, - beginDate = beginDate, - endDate = endDate - ) - FilterButton( - onClickOpenBottomSheetDialog = onClickOpenBottomDialog - ) - } - } - if (reservedSeatsList.isEmpty()) { - NoReservationsThisDate( - noThisDayReservations = dateFiltrationOnReserves, - filtration = filtrationOnReserves - ) - } else { - ReservationsOnThisDate( - reservedSeatsList = reservedSeatsList, - ) - } - } - } + if (isLoadingBooking) { + LoadingIndicator() + } else { + BookingBlock( + reservedSeatsList = reservedSeatsList, + dateFiltrationOnReserves = dateFiltrationOnReserves, + filtrationOnReserves = filtrationOnReserves, + ) } } } } @Composable -fun NoReservationsThisDate(noThisDayReservations: Boolean, filtration: Boolean) { +private fun BookingBlock( + modifier: Modifier = Modifier, + reservedSeatsList: List, + dateFiltrationOnReserves: Boolean, + filtrationOnReserves: Boolean, +) { + Column( + modifier = modifier.background(EffectiveTheme.colors.background.tertiary), + ) { + Row( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(MainRes.strings.upcoming_bookings), + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.lMedium, + modifier = Modifier.padding(vertical = 24.dp).padding(end = 16.dp), + fontWeight = FontWeight(500) + ) + } + if (reservedSeatsList.isEmpty()) { + NoReservationsThisDate( + noThisDayReservations = dateFiltrationOnReserves, + filtration = filtrationOnReserves + ) + } else { + ReservationsOnThisDate( + reservedSeatsList = reservedSeatsList, + ) + } + } +} + +@Composable +private fun NoReservationsThisDate( + modifier: Modifier = Modifier, + noThisDayReservations: Boolean, filtration: Boolean +) { Column( - modifier = Modifier.fillMaxSize(), + modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Text( text = stringResource( @@ -351,18 +242,21 @@ fun NoReservationsThisDate(noThisDayReservations: Boolean, filtration: Boolean) else MainRes.strings.none_booking_seats_by_the_employee } ), - fontSize = 15.sp, - color = textGrayColor, - textAlign = TextAlign.Center + style = EffectiveTheme.typography.mMedium, + color = EffectiveTheme.colors.text.secondary, + textAlign = TextAlign.Center, ) } } @Composable -fun ReservationsOnThisDate(reservedSeatsList: List) { +private fun ReservationsOnThisDate( + modifier: Modifier = Modifier, + reservedSeatsList: List +) { LazyColumn( - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(18.dp) + modifier = modifier.fillMaxSize().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(18.dp), ) { items(reservedSeatsList) { seat -> BookingCardUser( @@ -370,4 +264,4 @@ fun ReservationsOnThisDate(reservedSeatsList: List) { ) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployeeComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployeeComponent.kt index 8d69ecd638977024e908116d39768fa5a9651111..2f97d8ff37115cda88d17e8e98af72036c9d74e9 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployeeComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/AboutEmployeeComponent.kt @@ -41,5 +41,4 @@ class AboutEmployeeComponent( sealed interface Output { object OpenAllEmployee : Output } - -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/BookingCardUser.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/BookingCardUser.kt index 7b3db498d7e2d5a980b5fdf4a66e775654114f1c..b60f9307252832a1a15837792e1b681dac4e7d71 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/BookingCardUser.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/BookingCardUser.kt @@ -1,38 +1,48 @@ package band.effective.office.elevator.ui.employee.aboutEmployee.components -import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import band.effective.office.elevator.ui.main.components.SeatIcon -import band.effective.office.elevator.ui.main.components.SeatTitle +import androidx.compose.ui.unit.sp import band.effective.office.elevator.ui.models.ReservedSeat @Composable fun BookingCardUser( seat: ReservedSeat, ) { - Column( + Box( modifier = Modifier - .clip(RoundedCornerShape(16.dp)) - .background(Color.White) - .padding(horizontal = 16.dp, vertical = 16.dp) + .padding(horizontal = 16.dp) .fillMaxWidth() + .border( + width = 2.dp, + color = Color.LightGray, + shape = RoundedCornerShape(16.dp) + ) + .padding(horizontal = 28.dp, vertical = 16.dp) ) { - Row { - SeatIcon() - Spacer(modifier = Modifier.width(12.dp)) - SeatTitle(seat) + Column { + Text( + text = seat.seatName, + color = Color.Black, + fontSize = 18.sp, + fontWeight = FontWeight(500), + ) + Text( + text = seat.bookingDay, + color = Color.Gray, + fontSize = 18.sp, + fontWeight = FontWeight(500), + ) } } } - diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/ContactUserUIComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/ContactUserUIComponent.kt deleted file mode 100644 index 29a264553ba90f459f069e819261ea9c3c2d96c7..0000000000000000000000000000000000000000 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/ContactUserUIComponent.kt +++ /dev/null @@ -1,38 +0,0 @@ -package band.effective.office.elevator.ui.employee.aboutEmployee.components - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import band.effective.office.elevator.ExtendedThemeColors -import dev.icerock.moko.resources.ImageResource -import dev.icerock.moko.resources.compose.painterResource - -@Composable -fun ContactUserUIComponent(image:ImageResource, value: String?, modifier: Modifier){ - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically - ){ - Icon( - painter = painterResource(image), - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = ExtendedThemeColors.colors.purple_heart_800 - ) - value?.let{ - Text( - text = it, - style = MaterialTheme.typography.caption, - color = ExtendedThemeColors.colors.purple_heart_800, - modifier = Modifier.padding(start = 8.dp) - ) - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/EmployeeInfo.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/EmployeeInfo.kt new file mode 100644 index 0000000000000000000000000000000000000000..a395396f8ef054cbd77865b51e0c8a612a2414ff --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/components/EmployeeInfo.kt @@ -0,0 +1,105 @@ +package band.effective.office.elevator.ui.employee.aboutEmployee.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.EffectiveThemeColors +import band.effective.office.elevator.ExtendedThemeColors +import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.compose.painterResource + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun EmployeeInfo( + modifier: Modifier = Modifier, + icon: ImageResource, + iconTitle: String, + value: String?, + onClick: () -> Unit, + onLongClick: () -> Unit, +) { + Box( + modifier = modifier + .fillMaxWidth() + .background( + color = EffectiveTheme.colors.background.secondary, + shape = RoundedCornerShape(8.dp) + ) + .padding(12.dp) + .combinedClickable( + onLongClick = onLongClick, + onClick = onClick, + ), + + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Box( + modifier = Modifier + .size(32.dp) + .border( + border = BorderStroke( + width = 1.dp, + color = EffectiveTheme.colors.stroke.primary, + ), + shape = RoundedCornerShape(8.dp) + ) + .background( + color = EffectiveTheme.colors.background.primary, + shape = RoundedCornerShape(8.dp) + ), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(icon), + contentDescription = null, + modifier = Modifier + .size(24.dp) + .padding(4.dp), + tint = EffectiveTheme.colors.icon.accent, + ) + + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = iconTitle, + style = EffectiveTheme.typography.sMedium, + color = EffectiveTheme.colors.text.secondary, + ) + value?.let { + Text( + text = it, + style = EffectiveTheme.typography.sMedium, + color = EffectiveTheme.colors.text.secondary, + ) + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/store/AboutEmployeeStore.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/store/AboutEmployeeStore.kt index 561b55af51b8ddbc53fec1cde1a13d7206920ecc..28d88417ab476f9f7fc7651bf6008171b3e0f1d9 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/store/AboutEmployeeStore.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/aboutEmployee/store/AboutEmployeeStore.kt @@ -13,17 +13,9 @@ interface AboutEmployeeStore : Store): Intent object OpenBottomDialog: Intent data class CloseBottomDialog(val bookingsFilter: BookingsFilter): Intent - - data object OnClickCopyTelegram : Intent - - data object OnClickCopyEmail : Intent - - data object OnClickCopyPhone : Intent } data class State( @@ -43,5 +35,4 @@ interface AboutEmployeeStore : Store by storeFactory.create( name = "AboutEmployeeStore", initialState = State( - currentUser, + user = currentUser, reservedSeatsList = listOf(), beginDate = getCurrentDate(), endDate = getCurrentDate(), @@ -124,17 +124,6 @@ class AboutEmployeeStoreFactory( Intent.TelegramClicked -> pickTelegram(telegramNickParse(getState().user.telegram)) Intent.TelephoneClicked -> makeCall(getState().user.phoneNumber) Intent.TransferMoneyClicked -> pickSBP(getState().user.phoneNumber) - Intent.OpenCalendarClicked -> { - scope.launch { - publish(Label.OpenCalendar) - } - } - - Intent.CloseCalendarClicked -> { - scope.launch { - publish(Label.CloseCalendar) - } - } is Intent.OnClickApplyDate -> { scope.launch { @@ -184,25 +173,6 @@ class AboutEmployeeStoreFactory( dispatch(Msg.UpdateLoadingBookingState(false)) } } - - Intent.OnClickCopyPhone -> - setClipboardText( - text = getState().user.phoneNumber, - label = "Phone", - toastMessage = MainRes.strings.phone_clipboard - ) - Intent.OnClickCopyTelegram -> - setClipboardText( - text = "https://t.me/${getState().user.telegram}", - label = "Telegram", - toastMessage = MainRes.strings.telegram_clipboard - ) - Intent.OnClickCopyEmail -> - setClipboardText( - text = getState().user.email, - label = "Email", - toastMessage = MainRes.strings.email_clipboard - ) } } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/EmployeeScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/EmployeeScreen.kt index de8bd3e82dd940b0f0a07b524cdede98e3b7a49e..03db2b4d5a24879a8ef43e906c358074bf3b5a82 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/EmployeeScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/EmployeeScreen.kt @@ -3,46 +3,46 @@ package band.effective.office.elevator.ui.employee.allEmployee import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface +import androidx.compose.material.IconButton import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes import band.effective.office.elevator.components.LoadingIndicator +import band.effective.office.elevator.components.TitlePage import band.effective.office.elevator.components.generateImageLoader -import band.effective.office.elevator.textInBorderGray -import band.effective.office.elevator.theme_light_onPrimary import band.effective.office.elevator.ui.employee.allEmployee.models.mappers.EmployeeCard import band.effective.office.elevator.ui.employee.allEmployee.store.EmployeeStore import com.seiko.imageloader.model.ImageRequest @@ -55,7 +55,6 @@ fun EmployeeScreen(component: EmployeeComponent) { val employState by component.employState.collectAsState() val employeesData = employState.changeShowedEmployeeCards - val employeesCount = employState.countShowedEmployeeCards val userMessageState = employState.query @@ -73,7 +72,6 @@ fun EmployeeScreen(component: EmployeeComponent) { EmployeeScreenContent( isLoading = employState.isLoading, employeesData = employeesData, - employeesCount = employeesCount, userMessageState = userMessageState, onCardClick = { component.onEvent(EmployeeStore.Intent.OnClickOnEmployee(it)) }, onTextFieldUpdate = { component.onEvent(EmployeeStore.Intent.OnTextFieldUpdate(it)) }) @@ -81,8 +79,8 @@ fun EmployeeScreen(component: EmployeeComponent) { @Composable fun EmployeeScreenContent( + modifier: Modifier = Modifier, employeesData: List, - employeesCount: String, userMessageState: String, onCardClick: (String) -> Unit, onTextFieldUpdate: (String) -> Unit, @@ -93,80 +91,37 @@ fun EmployeeScreenContent( // see there: https://medium.com/androiddevelopers/effective-state-management-for-textfield-in-compose-d6e5b070fbe5 var query by remember { mutableStateOf(userMessageState) } - Column { + Column( + modifier = modifier + .background(EffectiveTheme.colors.background.primary) + ) { Column( modifier = Modifier - .background(theme_light_onPrimary) .padding(bottom = 15.dp) .fillMaxWidth() ) { Text( text = stringResource(MainRes.strings.employees), - fontSize = 20.sp, - fontWeight = FontWeight(600),//? - color = ExtendedThemeColors.colors.blackColor, - modifier = Modifier.padding(start = 20.dp, top = 55.dp, end = 15.dp, bottom = 25.dp) + style = EffectiveTheme.typography.mMedium, + textAlign = TextAlign.Center, + color = EffectiveTheme.colors.text.primary, + modifier = Modifier + .fillMaxWidth() + .padding(start = 20.dp, top = 55.dp, end = 15.dp, bottom = 25.dp) ) - TextField( - value = query, onValueChange = { + SearchTextField( + query = query, + onTextFieldUpdate = { query = it onTextFieldUpdate(it) - }, modifier = Modifier - .fillMaxWidth() - .padding(start = 20.dp, top = 10.dp, end = 20.dp, bottom = 5.dp), - textStyle = TextStyle( - color = ExtendedThemeColors.colors.trinidad_400, - fontSize = 16.sp, - fontWeight = FontWeight(500) - ), - colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = ExtendedThemeColors.colors.transparentColor, - disabledIndicatorColor = ExtendedThemeColors.colors.transparentColor, - unfocusedIndicatorColor = ExtendedThemeColors.colors.transparentColor, - backgroundColor = MaterialTheme.colors.background - ), - placeholder = { - Text( - text = stringResource(MainRes.strings.search_employee), - fontSize = 16.sp, - fontWeight = FontWeight(500),//Style. maththeme - color = textInBorderGray - ) - }, - leadingIcon = { - Icon( - painter = painterResource(MainRes.images.baseline_search_24), - contentDescription = "SearchField", - tint = textInBorderGray - ) - }, - singleLine = true, - shape = RoundedCornerShape(32.dp) - + } ) - } - Column ( + Column( modifier = Modifier - .background(MaterialTheme.colors.onBackground) .fillMaxSize() .padding(start = 20.dp, top = 25.dp, end = 20.dp) ) { - Row(modifier = Modifier.padding(bottom = 25.dp).fillMaxWidth()) { - Text( - text = stringResource(MainRes.strings.employees) + " ", - fontSize = 16.sp, - fontWeight = FontWeight(500), - color = ExtendedThemeColors.colors.blackColor - ) - Text( - text = "($employeesCount)", - fontSize = 16.sp, - fontWeight = FontWeight(400), - color = ExtendedThemeColors.colors.purple_heart_800 - ) - } - when (isLoading) { true -> { LoadingIndicator() @@ -185,8 +140,78 @@ fun EmployeeScreenContent( } @Composable +private fun SearchTextField( + modifier: Modifier = Modifier, + query: String, + onTextFieldUpdate: (String) -> Unit, +) { + val interactionSource = MutableInteractionSource() + val isFocused by interactionSource.collectIsFocusedAsState() + + Row( + modifier = modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + .border( + width = 1.dp, + color = EffectiveTheme.colors.stroke.primary, + RoundedCornerShape(8.dp) + ) + .padding(vertical = 4.dp) + .padding(start = 24.dp, end = 14.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(MainRes.images.ic_search), + tint = if (isFocused) EffectiveTheme.colors.icon.accent else EffectiveTheme.colors.icon.secondary, + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + BasicTextField( + modifier = Modifier + .padding(16.dp) + .weight(1f), + value = query, + onValueChange = onTextFieldUpdate, + interactionSource = interactionSource, + textStyle = EffectiveTheme.typography.mMedium, + singleLine = true, + decorationBox = { innerTextField -> + Box( + modifier = Modifier.wrapContentSize(align = Alignment.CenterStart), + contentAlignment = Alignment.CenterStart, + ) { + val hint = if (query.isEmpty()) { + stringResource(MainRes.strings.search_employee) + } else { + "" + } + Text( + text = hint, + style = EffectiveTheme.typography.mMedium, + color = EffectiveTheme.colors.text.additional, + ) + innerTextField() + } + } + ) + if (query.isNotEmpty()) { + IconButton( + onClick = { onTextFieldUpdate("") }, + ) { + Icon( + painter = painterResource(MainRes.images.ic_cross), + tint = EffectiveTheme.colors.icon.secondary, + contentDescription = stringResource(MainRes.strings.clear_text_field), + modifier = Modifier.size(24.dp), + ) + } + } + } +} -fun EveryEmployeeCard(emp: EmployeeCard, onCardClick: (String) -> Unit) { +@Composable +private fun EveryEmployeeCard(emp: EmployeeCard, onCardClick: (String) -> Unit) { var isExpanded by remember { mutableStateOf(false) } if (isExpanded) { onCardClick(emp.id) @@ -194,14 +219,12 @@ fun EveryEmployeeCard(emp: EmployeeCard, onCardClick: (String) -> Unit) { val imageLoader = generateImageLoader() - Surface( - shape = RoundedCornerShape(12.dp), + Box( modifier = Modifier .fillMaxSize() .padding(bottom = 15.dp) .animateContentSize() .clickable { isExpanded = !isExpanded }, - color = MaterialTheme.colors.onPrimary ) { Row(modifier = Modifier.padding(6.dp, 15.dp)) { emp.logoUrl.let { url -> @@ -218,7 +241,7 @@ fun EveryEmployeeCard(emp: EmployeeCard, onCardClick: (String) -> Unit) { ) Image( painter = painter, - contentDescription = "Employee logo", + contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier .clip(CircleShape) @@ -226,23 +249,21 @@ fun EveryEmployeeCard(emp: EmployeeCard, onCardClick: (String) -> Unit) { ) } - Column(modifier = Modifier.padding(15.dp, 0.dp)) { + Column(modifier = Modifier.padding(horizontal = 15.dp)) { Text( text = emp.name, - fontSize = 16.sp, + style = EffectiveTheme.typography.mMedium, fontWeight = FontWeight(500), - color = ExtendedThemeColors.colors.blackColor + color = EffectiveTheme.colors.text.primary, ) - Spacer(modifier = Modifier.padding(0.dp, 4.dp)) Text( text = emp.post, - fontSize = 16.sp, + style = EffectiveTheme.typography.sMedium, fontWeight = FontWeight(400), - color = textInBorderGray + color = EffectiveTheme.colors.text.secondary, ) } } } } - diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStore.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStore.kt index 5d1f5d7cfb16109c67449695e09d29879df71128..9dc959528e55f89b9a9453f3461f0daa6ced3e66 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStore.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStore.kt @@ -12,7 +12,6 @@ interface EmployeeStore: Store, - val countShowedEmployeeCards:String, val query:String, val isLoading: Boolean = true, val allEmployeeList: List // TODO(Artem Gruzdev) change model of this list diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStoreFactory.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStoreFactory.kt index 14ae2128223471ecdaa7ed56f0ec2cdfa4aa5f88..723934e61570c22c8955dd4615d4eeec000e67b5 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStoreFactory.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/employee/allEmployee/store/EmployeeStoreFactory.kt @@ -35,7 +35,6 @@ internal class EmployeeStoreFactory(private val storeFactory: StoreFactory) : Ko name = "EmployeeStore", initialState = EmployeeStore.State( changeShowedEmployeeCards = emptyList(), - countShowedEmployeeCards = "0", query = "", allEmployeeList = listOf() ), @@ -99,8 +98,7 @@ internal class EmployeeStoreFactory(private val storeFactory: StoreFactory) : Ko when (response) { is Either.Success ->{ var getUser : User = User.defaultUser - userUseCase.execute().collect{ - user -> + userUseCase.execute().collect { user -> withContext(Dispatchers.Main) { when (user) { is Either.Success -> { @@ -136,7 +134,6 @@ internal class EmployeeStoreFactory(private val storeFactory: StoreFactory) : Ko ) copy( changeShowedEmployeeCards = newEmployeesList, - countShowedEmployeeCards = newEmployeesList.count().toString(), allEmployeeList = msg.employeesInfo, isLoading = false ) @@ -151,7 +148,6 @@ internal class EmployeeStoreFactory(private val storeFactory: StoreFactory) : Ko copy( query = msg.query, changeShowedEmployeeCards = newEmployeesList, - countShowedEmployeeCards = newEmployeesList.count().toString(), isLoading = false ) } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/errors/ConnectionErrorScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/errors/ConnectionErrorScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..efec38a90e5a3871b645acfbbbc0c732c986baf3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/errors/ConnectionErrorScreen.kt @@ -0,0 +1,71 @@ +package band.effective.office.elevator.ui.errors + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.MainRes +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +fun ConnectionErrorScreen( + modifier: Modifier = Modifier, + updateClick: () -> Unit, +) { + Box( + modifier = modifier + .fillMaxSize() + .background(EffectiveTheme.colors.background.primary) + ) { + Column( + modifier = Modifier + .align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Icon( + painter = painterResource(MainRes.images.ic_wifi), + contentDescription = null, + modifier = Modifier.size(48.dp), + tint = Color.Unspecified, + ) + Spacer(modifier = Modifier.padding(16.dp)) + Text( + text = stringResource(MainRes.strings.connection_error_title), + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.mMedium, + ) + Spacer(modifier = Modifier.padding(4.dp)) + Text( + text = stringResource(MainRes.strings.connection_error_description), + color = EffectiveTheme.colors.text.secondary, + style = EffectiveTheme.typography.sMedium, + ) + Spacer(modifier = Modifier.padding(22.dp)) + TextButton( + onClick = updateClick, + content = { + Text( + text = stringResource(MainRes.strings.try_again), + color = EffectiveTheme.colors.text.accent, + style = EffectiveTheme.typography.sMedium, + ) + } + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/errors/ErrorDialog.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/errors/ErrorDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..18670b8ab60f0f82de53bd5bb6151c80f4b00a86 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/errors/ErrorDialog.kt @@ -0,0 +1,104 @@ +package band.effective.office.elevator.ui.errors + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.components.PrimaryButton +import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +fun ErrorDialog( + modifier: Modifier = Modifier, + onDismissRequest: () -> Unit, + onClickButton: () -> Unit, + onClickText: () -> Unit, + icon: ImageResource, + errorTitle: StringResource, + errorSubtitle: StringResource, + primaryButtonText: StringResource, + secondaryButtonText: StringResource, +) { + Dialog( + onDismissRequest = onDismissRequest, + properties = DialogProperties(usePlatformDefaultWidth = false), + content = { + Card( + modifier = modifier + .padding(16.dp) + .fillMaxWidth() + .background( + EffectiveTheme.colors.background.primary, + shape = RoundedCornerShape(28.dp) + ), + shape = RoundedCornerShape(28.dp), + border = BorderStroke(width = 1.dp, color = EffectiveTheme.colors.stroke.primary) + ) { + Column( + modifier = Modifier.padding(vertical = 40.dp, horizontal = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Icon( + painter = painterResource(icon), + contentDescription = null, + modifier = Modifier.size(48.dp), + tint = Color.Unspecified, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(errorTitle), + color = EffectiveTheme.colors.text.primary, + style = EffectiveTheme.typography.mMedium, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(errorSubtitle), + color = EffectiveTheme.colors.text.secondary, + style = EffectiveTheme.typography.sMedium, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(24.dp)) + PrimaryButton( + onClick = onClickButton, + buttonText = stringResource(primaryButtonText), + ) + TextButton( + onClick = onClickText, + content = { + Text( + text = stringResource(secondaryButtonText), + color = EffectiveTheme.colors.text.accent, + style = EffectiveTheme.typography.sMedium, + textAlign = TextAlign.Center, + ) + } + ) + } + } + } + ) +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/MainScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/MainScreen.kt index 4d1978e4b472f404428e3a236858af8f91cf1ac3..565d0ee48f6d45e9691afde507895901165253de 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/MainScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/MainScreen.kt @@ -305,3 +305,4 @@ fun MainScreenContent( } } +// TODO() \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/components/EffectiveBottomNavigation.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/components/EffectiveBottomNavigation.kt new file mode 100644 index 0000000000000000000000000000000000000000..47e2a1d36b839729aecf70cd009f44f93ee06320 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/components/EffectiveBottomNavigation.kt @@ -0,0 +1,51 @@ +package band.effective.office.elevator.ui.main.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.BottomNavigation +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme +import band.effective.office.elevator.ui.content.ContentComponent +import band.effective.office.elevator.components.TabNavigationItem +import band.effective.office.elevator.navigation.BookingTab +import band.effective.office.elevator.navigation.EmployeesTab +import band.effective.office.elevator.navigation.ProfileTab + +@Composable +fun EffectiveBottomNavigation( + activeComponent: ContentComponent.Child, + onOutput: (ContentComponent.Output) -> Unit +) { + Column { + Divider( + modifier = Modifier.fillMaxWidth(), + color = EffectiveTheme.colors.divider.primary, + thickness = 1.dp + ) + BottomNavigation( + backgroundColor = EffectiveTheme.colors.background.primary, + ) { + TabNavigationItem( + tab = BookingTab, + selected = activeComponent is ContentComponent.Child.Booking + ) { + onOutput(ContentComponent.Output.OpenBookingTab) + } + TabNavigationItem( + tab = EmployeesTab, + selected = activeComponent is ContentComponent.Child.Employee + ) { + onOutput(ContentComponent.Output.OpenEmployeeTab) + } + TabNavigationItem( + tab = ProfileTab, + selected = activeComponent is ContentComponent.Child.Profile + ) { + onOutput(ContentComponent.Output.OpenProfileTab) + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/components/SeatReservation.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/components/SeatReservation.kt index d6ebc53a689161de755c582f1a7464961d766610..9a4610108bd070dd49d5c749093cd0d60f6cc513 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/components/SeatReservation.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/main/components/SeatReservation.kt @@ -77,4 +77,4 @@ fun NonEmptyReservation( ) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/Profile.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/Profile.kt index 748761703606d788ff8b9019c2ae1c073d2f7142..7e123aa283781dbfa0b465c191462d14f68af935 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/Profile.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/Profile.kt @@ -20,4 +20,4 @@ fun Profile (component: ProfileComponent){ is ProfileComponent.Child.EditProfileChild -> ProfileEditScreen(child.component) } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/ProfileComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/ProfileComponent.kt index 5ad7013c164fb13306960d02d8d6781614b9cda3..07b9644dbcef22c76e0943a84b8ce0c13e0b6f8b 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/ProfileComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/ProfileComponent.kt @@ -8,8 +8,10 @@ import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.bringToFront import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.popWhile import com.arkivanov.decompose.router.stack.push import com.arkivanov.decompose.router.stack.replaceAll +import com.arkivanov.decompose.router.stack.replaceCurrent import com.arkivanov.decompose.value.Value import com.arkivanov.essenty.parcelable.Parcelable import com.arkivanov.essenty.parcelable.Parcelize @@ -48,7 +50,6 @@ class ProfileComponent( componentContext, storeFactory, ::editProfileOutput, - config.user ) ) } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/EditBlock.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/EditBlock.kt new file mode 100644 index 0000000000000000000000000000000000000000..adbfb9221dcbc71057c56b73854a3e5705b3abf5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/EditBlock.kt @@ -0,0 +1,118 @@ +package band.effective.office.elevator.ui.profile.editProfile + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import band.effective.office.elevator.EffectiveTheme +import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.compose.painterResource + +@Composable +fun EditBlock( + modifier: Modifier = Modifier, + icon: ImageResource, + iconTitle: String, + userInfo: String, + onInfoChange: (String) -> Unit, + errorText: String?, +) { + Column { + Box( + modifier = modifier + .fillMaxWidth() + .background( + color = EffectiveTheme.colors.input.lockNormal, + shape = RoundedCornerShape(8.dp), + ) + .border( + BorderStroke( + width = 1.dp, + color = if (errorText == null) { + EffectiveTheme.colors.input.lockNormal + } else { + EffectiveTheme.colors.stroke.error + }, + ), + shape = RoundedCornerShape(8.dp) + ) + .padding(12.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Box( + modifier = Modifier + .size(32.dp) + .border( + border = BorderStroke( + width = 1.dp, + color = EffectiveTheme.colors.stroke.primary, + ), + shape = RoundedCornerShape(8.dp) + ) + .background( + color = EffectiveTheme.colors.background.primary, + shape = RoundedCornerShape(8.dp) + ), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(icon), + contentDescription = null, + modifier = Modifier + .size(24.dp) + .padding(4.dp), + tint = EffectiveTheme.colors.icon.accent, + ) + } + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Text( + text = iconTitle, + style = EffectiveTheme.typography.xsMedium, + color = EffectiveTheme.colors.text.additional, + ) + BasicTextField( + value = userInfo, + onValueChange = onInfoChange, + textStyle = TextStyle.Default.copy( + color = EffectiveTheme.colors.text.primary, + ), + ) + } + } + } + + if (errorText != null) { + Spacer(modifier = Modifier.padding(8.dp)) + Text( + text = errorText, + color = EffectiveTheme.colors.text.error, + style = EffectiveTheme.typography.xsMedium, + textAlign = TextAlign.Start + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditComponent.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditComponent.kt index 063900261390a99bf3082a235165784602f6b108..b4276434886d26f979b5394c28a3e2eaaae21c29 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditComponent.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditComponent.kt @@ -1,43 +1,42 @@ package band.effective.office.elevator.ui.profile.editProfile -import band.effective.office.elevator.domain.models.User import band.effective.office.elevator.ui.profile.editProfile.store.ProfileEditStore import band.effective.office.elevator.ui.profile.editProfile.store.ProfileEditStoreFactory import com.arkivanov.decompose.ComponentContext import com.arkivanov.mvikotlin.core.instancekeeper.getStore import com.arkivanov.mvikotlin.core.store.StoreFactory -import com.arkivanov.mvikotlin.core.utils.ExperimentalMviKotlinApi import com.arkivanov.mvikotlin.extensions.coroutines.labels import com.arkivanov.mvikotlin.extensions.coroutines.stateFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -class ProfileEditComponent ( +@OptIn(ExperimentalCoroutinesApi::class) +class ProfileEditComponent( componentContext: ComponentContext, storeFactory: StoreFactory, private val output: (Output) -> Unit, - private val userEdit: String - ) : ComponentContext by componentContext { +) : ComponentContext by componentContext { - private val profileEditStore = instanceKeeper.getStore { - ProfileEditStoreFactory( - storeFactory = storeFactory, - ).create() - } + private val profileEditStore = instanceKeeper.getStore { + ProfileEditStoreFactory( + storeFactory = storeFactory, + ).create() + } + + val state: StateFlow = profileEditStore.stateFlow - @OptIn(ExperimentalMviKotlinApi::class) - val user: StateFlow = profileEditStore.stateFlow + val label: Flow = profileEditStore.labels - val label: Flow = profileEditStore.labels + fun onEvent(event: ProfileEditStore.Intent) { + profileEditStore.accept(event) + } - fun onEvent(event:ProfileEditStore.Intent){ - profileEditStore.accept(event) - } - fun onOutput(output: Output){ + fun onOutput(output: Output) { output(output) } - sealed interface Output { - object NavigationBack:Output - } -} \ No newline at end of file + sealed interface Output { + data object NavigationBack : Output + } +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditScreen.kt index de2b75e2616d255097abcd63259323de946b35c9..02d7764b57caeb64512c1bb258a24ab949a49427 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/ProfileEditScreen.kt @@ -1,29 +1,23 @@ package band.effective.office.elevator.ui.profile.editProfile +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Divider +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -33,55 +27,44 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes -import band.effective.office.elevator.components.EffectiveButton import band.effective.office.elevator.components.LoadingIndicator -import band.effective.office.elevator.components.OutlinedTextColorsSetup +import band.effective.office.elevator.components.PrimaryButton +import band.effective.office.elevator.components.PopupMessage +import band.effective.office.elevator.components.PopupMessageInfo import band.effective.office.elevator.components.TitlePage -import band.effective.office.elevator.components.UserInfoTextField -import band.effective.office.elevator.ui.main.SnackBarErrorMessage -import band.effective.office.elevator.ui.models.PhoneMaskTransformation -import band.effective.office.elevator.ui.models.UserDataTextFieldType -import band.effective.office.elevator.ui.models.getAllUserDataEditProfile -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import band.effective.office.elevator.components.generateImageLoader import band.effective.office.elevator.ui.profile.editProfile.store.ProfileEditStore +import com.seiko.imageloader.model.ImageRequest +import com.seiko.imageloader.rememberImagePainter import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.delay @Composable fun ProfileEditScreen(component: ProfileEditComponent) { - val user by component.user.collectAsState() + val state by component.state.collectAsState() - when { - user.isData -> { - ProfileEditView( - user = user, - component = component - ) - } - - user.isLoading -> { + when (val currentState = state) { + is ProfileEditStore.State.Loading -> { LoadingIndicator() } - else -> {} + is ProfileEditStore.State.Data -> { + ProfileEditContent( + state = currentState, + component = component + ) + } } - } @Composable -fun ProfileEditView(user: ProfileEditStore.State, component: ProfileEditComponent) { +fun ProfileEditContent(state: ProfileEditStore.State.Data, component: ProfileEditComponent) { - var errorMessage by remember { mutableStateOf(MainRes.strings.something_went_wrong) } - var isErrorMessageVisible by remember { mutableStateOf(false) } + var popupInfo: PopupMessageInfo? by remember { mutableStateOf(null) } LaunchedEffect(component) { component.label.collect { label -> @@ -90,35 +73,42 @@ fun ProfileEditView(user: ProfileEditStore.State, component: ProfileEditComponen ProfileEditComponent.Output.NavigationBack ) - is ProfileEditStore.Label.SavedChange -> component.onOutput(ProfileEditComponent.Output.NavigationBack) - is ProfileEditStore.Label.Error -> { - errorMessage = label.name - isErrorMessageVisible = true - delay(3000) - isErrorMessageVisible = false + is ProfileEditStore.Label.SavedChange -> { + popupInfo = PopupMessageInfo( + messageResource = MainRes.strings.profile_changes_saved, + type = PopupMessageInfo.Type.SUCCESS, + ) + } + + is ProfileEditStore.Label.ServerError -> { + popupInfo = PopupMessageInfo( + messageResource = MainRes.strings.server_error, + type = PopupMessageInfo.Type.ERROR, + ) } } } } ProfileEditScreenContent( - isErrorMessageVisible = isErrorMessageVisible, - errorMessage = errorMessage, - isErrorTelegram = user.isErrorTelegram, - isErrorPost = user.isErrorPost, - isErrorName = user.isErrorName, - isErrorPhone = user.isErrorPhone, - userName = user.user.userName, - post = user.user.post, - telegram = user.user.telegram, - phoneNumber = user.user.phoneNumber, + imageUrl = state.user.imageUrl, + telegramError = state.telegramError, + postError = state.postError, + nameError = state.nameError, + phoneError = state.phoneNumberError, + userName = state.user.userName, + post = state.user.post, + telegram = state.user.telegram, + phoneNumber = state.user.phoneNumber, onReturnToProfile = { component.onEvent(ProfileEditStore.Intent.BackInProfileClicked) }, + popupInfo = popupInfo, + onClosePopup = { popupInfo = null }, onSaveChange = { userName, post, phoneNumber, telegram -> component.onEvent( - ProfileEditStore.Intent.SaveChangeClicked( + ProfileEditStore.Intent.SaveChangesClicked( userName = userName, post = post, telegram = telegram, - phoneNumber = phoneNumber + phoneNumber = phoneNumber, ) ) } @@ -127,149 +117,125 @@ fun ProfileEditView(user: ProfileEditStore.State, component: ProfileEditComponen @Composable private fun ProfileEditScreenContent( - isErrorPhone: Boolean, + modifier: Modifier = Modifier, + imageUrl: String, userName: String, post: String, telegram: String, phoneNumber: String, onReturnToProfile: () -> Unit, onSaveChange: (userName: String, post: String, phoneNumber: String, telegram: String) -> Unit, - isErrorName: Boolean, - isErrorPost: Boolean, - isErrorTelegram: Boolean, - errorMessage: StringResource, - isErrorMessageVisible: Boolean + nameError: StringResource?, + postError: StringResource?, + telegramError: StringResource?, + phoneError: StringResource?, + popupInfo: PopupMessageInfo?, + onClosePopup: () -> Unit, ) { - val phone = if (phoneNumber.length > UserInfoValidator.phoneNumberSize) - phoneNumber.substring( - startIndex = phoneNumber.length % UserInfoValidator.phoneNumberSize, - ) - else - phoneNumber - - val userNameText = rememberSaveable { mutableStateOf(userName) } - val phoneNumberText = rememberSaveable { mutableStateOf(phone) } - val postText = rememberSaveable { mutableStateOf(post) } - val telegramText = rememberSaveable { mutableStateOf(telegram) } - - Box { - Column( - modifier = Modifier.fillMaxSize().background(ExtendedThemeColors.colors.whiteColor) - .padding(horizontal = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top - ) { - ProfileEditHeader(onReturnToProfile) - LazyColumn(modifier = Modifier.padding(top = 28.dp)) { - items(getAllUserDataEditProfile()) { item -> - when (item) { - UserDataTextFieldType.Phone -> { - Spacer(modifier = Modifier.height(16.dp)) - Text( - stringResource(item.title), - modifier = Modifier.padding(bottom = 8.dp), - style = MaterialTheme.typography.body1, - color = ExtendedThemeColors.colors.blackColor - ) - UserInfoTextField( - item = item, - text = phoneNumberText.value, - error = isErrorPhone, - visualTransformation = PhoneMaskTransformation, - keyboardType = KeyboardType.Phone, - onValueChange = { phoneNumberText.value = it } - ) - } - - UserDataTextFieldType.Person -> { - Spacer(modifier = Modifier.height(16.dp)) - Text( - stringResource(item.title), - modifier = Modifier.padding(bottom = 8.dp), - style = MaterialTheme.typography.body1, - color = ExtendedThemeColors.colors.blackColor - ) - UserInfoTextField( - item = item, - text = userNameText.value, - error = isErrorName, - onValueChange = { userNameText.value = it } - ) - } - - UserDataTextFieldType.Post -> { - Spacer(modifier = Modifier.height(16.dp)) - Text( - stringResource(item.title), - modifier = Modifier.padding(bottom = 8.dp), - style = MaterialTheme.typography.body1, - color = ExtendedThemeColors.colors.blackColor - ) - UserInfoTextField( - item = item, - text = postText.value, - error = isErrorPost, - onValueChange = { postText.value = it } - ) - } - - UserDataTextFieldType.Telegram -> { - Spacer(modifier = Modifier.height(16.dp)) - Text( - stringResource(item.title), - modifier = Modifier.padding(bottom = 8.dp), - style = MaterialTheme.typography.body1, - color = ExtendedThemeColors.colors.blackColor - ) - UserInfoTextField( - item = item, - text = telegramText.value, - error = isErrorTelegram, - onValueChange = { telegramText.value = it } - ) - } - } - } - item { - EffectiveButton( - onClick = { - onSaveChange( - userNameText.value, - postText.value, - phoneNumberText.value, - telegramText.value - ) - }, - buttonText = stringResource(MainRes.strings.save), - modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp, top = 32.dp) - ) - } - } + var userNameText by rememberSaveable { mutableStateOf(userName) } + var phoneNumberText by rememberSaveable { mutableStateOf(phoneNumber) } + var postText by rememberSaveable { mutableStateOf(post) } + var telegramText by rememberSaveable { mutableStateOf(telegram) } + val imageLoader = remember { generateImageLoader() } + val request = remember(imageUrl) { + ImageRequest { + data(imageUrl) } - SnackBarErrorMessage( - message = stringResource(errorMessage), - isVisible = isErrorMessageVisible, - modifier = Modifier.align(Alignment.BottomCenter), - ) } -} - -@Composable -fun ProfileEditHeader(onReturnToProfile: () -> Unit) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(top = 40.dp) + val painter = rememberImagePainter( + request = request, + imageLoader = imageLoader, + placeholderPainter = { painterResource(MainRes.images.logo_default) }, + errorPainter = { painterResource(MainRes.images.logo_default) } + ) + Column( + modifier = modifier + .fillMaxSize() + .background(EffectiveTheme.colors.background.primary) + .padding(16.dp), + verticalArrangement = Arrangement.Top ) { - IconButton(onClick = onReturnToProfile) { - Icon( - painter = painterResource(MainRes.images.back_button), - contentDescription = null, - tint = ExtendedThemeColors.colors.blackColor + Box(modifier = Modifier.fillMaxWidth()) { + TitlePage( + title = stringResource(MainRes.strings.profile), + modifier = Modifier.align(Alignment.Center), ) + IconButton( + onClick = onReturnToProfile, + modifier = Modifier.align(Alignment.CenterEnd), + ) { + Icon( + painter = painterResource(MainRes.images.ic_sing_out), + contentDescription = stringResource(MainRes.strings.exit), + modifier = Modifier.size(18.dp), + tint = EffectiveTheme.colors.icon.error, + ) + } + if (popupInfo != null) { + PopupMessage(info = popupInfo, onCloseClick = onClosePopup) + } } - TitlePage( - title = stringResource(MainRes.strings.profile_data), - modifier = Modifier.padding(start = 16.dp) + Spacer(modifier = Modifier.padding(24.dp)) + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .size(100.dp) + .border( + border = BorderStroke( + width = 2.dp, + color = EffectiveTheme.colors.stroke.accent, + ), + shape = CircleShape, + ) + .padding(2.dp) + .clip(CircleShape) + .align(Alignment.CenterHorizontally), + ) + Spacer(modifier = Modifier.height(20.dp)) + EditBlock( + icon = MainRes.images.ic_user_name, + iconTitle = stringResource(MainRes.strings.last_name_and_first_name), + userInfo = userNameText, + onInfoChange = { userNameText = it }, + errorText = nameError?.let { stringResource(it) }, + ) + Spacer(modifier = Modifier.height(8.dp)) + EditBlock( + icon = MainRes.images.ic_user_post, + iconTitle = stringResource(MainRes.strings.post), + userInfo = postText, + onInfoChange = { postText = it }, + errorText = postError?.let { stringResource(it) }, + ) + Spacer(modifier = Modifier.height(8.dp)) + EditBlock( + icon = MainRes.images.ic_telegram, + iconTitle = stringResource(MainRes.strings.telegram), + userInfo = telegramText, + onInfoChange = { telegramText = it }, + errorText = telegramError?.let { stringResource(it) }, + ) + Spacer(modifier = Modifier.height(8.dp)) + EditBlock( + icon = MainRes.images.ic_phone, + iconTitle = stringResource(MainRes.strings.phone_number), + userInfo = phoneNumberText, + onInfoChange = { phoneNumberText = it }, + errorText = phoneError?.let { stringResource(it) }, + ) + Spacer(modifier = Modifier.height(16.dp)) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + onSaveChange( + userNameText, + postText, + phoneNumberText, + telegramText + ) + }, + buttonText = stringResource(MainRes.strings.save), ) } } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStore.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStore.kt index 2dcfa3bb938b85810370d7332cffc7294cdf89aa..7e87e0f26584a2c449c12d1154be3561d94993a2 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStore.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStore.kt @@ -4,25 +4,37 @@ import band.effective.office.elevator.domain.models.User import com.arkivanov.mvikotlin.core.store.Store import dev.icerock.moko.resources.StringResource -interface ProfileEditStore : Store{ +interface ProfileEditStore : + Store { sealed interface Intent { - object BackInProfileClicked : Intent - data class SaveChangeClicked (val userName:String, val telegram:String, val post:String, val phoneNumber:String) : Intent + + data object BackInProfileClicked : Intent + + data class SaveChangesClicked( + val userName: String, + val telegram: String, + val post: String, + val phoneNumber: String + ) : Intent } - data class State( - val isData: Boolean = false, - val isLoading: Boolean = true, - val user: User, - val isErrorPhone: Boolean = false, - var isErrorName: Boolean = false, - var isErrorPost: Boolean = false, - var isErrorTelegram: Boolean = false - ) + sealed interface State { + + data object Loading : State + + data class Data( + val user: User, + val phoneNumberError: StringResource? = null, + val nameError: StringResource? = null, + val postError: StringResource? = null, + val telegramError: StringResource? = null, + ) : State + } sealed interface Label { - object ReturnedInProfile : Label - object SavedChange : Label - data class Error(val name:StringResource): Label + + data object ReturnedInProfile : Label + data object SavedChange : Label + data object ServerError : Label } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStoreFactory.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStoreFactory.kt index 65b71cb197e91483fd86ca2fb8dedf7a62924ec2..711744b653533f42e46b91443a9aa18f733f272a 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStoreFactory.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/editProfile/store/ProfileEditStoreFactory.kt @@ -1,10 +1,9 @@ package band.effective.office.elevator.ui.profile.editProfile.store -import band.effective.office.elevator.MainRes import band.effective.office.elevator.domain.models.User import band.effective.office.elevator.domain.useCase.GetUserUseCase import band.effective.office.elevator.domain.useCase.UpdateUserUseCase -import band.effective.office.elevator.ui.models.validator.UserInfoValidator +import band.effective.office.elevator.domain.validator.ExtendedUserInfoValidator import band.effective.office.elevator.ui.profile.editProfile.store.ProfileEditStore.* import band.effective.office.network.model.Either import com.arkivanov.mvikotlin.core.store.Reducer @@ -13,6 +12,7 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.utils.ExperimentalMviKotlinApi import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor import com.arkivanov.mvikotlin.extensions.coroutines.coroutineBootstrapper +import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.launch @@ -26,171 +26,146 @@ internal class ProfileEditStoreFactory( private val getUserUseCase: GetUserUseCase by inject() private val updateUserUseCase: UpdateUserUseCase by inject() - private val validator: UserInfoValidator = UserInfoValidator() + private val validator: ExtendedUserInfoValidator by inject() @OptIn(ExperimentalMviKotlinApi::class) fun create(): ProfileEditStore = - object : ProfileEditStore, - Store - by storeFactory.create( - name = "ProfileEditStore", - initialState = State(user = User.defaultUser), - bootstrapper = coroutineBootstrapper { - dispatch(Action.FetchUserInfo) - }, - executorFactory = ::ExecutorImpl, - reducer = ReducerImpl - ) {} + object : ProfileEditStore, Store by storeFactory.create( + name = "ProfileEditStore", + initialState = State.Loading, + bootstrapper = coroutineBootstrapper { + dispatch(Action.FetchUserInfo) + }, + executorFactory = ::ExecutorImpl, + reducer = ReducerImpl, + ) {} private sealed interface Action { - object FetchUserInfo : Action + data object FetchUserInfo : Action } private sealed interface Msg { data class ProfileData(val user: User) : Msg - data class ErrorPhone( - val errorPhone: Boolean - ) : Msg - data class ErrorName( - val isNameError: Boolean - ) : Msg + data class PhoneError(val message: StringResource) : Msg + data class NameError(val message: StringResource) : Msg + data class PostError(val message: StringResource) : Msg + data class TelegramError(val message: StringResource) : Msg - data class ErrorPost( - val isPostError: Boolean - ) : Msg - - data class ErrorTelegram( - val isTelegramError: Boolean - ) : Msg + data object ValidPhone : Msg + data object ValidName : Msg + data object ValidPost : Msg + data object ValidTelegram : Msg } private inner class ExecutorImpl : CoroutineExecutor() { + override fun executeIntent(intent: Intent, getState: () -> State) { + val state = getState() + when (intent) { - Intent.BackInProfileClicked -> doReturnProfile() - is Intent.SaveChangeClicked -> doSaveChange(getState(), intent) + Intent.BackInProfileClicked -> returnToProfile() + is Intent.SaveChangesClicked -> { + when (state) { + State.Loading -> return + is State.Data -> saveChanges(state, intent) + } + } } } - private fun doSaveChange(user: State, intent: Intent.SaveChangeClicked) { - scope.launch { - val uptUser = User( - id = user.user.id, - imageUrl = user.user.imageUrl, - userName = intent.userName, post = intent.post, + private fun saveChanges(data: State.Data, intent: Intent.SaveChangesClicked) { + scope.launch { + val updatedUser = data.user.copy( + userName = intent.userName, + post = intent.post, phoneNumber = intent.phoneNumber, telegram = intent.telegram, - email = user.user.email, ) - dispatch(Msg.ProfileData(user = uptUser)) - if (checkPhoneNumber(intent.phoneNumber) - && checkUserdata(userName = intent.userName) - && checkPost( - intent.post) && checkTelegram(intent.telegram) - ) { - updateUserUseCase.execute(uptUser).collect{ user -> - withContext(Dispatchers.Main) { - when (user) { - is Either.Success -> { - dispatch(Msg.ProfileData(user = user.data)) - publish(Label.SavedChange) - } - - is Either.Error -> { - publish(Label.Error(MainRes.strings.server_error)) - } + dispatch(Msg.ProfileData(user = updatedUser)) + val isPhoneNumberValid = checkPhoneNumber(intent.phoneNumber) + val isUserValid = checkUserdata(intent.userName) + val isPostValid = checkPost(intent.post) + val isTelegramValid = checkTelegram(intent.telegram) + + if (isPhoneNumberValid && isUserValid && isPostValid && isTelegramValid) { + withContext(Dispatchers.Main) { + updateUserUseCase.execute(updatedUser).collect { user -> + when (user) { + is Either.Success -> { + dispatch(Msg.ProfileData(user = updatedUser)) + publish(Label.SavedChange) + returnToProfile() } + is Either.Error -> { + publish(Label.ServerError) + } } - + } } - } } - } private fun checkTelegram(telegram: String): Boolean { - return if (validator.checkTelegramNick(telegram)) { - dispatch( - Msg.ErrorTelegram( - isTelegramError = false - ) - ) - true - } else { - dispatch( - Msg.ErrorTelegram( - isTelegramError = true - ) - ) - publish(Label.Error(MainRes.strings.telegram_format_error)) - false + return when (val result = validator.checkTelegramNick(telegram)) { + is ExtendedUserInfoValidator.Result.Invalid -> { + dispatch(Msg.TelegramError(result.message)) + false + } + + ExtendedUserInfoValidator.Result.Valid -> { + dispatch(Msg.ValidTelegram) + true + } } } private fun checkPost(post: String): Boolean { - return if (validator.checkPost(post)) { - dispatch( - Msg.ErrorPost( - isPostError = false - ) - ) - true - } else { - dispatch( - Msg.ErrorPost( - isPostError = true - ) - ) - publish(Label.Error(MainRes.strings.profile_post_format_error)) - false + return when (val result = validator.checkPost(post)) { + is ExtendedUserInfoValidator.Result.Invalid -> { + dispatch(Msg.PostError(result.message)) + false + } + + ExtendedUserInfoValidator.Result.Valid -> { + dispatch(Msg.ValidPost) + true + } } } private fun checkUserdata(userName: String): Boolean { - return if (validator.checkName(userName)) { - dispatch( - Msg.ErrorName( - isNameError = false - ) - ) - true - } else { - dispatch( - Msg.ErrorName( - isNameError = true - ) - ) - publish(Label.Error(MainRes.strings.profile_name_format_error)) - false + return when (val result = validator.checkName(userName)) { + is ExtendedUserInfoValidator.Result.Invalid -> { + dispatch(Msg.NameError(result.message)) + false + } + + ExtendedUserInfoValidator.Result.Valid -> { + dispatch(Msg.ValidName) + true + } } } private fun checkPhoneNumber(phone: String): Boolean { - return if (validator.checkPhone(phone)) { - dispatch( - Msg.ErrorPhone( - errorPhone = false - ) - ) - true - } else { - dispatch( - Msg.ErrorPhone( - errorPhone = true - ) - ) - publish(Label.Error(MainRes.strings.number_format_error)) - false - } + return when (val result = validator.checkPhone(phone)) { + is ExtendedUserInfoValidator.Result.Invalid -> { + dispatch(Msg.PhoneError(result.message)) + false + } + ExtendedUserInfoValidator.Result.Valid -> { + dispatch(Msg.ValidPhone) + true + } + } } - override fun executeAction(action: Action, getState: () -> State) { when (action) { Action.FetchUserInfo -> fetchUserInfo() @@ -219,28 +194,35 @@ internal class ProfileEditStoreFactory( } } - private fun doReturnProfile() { + private fun returnToProfile() { publish(Label.ReturnedInProfile) } } private object ReducerImpl : Reducer { - override fun State.reduce(message: Msg): State = - when (message) { - is Msg.ProfileData -> { - copy( - isData = true, - isLoading = false, - user = message.user - ) + override fun State.reduce(msg: Msg): State { + return when (this) { + State.Loading -> { + when (msg) { + is Msg.ProfileData -> State.Data(user = msg.user) + else -> this + } } - is Msg.ErrorPhone -> - copy(isErrorPhone = message.errorPhone) - - is Msg.ErrorName -> copy(isErrorName = message.isNameError) - is Msg.ErrorPost -> copy(isErrorPost = message.isPostError) - is Msg.ErrorTelegram -> copy(isErrorTelegram = message.isTelegramError) + is State.Data -> { + when (msg) { + is Msg.ProfileData -> State.Data(user = msg.user) + is Msg.NameError -> copy(nameError = msg.message) + is Msg.PhoneError -> copy(phoneNumberError = msg.message) + is Msg.PostError -> copy(postError = msg.message) + is Msg.TelegramError -> copy(telegramError = msg.message) + Msg.ValidName -> copy(nameError = null) + Msg.ValidPhone -> copy(phoneNumberError = null) + Msg.ValidPost -> copy(postError = null) + Msg.ValidTelegram -> copy(telegramError = null) + } + } } + } } -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/ProfileScreen.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/ProfileScreen.kt index ec2bdb6ee518efd7e3e479fed6785b3bf2618ee4..2fc1798d65af262f73be1121628c068c9e57b41d 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/ProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/ProfileScreen.kt @@ -3,52 +3,46 @@ package band.effective.office.elevator.ui.profile.mainProfile import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedButton -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import band.effective.office.elevator.ExtendedThemeColors +import band.effective.office.elevator.EffectiveTheme import band.effective.office.elevator.MainRes import band.effective.office.elevator.components.LoadingIndicator import band.effective.office.elevator.components.TitlePage +import band.effective.office.elevator.components.UserDetails import band.effective.office.elevator.components.generateImageLoader -import band.effective.office.elevator.textGrayColor -import band.effective.office.elevator.ui.models.UserData -import band.effective.office.elevator.ui.models.getAllUserDataProfile +import band.effective.office.elevator.expects.setClipboardText +import band.effective.office.elevator.ui.employee.aboutEmployee.components.EmployeeInfo import band.effective.office.elevator.ui.profile.mainProfile.store.ProfileStore +import band.effective.office.elevator.utils.prettifyPhoneNumber import com.seiko.imageloader.model.ImageRequest -import com.seiko.imageloader.rememberAsyncImagePainter import com.seiko.imageloader.rememberImagePainter import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import io.github.aakira.napier.Napier @Composable @@ -62,197 +56,167 @@ fun ProfileScreen(component: MainProfileComponent) { } } } - ProfileScreenContent( imageUrl = user.user.imageUrl, userName = user.user.userName, post = user.user.post, telegram = user.user.telegram, + email = user.user.email, isLoading = user.isLoading, phoneNumber = user.user.phoneNumber, - id = user.user.id, onSignOut = { component.onEvent(ProfileStore.Intent.SignOutClicked) }, - onEditProfile = { id -> + onEditProfile = { component.onOutput( - MainProfileComponent.Output.NavigateToEdit( - userEdit = id - ) + MainProfileComponent.Output.NavigateToEdit(userEdit = user.user.id) ) } ) } - @Composable -internal fun ProfileScreenContent( +private fun ProfileScreenContent( + modifier: Modifier = Modifier, imageUrl: String, userName: String, post: String, telegram: String, phoneNumber: String, + email: String?, onSignOut: () -> Unit, - onEditProfile: (id: String) -> Unit, - id: String, - isLoading: Boolean + onEditProfile: () -> Unit, + isLoading: Boolean, ) { Column( - modifier = Modifier.fillMaxSize().background(Color.White), + modifier = modifier + .fillMaxSize() + .background(EffectiveTheme.colors.background.primary) + .padding(horizontal = 16.dp) + .padding(top = 40.dp, bottom = 24.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top + verticalArrangement = Arrangement.Top, ) { - Row( - verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .padding(horizontal = 16.dp).fillMaxWidth().padding(top = 40.dp) + Box( + modifier = modifier.fillMaxWidth() ) { TitlePage( - stringResource(MainRes.strings.profile) + title = stringResource(MainRes.strings.profile), + modifier = Modifier.align(Alignment.Center) ) - Spacer(modifier = Modifier.weight(.1f)) - OutlinedButton( + IconButton( onClick = onSignOut, - shape = RoundedCornerShape(size = 8.dp), - border = BorderStroke(1.dp, MaterialTheme.colors.secondary), - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + modifier = Modifier.align(Alignment.CenterEnd), ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - painter = painterResource(MainRes.images.exit), - contentDescription = null, - tint = MaterialTheme.colors.secondary - ) - Text( - stringResource(MainRes.strings.exit), - style = MaterialTheme.typography.body2, - modifier = Modifier.padding(start = 8.dp) - ) - } + Icon( + painter = painterResource(MainRes.images.ic_sing_out), + contentDescription = stringResource(MainRes.strings.exit), + modifier = Modifier.size(18.dp), + tint = EffectiveTheme.colors.icon.error, + ) } } + Spacer(modifier = Modifier.padding(24.dp)) + val localUriHandler = LocalUriHandler.current when (isLoading) { true -> LoadingIndicator() false -> { - ProfileInfoAboutUser(imageUrl, userName, post, { onEditProfile(id) }, id) - - LazyColumn(modifier = Modifier.fillMaxSize().padding(top = 24.dp)) - { - items(getAllUserDataProfile()) { item -> - FieldsItemStyle( - item = item, - { onEditProfile(id) }, - id = id, - telegram = telegram, - phoneNumber = phoneNumber - ) - } - } + UserInfoBlock( + imageUrl = imageUrl, + userName = userName, + userPost = post, + telegram = telegram, + email = email, + phoneNumber = phoneNumber, + onOpenUri = { localUriHandler.openUri(it) }, + onCopyText = { value, label -> setClipboardText(value, label) }, + ) } } + Spacer(modifier = Modifier.padding(24.dp)) + Text( + text = stringResource(MainRes.strings.edit_profile), + style = EffectiveTheme.typography.mMedium, + color = EffectiveTheme.colors.text.secondary, + modifier = Modifier.clickable(onClick = { onEditProfile() }), + textAlign = TextAlign.Center, + ) } } @Composable -fun ProfileInfoAboutUser( +private fun UserInfoBlock( imageUrl: String, userName: String, - post: String, - onEditProfile: (id: String) -> Unit, - id: String + userPost: String, + telegram: String?, + email: String?, + phoneNumber: String?, + onOpenUri: (uri: String) -> Unit, + onCopyText: (value: String, label: String) -> Unit, ) { - val imageLoader = remember { generateImageLoader() } - - val request = remember(imageUrl) { - ImageRequest { - data(imageUrl) + Column { + val imageLoader = remember { generateImageLoader() } + val request = remember(imageUrl) { + ImageRequest { + data(imageUrl) + } } - } - - val painter = rememberImagePainter( - request = request, - imageLoader = imageLoader, - placeholderPainter = { painterResource(MainRes.images.logo_default) }, - errorPainter = { painterResource(MainRes.images.logo_default) } - ) + val painter = rememberImagePainter( + request = request, + imageLoader = imageLoader, + placeholderPainter = { painterResource(MainRes.images.logo_default) }, + errorPainter = { painterResource(MainRes.images.logo_default) } + ) - Box { - Surface( - modifier = Modifier.size(88.dp).align(Alignment.Center), - shape = CircleShape, - color = ExtendedThemeColors.colors.purple_heart_100 - ) { - Image( - modifier = Modifier.fillMaxSize().align(Alignment.Center), - painter = painter, - contentScale = ContentScale.Crop, - contentDescription = null, + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .size(100.dp) + .border( + BorderStroke( + width = 2.dp, + color = EffectiveTheme.colors.stroke.accent, + ), + shape = CircleShape, + ) + .padding(2.dp) + .clip(CircleShape) + .align(Alignment.CenterHorizontally), + ) + Spacer(modifier = Modifier.padding(12.dp)) + UserDetails(userName = userName, post = userPost) + Spacer(modifier = Modifier.padding(24.dp)) + if (telegram != null) { + val iconTitle = stringResource(MainRes.strings.telegram) + EmployeeInfo( + icon = MainRes.images.ic_telegram, + value = telegram, + iconTitle = iconTitle, + onClick = { onOpenUri("https://t.me/$telegram") }, + onLongClick = { onCopyText(telegram, iconTitle) }, ) + Spacer(modifier = Modifier.height(8.dp)) } - IconButton( - onClick = { onEditProfile(id) }, - modifier = Modifier.size(24.dp).align(Alignment.TopEnd) - ) { - Image( - painter = painterResource(MainRes.images.edit_profile_image), - contentDescription = null, - contentScale = ContentScale.Fit + if (email != null) { + val iconTitle = stringResource(MainRes.strings.email) + EmployeeInfo( + icon = MainRes.images.ic_email, + value = email, + iconTitle = iconTitle, + onClick = { onOpenUri("mailto:$email") }, + onLongClick = { onCopyText(email, iconTitle) }, ) + Spacer(modifier = Modifier.height(8.dp)) } - } - - Text( - userName, - style = MaterialTheme.typography.subtitle1, - color = Color.Black, - modifier = Modifier.padding(top = 12.dp) - ) - Text( - post, - style = MaterialTheme.typography.subtitle1, - color = textGrayColor, - modifier = Modifier.padding(top = 8.dp) - ) -} - - -@Composable -private fun FieldsItemStyle( - item: UserData, - onEditProfile: (id: String) -> Unit, - id: String, - phoneNumber: String, - telegram: String -) { - Row( - verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .padding(horizontal = 16.dp).fillMaxWidth() - ) { - Icon( - painter = painterResource(item.icon), - contentDescription = null, - tint = textGrayColor - ) - Text( - stringResource(item.title), - style = MaterialTheme.typography.subtitle1, - color = textGrayColor, - modifier = Modifier.padding(start = 12.dp) - ) - Spacer(modifier = Modifier.weight(.1f)) - var text = when (item) { - UserData.Phone -> phoneNumber - UserData.Telegram -> telegram - } - Text( - text = text, - style = MaterialTheme.typography.subtitle1, - color = ExtendedThemeColors.colors.blackColor - ) - IconButton(onClick = { onEditProfile(id) }) { - Icon( - painter = painterResource(MainRes.images.next), - contentDescription = null, - tint = ExtendedThemeColors.colors.purple_heart_700 + if (phoneNumber != null) { + val iconTitle = stringResource(MainRes.strings.phone_number) + EmployeeInfo( + icon = MainRes.images.ic_phone, + value = prettifyPhoneNumber(phoneNumber) ?: phoneNumber, + iconTitle = iconTitle, + onClick = { onOpenUri("tel:$phoneNumber") }, + onLongClick = { onCopyText(phoneNumber, iconTitle) }, ) } } - Divider(color = textGrayColor, thickness = 1.dp) } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/store/ProfileStoreFactory.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/store/ProfileStoreFactory.kt index 2d218440edc969f6591ddccffabd196a298ae990..71859565fe5405b08c73ca0bca2f3f6cbb4b6d99 100644 --- a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/store/ProfileStoreFactory.kt +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/ui/profile/mainProfile/store/ProfileStoreFactory.kt @@ -1,6 +1,5 @@ package band.effective.office.elevator.ui.profile.mainProfile.store -import band.effective.office.elevator.domain.GoogleSignIn import band.effective.office.elevator.domain.models.User import band.effective.office.elevator.domain.useCase.AuthorizationUseCase import band.effective.office.elevator.domain.useCase.GetUserUseCase @@ -68,6 +67,7 @@ internal class ProfileStoreFactory( private fun fetchUserInfo() { scope.launch(Dispatchers.IO) { + getUserUseCase getUserUseCase.execute().collect { user -> withContext(Dispatchers.Main) { when (user) { @@ -94,5 +94,4 @@ internal class ProfileStoreFactory( isLoading = false) } } - } diff --git a/composeApp/src/commonMain/kotlin/band/effective/office/elevator/utils/PrettifyPhoneNumber.kt b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/utils/PrettifyPhoneNumber.kt new file mode 100644 index 0000000000000000000000000000000000000000..fc9731aa941b68cec337870c55096aa24d02c7a6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/band/effective/office/elevator/utils/PrettifyPhoneNumber.kt @@ -0,0 +1,34 @@ +package band.effective.office.elevator.utils + +private const val FORMAT = "+# ### ### ##-##" + +internal fun prettifyPhoneNumber(rawPhoneNumber: String): String? { + var formatIndex = 0 + var inputIndex = 0 + + val stringBuilder = StringBuilder() + while (formatIndex < FORMAT.length && inputIndex < rawPhoneNumber.length) { + val currentFormatChar = FORMAT[formatIndex] + val currentInputChar = rawPhoneNumber[inputIndex] + + if (currentFormatChar == '#') { + if (currentInputChar !in '0'..'9') { + return null + } + stringBuilder.append(currentInputChar) + inputIndex++ + } else { + if (currentFormatChar == currentInputChar) { + inputIndex++ + } + stringBuilder.append(currentFormatChar) + } + formatIndex++ + } + + if (inputIndex != rawPhoneNumber.length || formatIndex != FORMAT.length) { + return null + } + + return stringBuilder.toString() +} diff --git a/composeApp/src/commonMain/moko-resources/base/strings_ru.xml b/composeApp/src/commonMain/moko-resources/base/strings_ru.xml index 5963aa3cd8457386591aa5d1fdf25bd79296436f..24f700120b46e0c0dd9bcd580c3bacb3f28f2fb4 100644 --- a/composeApp/src/commonMain/moko-resources/base/strings_ru.xml +++ b/composeApp/src/commonMain/moko-resources/base/strings_ru.xml @@ -1,31 +1,44 @@ Office Elevator - Авторизация через Google + Войти через Google Что-то пошло не так. Пожалуйста попробуйте еще раз - effective\noffice + effective + OFFICE Поиск сотрудника В офисе + Назад + Закрыть + Продолжить - - - 955-555-55-55 - Иван Иванов - Android-developer - telegramnick - - Введите номер - Укажите номер телефона, привязанный к СБП - Заполните профиль - Это необходимо для того, чтобы сотрудники смогли вас узнать - Укажите Telegram - Это необходимо для того, чтобы коллегам было удобно связаться с вами + Вперед бронировать! + Забронировать рабочее место + У вас пока нет занятых мест + Забронировать рабочее место можно по кнопке ниже + Это вы! + Теперь у вас есть профиль + + + + Номер телефона + Имя фамилия + Должность + Ник в телеграме + + Введите номер телефона + Чтобы коллеги смогли переводить \nвам деньги + Расскажите как вас зовут \nи кем вы работаете + Чтобы коллеги смогли вас узнать + Поделитесь вашим ником \nв телеграме + Чтобы коллеги смогли написать вам +7 @ + Всю информацию можно будет изменить в профиле + Фильтры @@ -131,8 +144,8 @@ Профиль Лифт Главная - Бронирование - Сотрудники + Брони + Коллеги Продолжить @@ -150,9 +163,19 @@ Заполните имя и фамилию Заполните должность Укажите ник в телеграмме + Поле должно быть заполнено + Имя должно быть написано на латинице + Ник должен начинаться с @ + Неверный номер + + Нет интернета + Проверьте подключение к интернету и повторите попытку + Повторить попытку + Не бронировать стол Лифт успешно вызван! + Вы изменили личные данные Выйти @@ -165,6 +188,7 @@ Должность Номер телефона Сохранить + Редактировать профиль %s числа @@ -178,13 +202,17 @@ На это время нет мест. Выберите другое время - О сотруднике + О коллеге Брони Перевод У этого сотрудника пока нет бронирований У этого сотрудника пока нет таких бронирований У этого сотрудника нет бронирований на этот день У этого сотрудника нет таких бронирований на этот день + Очистить текстовое поле + Телеграм + Почта + Телефон Номер телефона был успешно скопирован! diff --git a/composeApp/src/commonMain/moko-resources/en/strings_en.xml b/composeApp/src/commonMain/moko-resources/en/strings_en.xml index 696d4407413ed86e1320cc123f61e581ef07aa70..45607a5d225acc18566f4dbe0a4049a882e5e3b5 100644 --- a/composeApp/src/commonMain/moko-resources/en/strings_en.xml +++ b/composeApp/src/commonMain/moko-resources/en/strings_en.xml @@ -1,14 +1,17 @@ - Office Elevator - Sign in with google + Effective Office + Sign in with Google Something went wrong. Please try again - effective\noffice + effective + OFFICE Search employee In office Will be today No reservations + Back + Close @@ -18,11 +21,11 @@ 955-555-55-55 Fill in your profile This is necessary for employees to recognize you - Ivan Ivanov - Android-developer + Name Surname + Post Specify Telegram This is necessary for colleagues to be able to contact you conveniently - telegramnick + Telegram Period selection @@ -39,6 +42,14 @@ Cansel Delete OK + + Go ahead to book! + Book a work place + You don't have any occupied places yet + You can book a work place by clicking the button below + It's you! + Now you have a profile + Type booking Working place @@ -107,6 +118,8 @@ Continue + Information can be changed in the profile screen + Profile Main @@ -131,13 +144,22 @@ Specify nickname in telegram +7 @ + Field must be not empty + Name must consist of latin letters + Nickname must begin with @ + Invalid phone format + No Internet + Please check your internet connection and try again + Try Again + Do not book a table from %s to %s Successfully! + Changes saved successfully Exit @@ -161,6 +183,7 @@ Post Telephone number Save + Edit profile About the employee @@ -170,6 +193,10 @@ This employee dont have such bookings yet This employee dont have bookings on this date This employee dont have such bookings on this date + Clear text field + Telegram + Email + Phone number Phone number was successfully copied! diff --git a/composeApp/src/commonMain/moko-resources/images/effective_logo@0.75x.png b/composeApp/src/commonMain/moko-resources/images/effective_logo@0.75x.png index 008f8c920f5136b5f6d69646deb7826f3db2e1c0..9743b33a97e466a1a458f4f8bb10dc78eeea7c46 100644 Binary files a/composeApp/src/commonMain/moko-resources/images/effective_logo@0.75x.png and b/composeApp/src/commonMain/moko-resources/images/effective_logo@0.75x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/effective_logo@1.5x.png b/composeApp/src/commonMain/moko-resources/images/effective_logo@1.5x.png index 008f8c920f5136b5f6d69646deb7826f3db2e1c0..63413d30eeb94a885592edf14aea5dd30e8750f1 100644 Binary files a/composeApp/src/commonMain/moko-resources/images/effective_logo@1.5x.png and b/composeApp/src/commonMain/moko-resources/images/effective_logo@1.5x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/effective_logo@1x.png b/composeApp/src/commonMain/moko-resources/images/effective_logo@1x.png index 008f8c920f5136b5f6d69646deb7826f3db2e1c0..c7ed2ac3c239f0e9158691b48451e5096f7916d3 100644 Binary files a/composeApp/src/commonMain/moko-resources/images/effective_logo@1x.png and b/composeApp/src/commonMain/moko-resources/images/effective_logo@1x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/effective_logo@2x.png b/composeApp/src/commonMain/moko-resources/images/effective_logo@2x.png index 008f8c920f5136b5f6d69646deb7826f3db2e1c0..0fed99555c27de8008722438aad1add515a81dde 100644 Binary files a/composeApp/src/commonMain/moko-resources/images/effective_logo@2x.png and b/composeApp/src/commonMain/moko-resources/images/effective_logo@2x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/effective_logo@3x.png b/composeApp/src/commonMain/moko-resources/images/effective_logo@3x.png index 008f8c920f5136b5f6d69646deb7826f3db2e1c0..13dfd43afe309951e71519e0c6359bb41e77d857 100644 Binary files a/composeApp/src/commonMain/moko-resources/images/effective_logo@3x.png and b/composeApp/src/commonMain/moko-resources/images/effective_logo@3x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/effective_logo@4x.png b/composeApp/src/commonMain/moko-resources/images/effective_logo@4x.png index 008f8c920f5136b5f6d69646deb7826f3db2e1c0..b73b11f17936c3828ad89eb661d36df2a1097593 100644 Binary files a/composeApp/src/commonMain/moko-resources/images/effective_logo@4x.png and b/composeApp/src/commonMain/moko-resources/images/effective_logo@4x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/google_icon@0.75x.png b/composeApp/src/commonMain/moko-resources/images/google_icon@0.75x.png deleted file mode 100644 index 494acede0c520f847f75982ca1f671fc6eaa889e..0000000000000000000000000000000000000000 Binary files a/composeApp/src/commonMain/moko-resources/images/google_icon@0.75x.png and /dev/null differ diff --git a/composeApp/src/commonMain/moko-resources/images/google_icon@1.5x.png b/composeApp/src/commonMain/moko-resources/images/google_icon@1.5x.png deleted file mode 100644 index 494acede0c520f847f75982ca1f671fc6eaa889e..0000000000000000000000000000000000000000 Binary files a/composeApp/src/commonMain/moko-resources/images/google_icon@1.5x.png and /dev/null differ diff --git a/composeApp/src/commonMain/moko-resources/images/google_icon@1x.png b/composeApp/src/commonMain/moko-resources/images/google_icon@1x.png deleted file mode 100644 index 494acede0c520f847f75982ca1f671fc6eaa889e..0000000000000000000000000000000000000000 Binary files a/composeApp/src/commonMain/moko-resources/images/google_icon@1x.png and /dev/null differ diff --git a/composeApp/src/commonMain/moko-resources/images/google_icon@2x.png b/composeApp/src/commonMain/moko-resources/images/google_icon@2x.png deleted file mode 100644 index 494acede0c520f847f75982ca1f671fc6eaa889e..0000000000000000000000000000000000000000 Binary files a/composeApp/src/commonMain/moko-resources/images/google_icon@2x.png and /dev/null differ diff --git a/composeApp/src/commonMain/moko-resources/images/google_icon@3x.png b/composeApp/src/commonMain/moko-resources/images/google_icon@3x.png deleted file mode 100644 index 494acede0c520f847f75982ca1f671fc6eaa889e..0000000000000000000000000000000000000000 Binary files a/composeApp/src/commonMain/moko-resources/images/google_icon@3x.png and /dev/null differ diff --git a/composeApp/src/commonMain/moko-resources/images/google_icon@4x.png b/composeApp/src/commonMain/moko-resources/images/google_icon@4x.png deleted file mode 100644 index 494acede0c520f847f75982ca1f671fc6eaa889e..0000000000000000000000000000000000000000 Binary files a/composeApp/src/commonMain/moko-resources/images/google_icon@4x.png and /dev/null differ diff --git a/composeApp/src/commonMain/moko-resources/images/ic_booking.svg b/composeApp/src/commonMain/moko-resources/images/ic_booking.svg new file mode 100644 index 0000000000000000000000000000000000000000..32be1aa63c5ebee6fffeada78113f35feabf02a5 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_booking.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_close.svg b/composeApp/src/commonMain/moko-resources/images/ic_close.svg new file mode 100644 index 0000000000000000000000000000000000000000..88681fab3f0c10cb98a123d163f23eb38c69bc18 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_close.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_cross.svg b/composeApp/src/commonMain/moko-resources/images/ic_cross.svg new file mode 100644 index 0000000000000000000000000000000000000000..6a8ede0437ccc2cb5c9d177b3285e46091aa739f --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_effective_triangle.svg b/composeApp/src/commonMain/moko-resources/images/ic_effective_triangle.svg new file mode 100644 index 0000000000000000000000000000000000000000..a598e3869b55ece5a27863f32593077a0ec090e7 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_effective_triangle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_email.svg b/composeApp/src/commonMain/moko-resources/images/ic_email.svg new file mode 100644 index 0000000000000000000000000000000000000000..b5122f8cb5cc9dcb342a38116743911aa2c8852a --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_email.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_employee.svg b/composeApp/src/commonMain/moko-resources/images/ic_employee.svg new file mode 100644 index 0000000000000000000000000000000000000000..0323b0c19c8e95c8dc53f0a72e1979e6543ae775 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_employee.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_google@1x.png b/composeApp/src/commonMain/moko-resources/images/ic_google@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..49aee3bd90c4af9ccd2f219ed21bd3dab662f35c Binary files /dev/null and b/composeApp/src/commonMain/moko-resources/images/ic_google@1x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/ic_logo.svg b/composeApp/src/commonMain/moko-resources/images/ic_logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..aae735d5e2157e7e8f66f19e8b8271cd459699e1 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_phone.svg b/composeApp/src/commonMain/moko-resources/images/ic_phone.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa7887d9cdb014b4b63150a8aa77c8b0f5e9eb80 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_profile.svg b/composeApp/src/commonMain/moko-resources/images/ic_profile.svg new file mode 100644 index 0000000000000000000000000000000000000000..58500b9d1894d3a12f0b82a01ae8b0f72da685d8 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_profile.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_search.svg b/composeApp/src/commonMain/moko-resources/images/ic_search.svg new file mode 100644 index 0000000000000000000000000000000000000000..eecdf571d7a0f9615c3347dfe9b4426da66303a1 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_search.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_sing_out.svg b/composeApp/src/commonMain/moko-resources/images/ic_sing_out.svg new file mode 100644 index 0000000000000000000000000000000000000000..307df118f8901ce6f2c012ade27d9263e3de24e7 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_sing_out.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_success.svg b/composeApp/src/commonMain/moko-resources/images/ic_success.svg new file mode 100644 index 0000000000000000000000000000000000000000..1a9819ff5cac01d38012cb9fa445610227daa275 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_success.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_telegram.svg b/composeApp/src/commonMain/moko-resources/images/ic_telegram.svg new file mode 100644 index 0000000000000000000000000000000000000000..6cee4f87ead3376e9125ae6be167163229a4d403 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_telegram.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_user_name.svg b/composeApp/src/commonMain/moko-resources/images/ic_user_name.svg new file mode 100644 index 0000000000000000000000000000000000000000..1889c193c6c50317081b69793208ec7a2c81aa14 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_user_name.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_user_post.svg b/composeApp/src/commonMain/moko-resources/images/ic_user_post.svg new file mode 100644 index 0000000000000000000000000000000000000000..3ef8179e126de873697b745757d7110f1d880276 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_user_post.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/ic_wifi.svg b/composeApp/src/commonMain/moko-resources/images/ic_wifi.svg new file mode 100644 index 0000000000000000000000000000000000000000..b2c0b5a3eeaeec3dd4b060d973a2c70191fecae9 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/ic_wifi.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/composeApp/src/commonTest/kotlin/band/effective/office/elevator/utils/PrettifyPhoneNumberTest.kt b/composeApp/src/commonTest/kotlin/band/effective/office/elevator/utils/PrettifyPhoneNumberTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..60314b0adb6367cccd3716fdc055e285a93bead5 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/band/effective/office/elevator/utils/PrettifyPhoneNumberTest.kt @@ -0,0 +1,44 @@ +package band.effective.office.elevator.utils + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class PrettifyPhoneNumberTest { + + @Test + fun worksWellForPhoneNumbersWithoutPlus() { + val prettifiedPhoneNumber = prettifyPhoneNumber("79136188085") + assertEquals("+7 913 618 80-85", prettifiedPhoneNumber) + } + + @Test + fun worksWellForPhoneNumberWithPlus() { + val prettifiedPhoneNumber = prettifyPhoneNumber("+79136188085") + assertEquals("+7 913 618 80-85", prettifiedPhoneNumber) + } + + @Test + fun returnsNullForPhoneNumberWithInvalidCharacter() { + val prettifiedPhoneNumber = prettifyPhoneNumber("sdsdsdsdsd") + assertNull(prettifiedPhoneNumber) + } + + @Test + fun returnsNullForInvalidLength() { + val prettifiedPhoneNumber = prettifyPhoneNumber("79138085") + assertNull(prettifiedPhoneNumber) + } + + @Test + fun worksWellForPartiallyFormatedPhoneNumber() { + val prettifiedPhoneNumber = prettifyPhoneNumber("+7 9136188085") + assertEquals("+7 913 618 80-85", prettifiedPhoneNumber) + } + + @Test + fun worksWellForFormatedPhoneNumber() { + val prettifiedPhoneNumber = prettifyPhoneNumber("+7 913 618 80-85") + assertEquals("+7 913 618 80-85", prettifiedPhoneNumber) + } +} diff --git a/composeApp/src/iosMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.ios.kt b/composeApp/src/iosMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.ios.kt index 3bea62048e77ee5a5e0a3c537a2bffd458f12068..9f32b87444e35b2a92efbedd0d5d842968d61d18 100644 --- a/composeApp/src/iosMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.ios.kt +++ b/composeApp/src/iosMain/kotlin/band/effective/office/elevator/expects/PlatformExpects.ios.kt @@ -4,8 +4,7 @@ import platform.UIKit.UIPasteboard import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.desc.desc -actual fun setClipboardText(text: String, label: String, toastMessage: StringResource) { +actual fun setClipboardText(text: String, label: String) { UIPasteboard.generalPasteboard.string = text - showToast(toastMessage.desc().localized()) } diff --git a/contract/build.gradle.kts b/contract/build.gradle.kts index 433f163d476493c3df9fcd1c046ca7881b880bb1..3a038598e41850829c6091b1f5088a3b857db2a2 100644 --- a/contract/build.gradle.kts +++ b/contract/build.gradle.kts @@ -94,4 +94,4 @@ buildConfig { "debugServerUrl", "\"https://d5dfk1qp766h8rfsjrdl.apigw.yandexcloud.net\"" ) -} \ No newline at end of file +} diff --git a/effectiveOfficeBackend/build.gradle.kts b/effectiveOfficeBackend/build.gradle.kts index d05073c085263445bc8fcad38abd57dd5937a395..95d311b1d8a4bf8d07071a0699379cb460410221 100644 --- a/effectiveOfficeBackend/build.gradle.kts +++ b/effectiveOfficeBackend/build.gradle.kts @@ -66,6 +66,7 @@ dependencies { implementation("com.google.auth:google-auth-library-oauth2-http:1.3.0") implementation("com.google.oauth-client:google-oauth-client-jetty:1.34.1") implementation("com.google.firebase:firebase-admin:8.2.0") + implementation("com.google.guava:guava:33.4.0-jre") liquibaseRuntime("org.liquibase:liquibase-core:$liquibase_version") liquibaseRuntime("org.postgresql:postgresql:$postgresql_driver_version") @@ -78,4 +79,5 @@ dependencies { testImplementation("org.mockito:mockito-inline:$mockito_version") testImplementation("org.mockito.kotlin:mockito-kotlin:$mockito_version") testImplementation("com.h2database:h2:2.2.220") + implementation(kotlin("stdlib")) } diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt index 06f9c1ef5ac179bcec97664252ec3362ae6530e2..075334f7033ecbf8ee67d6f758f62102f4737445 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/converters/GoogleCalendarConverter.kt @@ -13,13 +13,13 @@ import office.effective.dto.UserDTO import office.effective.dto.WorkspaceDTO import office.effective.features.calendar.repository.CalendarIdsRepository import office.effective.features.user.converters.UserDTOModelConverter -import office.effective.features.user.repository.UserRepository import office.effective.features.workspace.converters.WorkspaceFacadeConverter import office.effective.model.Booking import office.effective.model.UserModel import office.effective.model.Workspace import office.effective.features.booking.converters.RecurrenceRuleFactory.getRecurrence import office.effective.features.booking.converters.RecurrenceRuleFactory.rule +import office.effective.features.user.repository.cache.UsersCache import office.effective.features.workspace.repository.WorkspaceRepository import org.slf4j.LoggerFactory import java.time.Instant @@ -30,7 +30,7 @@ import java.util.* */ class GoogleCalendarConverter( private val calendarIdsRepository: CalendarIdsRepository, - private val userRepository: UserRepository, + private val userCache: UsersCache, private val workspaceConverter: WorkspaceFacadeConverter, private val userConverter: UserDTOModelConverter, private val bookingConverter: BookingFacadeConverter, @@ -103,7 +103,7 @@ class GoogleCalendarConverter( } val recurrence = event.recurrence?.toString()?.getRecurrence() val model = Booking( - owner = userRepository.findById(userId) + owner = userCache.getById(userId) ?: run { logger.warn("[toWorkspaceBooking] can't find user with id ${userId}. Creating placeholder.") UserModel( @@ -156,7 +156,7 @@ class GoogleCalendarConverter( * @author Danil Kiselev, Max Mishenko, Daniil Zavyalov */ private fun getUser(email: String): UserDTO { - val userModel: UserModel = userRepository.findByEmail(email) + val userModel: UserModel = userCache.getByEmail(email) ?: run { logger.warn("[getUser] can't find a user with email ${email}. Creating placeholder.") UserModel( diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt index f3cc643b6df4c96e8cfadfe97d1176f55586bf76..4c160f76aa4d786ff3fa11562c6b8ff47ba41d0a 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingCalendarRepository.kt @@ -4,16 +4,15 @@ import com.google.api.client.googleapis.json.GoogleJsonResponseException import com.google.api.client.util.DateTime import com.google.api.services.calendar.Calendar import com.google.api.services.calendar.model.Event -import kotlinx.coroutines.* import office.effective.common.constants.BookingConstants import office.effective.common.exception.InstanceNotFoundException import office.effective.common.exception.MissingIdException import office.effective.common.exception.WorkspaceUnavailableException import office.effective.features.calendar.repository.CalendarIdsRepository import office.effective.features.booking.converters.GoogleCalendarConverter -import office.effective.features.user.repository.UserRepository import office.effective.model.Booking import office.effective.features.user.repository.UserEntity +import office.effective.features.user.repository.cache.UsersCache import org.slf4j.LoggerFactory import java.time.Instant import java.util.* @@ -27,7 +26,7 @@ import java.util.concurrent.Executors */ class BookingCalendarRepository( private val calendarIdsRepository: CalendarIdsRepository, - private val userRepository: UserRepository, + private val userCache: UsersCache, private val calendar: Calendar, private val googleCalendarConverter: GoogleCalendarConverter ) : IBookingRepository { @@ -181,7 +180,7 @@ class BookingCalendarRepository( * @author Danil Kiselev */ private fun findUserEmailByUserId(id: UUID): String { - return userRepository.findById(id)?.email ?: throw InstanceNotFoundException( + return userCache.getById(id)?.email ?: throw InstanceNotFoundException( UserEntity::class, "User with id $id not found" ) } diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingWorkspaceRepository.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingWorkspaceRepository.kt index 9485a3347281600338a8f78ed90f391608d44b4b..c6cbfbd6124495b9df95d94d366b132115c2a2f9 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingWorkspaceRepository.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/repository/BookingWorkspaceRepository.kt @@ -9,7 +9,7 @@ import office.effective.common.exception.InstanceNotFoundException import office.effective.common.exception.MissingIdException import office.effective.common.exception.WorkspaceUnavailableException import office.effective.features.booking.converters.GoogleCalendarConverter -import office.effective.features.user.repository.UserRepository +import office.effective.features.user.repository.cache.UsersCache import office.effective.features.workspace.repository.WorkspaceEntity import office.effective.features.workspace.repository.WorkspaceRepository import office.effective.model.Booking @@ -26,7 +26,7 @@ class BookingWorkspaceRepository( private val calendar: Calendar, private val googleCalendarConverter: GoogleCalendarConverter, private val workspaceRepository: WorkspaceRepository, - private val userRepository: UserRepository + private val userCache: UsersCache ) : IBookingRepository { private val calendarEvents = calendar.Events() private val workspaceCalendar: String = BookingConstants.WORKSPACE_CALENDAR @@ -244,7 +244,7 @@ class BookingWorkspaceRepository( logger.debug("[save] saving booking of workspace with id {}", booking.workspace.id) val workspaceId = booking.workspace.id ?: throw MissingIdException("Missing booking workspace id") val userId = booking.owner.id ?: throw MissingIdException("Missing booking owner id") - if (!userRepository.existsById(userId)) { + if (userCache.getById(userId)==null) { throw InstanceNotFoundException( WorkspaceEntity::class, "User with id $workspaceId not wound" ) diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt index d67893ecfe9ce249148a9af4422bd1ffd5cee2dc..118a16f6e8527dd6a1fb07e4a93ef0b85da64826 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/booking/service/BookingService.kt @@ -6,7 +6,7 @@ import office.effective.features.booking.repository.BookingCalendarRepository import office.effective.features.booking.repository.BookingWorkspaceRepository import office.effective.features.booking.repository.WorkspaceBookingEntity import office.effective.features.user.repository.UserEntity -import office.effective.features.user.repository.UserRepository +import office.effective.features.user.repository.cache.UsersCache import office.effective.features.workspace.repository.WorkspaceRepository import office.effective.model.* import office.effective.serviceapi.IBookingService @@ -22,7 +22,7 @@ import java.util.UUID class BookingService( private val bookingRepository: BookingCalendarRepository, private val bookingWorkspaceRepository: BookingWorkspaceRepository, - private val userRepository: UserRepository, + private val usersCache: UsersCache, private val workspaceRepository: WorkspaceRepository, ) : IBookingService { private val logger = LoggerFactory.getLogger(BookingService::class.java) @@ -65,7 +65,7 @@ class BookingService( participant.id?.let { userIds.add(it) } } booking.owner.id?.let { userIds.add(it) } - val integrations = userRepository.findAllIntegrationsByUserIds(userIds) + val integrations = usersCache.findAllIntegrationsByUserIds(userIds) booking.workspace.utilities = findUtilities(booking.workspace) booking.owner.integrations = integrations[booking.owner.id] ?: setOf() for (participant in booking.participants) { @@ -96,7 +96,7 @@ class BookingService( ?: throw InstanceNotFoundException( UserEntity::class, "User with id $workspaceId not found", workspaceId ) - if (!userRepository.existsById(userId)) throw InstanceNotFoundException( + if (!usersCache.existsById(userId)) throw InstanceNotFoundException( UserEntity::class, "User with id $userId not found", userId ) @@ -118,7 +118,7 @@ class BookingService( } userId != null -> { - if (!userRepository.existsById(userId)) throw InstanceNotFoundException( + if (!usersCache.existsById(userId)) throw InstanceNotFoundException( UserEntity::class, "User with id $userId not found", userId ) @@ -195,7 +195,7 @@ class BookingService( } } val utilities = workspaceRepository.findAllUtilitiesByWorkspaceIds(workspaceIds) - val integrations = userRepository.findAllIntegrationsByUserIds(userIds) + val integrations = usersCache.findAllIntegrationsByUserIds(userIds) return addIntegrationsAndUtilities(bookingList, integrations, utilities) } @@ -233,7 +233,7 @@ class BookingService( */ private fun findIntegrations(user: UserModel): Set { val userId = user.id ?: throw MissingIdException("User with name ${user.fullName} doesn't have an id") - return userRepository.findSetOfIntegrationsByUser(userId) + return usersCache.findSetOfIntegrationsByUser(userId) } /** diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/converters/UserDTOModelConverter.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/converters/UserDTOModelConverter.kt index 61772aca3e07f0e07f192eda60c7298cbc1f5184..5e93dd4a6f20d762fbbea65897216dbbf498232e 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/converters/UserDTOModelConverter.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/converters/UserDTOModelConverter.kt @@ -4,7 +4,7 @@ import office.effective.common.exception.InstanceNotFoundException import office.effective.common.utils.UuidValidator import office.effective.dto.IntegrationDTO import office.effective.dto.UserDTO -import office.effective.features.user.repository.UserRepository +import office.effective.features.user.repository.UsersRepository import office.effective.features.user.repository.UsersTagEntity import office.effective.model.IntegrationModel import office.effective.model.UserModel @@ -13,7 +13,7 @@ import java.util.* * Converters between [UserDTO] and [UserModel] * */ class UserDTOModelConverter( - private val repository: UserRepository, + private val usersRepository: UsersRepository, private val converter: IntegrationDTOModelConverter, private val uuidConverter: UuidValidator ) { @@ -32,7 +32,7 @@ class UserDTOModelConverter( } val tag: UsersTagEntity? = userId?.let { try { - repository.findTagByUserOrNull(userId); + usersRepository.findTagByUserOrNull(userId); } catch (ex: InstanceNotFoundException) { null } diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/di/userDIModule.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/di/userDIModule.kt index 30874450bf4c3463339e7d51359a9d68efbae6cd..0db30d119391dee456c7705391f378b134e7a16c 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/di/userDIModule.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/di/userDIModule.kt @@ -5,17 +5,19 @@ import office.effective.features.user.converters.IntegrationModelEntityConverter import office.effective.features.user.converters.UserDTOModelConverter import office.effective.features.user.converters.UserModelEntityConverter import office.effective.features.user.facade.UserFacade -import office.effective.features.user.repository.UserRepository +import office.effective.features.user.repository.UsersRepository +import office.effective.features.user.repository.cache.UsersCache import office.effective.serviceapi.IUserService import office.effective.features.user.service.UserService import org.koin.dsl.module val userDIModule = module(createdAtStart = true) { - single { UserService(get()) } + single { UserService(get(),get()) } single { IntegrationModelEntityConverter() } single { UserModelEntityConverter() } single { UserDTOModelConverter(get(), get(), get()) } - single { UserRepository(get(), get(), get()) } + single { UsersRepository(get(), get(), get()) } single { UserFacade(get(), get(), get()) } + single { UsersCache(get()) } single { IntegrationDTOModelConverter(get()) } } \ No newline at end of file diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/UserRepository.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/UsersRepository.kt similarity index 99% rename from effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/UserRepository.kt rename to effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/UsersRepository.kt index b458df7e4031f9d930f9fa7930b7c58e7414fc18..a786b940faaf0b965cd9c0cbf2c3c6f1963ff891 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/UserRepository.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/UsersRepository.kt @@ -15,7 +15,7 @@ import java.util.* /** * Perform database queries with users * */ -class UserRepository( +class UsersRepository( private val db: Database, private val converter: UserModelEntityConverter, private val integrationConverter: IntegrationModelEntityConverter diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/cache/UsersCache.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/cache/UsersCache.kt new file mode 100644 index 0000000000000000000000000000000000000000..2df496d2139de76d7ee9a08915bac63116696a44 --- /dev/null +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/cache/UsersCache.kt @@ -0,0 +1,99 @@ +package office.effective.features.user.repository.cache + +import office.effective.features.user.repository.UsersRepository +import office.effective.model.IntegrationModel +import office.effective.model.UserModel +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import kotlin.collections.HashSet + +class UsersCache(val usersRepository: UsersRepository) { + + @Volatile + var snapshot: UsersSnapshot = UsersSnapshot(setOf()); + + val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1); + + + init { + initSnapshot(); + scheduler.scheduleWithFixedDelay({ initSnapshot() }, 1, 1, TimeUnit.MINUTES); + } + + + fun invalidate() { + initSnapshot(); + } + + fun invalidateUser(id: UUID?) { + if (id == null) { + return; + } + usersRepository.findById(id)?.let { + snapshot.updateUser(it); + } + } + + fun stop() { + scheduler.shutdown(); + try { + scheduler.awaitTermination(1, TimeUnit.MINUTES); + } catch (e: InterruptedException) { + scheduler.shutdownNow(); + } + } + + + private fun initSnapshot() { + this.snapshot = UsersSnapshot(usersRepository.findAll()); + } + + fun findByTagId(id: UUID?): Set { + if (id == null) { + return emptySet(); + } + return snapshot.getList().filter { Objects.equals(it.tag?.id, id) }.toSet(); + } + + + fun getAll(): Set { + return snapshot.getList().toSet(); + } + + fun getById(id: UUID?): UserModel? { + if (id == null) { + return null; + } + return snapshot.getById(id); + } + + fun getByEmail(emailStr: String): UserModel? { + return snapshot.getList().find { Objects.equals(emailStr, it.email) }; + } + + fun findAllIntegrationsByUserIds(userIds: MutableSet): HashMap> { + val userModels: Map = snapshot.getByIds(userIds); + val res = HashMap>(); + for (userId in userIds) { + val usr = userModels.get(userId); + if (usr?.integrations != null) { + res.put(userId, HashSet(usr.integrations!!)) + } else { + res.put(userId, HashSet()); + } + } + return res; + } + + fun existsById(userId: UUID): Boolean { + return snapshot.getById(userId)!=null; + } + + fun findSetOfIntegrationsByUser(userId: UUID): Set { + var res = snapshot.getById(userId)?.integrations; + return HashSet(res ?: emptySet()); + } + +} \ No newline at end of file diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/cache/UsersSnapshot.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/cache/UsersSnapshot.kt new file mode 100644 index 0000000000000000000000000000000000000000..ec838057499039801ae3f86aa3ac65003b39f619 --- /dev/null +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/repository/cache/UsersSnapshot.kt @@ -0,0 +1,46 @@ +package office.effective.features.user.repository.cache + +import office.effective.model.UserModel +import java.util.ArrayList +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +class UsersSnapshot(users: Collection) { + + val usersById: MutableMap = ConcurrentHashMap(); + + init { + users.forEach { it -> + it.id?.let { userId -> + usersById.put(userId, it); + }; + } + } + + + fun getById(id: UUID): UserModel? { + return usersById.get(id); + } + + fun updateUser(user: UserModel) { + user.id?.let { + usersById.put(it, user); + } + } + + fun getList(): List { + return ArrayList(usersById.values); + } + + fun getByIds(userIds: MutableSet): Map { + val res = HashMap(); + for (userId in userIds) { + val usr = usersById.get(userId); + if (usr != null) { + res.put(userId, usr); + } + } + return res; + } + +} \ No newline at end of file diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/service/UserService.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/service/UserService.kt index 957f290954cc767766a591611a5a94d20e134cdc..e231bbcdc4e7fa959e4812b979d03e5ab7d49c06 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/service/UserService.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/features/user/service/UserService.kt @@ -1,6 +1,7 @@ package office.effective.features.user.service -import office.effective.features.user.repository.UserRepository +import office.effective.features.user.repository.UsersRepository +import office.effective.features.user.repository.cache.UsersCache import office.effective.model.UserModel import office.effective.serviceapi.IUserService import java.util.* @@ -8,10 +9,10 @@ import java.util.* /** * Class that provides methods to manipulate [UserModel] objects * */ -class UserService(private val repository: UserRepository) : IUserService { +class UserService(private val cache: UsersCache, private val repository: UsersRepository) : IUserService { override fun getUsersByTag(tagStr: String): Set { - return repository.findByTag(repository.findTagByName(tagStr).id) + return cache.findByTagId(repository.findTagByName(tagStr).id) } /** @@ -21,11 +22,11 @@ class UserService(private val repository: UserRepository) : IUserService { * @author Daniil Zavyalov * */ override fun getAll(): Set { - return repository.findAll() + return cache.getAll() } override fun getUserById(userIdStr: String): UserModel? { - return repository.findById(UUID.fromString(userIdStr)) + return cache.getById(UUID.fromString(userIdStr)); } /** @@ -36,7 +37,9 @@ class UserService(private val repository: UserRepository) : IUserService { * @author Kiselev Danil */ override fun updateUser(user: UserModel): UserModel { - return repository.updateUser(user) + val res = repository.updateUser(user) + cache.invalidateUser(res.id); + return res; } /** @@ -47,6 +50,6 @@ class UserService(private val repository: UserRepository) : IUserService { * @author Kiselev Danil * */ override fun getUserByEmail(emailStr: String): UserModel? { - return repository.findByEmail(emailStr) + return cache.getByEmail(emailStr) } } \ No newline at end of file diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/CustomAuthorizationPlugin.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/CustomAuthorizationPlugin.kt index d357da12d063cfbb97bd0ffe40b987abb4c58ab2..df9510613e8bec35a03a0a0da9b51b1782e9b242 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/CustomAuthorizationPlugin.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/CustomAuthorizationPlugin.kt @@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory * Run every time when receiving input call. Checks Authentication (bearer) header containment * */ val CustomAuthorizationPlugin = createApplicationPlugin(name = "CustomAuthorizationPlugin") { - val pluginOn: Boolean = System.getenv("VERIFICATION_PLUGIN_ENABLE").equals("true") // + val pluginOn: Boolean = ((System.getenv("VERIFICATION_PLUGIN_ENABLE"))?:"").equals("true") // val logger = LoggerFactory.getLogger(this::class.java) logger.info("Authorization plugin mode enabled?: $pluginOn") logger.info("Authorization plugin installed") diff --git a/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/Migration.kt b/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/Migration.kt index f4fc81c692452950732e068171c3e928f1a8df51..9a98e3646cbf6dcd87958768763be2a95e6b95fb 100644 --- a/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/Migration.kt +++ b/effectiveOfficeBackend/src/main/kotlin/office/effective/plugins/Migration.kt @@ -26,7 +26,7 @@ fun Application.configureMigration() { ?.getString() ?: "changelog/changelog-master.yaml" val defaultSchemaName: String = config.propertyOrNull("liquibase.defaultSchemaName") ?.getString() ?: "public" - val migrationsEnable: Boolean = System.getenv("MIGRATIONS_ENABLE").equals("true") + val migrationsEnable: Boolean = (System.getenv("MIGRATIONS_ENABLE")?:"true").equals("true") if (migrationsEnable) { val connection = DriverManager.getConnection( diff --git a/foosball/Arduino/Makefile b/foosball/Arduino/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4d1c3e2d014dabb0caa7d0c7a9c788295c683f17 --- /dev/null +++ b/foosball/Arduino/Makefile @@ -0,0 +1,31 @@ +TARGET = sketch_jul16a + +# Arduino плата (например, uno, nano, mega2560) +BOARD = nano + +# Порт, к которому подключена Arduino +PORT = $(PORT) + +# Директория Arduino Core и инструменты +ARDUINO_DIR=$(HOME)/Documents/Arduino + +# Опции компилятора и загрузчика +CFLAGS = -Wall -Os +UPLOAD_RATE = 115200 + +# Пути к утилитам +ARDUINO_AVR_DIR = $(ARDUINO_DIR)/hardware/arduino/avr +AVRDUDE = $(ARDUINO_AVR_DIR)/tools/avr/bin/avrdude +AVRDUDE_CONF = $(ARDUINO_AVR_DIR)/etc/avrdude.conf +ARDUINO_BUILDER = $(ARDUINO_DIR)/arduino-builder + +all: upload + +$(TARGET).hex: $(TARGET).ino + $(ARDUINO_BUILDER) -compile -hardware $(ARDUINO_DIR)/hardware -tools $(ARDUINO_DIR)/tools-builder -libraries $(ARDUINO_DIR)/libraries -fqbn=arduino:avr:$(BOARD) -build-path . $(TARGET).ino + +upload: $(TARGET).hex + $(AVRDUDE) -C$(AVRDUDE_CONF) -v -p$(BOARD) -carduino -P$(PORT) -b$(UPLOAD_RATE) -D -Uflash:w:$(TARGET).hex:i + +clean: + rm -f *.hex \ No newline at end of file diff --git a/foosball/Arduino/README.md b/foosball/Arduino/README.md new file mode 100644 index 0000000000000000000000000000000000000000..213627f90ba69138527a5732ed715733b4d3b28b --- /dev/null +++ b/foosball/Arduino/README.md @@ -0,0 +1,53 @@ +# Arduino Foosball Score Tracker + +Этот репозиторий содержит исходный код и схемы для автоматической фиксации голов в настольном футболе (кикере) с использованием Arduino. Система использует лазерные модули и фотоприемники для обнаружения забитых голов, отправляя данные о голах в консоль. Проект включает в себя поддержку двух ворот, позволяя отслеживать счет для обеих команд. + +## Особенности + +- Автоматическое обнаружение голов с использованием лазерных и инфракрасных датчиков. +- Поддержка двух ворот для учета голов обеих команд. +- Простота настройки и использования. +- Возможность расширения и модификации для добавления дополнительных функций. + +## Используемые компоненты + +- Arduino (любая совместимая плата) +- 2 лазерных модуля +- 2 фотоприемника (или инфракрасных датчика) +- Провода и резисторы +- Макетная плата (breadboard) + +## Как использовать + +1. Подключите оборудование: +2. Подключите лазеры и фотоприемники к Arduino согласно схеме. +3. Сборка и прошивка: +Перейдите в директорию с Arduino IDE (например, /path/to/arduino). +Выполните команду task build для компиляции. +Выполните команду task upload для прошивки на Arduino. +4. Установка: +Установите Arduino на стол, чтобы лазеры были направлены на фотоприемники. +Запуск: +Начните игру и наблюдайте, как система фиксирует голы. +## Пример подключения + +```diff +Лазерный модуль 1: +- VCC -> 5V +- GND -> GND +- OUT -> Pin 7 + +Фотоприемник 1: +- VCC -> 5V +- GND -> GND +- OUT -> Pin 2 + +Лазерный модуль 2: +- VCC -> 5V +- GND -> GND +- OUT -> Pin 8 + +Фотоприемник 2: +- VCC -> 5V +- GND -> GND +- OUT -> Pin 3 diff --git a/foosball/Arduino/Taskfile.yml b/foosball/Arduino/Taskfile.yml new file mode 100644 index 0000000000000000000000000000000000000000..512b48ec540eb1e03b4540346effdae4eca058a9 --- /dev/null +++ b/foosball/Arduino/Taskfile.yml @@ -0,0 +1,17 @@ +version: '1' + +tasks: + build: + desc: "Компиляция проекта" + cmds: + - "make" + + upload: + desc: "Прошивка на Arduino" + cmds: + - "make upload" + + clean: + desc: "Очистка скомпилированных файлов" + cmds: + - "make clean" \ No newline at end of file diff --git a/foosball/Arduino/sketch_arduino/sketch_arduino.ino b/foosball/Arduino/sketch_arduino/sketch_arduino.ino new file mode 100644 index 0000000000000000000000000000000000000000..098cd20a8c5a74b80816ab6d0bbe59323ed21a4a --- /dev/null +++ b/foosball/Arduino/sketch_arduino/sketch_arduino.ino @@ -0,0 +1,48 @@ +const int laserPin1 = 7; +const int sensorPin1 = 2; +const int laserPin2 = 8; +const int sensorPin2 = 3; + +int state1 = 0; +int prevState1 = 0; +int state2 = 0; +int prevState2 = 0; + +unsigned long lastDebounceTime1 = 0; +unsigned long lastDebounceTime2 = 0; +const unsigned long debounceDelay = 50; + +inline void handleSensorChange(int& state, int& prevState, unsigned long& lastDebounceTime, int sensorPin, int outputValue) { + int reading = digitalRead(sensorPin); + + if (reading != prevState) { + lastDebounceTime = millis(); + } + + if ((millis() - lastDebounceTime) > debounceDelay) { + if (reading != state) { + state = reading; + + if (state == LOW) { + Serial.write(outputValue); + } + } + } + prevState = reading; +} + +void setup() { + pinMode(laserPin1, OUTPUT); + pinMode(laserPin2, OUTPUT); + pinMode(sensorPin1, INPUT); + pinMode(sensorPin2, INPUT); + Serial.begin(115200); + + digitalWrite(laserPin1, HIGH); + digitalWrite(laserPin2, HIGH); +} + +void loop() { + handleSensorChange(state1, prevState1, lastDebounceTime1, sensorPin1, 0x00); + handleSensorChange(state2, prevState2, lastDebounceTime2, sensorPin2, 0x01); +} diff --git a/foosball/foosballApp/app/.gitignore b/foosball/foosballApp/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8 --- /dev/null +++ b/foosball/foosballApp/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/foosball/foosballApp/app/build.gradle.kts b/foosball/foosballApp/app/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..4d5c756aa0459995e7ad4b879bb7a2b7f8a4fac6 --- /dev/null +++ b/foosball/foosballApp/app/build.gradle.kts @@ -0,0 +1,126 @@ +import java.io.FileInputStream +import java.util.Properties + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) + id("io.gitlab.arturbosch.detekt") version("1.23.3") apply false + id("kotlin-kapt") + kotlin("plugin.serialization") version "1.9.0" +} +val localProperties = Properties().apply { + load(FileInputStream(rootProject.file("local.properties"))) +} +android { + namespace = "com.example.effectivefoosball" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.effectivefoosball" + minSdk = 22 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + buildConfigField( + "String", + "SUPABASE_URL", + "\"${localProperties.getProperty("SUPABASE_URL") ?: "default_url"}\"" + ) + buildConfigField( + "String", + "SUPABASE_KEY", + "\"${localProperties.getProperty("SUPABASE_KEY") ?: "default_key"}\"" + ) + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.testng) + implementation(libs.androidx.ui.test.junit4.desktop) + implementation(libs.protolite.well.known.types) + implementation(libs.androidx.runtime.livedata) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + + // Navigation compose + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.core.ktx.v160) + + // Room + implementation("androidx.room:room-ktx:2.6.1") + kapt("androidx.room:room-compiler:2.6.1") + androidTestImplementation("androidx.room:room-testing:2.6.1") + + // Usb Serial + implementation(libs.usbSerial) + + // Supabase + implementation(platform("io.github.jan-tennert.supabase:bom:2.6.0")) + implementation("io.github.jan-tennert.supabase:postgrest-kt") + implementation("io.github.jan-tennert.supabase:gotrue-kt") + implementation("io.github.jan-tennert.supabase:realtime-kt") + implementation("io.ktor:ktor-client-cio:2.3.12") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2") + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:okhttp:4.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.9.0") + + // Test + androidTestImplementation(libs.androidx.ui.test.junit4.android) + debugImplementation(libs.ui.test.manifest) + + implementation("androidx.graphics:graphics-shapes:1.0.0-rc01") +} diff --git a/foosball/foosballApp/app/proguard-rules.pro b/foosball/foosballApp/app/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce --- /dev/null +++ b/foosball/foosballApp/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/AndroidManifest.xml b/foosball/foosballApp/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..8bc520dc25029a36d7284cfce3330835ff8037ef --- /dev/null +++ b/foosball/foosballApp/app/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/ic_launcher-playstore.png b/foosball/foosballApp/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..d6191cd3f701bc930962a2a1f981955e380d1861 Binary files /dev/null and b/foosball/foosballApp/app/src/main/ic_launcher-playstore.png differ diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/MainActivity.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/MainActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..954ba905ed32a210e15cd371ea95d6cdecf817bd --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/MainActivity.kt @@ -0,0 +1,74 @@ +package band.effective.foosball + +import android.app.Activity +import android.content.pm.ActivityInfo +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import band.effective.foosball.network.UserViewModel +import band.effective.foosball.presentation.components.MainScreen +import band.effective.foosball.presentation.screens.onboarding.OnBoardingScreen +import band.effective.foosball.roomdatabase.GameScoreDatabase +import band.effective.foosball.roomdatabase.GameScoreRepositoryImpl +import band.effective.foosball.ui.theme.EffectiveFoosballTheme +import band.effective.foosball.vievmodel.ScoreViewModel +import band.effective.foosball.vievmodel.ScoreViewModelFactory + +class MainActivity : ComponentActivity() { + + private val gameScoreDao by lazy { GameScoreDatabase.getDatabase(this).gameScoreDao() } + private val scoreViewModel: ScoreViewModel by viewModels { + ScoreViewModelFactory(GameScoreRepositoryImpl(gameScoreDao)) + } + private val userViewModel: UserViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + scoreViewModel.startUsbConnection(this) + setContent { + EffectiveFoosballTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + var isScreenVisible by remember { mutableStateOf(true) } + + if (isScreenVisible) { + Box( + modifier = Modifier + .fillMaxSize() + .pointerInput(Unit) { + detectTapGestures( + onTap = { isScreenVisible = false } + ) + } + ) { + OnBoardingScreen() + } + } else { + MainScreen( + scoreViewModel = scoreViewModel, + userViewModel= userViewModel + ) + } + } + } + } + } +} + diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/arduinoConnection/UsbHandler.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/arduinoConnection/UsbHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..709aa76b1300d3515a96976b171c2a8ec63f2fed --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/arduinoConnection/UsbHandler.kt @@ -0,0 +1,80 @@ +package band.effective.foosball.arduinoconnection + +import android.app.Activity +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.hardware.usb.UsbManager +import android.util.Log +import band.effective.foosball.presentation.components.routes.Constants +import band.effective.foosball.presentation.components.routes.Constants.STOPING_FLOW_1_SEC +import band.effective.foosball.presentation.components.routes.Constants.WAITING_TIME_SEC +import com.hoho.android.usbserial.driver.UsbSerialPort +import com.hoho.android.usbserial.driver.UsbSerialProber +import com.hoho.android.usbserial.util.SerialInputOutputManager + +class UsbHandler(private val newDataHandler: (ByteArray) -> Unit) : SerialInputOutputManager.Listener { + + private var usbIoManager: SerialInputOutputManager? = null + + fun setup(context: Context) { + val manager = context.getSystemService(Activity.USB_SERVICE) as UsbManager + val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager) + + if (availableDrivers.isEmpty()) { + throw IllegalStateException("no available drivers") + } + val driver = availableDrivers[0] + if (!manager.hasPermission(driver.device)) { + Log.d("VZ", "No Permissions") + val granted = arrayOf(null) + val usbReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) + } + } + + val intent = Intent("com.android.example.USB_PERMISSION") + intent.setPackage(context.packageName) + val permissionIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE) + val filter = IntentFilter("com.android.example.USB_PERMISSION") + context.registerReceiver(usbReceiver, filter) + + manager.requestPermission(driver.device, permissionIntent) + + for (i in 0..WAITING_TIME_SEC) { + if (granted[0] != null) break + Thread.sleep(STOPING_FLOW_1_SEC.toLong()) + } + Log.d("VZ", "Got permissions") + } + val connection = manager.openDevice(driver.device) + if (connection == null) { + throw IllegalStateException("connection is null") + } + val port = driver.ports[0] + + port.open(connection) + port.setParameters( + Constants.BAUD_RATE, + Constants.DATA_BITS, + UsbSerialPort.STOPBITS_1, + UsbSerialPort.PARITY_NONE + ) + + usbIoManager = SerialInputOutputManager(port, this) + usbIoManager!!.start() + } + + override fun onNewData(data: ByteArray?) { + if (data != null) { + newDataHandler(data) + } + } + + override fun onRunError(e: java.lang.Exception?) { + Log.e("UsbHandler", "OnRunError $e") + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/config/detekt.yml b/foosball/foosballApp/app/src/main/java/band/effective/foosball/config/detekt.yml new file mode 100644 index 0000000000000000000000000000000000000000..57af0036bf4aac71caf91e6e1a777f1e7aa501cb --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/config/detekt.yml @@ -0,0 +1,705 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: false + checkExhaustiveness: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'LiteFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + # - 'MdOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + UndocumentedPublicFunction: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedProperty: false + +complexity: + active: true + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: false + threshold: 6 + NamedArguments: + active: false + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: false + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ignoreOverridden: true + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + ignoreAnnotated: ['Composable'] + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: false + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + SpreadOperator: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + UnnecessaryPartOfBinaryExpression: + active: false + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - '*.CheckResult' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: false + excludes: ['**/*.kts'] + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullCheck: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AlsoCouldBeApply: + active: false + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenComment: + active: true + values: + - 'FIXME:' + - 'STOPSHIP:' + - 'TODO:' + allowedPatterns: '' + customMessage: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + ForbiddenSuppress: + active: false + rules: [] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: true + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesIfStatements: + active: false + MandatoryBracesLoops: + active: false + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + MultilineRawStringIndentation: + active: false + indentSize: 4 + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: false + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: false + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + allowForUnclearPrecedence: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '(_|ignored|expected|serialVersionUID)' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + UseIsNullOrEmpty: + active: true + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UseSumOfInsteadOfFlatMapSize: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: true + excludeImports: + - 'java.util.*' \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/datamodelsupabase/GameScoreSupabase.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/datamodelsupabase/GameScoreSupabase.kt new file mode 100644 index 0000000000000000000000000000000000000000..5c2c50030df47386e52f0a2564467b056ece497e --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/datamodelsupabase/GameScoreSupabase.kt @@ -0,0 +1,14 @@ +package band.effective.foosball.datamodelsupabase + +import kotlinx.serialization.Serializable + +@Serializable +data class GameScoreSupabase( + val gameDate: String, + val redTeamMember1: String, + val redTeamMember2: String, + val blueTeamMember1: String, + val blueTeamMember2: String, + val scoreRed: Int, + val scoreBlue: Int +) \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/RetrofitInstance.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/RetrofitInstance.kt new file mode 100644 index 0000000000000000000000000000000000000000..28c4435942648268460db1de87d37dd0dc881628 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/RetrofitInstance.kt @@ -0,0 +1,34 @@ +package band.effective.foosball.network + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Request + + +object RetrofitInstance { + + private val authInterceptor = Interceptor { chain -> + val newRequest: Request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer jknlsdfanansvgfn") + .build() + chain.proceed(newRequest) + } + + private val okHttpClient = OkHttpClient.Builder() + .addInterceptor(authInterceptor) + .build() + + private val retrofit by lazy { + Retrofit.Builder() + .baseUrl("https://d5dfk1qp766h8rfsjrdl.apigw.yandexcloud.net") + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + val api: UserService by lazy { + retrofit.create(UserService::class.java) + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserRepository.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..727787b9c87b1962f97fbf6846e5150c1e25ec78 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserRepository.kt @@ -0,0 +1,9 @@ +package band.effective.foosball.network + +object UserRepository { + + suspend fun fetchUserNames(tag: String? = null, email: String? = null): List { + val users = RetrofitInstance.api.getUsers(tag, email) + return users.map { it.fullName } + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserResponse.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserResponse.kt new file mode 100644 index 0000000000000000000000000000000000000000..3359579d12af20719934ccedbd839527191cd496 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserResponse.kt @@ -0,0 +1,17 @@ +package band.effective.foosball.network +data class UserResponse( + val id: String, + val fullName: String, + val active: Boolean, + val role: String, + val avatarUrl: String, + val integrations: List, + val email: String, + val tag: String +) + +data class Integration( + val id: String, + val name: String, + val value: String +) \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserService.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserService.kt new file mode 100644 index 0000000000000000000000000000000000000000..d9fea65bbc27173369bfc8bfbae8f5753870cd03 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserService.kt @@ -0,0 +1,12 @@ +package band.effective.foosball.network + +import retrofit2.http.GET +import retrofit2.http.Query + +interface UserService { + @GET("/api/v1/users") + suspend fun getUsers( + @Query("user_tag") tag: String? = null, + @Query("email") email: String? = null + ): List +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserViewModel.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..30a9f50f2c442fd405ee365b1576d765e8d5153f --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/network/UserViewModel.kt @@ -0,0 +1,36 @@ +package band.effective.foosball.network + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class UserViewModel : ViewModel() { + private val _userNames = MutableStateFlow>(emptyList()) + val userNames: StateFlow> = _userNames + + private var dataLoaded = false + + init { + // Загружаем данные при создании ViewModel, если они еще не загружены + if (!dataLoaded) { + loadUserNames() + } + } + + fun loadUserNames(tag: String? = null, email: String? = null) { + if (dataLoaded) return // Предотвращаем повторные запросы + + viewModelScope.launch { + try { + val names = UserRepository.fetchUserNames(tag, email) + _userNames.value = names + dataLoaded = true // Обозначаем, что данные уже загружены + } catch (e: Exception) { + Log.e("UserViewModel", "Error fetching user names", e) + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/AlertDialog.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/AlertDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..3194e8c8e67c4f9154ad7cc0063e539edf4ad0fa --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/AlertDialog.kt @@ -0,0 +1,79 @@ +package band.effective.foosball.presentation.components + +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun AlertDialog() { + val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(976.dp) + .height(632.dp) + .background(LightBlack, shape = RoundedCornerShape(30.dp)) + .clip(RoundedCornerShape(50.dp)) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.fillMaxHeight(), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.coding), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ) + ) + Icon( + painter = painterResource(id = R.drawable.ic_codding), + contentDescription = null, + tint = White + ) + CustomButton( + defaultColor = LightBlack, + text = stringResource(id = R.string.ok), + onClick = { onBackPressedDispatcher?.onBackPressed() }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(3.dp, Orange), + cornerRadius = 80.dp + ) + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/Clock.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/Clock.kt new file mode 100644 index 0000000000000000000000000000000000000000..b32c2b3ca06ea16a9904d44dbd83329d007f45b8 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/Clock.kt @@ -0,0 +1,55 @@ +package band.effective.foosball.presentation.components + +import android.os.CountDownTimer +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import band.effective.foosball.presentation.components.routes.Constants +import band.effective.foosball.ui.theme.Typography +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Composable +fun Clock() { + var currentTime by remember { mutableStateOf("") } + + LaunchedEffect(Unit) { + val timer = object : CountDownTimer(Long.MAX_VALUE, Constants.COUNT_DOWN_INTERVAL.toLong()) { + override fun onTick(millisUntilFinished: Long) { + currentTime = getCurrentDate() + } + + override fun onFinish() { + this.start() + } + } + timer.start() + } + + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(top = 8.dp) + ) { + Text( + text = currentTime, + style = Typography.displayMedium + ) + } +} + +private fun getCurrentDate(): String { + val sdf = SimpleDateFormat("HH:mm", Locale.getDefault()) + return sdf.format(Date()) +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/CounterBox.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/CounterBox.kt new file mode 100644 index 0000000000000000000000000000000000000000..e935a8bcc09b912d278e8806a770a69ff0b9ac8d --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/CounterBox.kt @@ -0,0 +1,70 @@ +package band.effective.foosball.presentation.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import band.effective.foosball.presentation.components.routes.Constants +import band.effective.foosball.ui.theme.BackgroundColor +import band.effective.foosball.ui.theme.Typography + +@Composable +fun CounterBox(color: Color, state: MutableState) { + Box( + modifier = Modifier + .width(430.dp) + .height(497.dp) + .border(4.dp, color = color, shape = RoundedCornerShape(size = 30.dp)) + .background(color = BackgroundColor, shape = RoundedCornerShape(size = 30.dp)), + contentAlignment = Alignment.Center, + + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(4.dp) + ) { + Box( + modifier = Modifier + .weight(1f) + .clip(shape = RoundedCornerShape(size = 30.dp)) + .fillMaxWidth() + .clickable { + if (state.value > Constants.MIN_LENGTH_COUNTER_BOX) { + state.value-- + } + } + ) + Box( + modifier = Modifier + .weight(1f) + .clip(shape = RoundedCornerShape(size = 30.dp)) + .fillMaxWidth() + .clickable { + if (state.value < Constants.MAX_LENGTH_COUNTER_BOX) { + state.value++ + } + } + ) + } + Text( + text = state.value.toString(), + style = Typography.displayLarge + ) + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/CustomButton.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/CustomButton.kt new file mode 100644 index 0000000000000000000000000000000000000000..0b2ce5753f61c2e160218a50bf6b2d49517520fa --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/CustomButton.kt @@ -0,0 +1,56 @@ +package band.effective.foosball.presentation.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults.buttonColors +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import band.effective.foosball.ui.theme.PressedButton +import band.effective.foosball.ui.theme.Typography + +@Composable +fun CustomButton( + defaultColor: Color, + pressedColor: Color = PressedButton, + text: String, + onClick: () -> Unit, + width: Dp, + height: Dp, + cornerRadius: Dp = 30.dp, + border: BorderStroke +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + val buttonColor = if (isPressed) pressedColor else defaultColor + + Button( + onClick = onClick, + modifier = Modifier + .padding(top = 24.dp, bottom = 24.dp) + .size(width = width, height = height) + .background(color = buttonColor, shape = RoundedCornerShape(cornerRadius)), + colors = buttonColors(buttonColor), + interactionSource = interactionSource, + shape = RoundedCornerShape(cornerRadius), + border = border + ) { + Text( + text = text, + style = Typography.labelLarge + ) + } +} + diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/MarqueeText.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/MarqueeText.kt new file mode 100644 index 0000000000000000000000000000000000000000..b63cdf9ada6be1cceaded20ae6191d0924d396a4 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/MarqueeText.kt @@ -0,0 +1,241 @@ +package band.effective.foosball.presentation.components + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.TargetBasedAnimation +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.withFrameNanos +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import band.effective.foosball.ui.theme.White +import kotlinx.coroutines.delay + +// github с автором: +// https://github.com/MakeItEasyDev/Jetpack-Compose-Marquee-Text-And-Inline-Content-With-Image?tab=readme-ov-file +// Ссылка на статью с объяснением, как это работает: +// https://victorbrandalise.com/marquee-with-jetpack-compose/ +@Composable +fun MarqueeText( + text: String, + modifier: Modifier = Modifier, + textModifier: Modifier = Modifier, + gradientEdgeColor: Color = Color.White, + color: Color = Color.Unspecified, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + onTextLayout: (TextLayoutResult) -> Unit = {}, + style: TextStyle = LocalTextStyle.current, +) { + val createText = @Composable { localModifier: Modifier -> + Text( + text, + textAlign = textAlign, + modifier = localModifier, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + maxLines = 1, + onTextLayout = onTextLayout, + style = style, + ) + } + var offset by remember { mutableStateOf(0) } + val textLayoutInfoState = remember { mutableStateOf(null) } + LaunchedEffect(textLayoutInfoState.value) { + val textLayoutInfo = textLayoutInfoState.value ?: return@LaunchedEffect + if (textLayoutInfo.textWidth <= textLayoutInfo.containerWidth) return@LaunchedEffect + if (textLayoutInfo.containerWidth == 0) { + // Обработка ошибки, когда containerWidth равен 0 + textLayoutInfoState.value = null + return@LaunchedEffect + } + val duration = 7500 * textLayoutInfo.textWidth / textLayoutInfo.containerWidth + val delay = 1000L + + do { + val animation = TargetBasedAnimation( + animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = duration, + delayMillis = 1000, + easing = LinearEasing, + ), + repeatMode = RepeatMode.Restart + ), + typeConverter = Int.VectorConverter, + initialValue = 0, + targetValue = -textLayoutInfo.textWidth + ) + val startTime = withFrameNanos { it } + do { + val playTime = withFrameNanos { it } - startTime + offset = (animation.getValueFromNanos(playTime)) + } while (!animation.isFinishedFromNanos(playTime)) + delay(delay) + } while (true) + } + + SubcomposeLayout( + modifier = modifier.clipToBounds() + ) { constraints -> + val infiniteWidthConstraints = constraints.copy(maxWidth = Int.MAX_VALUE) + var mainText = subcompose(MarqueeLayers.MainText) { + createText(textModifier) + }.first().measure(infiniteWidthConstraints) + + var gradient: Placeable? = null + + var secondPlaceableWithOffset: Pair? = null + if (mainText.width <= constraints.maxWidth) { + mainText = subcompose(MarqueeLayers.SecondaryText) { + createText(textModifier.fillMaxWidth()) + }.first().measure(constraints) + textLayoutInfoState.value = null + } else { + val spacing = constraints.maxWidth * 2 / 3 + textLayoutInfoState.value = TextLayoutInfo( + textWidth = mainText.width + spacing, + containerWidth = constraints.maxWidth + ) + val secondTextOffset = mainText.width + offset + spacing + val secondTextSpace = constraints.maxWidth - secondTextOffset + if (secondTextSpace > 0) { + secondPlaceableWithOffset = subcompose(MarqueeLayers.SecondaryText) { + createText(textModifier) + }.first().measure(infiniteWidthConstraints) to secondTextOffset + } + gradient = subcompose(MarqueeLayers.EdgesGradient) { + Row { + GradientEdge(gradientEdgeColor, Color.Transparent) + Spacer(Modifier.weight(1f)) + GradientEdge(Color.Transparent, gradientEdgeColor) + } + }.first().measure(constraints.copy(maxHeight = mainText.height)) + } + + layout( + width = constraints.maxWidth, + height = mainText.height + ) { + mainText.place(offset, 0) + secondPlaceableWithOffset?.let { + it.first.place(it.second, 0) + } + gradient?.place(0, 0) + } + } +} + +@Composable +private fun GradientEdge( + startColor: Color, + endColor: Color, +) { + Box( + modifier = Modifier + .width(10.dp) + .fillMaxHeight() + .background( + brush = Brush.horizontalGradient( + 0f to startColor, + 1f to endColor, + ) + ) + ) +} + +private enum class MarqueeLayers { MainText, SecondaryText, EdgesGradient } +private data class TextLayoutInfo(val textWidth: Int, val containerWidth: Int) + +@Composable +fun MultiLineMarqueeText( + text: String, + modifier: Modifier = Modifier, + textModifier: Modifier = Modifier, + gradientEdgeColor: Color = Color.White, + color: Color = Color.Unspecified, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + onTextLayout: (TextLayoutResult) -> Unit = {}, + style: TextStyle = LocalTextStyle.current, +) { + val lines = text.split("\n") + + Column(modifier = modifier) { + lines.forEach { line -> + MarqueeText( + text = line, + textModifier = textModifier, + gradientEdgeColor = gradientEdgeColor, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + onTextLayout = onTextLayout, + style = style + ) + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/Navigation.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/Navigation.kt new file mode 100644 index 0000000000000000000000000000000000000000..bc186db497e272005fa7bd46e5d608ea53e55fc8 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/Navigation.kt @@ -0,0 +1,195 @@ +package band.effective.foosball.presentation.components + +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import band.effective.foosball.network.UserViewModel +import band.effective.foosball.presentation.components.routes.Constants +import band.effective.foosball.presentation.components.routes.GameMode +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.presentation.screens.competitionGame.CreateTeamFastGame +import band.effective.foosball.presentation.screens.competitionGame.ScoreScreenComp +import band.effective.foosball.presentation.screens.competitionGame.TeamsShowScreen +import band.effective.foosball.presentation.screens.competitionGame.WinnerScreenComp +import band.effective.foosball.presentation.screens.competitionGame.competitionDialog.CompNewGameDialog +import band.effective.foosball.presentation.screens.dialogs.EndGame +import band.effective.foosball.presentation.screens.dialogs.GameIsFinished +import band.effective.foosball.presentation.screens.fastGame.ScoreScreen +import band.effective.foosball.presentation.screens.fastGame.StartGameScreen +import band.effective.foosball.presentation.screens.fastGame.fastDialog.NewGameDialog +import band.effective.foosball.presentation.screens.onboarding.MainMenu +import band.effective.foosball.presentation.screens.tourGame.GameOrderScreen +import band.effective.foosball.presentation.screens.tourGame.OrderOfGamesScreen +import band.effective.foosball.presentation.screens.tourGame.TeamDistributionScreen +import band.effective.foosball.presentation.screens.tourGame.TourStartScreen +import band.effective.foosball.vievmodel.GameOrderViewModel +import band.effective.foosball.vievmodel.ScoreViewModel + +@Composable +fun MainScreen(scoreViewModel: ScoreViewModel,userViewModel: UserViewModel) { + val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + val navController = rememberNavController() + + Row( + modifier = Modifier.fillMaxSize() + ) { + Box( + modifier = Modifier + .fillMaxSize() + .weight(1f) + ) { + NavHost( + navController = navController, + startDestination = Routes.MAIN_MENU, + modifier = Modifier.fillMaxSize() + ) { + composable(Routes.MAIN_MENU) { MainMenu(navController) } + composable(Routes.START_GAME_SCREEN) { + ScreenWithNavigation(navController) { + scoreViewModel.setGameMode(GameMode.FAST) + scoreViewModel.resetScore() + StartGameScreen(navController) + } + } + composable(Routes.TEAM_FAST_GAME) { + ScreenWithNavigation(navController) { + scoreViewModel.setGameMode(GameMode.COMPETITIVE) + CreateTeamFastGame(navController, scoreViewModel, userViewModel ) + } + } + composable( + "displayScreen/{redTeamMember1}/{redTeamMember2}/{blueTeamMember1}/{blueTeamMember2}", + arguments = listOf( + navArgument("redTeamMember1") { type = NavType.StringType }, + navArgument("redTeamMember2") { type = NavType.StringType }, + navArgument("blueTeamMember1") { type = NavType.StringType }, + navArgument("blueTeamMember2") { type = NavType.StringType } + ) + ) { backStackEntry -> + val redTeamMember1 = backStackEntry.arguments?.getString("redTeamMember1") ?: "" + val redTeamMember2 = backStackEntry.arguments?.getString("redTeamMember2") ?: "" + val blueTeamMember1 = + backStackEntry.arguments?.getString("blueTeamMember1") ?: "" + val blueTeamMember2 = + backStackEntry.arguments?.getString("blueTeamMember2") ?: "" + TeamsShowScreen( + redTeamMember1 = redTeamMember1, + redTeamMember2 = redTeamMember2, + blueTeamMember1 = blueTeamMember1, + blueTeamMember2 = blueTeamMember2, + navController + ) + } + + composable( + "${Routes.SCORE_SCREEN_COMP}/{redTeamMember1}/{redTeamMember2}/{blueTeamMember1}/{blueTeamMember2}", + arguments = listOf( + navArgument("redTeamMember1") { type = NavType.StringType }, + navArgument("redTeamMember2") { type = NavType.StringType }, + navArgument("blueTeamMember1") { type = NavType.StringType }, + navArgument("blueTeamMember2") { type = NavType.StringType } + ) + ) { + backStackEntry -> + val redTeamMember1 = backStackEntry.arguments?.getString("redTeamMember1") ?: "" + val redTeamMember2 = backStackEntry.arguments?.getString("redTeamMember2") ?: "" + val blueTeamMember1 = + backStackEntry.arguments?.getString("blueTeamMember1") ?: "" + val blueTeamMember2 = + backStackEntry.arguments?.getString("blueTeamMember2") ?: "" + ScreenWithNavigation(navController) { + ScoreScreenComp( + scoreViewModel = scoreViewModel, + redTeamMember1 = redTeamMember1, + redTeamMember2 = redTeamMember2, + blueTeamMember1 = blueTeamMember1, + blueTeamMember2 = blueTeamMember2, + navController = navController + ) + } + } + + composable(Routes.GAME_IS_FINISHED) { GameIsFinished(navController) } + composable(Routes.SCORE_SCREEN) { + ScreenWithNavigation(navController) { + ScoreScreen(scoreViewModel) + } + } + composable(Routes.ALERT_DIALOG) { AlertDialog() } + composable(Routes.SELECT) { EndGame(navController, scoreViewModel) } + composable(Routes.HOME) { MainMenu(navController) } + composable(Routes.BACK) { onBackPressedDispatcher?.onBackPressed() } + composable(Routes.PLAY) { ScoreScreen(scoreViewModel) } + composable(Routes.MUTE) { ScoreScreen(scoreViewModel) } + composable(Routes.NEW_FAST_GAME_DIALOG) { NewGameDialog(navController) } + composable(Routes.NEW_COMP_GAME_DIALOG) { CompNewGameDialog(navController) } + //composable(Routes.NEW_TOUR_GAME_DIALOG) { TourNewGameDialog(navController) } + composable(Routes.WINNER_SCREEN_COMP) { + val redTeamScore = scoreViewModel.getLeftScore().value + val blueTeamScore = scoreViewModel.getRightScore().value + val redTeamMember1 = scoreViewModel.redTeamMember1 + val redTeamMember2 = scoreViewModel.redTeamMember2 + val blueTeamMember1 = scoreViewModel.blueTeamMember1 + val blueTeamMember2 = scoreViewModel.blueTeamMember2 + + WinnerScreenComp( + navController = navController, + redTeamMember1 = redTeamMember1, + redTeamMember2 = redTeamMember2, + blueTeamMember1 = blueTeamMember1, + blueTeamMember2 = blueTeamMember2, + redTeamScore = redTeamScore, + blueTeamScore = blueTeamScore, + scoreViewModel = scoreViewModel + ) + } + + composable(Routes.TOUR_START_SCREEN) { + ScreenWithNavigation(navController) { + TourStartScreen(navController) + } + } + composable(Routes.TEAM_DRAG_DROP_LIST_SCREEN) { + ScreenWithNavigation(navController) { + val gameOrderViewModel: GameOrderViewModel = viewModel() + GameOrderScreen( + viewModel = gameOrderViewModel, + navController = navController + ) + } + } + composable( + "${Routes.TEAM_DISTRIBUTION_SCREEN}/{teamCount}", + arguments = listOf(navArgument("teamCount") { type = NavType.IntType }) + ) { + backStackEntry -> + val teamCount = backStackEntry.arguments?.getInt("teamCount") + ?: Constants.MIN_LENGTH_COUNTER_TEAM + TeamDistributionScreen(teamCount = teamCount, navController) + } + composable( + "game1/{team1}/{team2}", + arguments = listOf( + navArgument("team1") { type = NavType.StringType }, + navArgument("team2") { type = NavType.StringType } + ) + ) { backStackEntry -> + val team1 = + backStackEntry.arguments?.getString("team1")?.split(",") ?: emptyList() + val team2 = + backStackEntry.arguments?.getString("team2")?.split(",") ?: emptyList() + OrderOfGamesScreen(team1 = team1, team2 = team2) + } + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/NavigationPanel.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/NavigationPanel.kt new file mode 100644 index 0000000000000000000000000000000000000000..c1d765b68b69c8fcd4c86e561e38df99b4ec419e --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/NavigationPanel.kt @@ -0,0 +1,152 @@ +package band.effective.foosball.presentation.components + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.BackgroundColor +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Typography +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +val screensWithoutSelect = listOf( + Routes.MAIN_MENU, + Routes.START_GAME_SCREEN, + Routes.TOUR_START_SCREEN, + Routes.TEAM_DISTRIBUTION_SCREEN, + Routes.TEAM_FAST_GAME, + Routes.TEAM_DISTRIBUTION_SCREEN, + Routes.TEAM_DRAG_DROP_LIST_SCREEN +) + +@Composable +fun NavigationPanel(navController: NavController) { + val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route + val showSelect = currentRoute != null && currentRoute !in screensWithoutSelect + val selectVisible = remember { mutableStateOf(showSelect) } + + LaunchedEffect(showSelect) { + selectVisible.value = showSelect + } + + Box( + modifier = Modifier + .fillMaxHeight() + .width(IntrinsicSize.Min) + .background(BackgroundColor) + .padding(vertical = 16.dp) + ) { + Column( + modifier = Modifier + .fillMaxHeight() + .align(Alignment.CenterEnd), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + AnimatedVisibility( + visible = selectVisible.value, + enter = slideInHorizontally(initialOffsetX = { it }) + expandVertically(expandFrom = Alignment.Top), + exit = slideOutHorizontally(targetOffsetX = { it }) + shrinkVertically(shrinkTowards = Alignment.Top) + ) { + NavigationItem( + icon = R.drawable.ic_select, + label = R.string.finalize, + onClick = { navController.navigate(Routes.SELECT) }, + isSelected = currentRoute == Routes.SELECT + ) + } + + NavigationItem( + icon = R.drawable.ic_home, + label = R.string.main, + onClick = { navController.navigate(Routes.HOME) }, + isSelected = currentRoute == Routes.HOME + ) + + NavigationItem( + icon = R.drawable.ic_back, + label = R.string.back, + onClick = { navController.navigate(Routes.BACK) }, + isSelected = currentRoute == Routes.BACK + ) + + NavigationItem( + icon = R.drawable.ic_play, + label = R.string.recording, + onClick = { navController.navigate(Routes.ALERT_DIALOG) }, + isSelected = currentRoute == Routes.PLAY + ) + + NavigationItem( + icon = R.drawable.ic_notstream, + label = R.string.stream, + onClick = { Routes.ALERT_DIALOG }, + isSelected = currentRoute == Routes.MUTE + ) + } + } +} + +@Composable +fun NavigationItem( + @DrawableRes icon: Int, + @StringRes label: Int, + onClick: () -> Unit, + isSelected: Boolean +) { + val iconColor = if (isSelected) Orange else White + val textColor = if (isSelected) Orange else White + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + IconButton(onClick = onClick) { + Icon( + painter = painterResource(icon), + contentDescription = null, + tint = iconColor + ) + } + Text( + text = stringResource(label), + style = Typography.labelSmall, + color = textColor + ) + } +} + +@Preview() +@Composable +fun Preview() { + NavigationPanel(navController = rememberNavController()) +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/ScreenWithNavigation.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/ScreenWithNavigation.kt new file mode 100644 index 0000000000000000000000000000000000000000..272d3487bc65c959743f9fc2ed2524546e1b98b8 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/ScreenWithNavigation.kt @@ -0,0 +1,27 @@ +package band.effective.foosball.presentation.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController + +@Composable +fun ScreenWithNavigation( + navController: NavHostController, + content: @Composable () -> Unit +) { + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Box(modifier = Modifier.weight(1f)) { + content() + } + NavigationPanel(navController = navController) + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/Constants.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/Constants.kt new file mode 100644 index 0000000000000000000000000000000000000000..0eda6bc41a22169d07c9d03e9409b94ee01452ba --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/Constants.kt @@ -0,0 +1,17 @@ +package band.effective.foosball.presentation.components.routes + +object Constants { + const val MIN_LENGTH_COUNTER_BOX = 0 + const val MAX_LENGTH_COUNTER_BOX = 999 + const val MIN_LENGTH_COUNTER_TEAM = 3 + const val MAX_LENGTH_COUNTER_TEAM = 15 + + const val COUNT_DOWN_INTERVAL = 1000 + const val MAX_LENGTH_STROKE = 13 + + const val BAUD_RATE = 115200 + const val DATA_BITS = 8 + const val WAITING_TIME_SEC = 200 + const val STOPING_FLOW_1_SEC = 1000 // 1000 MILLIS = 1 SEC + const val MAX_SCORE_COMP = 10 // 1000 MILLIS = 1 SEC +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/GameMode.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/GameMode.kt new file mode 100644 index 0000000000000000000000000000000000000000..11ee2cec2e0ff2720ddabe11165edd493aa6b9a3 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/GameMode.kt @@ -0,0 +1,5 @@ +package band.effective.foosball.presentation.components.routes + +enum class GameMode { + FAST, COMPETITIVE, TOURNAMENT, MAIN_MANU +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/Routes.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/Routes.kt new file mode 100644 index 0000000000000000000000000000000000000000..3e360ca944854127fdbe578ab25a3d4bb8838575 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/components/routes/Routes.kt @@ -0,0 +1,23 @@ +package band.effective.foosball.presentation.components.routes + +object Routes { + const val SELECT = "select" + const val HOME = "home" + const val BACK = "back" + const val PLAY = "play" + const val MUTE = "mute" + const val SCORE_SCREEN = "score_screen" + const val START_GAME_SCREEN = "start_game_screen" + const val SCORE_SCREEN_COMP = "score_screen_comp" + const val TEAM_FAST_GAME = "team_fast_game" + const val MAIN_MENU = "main_menu" + const val GAME_IS_FINISHED = "game_is_finished" + const val TOUR_START_SCREEN = "tour_start_screen" + const val TEAM_DISTRIBUTION_SCREEN = "team_distribution_screen" + const val TEAM_DRAG_DROP_LIST_SCREEN = "team_drag_drop_list_screen" + const val NEW_FAST_GAME_DIALOG = "new_fast_game_dialog" + const val NEW_COMP_GAME_DIALOG = "new_comp_game_dialog" + const val NEW_TOUR_GAME_DIALOG = "new_tour_game_dialog" + const val WINNER_SCREEN_COMP = "winner_screen_comp" + const val ALERT_DIALOG = "alert_dialog" +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/CreateTeamFastGame.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/CreateTeamFastGame.kt new file mode 100644 index 0000000000000000000000000000000000000000..6d65415cbb9b2a62211385696f7f5633e8cf9ad9 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/CreateTeamFastGame.kt @@ -0,0 +1,376 @@ +package band.effective.foosball.presentation.screens.competitionGame + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.ui.geometry.Size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.toSize +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import band.effective.foosball.network.UserViewModel +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.ui.theme.Blue +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Red +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.SuperLightGray +import band.effective.foosball.ui.theme.TeamList +import band.effective.foosball.ui.theme.White +import band.effective.foosball.vievmodel.ScoreViewModel +import com.example.effectivefoosball.R + + + +@Composable +fun CreateTeamFastGame(navController: NavController, scoreViewModel: ScoreViewModel, userViewModel: UserViewModel) { + var redTeamMember1 by remember { mutableStateOf("") } + var redTeamMember2 by remember { mutableStateOf("") } + var blueTeamMember1 by remember { mutableStateOf("") } + var blueTeamMember2 by remember { mutableStateOf("") } + + var showDialog by remember { mutableStateOf(false) } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxHeight() + .padding(16.dp) + ) { + Text( + text = stringResource(id = R.string.teams), + style = TextStyle( + color = White, + fontSize = 45.sp, + fontFamily = DrukWide + ), + modifier = Modifier.padding(bottom = 32.dp) + ) + TeamInputField( + teamName = stringResource(id = R.string.team_red), + member1 = redTeamMember1, + member2 = redTeamMember2, + onMember1Change = { value -> + redTeamMember1 = value + scoreViewModel.updateRedTeam(value, redTeamMember2) + }, + onMember2Change = { value -> + redTeamMember2 = value + scoreViewModel.updateRedTeam(redTeamMember1, value) + }, + borderColor = Red, + userViewModel = userViewModel + ) + TeamInputField( + teamName = stringResource(id = R.string.team_blue), + member1 = blueTeamMember1, + member2 = blueTeamMember2, + onMember1Change = { value -> + blueTeamMember1 = value + scoreViewModel.updateBlueTeam(value, blueTeamMember2) + }, + onMember2Change = { value -> + blueTeamMember2 = value + scoreViewModel.updateBlueTeam(blueTeamMember1, value) + }, + borderColor = Blue, + userViewModel = userViewModel + ) + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.next), + onClick = { + if (redTeamMember1.isNotBlank() && + redTeamMember2.isNotBlank() && + blueTeamMember1.isNotBlank() && + blueTeamMember2.isNotBlank() + ) { + navController.navigate( + "displayScreen/$redTeamMember1/$redTeamMember2/$blueTeamMember1/$blueTeamMember2" + ) + } else { + showDialog = true + } + }, + width = 340.dp, + height = 98.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { + Text( + text = stringResource(id = R.string.error), + style = TextStyle( + color = White, + fontSize = 20.sp, + fontFamily = DrukWide + ) + ) + }, + text = { + Text( + text = stringResource(id = R.string.fill_all_fields), + style = TextStyle( + color = White, + fontSize = 23.sp, + fontFamily = Roboto + ) + ) + }, + confirmButton = { + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.ok), + onClick = { showDialog = false }, + width = 100.dp, + height = 50.dp, + border = BorderStroke(0.dp, Orange) + ) + }, + containerColor = TeamList, + shape = RoundedCornerShape(16.dp) + ) + } + } + } +} + +@Composable +fun TeamInputField( + userViewModel: UserViewModel, + teamName: String, + member1: String, + member2: String, + onMember1Change: (String) -> Unit, + onMember2Change: (String) -> Unit, + borderColor: Color, +) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Text( + text = teamName, + style = TextStyle( + color = White, + fontSize = 40.sp, + fontFamily = Roboto + ), + modifier = Modifier.width(200.dp) + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + PlayerInputWithDropdown( + value = member1, + onValueChange = onMember1Change, + borderColor = borderColor, + userViewModel = userViewModel + ) + Text( + "/", + color = White, + fontSize = 40.sp, + modifier = Modifier.padding(horizontal = 8.dp) + ) + PlayerInputWithDropdown( + value = member2, + onValueChange = onMember2Change, + borderColor = borderColor, + userViewModel = userViewModel + ) + } + } +} + +@Composable +fun PlayerInputWithDropdown( + userViewModel: UserViewModel = viewModel(), + value: String, + onValueChange: (String) -> Unit, + borderColor: Color +) { + LaunchedEffect(Unit) { + userViewModel.loadUserNames() + } + + val userNames by userViewModel.userNames.collectAsState() + var expanded by remember { mutableStateOf(false) } + var textFieldSize by remember { mutableStateOf(Size.Zero) } + + // Добавляем анимацию для поворота стрелки + val rotationState by animateFloatAsState( + targetValue = if (expanded) 180f else 0f, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) + ) + + Column(modifier = Modifier.width(390.dp)) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .height(120.dp) + .border( + width = 1.8.dp, + color = borderColor, + shape = RoundedCornerShape(30.dp) + ) + .onGloballyPositioned { coordinates -> + textFieldSize = coordinates.size.toSize() + }, + shape = RoundedCornerShape(30.dp), + value = value, + onValueChange = { + // Log.d("PlayerInputWithDropdown", "Value changed: $it") + onValueChange(it) + expanded = true + }, + placeholder = { + Text( + text = stringResource(id = R.string.player), + style = TextStyle( + color = SuperLightGray, + fontSize = 30.sp, + fontFamily = Roboto + ) + ) + }, + colors = TextFieldDefaults.colors( + disabledContainerColor = TeamList, + focusedContainerColor = TeamList, + unfocusedContainerColor = TeamList + ), + textStyle = TextStyle( + color = White, + fontSize = 30.sp, + fontFamily = Roboto + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done + ), + singleLine = true, + trailingIcon = { + IconButton(onClick = { expanded = !expanded }) { + Icon( + modifier = Modifier + .size(24.dp) + .rotate(rotationState), // Применяем анимацию поворота + painter = painterResource(id = R.drawable.ic_unwarp), + contentDescription = null, + tint = SuperLightGray + ) + } + } + ) + AnimatedVisibility( + visible = expanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Card( + modifier = Modifier + .width(390.dp) + .heightIn(max = 250.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = TeamList) + ) { + LazyColumn { + val filteredPlayers = if (value.isNotEmpty()) { + userNames.filter { it.lowercase().contains(value.lowercase()) } + } else { + userNames + } + items(filteredPlayers) { player -> + ItemsCategory(title = player) { selectedPlayer -> + onValueChange(selectedPlayer) + expanded = false + } + } + } + } + } + } +} + +@Composable +fun ItemsCategory( + title: String, + onSelect: (String) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onSelect(title) } + .padding(16.dp) + ) { + Text( + text = title, + fontSize = 30.sp, + color = White, + fontFamily = Roboto + ) + } +} + diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/ScoreScreenComp.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/ScoreScreenComp.kt new file mode 100644 index 0000000000000000000000000000000000000000..b42f8458dadfb6b5f1888f9c6019081b04d11779 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/ScoreScreenComp.kt @@ -0,0 +1,120 @@ +package band.effective.foosball.presentation.screens.competitionGame + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.Clock +import band.effective.foosball.presentation.components.CounterBox +import band.effective.foosball.presentation.components.routes.Constants +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.Blue +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Red +import band.effective.foosball.ui.theme.White +import band.effective.foosball.vievmodel.ScoreViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Composable +fun ScoreScreenComp( + navController: NavController, + scoreViewModel: ScoreViewModel, + redTeamMember1: String, + redTeamMember2: String, + blueTeamMember1: String, + blueTeamMember2: String +) { + val leftScore = scoreViewModel.getLeftScore().value + val rightScore = scoreViewModel.getRightScore().value + + LaunchedEffect(leftScore, rightScore) { + if (leftScore == Constants.MAX_SCORE_COMP || rightScore == Constants.MAX_SCORE_COMP) { + val gameDate = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) + + // Сохранение данных игры + scoreViewModel.saveGameScore( + gameDate, + redTeamMember1 = redTeamMember1, + redTeamMember2 = redTeamMember2, + blueTeamMember1 = blueTeamMember1, + blueTeamMember2 = blueTeamMember2 + ) + + // Отправка данных в Supabase + GlobalScope.launch(Dispatchers.IO) { + scoreViewModel.sendGameScoreToSupabase(gameDate = gameDate) + scoreViewModel.resetScore() + } + + // Переход на экран победителя + navController.navigate(Routes.WINNER_SCREEN_COMP) { + popUpTo(Routes.SCORE_SCREEN) { inclusive = true } + } + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(start = 80.dp), + verticalArrangement = Arrangement.SpaceAround, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Clock() + Row( + horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Spacer(modifier = Modifier.width(100.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier + .padding(bottom = 20.dp), + text = "$redTeamMember1\n$redTeamMember2", + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 30.sp + ) + ) + CounterBox(Red, scoreViewModel.getLeftScore()) + } + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier.padding(bottom = 20.dp), + text = "$blueTeamMember1\n$blueTeamMember2", + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 30.sp + ) + ) + CounterBox(Blue, scoreViewModel.getRightScore()) + } + Spacer(modifier = Modifier.width(100.dp)) + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/TeamsShowScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/TeamsShowScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..0990809cce592394faf2e54a3caa35d401ba3005 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/TeamsShowScreen.kt @@ -0,0 +1,93 @@ +package band.effective.foosball.presentation.screens.competitionGame + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun TeamsShowScreen( + redTeamMember1: String, + redTeamMember2: String, + blueTeamMember1: String, + blueTeamMember2: String, + navController: NavController +) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceAround, + ) { + Text( + text = "$redTeamMember1\n$redTeamMember2", + style = androidx.compose.ui.text.TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ), + modifier = Modifier.padding(end = 50.dp) + ) + + Text( + text = "VS", + style = androidx.compose.ui.text.TextStyle( + color = Orange, + fontFamily = DrukWide, + fontSize = 30.sp + ), + modifier = Modifier.padding(horizontal = 10.dp) + ) + + Text( + text = "$blueTeamMember1\n$blueTeamMember2", + style = androidx.compose.ui.text.TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ), + modifier = Modifier.padding(start = 50.dp) + ) + } + + Spacer(modifier = Modifier.height(48.dp)) + + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.start_game), + onClick = { + navController.navigate( + "${Routes.SCORE_SCREEN_COMP}/$redTeamMember1/$redTeamMember2/$blueTeamMember1/$blueTeamMember2" + ) + }, + width = 340.dp, + height = 98.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/WinnerScreenComp.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/WinnerScreenComp.kt new file mode 100644 index 0000000000000000000000000000000000000000..23eb903ec77d41a7220f9391d2b4c4319710ca9f --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/WinnerScreenComp.kt @@ -0,0 +1,105 @@ +package band.effective.foosball.presentation.screens.competitionGame + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.Blue +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Red +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.Typography +import band.effective.foosball.ui.theme.White +import band.effective.foosball.vievmodel.ScoreViewModel +import com.example.effectivefoosball.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Composable +fun WinnerScreenComp( + navController: NavController, + scoreViewModel: ScoreViewModel, + redTeamMember1: String, + redTeamMember2: String, + blueTeamMember1: String, + blueTeamMember2: String, + redTeamScore: Int, + blueTeamScore: Int +) { + val winningTeam = if (redTeamScore > blueTeamScore) { + "$redTeamMember1\n$redTeamMember2" + } else { + "$blueTeamMember1\n$blueTeamMember2" + } + + LaunchedEffect(Unit) { + delay(3000) // Задержка в 3 секунды + navController.navigate(Routes.NEW_COMP_GAME_DIALOG) { + popUpTo(Routes.WINNER_SCREEN_COMP) { inclusive = true } + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.winner_member), + style = Typography.displayMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 8.dp) + ) + Text( + text = stringResource(id = R.string.winner_team), + style = Typography.displayMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 90.dp) + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .border( + width = 2.dp, + color = if (redTeamScore > blueTeamScore) Red else Blue, + shape = RoundedCornerShape(20.dp) + ) + .width(746.dp) + .height(238.dp) + .padding(30.dp) + ) { + Text( + text = winningTeam, + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 50.sp + ) + ) + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/competitionDialog/CompNewGameDialog.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/competitionDialog/CompNewGameDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..663969452d79d3a9bfd869af9cc95da371127837 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/competitionDialog/CompNewGameDialog.kt @@ -0,0 +1,83 @@ +package band.effective.foosball.presentation.screens.competitionGame.competitionDialog + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun CompNewGameDialog(navController: NavController) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(976.dp) + .height(632.dp) + .background(LightBlack, shape = RoundedCornerShape(30.dp)) + .clip(RoundedCornerShape(50.dp)) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .padding(100.dp) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.end_game), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ) + ) + CustomButton( + defaultColor = LightBlack, + text = stringResource(id = R.string.main_menu), + onClick = { navController.navigate(Routes.MAIN_MENU) }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(3.dp, Orange), + cornerRadius = 80.dp + ) + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.new_comp_game), + onClick = { navController.navigate(Routes.TEAM_FAST_GAME) }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/competitionDialog/CompetitiveGameEndButtons.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/competitionDialog/CompetitiveGameEndButtons.kt new file mode 100644 index 0000000000000000000000000000000000000000..021cd595d2a6d81077423e6b6b87efb2ec4e1a4b --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/competitionGame/competitionDialog/CompetitiveGameEndButtons.kt @@ -0,0 +1,74 @@ +package band.effective.foosball.presentation.screens.competitionGame.competitionDialog + +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.vievmodel.ScoreViewModel +import com.example.effectivefoosball.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Composable +fun CompetitiveGameEndButtons(navController: NavController, scoreViewModel: ScoreViewModel) { + val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + Column { + CustomButton( + defaultColor = LightBlack, + text = stringResource(R.string.no), + onClick = { onBackPressedDispatcher?.onBackPressed() }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(3.dp, Orange), + cornerRadius = 80.dp + ) + CustomButton( + defaultColor = Orange, + text = stringResource(R.string.yes_save_game), + onClick = { + val gameDate = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format( + Date() + ) + scoreViewModel.getLeftScore().value + scoreViewModel.getRightScore().value + val redTeamMember1 = scoreViewModel.redTeamMember1 + val redTeamMember2 = scoreViewModel.redTeamMember2 + val blueTeamMember1 = scoreViewModel.blueTeamMember1 + val blueTeamMember2 = scoreViewModel.blueTeamMember2 + + System.currentTimeMillis() - scoreViewModel.startTime + + scoreViewModel.saveGameScore( + gameDate, + redTeamMember1, + redTeamMember2, + blueTeamMember1, + blueTeamMember2 + ) + GlobalScope.launch(Dispatchers.IO) { + scoreViewModel.sendGameScoreToSupabase( + gameDate = gameDate + ) + scoreViewModel.resetScore() + } + navController.navigate(Routes.NEW_COMP_GAME_DIALOG) + }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/dialogs/EndGame.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/dialogs/EndGame.kt new file mode 100644 index 0000000000000000000000000000000000000000..1f1763412bc81323ac8a3c6371340a3c84576f6c --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/dialogs/EndGame.kt @@ -0,0 +1,78 @@ +package band.effective.foosball.presentation.screens.dialogs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.routes.GameMode +import band.effective.foosball.presentation.screens.competitionGame.competitionDialog.CompetitiveGameEndButtons +import band.effective.foosball.presentation.screens.fastGame.fastDialog.FastGameEndButtons +import band.effective.foosball.presentation.screens.tourGame.tourDialog.TournamentGameEndButtons +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.White +import band.effective.foosball.vievmodel.ScoreViewModel +import com.example.effectivefoosball.R + +@Composable +fun EndGame( + navController: NavController, + scoreViewModel: ScoreViewModel +) { + val currentGameMode by scoreViewModel.currentGameMode.collectAsState() + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(976.dp) + .height(632.dp) + .background(LightBlack, shape = RoundedCornerShape(30.dp)) + .clip(RoundedCornerShape(50.dp)) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.padding(100.dp).fillMaxHeight(), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.give_up), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ) + ) + + when (currentGameMode) { + GameMode.FAST -> FastGameEndButtons(navController, scoreViewModel) + GameMode.COMPETITIVE -> CompetitiveGameEndButtons(navController, scoreViewModel) + GameMode.TOURNAMENT -> TournamentGameEndButtons(navController, scoreViewModel) + GameMode.MAIN_MANU -> TODO() + } + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/dialogs/GameIsFinished.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/dialogs/GameIsFinished.kt new file mode 100644 index 0000000000000000000000000000000000000000..78d4887e471390bab0157b25a3a594292a7ec022 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/dialogs/GameIsFinished.kt @@ -0,0 +1,178 @@ +package band.effective.foosball.presentation.screens.dialogs + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Purple +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun GameIsFinished( + navController: NavController, + // content: @Composable () -> Unit +) { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(984.dp) + .height(646.dp) + .background(LightBlack, shape = RoundedCornerShape(30.dp)) + .clip(RoundedCornerShape(50.dp)), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .fillMaxSize() + .wrapContentHeight() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.SpaceAround, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.game_is_finished), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 50.sp + ) + ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 100.dp, vertical = 20.dp) + ) { + Button( + onClick = { /* TODO */ }, + colors = ButtonDefaults.buttonColors(LightBlack), + shape = RoundedCornerShape(80.dp), + border = BorderStroke(3.dp, Purple), + modifier = Modifier + .weight(1f) + .height(122.dp) + .padding(horizontal = 8.dp) + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize() + ) { + Icon( + painter = painterResource(id = R.drawable.ic_pausa), + contentDescription = null, + tint = Color.White + ) + Text( + text = stringResource(id = R.string.stop_recording), + fontSize = 20.sp, + color = Color.White + ) + } + } + Button( + onClick = { /* TODO */ }, + colors = ButtonDefaults.buttonColors(LightBlack), + shape = RoundedCornerShape(80.dp), + border = BorderStroke(3.dp, Purple), + modifier = Modifier + .weight(1f) + .height(122.dp) + .padding(horizontal = 8.dp) + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize() + ) { + Icon( + painter = painterResource(id = R.drawable.ic_stop_streem), + contentDescription = null, + tint = Color.White + ) + Text( + text = stringResource(id = R.string.stop_stream), + fontSize = 20.sp, + color = Color.White + ) + } + } + Button( + onClick = { /* TODO */ }, + colors = ButtonDefaults.buttonColors(LightBlack), + shape = RoundedCornerShape(80.dp), + border = BorderStroke(3.dp, Purple), + modifier = Modifier + .weight(1f) + .height(122.dp) + .padding(horizontal = 8.dp) + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize() + ) { + Icon( + painter = painterResource(id = R.drawable.ic_save), + contentDescription = null, + tint = Color.White + ) + Text( + text = stringResource(id = R.string.saave_recording), + fontSize = 20.sp, + color = Color.White + ) + } + } + } + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.new_game), + onClick = { navController.navigate(Routes.SCORE_SCREEN) }, + width = 339.dp, + height = 98.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + // content() + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/ScoreScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/ScoreScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..126e9b044bea318255900c9539a57da0c0af1317 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/ScoreScreen.kt @@ -0,0 +1,43 @@ +package band.effective.foosball.presentation.screens.fastGame + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import band.effective.foosball.presentation.components.Clock +import band.effective.foosball.presentation.components.CounterBox +import band.effective.foosball.ui.theme.Blue +import band.effective.foosball.ui.theme.Red +import band.effective.foosball.vievmodel.ScoreViewModel + +@Composable +fun ScoreScreen(scoreViewModel: ScoreViewModel) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(start = 80.dp), + verticalArrangement = Arrangement.SpaceAround, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Clock() + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + ) { + CounterBox(Red, scoreViewModel.getLeftScore()) + Spacer(modifier = Modifier.width(16.dp)) + CounterBox(Blue, scoreViewModel.getRightScore()) + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/StartGameScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/StartGameScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..08299bd51728ef5ccbc49d3e997b3a558b5bb155 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/StartGameScreen.kt @@ -0,0 +1,72 @@ +package band.effective.foosball.presentation.screens.fastGame + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.Orange +import com.example.effectivefoosball.R + +@Composable +fun StartGameScreen(navHostController: NavHostController) { + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + .wrapContentHeight() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button( + onClick = { + navHostController.navigate(Routes.SCORE_SCREEN) + }, + colors = ButtonDefaults.buttonColors(Orange), + shape = RoundedCornerShape(80.dp), + modifier = Modifier + .width(686.dp) + .height(118.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxSize() + ) { + Text( + text = stringResource(id = R.string.start_game), + fontSize = 24.sp, + color = Color.White + ) + Spacer(modifier = Modifier.width(15.dp)) + Icon( + painter = painterResource(id = R.drawable.ic_next), + contentDescription = null, + tint = Color.White + ) + } + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/fastDialog/FastGameEndButtons.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/fastDialog/FastGameEndButtons.kt new file mode 100644 index 0000000000000000000000000000000000000000..abc3636c61f60b2b6f2972dded3891c583511f84 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/fastDialog/FastGameEndButtons.kt @@ -0,0 +1,43 @@ +package band.effective.foosball.presentation.screens.fastGame.fastDialog + +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.vievmodel.ScoreViewModel +import com.example.effectivefoosball.R + +@Composable +fun FastGameEndButtons(navController: NavController, scoreViewModel: ScoreViewModel) { + val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + Column { + CustomButton( + defaultColor = LightBlack, + text = stringResource(R.string.yes), + onClick = { + scoreViewModel.resetScore() + navController.navigate(Routes.NEW_FAST_GAME_DIALOG) + }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(3.dp, Orange), + cornerRadius = 80.dp + ) + CustomButton( + defaultColor = Orange, + text = stringResource(R.string.no_continue_game), + onClick = { onBackPressedDispatcher?.onBackPressed() }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/fastDialog/NewGameDialog.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/fastDialog/NewGameDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..c8d39e6c8c1cd087c50c5c30f0462a7f2a9bb642 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/fastGame/fastDialog/NewGameDialog.kt @@ -0,0 +1,81 @@ +package band.effective.foosball.presentation.screens.fastGame.fastDialog + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun NewGameDialog(navController: NavController) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(976.dp) + .height(632.dp) + .background(LightBlack, shape = RoundedCornerShape(30.dp)) + .clip(RoundedCornerShape(50.dp)) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.padding(100.dp).fillMaxHeight(), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.end_game), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ) + ) + CustomButton( + defaultColor = LightBlack, + text = stringResource(id = R.string.main_menu), + onClick = { navController.navigate(Routes.MAIN_MENU) }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(3.dp, Orange), + cornerRadius = 80.dp + ) + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.new_fast_game), + onClick = { navController.navigate(Routes.SCORE_SCREEN) }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/onboarding/MainMenu.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/onboarding/MainMenu.kt new file mode 100644 index 0000000000000000000000000000000000000000..09c3d2db89e2470f13212385f1601fc48d4639d4 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/onboarding/MainMenu.kt @@ -0,0 +1,82 @@ +package band.effective.foosball.presentation.screens.onboarding + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Typography +import com.example.effectivefoosball.R + +@Composable +fun MainMenu(navHostController: NavHostController) { + Box(modifier = Modifier.fillMaxSize()) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier.padding(top = 50.dp), + text = stringResource(R.string.select_play), + style = Typography.displayMedium + ) + Row( + modifier = Modifier + .fillMaxSize() + .wrapContentHeight() + .verticalScroll( + rememberScrollState() + ), + horizontalArrangement = Arrangement.Center + ) { + CustomButton( + text = stringResource(R.string.tour_play), + onClick = { navHostController.navigate(Routes.ALERT_DIALOG) }, + cornerRadius = 30.dp, + defaultColor = LightBlack, + border = BorderStroke(0.dp, LightBlack), + width = 318.dp, + height = 372.dp, + ) + Spacer(modifier = Modifier.width(30.dp)) + CustomButton( + text = stringResource(R.string.play), + onClick = { navHostController.navigate(Routes.START_GAME_SCREEN) }, + cornerRadius = 30.dp, + defaultColor = LightBlack, + border = BorderStroke(3.dp, Orange), + width = 318.dp, + height = 372.dp, + ) + Spacer(modifier = Modifier.width(30.dp)) + CustomButton( + text = stringResource(R.string.competitive_game), + onClick = { navHostController.navigate(Routes.TEAM_FAST_GAME) }, + cornerRadius = 30.dp, + defaultColor = LightBlack, + border = BorderStroke(0.dp, LightBlack), + width = 318.dp, + height = 372.dp, + ) + } + } + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/onboarding/OnBoardingScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/onboarding/OnBoardingScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..e4d69b20306c2db89062b4db086688945d9e7553 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/onboarding/OnBoardingScreen.kt @@ -0,0 +1,112 @@ +package band.effective.foosball.presentation.screens.onboarding + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import band.effective.foosball.ui.theme.OrangeCircle +import band.effective.foosball.ui.theme.PurpleCircle +import com.example.effectivefoosball.R + +@Composable +fun OnBoardingScreen() { + val infiniteTransition = rememberInfiniteTransition(label = "CircleAnim") + + val ball1X by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 6000, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "ball1x" + ) + + val ball1Y by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 5000, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "ball1y" + ) + + val ball2X by infiniteTransition.animateFloat( + initialValue = 1f, + targetValue = 0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 4000, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "ball2x" + ) + + val ball2Y by infiniteTransition.animateFloat( + initialValue = 1f, + targetValue = 0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 7000, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "ball2y" + ) + + Box(modifier = Modifier.fillMaxSize()) { + Canvas(modifier = Modifier.fillMaxSize()) { + val radius = size.maxDimension + val ball1Offset = Offset(ball1X * size.width, ball1Y * size.height) + val ball2Offset = Offset(ball2X * size.width, ball2Y * size.height) + + drawCircle( + brush = Brush.radialGradient( + colors = listOf(OrangeCircle, Color.Transparent), + center = ball1Offset, + radius = radius * 0.5f + ), + radius = radius, + center = ball1Offset + ) + + drawCircle( + brush = Brush.radialGradient( + colors = listOf(PurpleCircle, Color.Transparent), + center = ball2Offset, + radius = radius * 0.5f + ), + radius = radius, + center = ball2Offset + ) + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier + .width(500.dp) + .height(500.dp), + painter = painterResource(id = R.drawable.effective_logo), + contentDescription = null + ) + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/GracWinnerScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/GracWinnerScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..d9e95f26769576b8806169432f3b9f9b7c8aebd6 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/GracWinnerScreen.kt @@ -0,0 +1,151 @@ +package band.effective.foosball.presentation.screens.tourGame + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.ui.theme.BackgroundColor +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Purple +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun GracWinnerScreen() { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + Box { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.grac_winner), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 58.sp + ) + ) + Text( + text = "Roman K.\nSvetlana M.", + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 50.sp + ) + ) + } + } + Box { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.next_game), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 40.sp + ) + ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Text( + text = "Lera M.\nPetya P.", + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 40.sp + ) + ) + Text( + text = stringResource(id = R.string.vs), + style = TextStyle( + color = Orange, + fontFamily = DrukWide, + fontSize = 30.sp + ) + ) + Text( + text = "Lera M.\nPetya P.", + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 40.sp + ) + ) + } + } + } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Button( + onClick = { /*TODO*/ }, + modifier = Modifier + .width(400.dp) + .height(98.dp) + .border(3.dp, Purple, shape = RoundedCornerShape(80.dp)), + colors = ButtonDefaults.buttonColors(containerColor = BackgroundColor), + shape = RoundedCornerShape(80.dp) + ) { + Column( + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.ic_invitation), + contentDescription = null, + tint = White + ) + Text( + text = stringResource(id = R.string.invite_the_following_teams), + style = TextStyle( + color = Color.White, + fontFamily = Roboto, + fontSize = 20.sp + ) + ) + } + } + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.next), + onClick = { /*TODO*/ }, + width = 400.dp, + height = 98.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/OrderOfGamesScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/OrderOfGamesScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..3392c076d24f2adaccf176514cf534f912c36851 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/OrderOfGamesScreen.kt @@ -0,0 +1,89 @@ +package band.effective.foosball.presentation.screens.tourGame + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun OrderOfGamesScreen( + team1: List, + team2: List +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly, + ) { + Text( + text = stringResource(id = R.string.game), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 60.sp + ) + ) + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + // Отображение первой команды + Column { + team1.forEach { player -> + Text( + text = player, + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 50.sp + ) + ) + } + } + Text( + text = "vs", + style = TextStyle( + color = Orange, + fontFamily = DrukWide, + fontSize = 40.sp + ) + ) + // Отображение второй команды + Column { + team2.forEach { player -> + Text( + text = player, + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 50.sp + ) + ) + } + } + } + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.start_game), + onClick = { /*TODO*/ }, + width = 400.dp, + height = 98.dp, + cornerRadius = 80.dp, + border = BorderStroke(3.dp, Orange) + ) + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TeamDistributionScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TeamDistributionScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..46580e37d8aa60e0711bb254b860414c740e1648 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TeamDistributionScreen.kt @@ -0,0 +1,60 @@ +package band.effective.foosball.presentation.screens.tourGame + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.presentation.screens.tourGame.tourComponents.TeamRow +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Typography +import com.example.effectivefoosball.R + +@Composable +fun TeamDistributionScreen(teamCount: Int, navController: NavController) { + Column( + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.distribution_of_teams), + style = Typography.displayMedium, + modifier = Modifier.padding(top = 32.dp, bottom = 30.dp) + ) + + LazyColumn( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(45.dp) + ) { + items(teamCount) { index -> + TeamRow(index + 1) + } + } + + Button( + onClick = { navController.navigate(Routes.TEAM_DRAG_DROP_LIST_SCREEN) }, + modifier = Modifier + .width(400.dp) + .height(98.dp), + colors = ButtonDefaults.buttonColors(containerColor = Orange), + shape = RoundedCornerShape(80.dp) + ) { + Text( + text = stringResource(id = R.string.next), + style = Typography.labelLarge + ) + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TeamDragDropListScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TeamDragDropListScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..bbc25138a3eb8177741eb921bbcc1bc0e953e98c --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TeamDragDropListScreen.kt @@ -0,0 +1,179 @@ +package band.effective.foosball.presentation.screens.tourGame + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.screens.tourGame.tourComponents.DragDropList +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.TeamList +import band.effective.foosball.ui.theme.Typography +import band.effective.foosball.ui.theme.White +import band.effective.foosball.vievmodel.GameOrderViewModel +import com.example.effectivefoosball.R + +data class Game( + val id: Int, + val team1: List, + val team2: List +) + +@Composable +fun GameOrderScreen(viewModel: GameOrderViewModel, navController: NavController) { + val games = viewModel.games + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + Text( + text = stringResource(id = R.string.sequence_of_games), + style = Typography.displayMedium, + modifier = Modifier.padding(bottom = 16.dp) + ) + Row( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Column(modifier = Modifier.width(80.dp)) { + repeat(5) { index -> + Box( + modifier = Modifier + .height(98.dp) + .fillMaxSize() + .padding(end = 5.dp), + contentAlignment = Alignment.Center + ) { + Text( + "${stringResource(id = R.string.game)} ${index + 1}", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 20.sp + ) + ) + } + } + } + DragDropList( + items = games, + onMove = { from, to -> + viewModel.reorderGames(from, to) + }, + modifier = Modifier.width(712.dp) + ) { game -> + GameItem(game) + } + } + + Button( + onClick = { + val firstGame = games.firstOrNull() + if (firstGame != null) { + val team1String = firstGame.team1.joinToString(",") + val team2String = firstGame.team2.joinToString(",") + navController.navigate("game1/$team1String/$team2String") + } + }, + modifier = Modifier + .width(390.dp) + .height(98.dp), + colors = ButtonDefaults.buttonColors(containerColor = Orange), + shape = RoundedCornerShape(80.dp) + ) { + Text( + text = stringResource(id = R.string.next), + style = Typography.labelLarge + ) + } + } +} + + +@Composable +fun GameItem(game: Game) { + Card( + modifier = Modifier + .height(98.dp) + .fillMaxSize() + .padding(vertical = 4.dp), + colors = CardDefaults.cardColors(containerColor = TeamList), + shape = RoundedCornerShape(8.dp) + ) { + Row( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_dragburger), + contentDescription = null, + tint = White, + modifier = Modifier.padding(end = 16.dp) + ) + TeamNames(game.team1) + Text( + text = stringResource(id = R.string.vs), + style = TextStyle( + color = Orange, + fontFamily = DrukWide, + fontSize = 25.sp + ), + modifier = Modifier.padding(horizontal = 16.dp) + ) + TeamNames(game.team2) + } + } +} + +@Composable +fun TeamNames(names: List, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + names.forEach { name -> + Text(name, style = TextStyle(fontFamily = Roboto, fontSize = 25.sp, color = White)) + } + } +} + +fun generateGames(): List { + return listOf( + Game(1, listOf("Grisha T.", "Misha J."), listOf("Petya L.", "Maya K.")), + Game(2, listOf("Genia D.", "Toria N."), listOf("Grisha F.", "Misha R.")), + Game(3, listOf("Katia H.", "Olga R."), listOf("Petya F.", "Yana R.")), + Game(4, listOf("Ivan K.", "Jack A."), listOf("Kate U.", "Olga R.")), + Game(5, listOf("Roman K.", "Grisha R."), listOf("Arina F.", "Yana N.")) + ) +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourFinalScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourFinalScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..07f571254feaa932cb5fa23fad0359df2fae5d06 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourFinalScreen.kt @@ -0,0 +1,228 @@ +package band.effective.foosball.presentation.screens.tourGame + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.TeamList +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun TourFinalScreen() { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + Text( + text = stringResource(id = R.string.tour_final), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 60.sp + ) + ) + Text( + text = stringResource(id = R.string.game_first_place), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 40.sp + ) + ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Box( + modifier = Modifier + .padding(30.dp) + .width(434.dp) + .height(102.dp) + .background(color = TeamList, shape = RoundedCornerShape(16.dp)) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Konstantin Y.\nAlexander L.", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 32.sp + ), + modifier = Modifier.padding(16.dp) + ) + Text( + text = "15 б", + style = TextStyle( + color = Orange, + fontFamily = Roboto, + fontSize = 36.sp + ), + modifier = Modifier.padding(16.dp) + ) + } + } + Text( + text = stringResource(id = R.string.vs), + style = TextStyle( + color = Orange, + fontFamily = DrukWide, + fontSize = 30.sp + ) + ) + Box( + modifier = Modifier + .padding(30.dp) + .width(434.dp) + .height(102.dp) + .background(color = TeamList, shape = RoundedCornerShape(16.dp)) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Anastasia D.\nMiroslava L.", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 32.sp + ), + modifier = Modifier.padding(16.dp) + ) + Text( + text = "15 б", + style = TextStyle( + color = Orange, + fontFamily = Roboto, + fontSize = 36.sp + ), + modifier = Modifier.padding(16.dp) + ) + } + } + } + Text( + text = stringResource(id = R.string.game_third_place), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ) + ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Box( + modifier = Modifier + .padding(30.dp) + .width(434.dp) + .height(102.dp) + .background(color = TeamList, shape = RoundedCornerShape(16.dp)) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Grisha\nMisha", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 32.sp + ), + modifier = Modifier.padding(16.dp) + ) + Text( + text = "15 б", + style = TextStyle( + color = Orange, + fontFamily = Roboto, + fontSize = 36.sp + ), + modifier = Modifier.padding(16.dp) + ) + } + } + Text( + text = stringResource(id = R.string.vs), + style = TextStyle( + color = Orange, + fontFamily = DrukWide, + fontSize = 30.sp + ) + ) + Box( + modifier = Modifier + .padding(30.dp) + .width(434.dp) + .height(102.dp) + .background(color = TeamList, shape = RoundedCornerShape(16.dp)) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Petya\nYana", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 32.sp + ), + modifier = Modifier.padding(16.dp) + ) + Text( + text = "15 б", + style = TextStyle( + color = Orange, + fontFamily = Roboto, + fontSize = 36.sp + ), + modifier = Modifier.padding(16.dp) + ) + } + } + } + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.next), + onClick = { /*TODO*/ }, + width = 400.dp, + height = 98.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourGameResults.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourGameResults.kt new file mode 100644 index 0000000000000000000000000000000000000000..0e653401011dce6bb0b3bd31ceefe814d75ad3ce --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourGameResults.kt @@ -0,0 +1,274 @@ +package band.effective.foosball.presentation.screens.tourGame + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.graphics.shapes.CornerRounding +import androidx.graphics.shapes.RoundedPolygon +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.screens.tourGame.tourComponents.RoundedPolygonShape +import band.effective.foosball.ui.theme.DarkOrange +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.Gold +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.Silver +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun TourGameResults() { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + Text( + text = stringResource(id = R.string.tour_results), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 45.sp + ) + ) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .padding(15.dp) + .width(478.dp) + .height(120.dp) + .background(Gold, shape = RoundedCornerShape(22.dp)) + .border(2.dp, Orange, shape = RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + val hexagon = remember { + RoundedPolygon( + 6, + rounding = CornerRounding(0.2f) + ) + } + val clip = remember(hexagon) { + RoundedPolygonShape(polygon = hexagon) + } + Box( + modifier = Modifier + .clip(clip) + .background(White) + .size(80.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(id = R.string.first_place), + style = TextStyle( + color = Orange, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + Text( + text = "Женя/Витя", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + } + Box( + modifier = Modifier + .padding(15.dp) + .width(238.dp) + .height(120.dp) + .background(Gold, shape = RoundedCornerShape(22.dp)) + .border(2.dp, Orange, shape = RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + Text( + text = "30 баллов!", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + } + Row { + Box( + modifier = Modifier + .padding(15.dp) + .width(478.dp) + .height(120.dp) + .background(Silver, shape = RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + val hexagon = remember { + RoundedPolygon( + 6, + rounding = CornerRounding(0.2f) + ) + } + val clip = remember(hexagon) { + RoundedPolygonShape(polygon = hexagon) + } + Box( + modifier = Modifier + .clip(clip) + .background(White) + .size(80.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(id = R.string.second_place), + style = TextStyle( + color = Orange, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + Text( + text = "Гриша/Миша", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + } + Box( + modifier = Modifier + .padding(15.dp) + .width(238.dp) + .height(120.dp) + .background(Silver, shape = RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + Text( + text = "25 баллов!", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + } + Row { + Box( + modifier = Modifier + .padding(15.dp) + .width(478.dp) + .height(120.dp) + .background(DarkOrange, shape = RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + val hexagon = remember { + RoundedPolygon( + 6, + rounding = CornerRounding(0.2f) + ) + } + val clip = remember(hexagon) { + RoundedPolygonShape(polygon = hexagon) + } + Box( + modifier = Modifier + .clip(clip) + .background(White) + .size(80.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(id = R.string.third_place), + style = TextStyle( + color = Orange, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + Text( + text = "Катя/Оля", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + } + Box( + modifier = Modifier + .padding(15.dp) + .width(238.dp) + .height(120.dp) + .background(DarkOrange, shape = RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + Text( + text = "23 балла!", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + } + } + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.next), + onClick = { /*TODO*/ }, + width = 400.dp, + height = 98.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourStartScreen.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourStartScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..a791f15d2270e5eb649b0ef98c1df65ec1f22fac --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/TourStartScreen.kt @@ -0,0 +1,186 @@ +package band.effective.foosball.presentation.screens.tourGame + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.routes.Constants +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.BackgroundColor +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.Purple +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.Score +import band.effective.foosball.ui.theme.Typography +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun TourStartScreen(navController: NavController) { + var count by remember { mutableIntStateOf(Constants.MIN_LENGTH_COUNTER_TEAM) } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResource(id = R.string.tour_game), + style = Typography.displayMedium, + modifier = Modifier.padding(top = 32.dp) + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.count_team_tour_game), + style = TextStyle( + fontFamily = DrukWide, + fontSize = 35.sp, + color = White + ), + modifier = Modifier.padding(bottom = 16.dp) + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(60.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = { if (count > Constants.MIN_LENGTH_COUNTER_TEAM) count-- }, + modifier = Modifier + .size(60.dp) + .background(LightBlack, shape = RoundedCornerShape(8.dp)) + ) { + Text( + text = "-", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 30.sp + ) + ) + } + + Box( + modifier = Modifier + .size(150.dp) + .background(LightBlack, shape = RoundedCornerShape(22.dp)) + .border(3.dp, Score, shape = RoundedCornerShape(22.dp)) + .clip(RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + Text( + text = count.toString(), + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 50.sp + ) + ) + } + + IconButton( + onClick = { if (count < Constants.MAX_LENGTH_COUNTER_TEAM) count++ }, + modifier = Modifier + .size(60.dp) + .background(LightBlack, shape = RoundedCornerShape(8.dp)) + ) { + Text( + text = "+", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 30.sp + ) + ) + } + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Button( + onClick = { }, + modifier = Modifier + .width(500.dp) + .height(98.dp) + .border(3.dp, Purple, shape = RoundedCornerShape(80.dp)), + colors = ButtonDefaults.buttonColors(containerColor = BackgroundColor), + shape = RoundedCornerShape(80.dp) + ) { + Column( + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(R.drawable.ic_history), + contentDescription = null, + tint = White + ) + Text( + text = stringResource(id = R.string.game_history), + color = Color.White + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { + navController.navigate("${Routes.TEAM_DISTRIBUTION_SCREEN}/$count") + }, + modifier = Modifier + .width(390.dp) + .height(98.dp), + colors = ButtonDefaults.buttonColors(containerColor = Orange), + shape = RoundedCornerShape(80.dp) + ) { + Text( + text = stringResource(id = R.string.next), + style = Typography.labelLarge + ) + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropList.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropList.kt new file mode 100644 index 0000000000000000000000000000000000000000..89585db7a2516528711b2caa79a6511de65ffee6 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropList.kt @@ -0,0 +1,97 @@ +package band.effective.foosball.presentation.screens.tourGame.tourComponents + +import androidx.compose.animation.core.Spring +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.gestures.scrollBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.consumeAllChanges +import androidx.compose.ui.input.pointer.pointerInput +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi +import band.effective.foosball.presentation.screens.tourGame.Game + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun DragDropList( + items: List, + onMove: (Int, Int) -> Unit, + modifier: Modifier = Modifier, + itemContent: @Composable (Game) -> Unit +) { + val scope = rememberCoroutineScope() + var overScrollJob by remember { mutableStateOf(null) } + val dragDropListState = rememberDragDropListState(onMove = onMove) + + LazyColumn( + modifier = modifier + .pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDrag = { change, offset -> + change.consumeAllChanges() + dragDropListState.onDrag(offset = offset) + + if (overScrollJob?.isActive == true) return@detectDragGesturesAfterLongPress + + dragDropListState + .checkForOverScroll() + .takeIf { it != 0f } + ?.let { + overScrollJob = scope.launch { + dragDropListState.lazyListState.scrollBy(it) + } + } ?: run { overScrollJob?.cancel() } + }, + onDragStart = { offset -> dragDropListState.onDragStart(offset) }, + onDragEnd = { dragDropListState.onDragInterrupted() }, + onDragCancel = { dragDropListState.onDragInterrupted() } + ) + }, + state = dragDropListState.lazyListState + ) { + itemsIndexed(items) { index, item -> + val isDragging = index == dragDropListState.currentIndexOfDraggedItem + val offsetY by animateFloatAsState( + targetValue = if (isDragging) dragDropListState.elementDisplacement ?: 0f else 0f, + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ) + val scale by animateFloatAsState( + targetValue = if (isDragging) 1.05f else 1f, + animationSpec = tween(durationMillis = 100) + ) + + Box( + modifier = Modifier + .animateItemPlacement( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ) + .graphicsLayer { + translationY = offsetY + scaleX = scale + scaleY = scale + } + ) { + itemContent(item) + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropListExtensions.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropListExtensions.kt new file mode 100644 index 0000000000000000000000000000000000000000..25731430604d1aec9e5824985afa7aef02a60644 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropListExtensions.kt @@ -0,0 +1,20 @@ +package band.effective.foosball.presentation.screens.tourGame.tourComponents + +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState + +fun LazyListState.getVisibleItemInfoFor(absolute: Int): LazyListItemInfo? { + return this.layoutInfo.visibleItemsInfo.getOrNull(absolute - this.layoutInfo.visibleItemsInfo.first().index) +} + +val LazyListItemInfo.offsetEnd: Int + get() = this.offset + this.size + +fun MutableList.move(from: Int, to: Int) { + if (from == to) { + return + } + + val element = this.removeAt(from) ?: return + this.add(to, element) +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropListState.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropListState.kt new file mode 100644 index 0000000000000000000000000000000000000000..cea773e69c1e360bce5a870da38d8e2dff4f35de --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/DragDropListState.kt @@ -0,0 +1,112 @@ +package band.effective.foosball.presentation.screens.tourGame.tourComponents + +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import kotlinx.coroutines.Job + +@Composable +fun rememberDragDropListState( + lazyListState: LazyListState = rememberLazyListState(), + onMove: (Int, Int) -> Unit +): DragDropListState { + return remember { DragDropListState(lazyListState = lazyListState, onMove = onMove) } +} + +class DragDropListState( + val lazyListState: LazyListState, + private val onMove: (Int, Int) -> Unit +) { + var draggedDistance by mutableStateOf(0f) + var initiallyDraggedElement by mutableStateOf(null) + var currentIndexOfDraggedItem by mutableStateOf(null) + val initialOffsets: Pair? + get() = initiallyDraggedElement?.let { + Pair(it.offset, it.offsetEnd) + } + val elementDisplacement: Float? + get() = currentIndexOfDraggedItem + ?.let { + lazyListState.getVisibleItemInfoFor(absolute = it) + } + ?.let { item -> + (initiallyDraggedElement?.offset ?: 0f).toFloat() + draggedDistance - item.offset + } + + val currentElement: LazyListItemInfo? + get() = currentIndexOfDraggedItem?.let { + lazyListState.getVisibleItemInfoFor(absolute = it) + } + + var overScrollJob by mutableStateOf(null) + + fun onDragStart(offset: Offset) { + lazyListState.layoutInfo.visibleItemsInfo + .firstOrNull { item -> + offset.y.toInt() in item.offset..(item.offset + item.size) + }?.also { + currentIndexOfDraggedItem = it.index + initiallyDraggedElement = it + } + } + + fun onDragInterrupted() { + draggedDistance = 0f + currentIndexOfDraggedItem = null + initiallyDraggedElement = null + overScrollJob?.cancel() + } + + fun onDrag(offset: Offset) { + draggedDistance += offset.y + + initialOffsets?.let { (topOffset, bottomOffset) -> + val startOffset = topOffset + draggedDistance + val endOffset = bottomOffset + draggedDistance + + currentElement?.let { hovered -> + lazyListState.layoutInfo.visibleItemsInfo + .filterNot { item -> + item.offsetEnd < startOffset || item.offset > endOffset || hovered.index == item.index + } + .firstOrNull { item -> + val delta = startOffset - hovered.offset + when { + delta > 0 -> (endOffset > item.offsetEnd) + else -> (startOffset < item.offset) + } + }?.also { item -> + currentIndexOfDraggedItem?.let { current -> + onMove.invoke(current, item.index) + } + currentIndexOfDraggedItem = item.index + } + } + } + } + + fun checkForOverScroll(): Float { + return initiallyDraggedElement?.let { + val startOffset = it.offset + draggedDistance + val endOffset = it.offsetEnd + draggedDistance + + return@let when { + draggedDistance > 0 -> (endOffset - lazyListState.layoutInfo.viewportEndOffset).takeIf { diff -> + diff > 0 + } + + draggedDistance < 0 -> (startOffset - lazyListState.layoutInfo.viewportStartOffset).takeIf { diff -> + diff < 0 + } + + else -> null + } + } ?: 0f + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/PlayerDropdown.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/PlayerDropdown.kt new file mode 100644 index 0000000000000000000000000000000000000000..c6bcf1ec133aa242b076cfb6b5d8e355220fe9c5 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/PlayerDropdown.kt @@ -0,0 +1,67 @@ +package band.effective.foosball.presentation.screens.tourGame.tourComponents + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color.Companion.Gray +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Roboto +import com.example.effectivefoosball.R + +@Composable +fun PlayerDropdown() { + var expanded by remember { mutableStateOf(false) } + + Box( + modifier = Modifier + .width(390.dp) + .height(80.dp) + .clip(shape = RoundedCornerShape(30.dp)) + .background(LightBlack, RoundedCornerShape(30.dp)) + .clickable { expanded = true } + .padding(start = 30.dp, end = 30.dp) + ) { + Text( + text = stringResource(id = R.string.player), + modifier = Modifier.align(Alignment.CenterStart), + style = TextStyle( + color = Gray, + fontSize = 30.sp, + fontFamily = Roboto + ) + ) + Icon( + painter = painterResource(id = R.drawable.ic_unwarp), + contentDescription = null, + tint = Gray, + modifier = Modifier.align(Alignment.CenterEnd) + ) + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + // Тут должен быть выпадающий список + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/RoundedPolygonShape.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/RoundedPolygonShape.kt new file mode 100644 index 0000000000000000000000000000000000000000..381abddb761eeb3391e68e2e5ce135510419ca8c --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/RoundedPolygonShape.kt @@ -0,0 +1,38 @@ +package band.effective.foosball.presentation.screens.tourGame.tourComponents + +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.asComposePath +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.graphics.shapes.RoundedPolygon +import androidx.graphics.shapes.toPath +import kotlin.math.max + +fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) } +class RoundedPolygonShape( + private val polygon: RoundedPolygon, + private var matrix: Matrix = Matrix() +) : Shape { + private var path = Path() + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density + ): Outline { + path.rewind() + path = polygon.toPath().asComposePath() + matrix.reset() + val bounds = polygon.getBounds() + val maxDimension = max(bounds.width, bounds.height) + matrix.scale(size.width / maxDimension, size.height / maxDimension) + matrix.translate(-bounds.left, -bounds.top) + + path.transform(matrix) + return Outline.Generic(path) + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/TeamRow.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/TeamRow.kt new file mode 100644 index 0000000000000000000000000000000000000000..31e641a7be7d8f74869482c1881a42969a5db2a6 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourComponents/TeamRow.kt @@ -0,0 +1,49 @@ +package band.effective.foosball.presentation.screens.tourGame.tourComponents + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import band.effective.foosball.ui.theme.Roboto +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun TeamRow(teamNumber: Int) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${stringResource(id = R.string.team)} $teamNumber", + style = TextStyle( + color = White, + fontSize = 40.sp, + fontFamily = Roboto + ), + ) + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + PlayerDropdown() + Text( + text = "/", + style = TextStyle( + color = White, + fontFamily = Roboto, + fontSize = 40.sp + ) + ) + PlayerDropdown() + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourDialog/TourNewGameDialog.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourDialog/TourNewGameDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..18f13bb9e0a4b1ebadf41b685434a7ceb9d296ce --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourDialog/TourNewGameDialog.kt @@ -0,0 +1,83 @@ +package band.effective.foosball.presentation.screens.tourGame.tourDialog + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.DrukWide +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.ui.theme.White +import com.example.effectivefoosball.R + +@Composable +fun TourNewGameDialog(navController: NavController) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(976.dp) + .height(632.dp) + .background(LightBlack, shape = RoundedCornerShape(30.dp)) + .clip(RoundedCornerShape(50.dp)) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .padding(100.dp) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.end_game), + style = TextStyle( + color = White, + fontFamily = DrukWide, + fontSize = 35.sp + ) + ) + CustomButton( + defaultColor = LightBlack, + text = stringResource(id = R.string.main_menu), + onClick = { navController.navigate(Routes.MAIN_MENU) }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(3.dp, Orange), + cornerRadius = 80.dp + ) + CustomButton( + defaultColor = Orange, + text = stringResource(id = R.string.new_tour_game), + onClick = { navController.navigate(Routes.SCORE_SCREEN) }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourDialog/TournamentGameEndButtons.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourDialog/TournamentGameEndButtons.kt new file mode 100644 index 0000000000000000000000000000000000000000..d21e4ddb8b587753b2dcdffa59525e396d6ce31a --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/presentation/screens/tourGame/tourDialog/TournamentGameEndButtons.kt @@ -0,0 +1,40 @@ +package band.effective.foosball.presentation.screens.tourGame.tourDialog + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import band.effective.foosball.presentation.components.CustomButton +import band.effective.foosball.presentation.components.routes.Routes +import band.effective.foosball.ui.theme.LightBlack +import band.effective.foosball.ui.theme.Orange +import band.effective.foosball.vievmodel.ScoreViewModel +import com.example.effectivefoosball.R + +@Composable +fun TournamentGameEndButtons(navController: NavController, scoreViewModel: ScoreViewModel) { + Column { + CustomButton( + defaultColor = LightBlack, + text = stringResource(R.string.no), + onClick = { }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(3.dp, Orange), + cornerRadius = 80.dp + ) + CustomButton( + defaultColor = Orange, + text = stringResource(R.string.yes_save_game), + onClick = { + navController.navigate(Routes.GAME_IS_FINISHED) + }, + width = 584.dp, + height = 132.dp, + border = BorderStroke(0.dp, Orange), + cornerRadius = 80.dp + ) + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScore.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScore.kt new file mode 100644 index 0000000000000000000000000000000000000000..2dff0fb0baf386368715c2ac2371459b928f341e --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScore.kt @@ -0,0 +1,17 @@ +package band.effective.foosball.roomdatabase + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "game_scores") +data class GameScore( + val gameDate: String, + val redTeamMember1: String, + val redTeamMember2: String, + val blueTeamMember1: String, + val blueTeamMember2: String, + val scoreBlue: Int, + val scoreRed: Int, + @PrimaryKey(autoGenerate = true) + val gameId: Int = 0 +) diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreDao.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreDao.kt new file mode 100644 index 0000000000000000000000000000000000000000..9515aac33517fd855eaa2e8b93398cd43d05cca5 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreDao.kt @@ -0,0 +1,10 @@ +package band.effective.foosball.roomdatabase + +import androidx.room.Dao +import androidx.room.Insert + +@Dao +interface GameScoreDao { + @Insert + suspend fun insertGameScore(gameScore: GameScore) +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreDatabase.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreDatabase.kt new file mode 100644 index 0000000000000000000000000000000000000000..7fa4df0acfd9983c4741a81fe31670052d173695 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreDatabase.kt @@ -0,0 +1,28 @@ +package band.effective.foosball.roomdatabase + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [GameScore::class], version = 1) +abstract class GameScoreDatabase : RoomDatabase() { + abstract fun gameScoreDao(): GameScoreDao + + companion object { + @Volatile + private var INSTANCE: GameScoreDatabase? = null + + fun getDatabase(context: Context): GameScoreDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + GameScoreDatabase::class.java, + "game_score_database" + ).build() + INSTANCE = instance + instance + } + } + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreRepository.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..a08abd4a452349899577885e1eb69aaf44a05985 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/roomdatabase/GameScoreRepository.kt @@ -0,0 +1,11 @@ +package band.effective.foosball.roomdatabase + +interface GameScoreRepository { + suspend fun insertGameScore(gameScore: GameScore) +} + +class GameScoreRepositoryImpl(private val gameScoreDao: GameScoreDao) : GameScoreRepository { + override suspend fun insertGameScore(gameScore: GameScore) { + gameScoreDao.insertGameScore(gameScore) + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Color.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Color.kt new file mode 100644 index 0000000000000000000000000000000000000000..d6fa93aac464900479d0bd2d75b38a809e184014 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Color.kt @@ -0,0 +1,31 @@ +package band.effective.foosball.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) + +val Black = Color(0xFF000000) +val Purple = Color(0xFFB98DF1) +val Orange = Color(0xFFEF7234) +val LightOrange = Color(0xFFF68E56) +val BackgroundColor = Color(0xFF1A1A1A) +val White = Color(0xFFFFFFFF) +val Score = Color(0xFFF8AE86) +val ButtonBack = Color(0xFF404040) +val LightBlack = Color(0xFF262626) +val Red = Color(0xFFEB5454) +val Blue = Color(0xFF5463EB) +val PurpleCircle = Color(0xFF5800CB) +val OrangeCircle = Color(0xFFFF9950) +val PressedButton = Color(0xFFF68E56) +val TeamList = Color(0xFFF302D2C) +val DarkOrange = Color(0xFFAC5C22) +val Gold = Color(0xFFF8C154) +val Silver = Color(0xFF887F7F) +val SuperLightGray = Color(0xFF6E6E6E) \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Theme.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Theme.kt new file mode 100644 index 0000000000000000000000000000000000000000..029134fe8305720a2103c939add3e7110461e253 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Theme.kt @@ -0,0 +1,24 @@ +package band.effective.foosball.ui.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable + +private val DarkColorScheme = darkColorScheme( + background = BackgroundColor, + secondary = BackgroundColor, + tertiary = BackgroundColor +) + +@Composable +fun EffectiveFoosballTheme( + content: @Composable () -> Unit +) { + val colorScheme = DarkColorScheme + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Type.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Type.kt new file mode 100644 index 0000000000000000000000000000000000000000..fd6c5ae88e6201b5a5210e8d5dc02511d926daa1 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/ui/theme/Type.kt @@ -0,0 +1,53 @@ +package band.effective.foosball.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import com.example.effectivefoosball.R + +val Roboto = FontFamily( + fonts = listOf( + Font(R.font.roboto_medium, FontWeight.Medium) + ) +) +val DrukWide = FontFamily( + fonts = listOf( + Font(R.font.druk_wide_medium_regular, FontWeight.Normal) + ) +) + +val Typography = Typography( + displayLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Normal, + fontSize = 200.sp, + color = White + ), + displayMedium = TextStyle( + fontFamily = DrukWide, + fontWeight = FontWeight.Bold, + fontSize = 45.sp, + color = White + ), + labelLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Normal, + fontSize = 28.sp, + color = White + ), + labelSmall = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + color = White + ), + bodyLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + color = White + ) +) diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/GameOrderViewModel.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/GameOrderViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..fd318fc195d4277294c40f4f9359be2ab8688846 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/GameOrderViewModel.kt @@ -0,0 +1,21 @@ +package band.effective.foosball.vievmodel + +import androidx.compose.runtime.mutableStateListOf +import androidx.lifecycle.ViewModel +import band.effective.foosball.presentation.screens.tourGame.Game +import band.effective.foosball.presentation.screens.tourGame.generateGames + +class GameOrderViewModel : ViewModel() { + private val _games = mutableStateListOf() + val games: List = _games + + init { + _games.addAll(generateGames()) + } + + fun reorderGames(from: Int, to: Int) { + _games.apply { + add(to, removeAt(from)) + } + } +} \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/ScoreViewModel.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/ScoreViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..5cd8a19f55340fe45490b132266ad7acd885d87c --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/ScoreViewModel.kt @@ -0,0 +1,138 @@ +package band.effective.foosball.vievmodel + +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import band.effective.foosball.arduinoconnection.UsbHandler +import band.effective.foosball.datamodelsupabase.GameScoreSupabase +import band.effective.foosball.network.UserRepository +import band.effective.foosball.presentation.components.routes.GameMode +import band.effective.foosball.roomdatabase.GameScore +import band.effective.foosball.roomdatabase.GameScoreRepository +import com.example.effectivefoosball.BuildConfig +import io.github.jan.supabase.createSupabaseClient +import io.github.jan.supabase.postgrest.Postgrest +import io.github.jan.supabase.postgrest.from +import io.github.jan.supabase.realtime.Realtime +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ScoreViewModel(private val repository: GameScoreRepository) : ViewModel() { + + private var leftScore = mutableStateOf(0) + private var rightScore = mutableStateOf(0) + private var usbHandler: UsbHandler? = null + var startTime: Long = 0L + val supabaseUrl = BuildConfig.SUPABASE_URL + val supabaseKey = BuildConfig.SUPABASE_KEY + + val supabaseClient = createSupabaseClient(supabaseUrl, supabaseKey) { + install(Realtime) + install(Postgrest) + } + + fun getLeftScore() = leftScore + fun getRightScore() = rightScore + + var redTeamMember1 by mutableStateOf("") + private set + var redTeamMember2 by mutableStateOf("") + private set + var blueTeamMember1 by mutableStateOf("") + private set + var blueTeamMember2 by mutableStateOf("") + private set + + fun updateRedTeam(member1: String, member2: String) { + redTeamMember1 = member1 + redTeamMember2 = member2 + } + + fun updateBlueTeam(member1: String, member2: String) { + blueTeamMember1 = member1 + blueTeamMember2 = member2 + } + + fun resetScore() { + leftScore.value = 0 + rightScore.value = 0 + } + + fun startUsbConnection(context: Context) { + usbHandler = UsbHandler { bytes -> + processUsbData(bytes) + } + + viewModelScope.launch { + withContext(Dispatchers.IO) { + try { + usbHandler?.setup(context) + } catch (ex: Exception) { + } + } + } + } + + private fun processUsbData(bytes: ByteArray) { + if (bytes.size == 1) { + if (bytes[0].toInt() == 0) { + leftScore.value += 1 + } else { + rightScore.value += 1 + } + } + } + + fun saveGameScore( + gameDate: String, + redTeamMember1: String, + redTeamMember2: String, + blueTeamMember1: String, + blueTeamMember2: String, + ) { + val gameScore = GameScore( + gameDate = gameDate, + redTeamMember1 = this.redTeamMember1, + redTeamMember2 = this.redTeamMember2, + blueTeamMember1 = this.blueTeamMember1, + blueTeamMember2 = this.blueTeamMember2, + scoreRed = leftScore.value, + scoreBlue = rightScore.value, + ) + + viewModelScope.launch { + repository.insertGameScore(gameScore) + } + } + + suspend fun sendGameScoreToSupabase(gameDate: String) { + val gameScore = GameScoreSupabase( + gameDate = gameDate, + redTeamMember1 = redTeamMember1, + redTeamMember2 = redTeamMember2, + blueTeamMember1 = blueTeamMember1, + blueTeamMember2 = blueTeamMember2, + scoreRed = leftScore.value, + scoreBlue = rightScore.value, + ) + + supabaseClient + .from("game_scores") + .insert(gameScore) + } + + private val _currentGameMode = MutableStateFlow(GameMode.MAIN_MANU) + val currentGameMode: StateFlow = _currentGameMode.asStateFlow() + + fun setGameMode(mode: GameMode) { + _currentGameMode.value = mode + } +} diff --git a/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/ScoreViewModelFactory.kt b/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/ScoreViewModelFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..b582f363ac8948fdc237128ce8a1616289446965 --- /dev/null +++ b/foosball/foosballApp/app/src/main/java/band/effective/foosball/vievmodel/ScoreViewModelFactory.kt @@ -0,0 +1,15 @@ +package band.effective.foosball.vievmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import band.effective.foosball.roomdatabase.GameScoreRepository + +class ScoreViewModelFactory(private val repository: GameScoreRepository) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return ScoreViewModel(repository) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} diff --git a/foosball/foosballApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/foosball/foosballApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..2b068d11462a4b96669193de13a711a3a36220a0 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/res/drawable/effective_logo.xml b/foosball/foosballApp/app/src/main/res/drawable/effective_logo.xml new file mode 100644 index 0000000000000000000000000000000000000000..54e87c539e593a8be1ec10df86a629e24d493451 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/effective_logo.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_back.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000000000000000000000000000000000000..a074d233736c5c582472fc473563b61de245a3ef --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_codding.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_codding.xml new file mode 100644 index 0000000000000000000000000000000000000000..07853076aae86c04c2826638afbd76de6b059581 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_codding.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_dragburger.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_dragburger.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4dddadd82cb33e0ffe55ebdcd58cba635cf6577 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_dragburger.xml @@ -0,0 +1,9 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_history.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 0000000000000000000000000000000000000000..d7f78ad30f27ca35c581682e59771f4014a8bf7c --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,13 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_home.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000000000000000000000000000000000000..50946447c126cbc13570e535ee3853f6da792086 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,9 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_invitation.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_invitation.xml new file mode 100644 index 0000000000000000000000000000000000000000..732bdb6e224aa9b63693ecede7165b180a33b8e0 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_invitation.xml @@ -0,0 +1,10 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_launcher_background.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..ca3826a46ce070f906d0d3fbe6987df882134381 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_next.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_next.xml new file mode 100644 index 0000000000000000000000000000000000000000..1faa6359d0faf0a89b6e28d8b3009408f553791e --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_next.xml @@ -0,0 +1,9 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_notstream.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_notstream.xml new file mode 100644 index 0000000000000000000000000000000000000000..36b1d182ddee3f7ae60a292989991b5bba7f2e04 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_notstream.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_pausa.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_pausa.xml new file mode 100644 index 0000000000000000000000000000000000000000..e5e2af4c2eb8c1939a32284ea7c1f361482ff61a --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_pausa.xml @@ -0,0 +1,9 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_play.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_play.xml new file mode 100644 index 0000000000000000000000000000000000000000..68e036b8818508e88fddf5b993e51b6df3067e39 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_play.xml @@ -0,0 +1,12 @@ + + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_save.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 0000000000000000000000000000000000000000..e1f704b0ce71564164a0f8f2459fe7981114f02e --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,10 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_select.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_select.xml new file mode 100644 index 0000000000000000000000000000000000000000..b57035ea22eb2a70057f5c13af605bb04af61794 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_select.xml @@ -0,0 +1,15 @@ + + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_stop_streem.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_stop_streem.xml new file mode 100644 index 0000000000000000000000000000000000000000..da7477951fe22407e6e379b99500517e39dfbcd0 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_stop_streem.xml @@ -0,0 +1,9 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/drawable/ic_unwarp.xml b/foosball/foosballApp/app/src/main/res/drawable/ic_unwarp.xml new file mode 100644 index 0000000000000000000000000000000000000000..c51edb5edf90cd2d0939669dd793e2794b782acc --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/drawable/ic_unwarp.xml @@ -0,0 +1,9 @@ + + + diff --git a/foosball/foosballApp/app/src/main/res/font/druk_wide_medium.ttf b/foosball/foosballApp/app/src/main/res/font/druk_wide_medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..cd828b64d0907794b1f02ab1f0a6e7bf2d599b68 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/font/druk_wide_medium.ttf differ diff --git a/foosball/foosballApp/app/src/main/res/font/druk_wide_medium_regular.ttf b/foosball/foosballApp/app/src/main/res/font/druk_wide_medium_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..59a022b9e2013d311481a94777053b41f7db3538 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/font/druk_wide_medium_regular.ttf differ diff --git a/foosball/foosballApp/app/src/main/res/font/museo_cyrl.otf b/foosball/foosballApp/app/src/main/res/font/museo_cyrl.otf new file mode 100644 index 0000000000000000000000000000000000000000..7554d97ce4900d01a6b6be56e418ba525cae7b90 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/font/museo_cyrl.otf differ diff --git a/foosball/foosballApp/app/src/main/res/font/museomoderno_medium.ttf b/foosball/foosballApp/app/src/main/res/font/museomoderno_medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..95046c078d423d882ba5a3992d62eead4f0f83f1 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/font/museomoderno_medium.ttf differ diff --git a/foosball/foosballApp/app/src/main/res/font/roboto_medium.ttf b/foosball/foosballApp/app/src/main/res/font/roboto_medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ac0f908b9c9c73da558b45d65cc5c6094874d3e8 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/font/roboto_medium.ttf differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/foosball/foosballApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000000000000000000000000000000000..036d09bc5fd523323794379703c4a111d1e28a04 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/foosball/foosballApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000000000000000000000000000000000..036d09bc5fd523323794379703c4a111d1e28a04 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..63427bb729fb6e1552ff331de68a5b9377f9b41b Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..4627c3bf009533a0558c78226153cc5cb9c1aae0 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..8f4d69539282e406fc76135ae52c0fc959266ff5 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..3d5199f2419531b6f926273bfad7c54bb40ae761 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..739fd0657d568c3938a731d7c1fa9e1ebd7f1aa7 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..529fb829073b5dacb02c4e6cf6b1b4c7fd0d773b Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4c58b59827e2d9016e50a66687d4c1b89aaa1e13 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..9d7f760157201539bcd15618d1983d3bd2f205cb Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..35f8571e1af86ad36bd12382d3dace997105dd81 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..35b695a32cf4dc456debf502b2cd161ec82c004f Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..ab7403eaf33416bc1eef2b537adaad32057b6c9a Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..be4cbd9eec8dc397fb1566ded7c33529d3989990 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..ca01ab7dd718e07e24f39799eead11707065539e Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..ee095f022305d341e64ac36ee761ee84e88b18aa Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..d9458725a555e8ec22aa8f15e9395f9fc70584a8 Binary files /dev/null and b/foosball/foosballApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/foosball/foosballApp/app/src/main/res/values/colors.xml b/foosball/foosballApp/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000000000000000000000000000000000..be781246c046467794d56fadb855c93602a2fe89 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/values/colors.xml @@ -0,0 +1,12 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + #ffffff00 + + \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/res/values/ic_launcher_background.xml b/foosball/foosballApp/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..89d6d6be5f02e11d33053609cfbff94bc7d6173a --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #111111 + \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/res/values/strings.xml b/foosball/foosballApp/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..4ea5466350ec0af651d4eb8a8d8242d2bd2cbf3f --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/values/strings.xml @@ -0,0 +1,67 @@ + + foosballEffective + effective + Foosball + Быстрая + Новая игра + Турнирная + Выбор игры + Остановить запись + Остановить стрим + Скачать запись + Игра окончена + Закончить игру + Продолжить игру + Команды + Команда + Команда\nсиних + Команда\nкрасных + Далее + Имя + Начать игру + Запись + Трансляция + Главная + Завершить + Назад + Предупреждение + Ок + Пожалуйста, заполните все поля + Соревновательная + Турнирная игра + Количество команд + История игр + Распределение команд + Игрок + Развернуть + Порядок игр + Игра + vs + Поздравляем с победой! + Следующая игра: + Пригласить следующие команды + Игра за 1 место + Игра за 3 место + Финал + Итоги турнирной игры + 1 + 2 + 3 + Сдаётесь? + Нет + Да + Да, сохранить текущий счет + Сохранить результат матча? + Нет, продолжить игру + Игра окончена + Новая быстрая игра + Главное меню + История игр + Новая соревновательная игра + Новая турнирная игра + Поздравляем с победой + команду + Идет активная разработка раздела + Ошибка + Пожалуйста, заполните все поля + \ No newline at end of file diff --git a/foosball/foosballApp/app/src/main/res/values/themes.xml b/foosball/foosballApp/app/src/main/res/values/themes.xml new file mode 100644 index 0000000000000000000000000000000000000000..8140de3273373a94664670c08b770c654158fab6 --- /dev/null +++ b/foosball/foosballApp/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +