From 704135d3430d1ddfce8c8a1411eecb0423d455d9 Mon Sep 17 00:00:00 2001 From: Spring Date: Mon, 24 Aug 2026 22:58:45 +0700 Subject: [PATCH 01/35] build: configure Jetpack Compose support and dependencies --- .gitignore | 6 ++++++ app/build.gradle.kts | 12 ++++++++++++ app/src/main/AndroidManifest.xml | 14 ++++++++++++++ build.gradle.kts | 1 + gradle/libs.versions.toml | 12 ++++++++++++ 5 files changed, 45 insertions(+) diff --git a/.gitignore b/.gitignore index 5fc9f0870b6..39baccaee70 100644 --- a/.gitignore +++ b/.gitignore @@ -226,3 +226,9 @@ fabric.properties !/gradle/wrapper/gradle-wrapper.jar # End of https://www.toptal.com/developers/gitignore/api/kotlin,java,android,androidstudio,visualstudiocode +.agents/rules/antigravity-rtk-rules.md +.agents/skills/cloudstream/SKILL.md +.codegraph/.gitignore +.rtk/filters.toml +CLAUDE.md +gradle/gradle-daemon-jvm.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ad8451a352..c6990b36d3e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.dokka) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.compose) } val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) @@ -199,6 +200,7 @@ android { buildFeatures { buildConfig = true viewBinding = true + compose = true } packaging { @@ -239,6 +241,16 @@ dependencies { implementation(libs.material) implementation(libs.constraintlayout) + // Jetpack Compose + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.activity.compose) + + // Coil Image Loading implementation(libs.bundles.coil) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee4c978f2be..d3489a33c40 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -275,6 +275,20 @@ android:name=".ui.ControllerActivity" android:exported="false" /> + + + + + + Date: Mon, 24 Aug 2026 22:59:00 +0700 Subject: [PATCH 02/35] feat(design-system): add Cloneflix design tokens, drawables, and XML resources --- .../main/res/drawable/cloneflix_bg_card.xml | 9 + .../drawable/cloneflix_bg_card_elevated.xml | 9 + .../res/drawable/cloneflix_bg_header_icon.xml | 8 + .../res/drawable/cloneflix_bg_input_field.xml | 21 ++ .../drawable/cloneflix_bg_swatch_preview.xml | 8 + .../main/res/drawable/cloneflix_btn_ghost.xml | 10 + .../res/drawable/cloneflix_btn_outline.xml | 13 + .../res/drawable/cloneflix_btn_primary.xml | 10 + .../res/drawable/cloneflix_btn_secondary.xml | 10 + .../main/res/drawable/cloneflix_ic_about.xml | 9 + .../res/drawable/cloneflix_ic_account.xml | 9 + app/src/main/res/drawable/cloneflix_ic_ad.xml | 9 + .../res/drawable/cloneflix_ic_arrow_down.xml | 9 + .../res/drawable/cloneflix_ic_arrow_left.xml | 9 + .../res/drawable/cloneflix_ic_arrow_right.xml | 9 + .../cloneflix_ic_arrow_right_narrow.xml | 9 + .../res/drawable/cloneflix_ic_arrow_up.xml | 9 + .../main/res/drawable/cloneflix_ic_check.xml | 9 + .../drawable/cloneflix_ic_chevron_right.xml | 13 + .../drawable/cloneflix_ic_circle_error.xml | 17 ++ .../main/res/drawable/cloneflix_ic_close.xml | 9 + .../main/res/drawable/cloneflix_ic_colors.xml | 9 + .../main/res/drawable/cloneflix_ic_copy.xml | 9 + .../drawable/cloneflix_ic_double_thumb_up.xml | 12 + .../res/drawable/cloneflix_ic_dropdown.xml | 9 + .../main/res/drawable/cloneflix_ic_edit.xml | 9 + .../drawable/cloneflix_ic_error_circle.xml | 9 + .../res/drawable/cloneflix_ic_feedback.xml | 9 + .../main/res/drawable/cloneflix_ic_flag.xml | 9 + .../main/res/drawable/cloneflix_ic_help.xml | 9 + .../main/res/drawable/cloneflix_ic_info.xml | 9 + .../res/drawable/cloneflix_ic_input_field.xml | 12 + .../main/res/drawable/cloneflix_ic_labels.xml | 9 + .../main/res/drawable/cloneflix_ic_mute.xml | 9 + .../drawable/cloneflix_ic_notification.xml | 9 + .../res/drawable/cloneflix_ic_open_in_new.xml | 9 + .../main/res/drawable/cloneflix_ic_person.xml | 9 + .../main/res/drawable/cloneflix_ic_play.xml | 9 + .../main/res/drawable/cloneflix_ic_plus.xml | 9 + .../main/res/drawable/cloneflix_ic_replay.xml | 9 + .../drawable/cloneflix_ic_replay_arrow.xml | 9 + .../main/res/drawable/cloneflix_ic_search.xml | 9 + .../res/drawable/cloneflix_ic_social_fb.xml | 9 + .../res/drawable/cloneflix_ic_social_ig.xml | 9 + .../res/drawable/cloneflix_ic_social_x.xml | 9 + .../res/drawable/cloneflix_ic_social_yt.xml | 9 + .../res/drawable/cloneflix_ic_thumb_down.xml | 9 + .../res/drawable/cloneflix_ic_thumb_up.xml | 9 + .../res/drawable/cloneflix_ic_typography.xml | 9 + .../res/drawable/cloneflix_ic_videoplayer.xml | 9 + .../res/drawable/cloneflix_ic_visibility.xml | 9 + .../drawable/cloneflix_ic_visibility_off.xml | 9 + .../main/res/drawable/cloneflix_ic_volume.xml | 9 + .../layout/revamp_activity_design_system.xml | 6 + .../main/res/layout/revamp_fragment_about.xml | 69 +++++ .../res/layout/revamp_fragment_colors.xml | 35 +++ .../res/layout/revamp_fragment_components.xml | 203 +++++++++++++ .../layout/revamp_fragment_design_system.xml | 63 ++++ .../res/layout/revamp_fragment_typography.xml | 105 +++++++ .../res/layout/revamp_item_color_section.xml | 25 ++ .../res/layout/revamp_item_color_swatch.xml | 58 ++++ .../layout/revamp_item_typography_style.xml | 48 ++++ .../res/layout/revamp_view_about_card.xml | 86 ++++++ .../main/res/layout/revamp_view_header.xml | 68 +++++ app/src/main/res/values/cloneflix_attrs.xml | 70 +++++ app/src/main/res/values/cloneflix_colors.xml | 57 ++++ app/src/main/res/values/cloneflix_dimens.xml | 88 ++++++ app/src/main/res/values/cloneflix_strings.xml | 68 +++++ app/src/main/res/values/cloneflix_styles.xml | 270 ++++++++++++++++++ 69 files changed, 1831 insertions(+) create mode 100644 app/src/main/res/drawable/cloneflix_bg_card.xml create mode 100644 app/src/main/res/drawable/cloneflix_bg_card_elevated.xml create mode 100644 app/src/main/res/drawable/cloneflix_bg_header_icon.xml create mode 100644 app/src/main/res/drawable/cloneflix_bg_input_field.xml create mode 100644 app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml create mode 100644 app/src/main/res/drawable/cloneflix_btn_ghost.xml create mode 100644 app/src/main/res/drawable/cloneflix_btn_outline.xml create mode 100644 app/src/main/res/drawable/cloneflix_btn_primary.xml create mode 100644 app/src/main/res/drawable/cloneflix_btn_secondary.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_about.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_account.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_ad.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_down.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_left.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_right.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_up.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_check.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_chevron_right.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_circle_error.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_close.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_colors.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_copy.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_dropdown.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_edit.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_error_circle.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_feedback.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_flag.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_help.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_info.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_input_field.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_labels.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_mute.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_notification.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_open_in_new.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_person.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_play.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_plus.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_replay.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_search.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_social_fb.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_social_ig.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_social_x.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_social_yt.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_thumb_down.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_thumb_up.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_typography.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_videoplayer.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_visibility.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_visibility_off.xml create mode 100644 app/src/main/res/drawable/cloneflix_ic_volume.xml create mode 100644 app/src/main/res/layout/revamp_activity_design_system.xml create mode 100644 app/src/main/res/layout/revamp_fragment_about.xml create mode 100644 app/src/main/res/layout/revamp_fragment_colors.xml create mode 100644 app/src/main/res/layout/revamp_fragment_components.xml create mode 100644 app/src/main/res/layout/revamp_fragment_design_system.xml create mode 100644 app/src/main/res/layout/revamp_fragment_typography.xml create mode 100644 app/src/main/res/layout/revamp_item_color_section.xml create mode 100644 app/src/main/res/layout/revamp_item_color_swatch.xml create mode 100644 app/src/main/res/layout/revamp_item_typography_style.xml create mode 100644 app/src/main/res/layout/revamp_view_about_card.xml create mode 100644 app/src/main/res/layout/revamp_view_header.xml create mode 100644 app/src/main/res/values/cloneflix_attrs.xml create mode 100644 app/src/main/res/values/cloneflix_colors.xml create mode 100644 app/src/main/res/values/cloneflix_dimens.xml create mode 100644 app/src/main/res/values/cloneflix_strings.xml create mode 100644 app/src/main/res/values/cloneflix_styles.xml diff --git a/app/src/main/res/drawable/cloneflix_bg_card.xml b/app/src/main/res/drawable/cloneflix_bg_card.xml new file mode 100644 index 00000000000..7171b555c1d --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_bg_card.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/cloneflix_bg_card_elevated.xml b/app/src/main/res/drawable/cloneflix_bg_card_elevated.xml new file mode 100644 index 00000000000..3fc4058710f --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_bg_card_elevated.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/cloneflix_bg_header_icon.xml b/app/src/main/res/drawable/cloneflix_bg_header_icon.xml new file mode 100644 index 00000000000..7211a98c88f --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_bg_header_icon.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/cloneflix_bg_input_field.xml b/app/src/main/res/drawable/cloneflix_bg_input_field.xml new file mode 100644 index 00000000000..63b61482418 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_bg_input_field.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml b/app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml new file mode 100644 index 00000000000..a95dbb28122 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/cloneflix_btn_ghost.xml b/app/src/main/res/drawable/cloneflix_btn_ghost.xml new file mode 100644 index 00000000000..372d0556001 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_btn_ghost.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/cloneflix_btn_outline.xml b/app/src/main/res/drawable/cloneflix_btn_outline.xml new file mode 100644 index 00000000000..f5063b7f9de --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_btn_outline.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/cloneflix_btn_primary.xml b/app/src/main/res/drawable/cloneflix_btn_primary.xml new file mode 100644 index 00000000000..8ffe51a8bf0 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_btn_primary.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/cloneflix_btn_secondary.xml b/app/src/main/res/drawable/cloneflix_btn_secondary.xml new file mode 100644 index 00000000000..20525949d09 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_btn_secondary.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_about.xml b/app/src/main/res/drawable/cloneflix_ic_about.xml new file mode 100644 index 00000000000..b6dec8b6fbf --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_about.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_account.xml b/app/src/main/res/drawable/cloneflix_ic_account.xml new file mode 100644 index 00000000000..03572e7e9c1 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_account.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_ad.xml b/app/src/main/res/drawable/cloneflix_ic_ad.xml new file mode 100644 index 00000000000..ec135ce2fef --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_ad.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_down.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_down.xml new file mode 100644 index 00000000000..748ed226427 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_arrow_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_left.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_left.xml new file mode 100644 index 00000000000..d37be42d2ad --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_arrow_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_right.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_right.xml new file mode 100644 index 00000000000..e8d20a05af1 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_arrow_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml new file mode 100644 index 00000000000..e0636536296 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_up.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_up.xml new file mode 100644 index 00000000000..9c911645664 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_arrow_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_check.xml b/app/src/main/res/drawable/cloneflix_ic_check.xml new file mode 100644 index 00000000000..09f9186706c --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_chevron_right.xml b/app/src/main/res/drawable/cloneflix_ic_chevron_right.xml new file mode 100644 index 00000000000..5124bcac0d7 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_chevron_right.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_circle_error.xml b/app/src/main/res/drawable/cloneflix_ic_circle_error.xml new file mode 100644 index 00000000000..5fa180d5082 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_circle_error.xml @@ -0,0 +1,17 @@ + + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_close.xml b/app/src/main/res/drawable/cloneflix_ic_close.xml new file mode 100644 index 00000000000..5de778d0621 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_colors.xml b/app/src/main/res/drawable/cloneflix_ic_colors.xml new file mode 100644 index 00000000000..13066f556be --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_colors.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_copy.xml b/app/src/main/res/drawable/cloneflix_ic_copy.xml new file mode 100644 index 00000000000..048265f78e2 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_copy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml b/app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml new file mode 100644 index 00000000000..3193dee30da --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_dropdown.xml b/app/src/main/res/drawable/cloneflix_ic_dropdown.xml new file mode 100644 index 00000000000..f9419db01c6 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_dropdown.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_edit.xml b/app/src/main/res/drawable/cloneflix_ic_edit.xml new file mode 100644 index 00000000000..d123eb107ac --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_edit.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_error_circle.xml b/app/src/main/res/drawable/cloneflix_ic_error_circle.xml new file mode 100644 index 00000000000..ad4bdca4bbf --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_error_circle.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_feedback.xml b/app/src/main/res/drawable/cloneflix_ic_feedback.xml new file mode 100644 index 00000000000..39631e3ec8b --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_feedback.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_flag.xml b/app/src/main/res/drawable/cloneflix_ic_flag.xml new file mode 100644 index 00000000000..cd43e572858 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_flag.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_help.xml b/app/src/main/res/drawable/cloneflix_ic_help.xml new file mode 100644 index 00000000000..47f5e55eab6 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_help.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_info.xml b/app/src/main/res/drawable/cloneflix_ic_info.xml new file mode 100644 index 00000000000..d522173abf7 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_input_field.xml b/app/src/main/res/drawable/cloneflix_ic_input_field.xml new file mode 100644 index 00000000000..cd21cf23975 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_input_field.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_labels.xml b/app/src/main/res/drawable/cloneflix_ic_labels.xml new file mode 100644 index 00000000000..f985fb53c4d --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_labels.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_mute.xml b/app/src/main/res/drawable/cloneflix_ic_mute.xml new file mode 100644 index 00000000000..f782a2ec788 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_mute.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_notification.xml b/app/src/main/res/drawable/cloneflix_ic_notification.xml new file mode 100644 index 00000000000..f906b77cf5a --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_notification.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_open_in_new.xml b/app/src/main/res/drawable/cloneflix_ic_open_in_new.xml new file mode 100644 index 00000000000..673ed03eac6 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_open_in_new.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_person.xml b/app/src/main/res/drawable/cloneflix_ic_person.xml new file mode 100644 index 00000000000..d81d7dc1aea --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_person.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_play.xml b/app/src/main/res/drawable/cloneflix_ic_play.xml new file mode 100644 index 00000000000..5489ac7f16c --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_play.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_plus.xml b/app/src/main/res/drawable/cloneflix_ic_plus.xml new file mode 100644 index 00000000000..346be53277d --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_plus.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_replay.xml b/app/src/main/res/drawable/cloneflix_ic_replay.xml new file mode 100644 index 00000000000..2f8e48c887e --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_replay.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml b/app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml new file mode 100644 index 00000000000..2c2a810f4b4 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_search.xml b/app/src/main/res/drawable/cloneflix_ic_search.xml new file mode 100644 index 00000000000..6790961fce5 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_social_fb.xml b/app/src/main/res/drawable/cloneflix_ic_social_fb.xml new file mode 100644 index 00000000000..3eca65fa879 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_social_fb.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_social_ig.xml b/app/src/main/res/drawable/cloneflix_ic_social_ig.xml new file mode 100644 index 00000000000..7dd0bdae8a5 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_social_ig.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_social_x.xml b/app/src/main/res/drawable/cloneflix_ic_social_x.xml new file mode 100644 index 00000000000..67e638e3a5e --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_social_x.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_social_yt.xml b/app/src/main/res/drawable/cloneflix_ic_social_yt.xml new file mode 100644 index 00000000000..f99c797c5ac --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_social_yt.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_thumb_down.xml b/app/src/main/res/drawable/cloneflix_ic_thumb_down.xml new file mode 100644 index 00000000000..7295507eab1 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_thumb_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_thumb_up.xml b/app/src/main/res/drawable/cloneflix_ic_thumb_up.xml new file mode 100644 index 00000000000..8d2fb9eea72 --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_thumb_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_typography.xml b/app/src/main/res/drawable/cloneflix_ic_typography.xml new file mode 100644 index 00000000000..6f7c3fdc36c --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_typography.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_videoplayer.xml b/app/src/main/res/drawable/cloneflix_ic_videoplayer.xml new file mode 100644 index 00000000000..b56a1cf043b --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_videoplayer.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_visibility.xml b/app/src/main/res/drawable/cloneflix_ic_visibility.xml new file mode 100644 index 00000000000..6e68a22b58c --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_visibility.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_visibility_off.xml b/app/src/main/res/drawable/cloneflix_ic_visibility_off.xml new file mode 100644 index 00000000000..7769749c1bf --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_visibility_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/cloneflix_ic_volume.xml b/app/src/main/res/drawable/cloneflix_ic_volume.xml new file mode 100644 index 00000000000..72e2231319c --- /dev/null +++ b/app/src/main/res/drawable/cloneflix_ic_volume.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/revamp_activity_design_system.xml b/app/src/main/res/layout/revamp_activity_design_system.xml new file mode 100644 index 00000000000..3d09e455936 --- /dev/null +++ b/app/src/main/res/layout/revamp_activity_design_system.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/revamp_fragment_about.xml b/app/src/main/res/layout/revamp_fragment_about.xml new file mode 100644 index 00000000000..1ad72f660c0 --- /dev/null +++ b/app/src/main/res/layout/revamp_fragment_about.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_fragment_colors.xml b/app/src/main/res/layout/revamp_fragment_colors.xml new file mode 100644 index 00000000000..5479a1fbbb8 --- /dev/null +++ b/app/src/main/res/layout/revamp_fragment_colors.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_fragment_components.xml b/app/src/main/res/layout/revamp_fragment_components.xml new file mode 100644 index 00000000000..b988ad613b1 --- /dev/null +++ b/app/src/main/res/layout/revamp_fragment_components.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_fragment_design_system.xml b/app/src/main/res/layout/revamp_fragment_design_system.xml new file mode 100644 index 00000000000..d9faa297bc4 --- /dev/null +++ b/app/src/main/res/layout/revamp_fragment_design_system.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_fragment_typography.xml b/app/src/main/res/layout/revamp_fragment_typography.xml new file mode 100644 index 00000000000..59641b33156 --- /dev/null +++ b/app/src/main/res/layout/revamp_fragment_typography.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_item_color_section.xml b/app/src/main/res/layout/revamp_item_color_section.xml new file mode 100644 index 00000000000..5bc3e50b7d5 --- /dev/null +++ b/app/src/main/res/layout/revamp_item_color_section.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/layout/revamp_item_color_swatch.xml b/app/src/main/res/layout/revamp_item_color_swatch.xml new file mode 100644 index 00000000000..0e7d31d2895 --- /dev/null +++ b/app/src/main/res/layout/revamp_item_color_swatch.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_item_typography_style.xml b/app/src/main/res/layout/revamp_item_typography_style.xml new file mode 100644 index 00000000000..d76e5a990a4 --- /dev/null +++ b/app/src/main/res/layout/revamp_item_typography_style.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_view_about_card.xml b/app/src/main/res/layout/revamp_view_about_card.xml new file mode 100644 index 00000000000..68423b40bcb --- /dev/null +++ b/app/src/main/res/layout/revamp_view_about_card.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/revamp_view_header.xml b/app/src/main/res/layout/revamp_view_header.xml new file mode 100644 index 00000000000..5c44fe26295 --- /dev/null +++ b/app/src/main/res/layout/revamp_view_header.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/cloneflix_attrs.xml b/app/src/main/res/values/cloneflix_attrs.xml new file mode 100644 index 00000000000..06147ca0366 --- /dev/null +++ b/app/src/main/res/values/cloneflix_attrs.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/cloneflix_colors.xml b/app/src/main/res/values/cloneflix_colors.xml new file mode 100644 index 00000000000..159bb6a627a --- /dev/null +++ b/app/src/main/res/values/cloneflix_colors.xml @@ -0,0 +1,57 @@ + + + #E50914 + #000000 + #FFFFFF + + #EB3942 + #C11119 + #F50723 + + #0071EB + #448EF4 + #54B9C5 + + #46D369 + + #E5E5E5 + #DCDCDC + #D2D2D2 + #BCBCBC + #B3B3B3 + #979797 + #808080 + #777777 + #666D6D6E + #B26D6D6E + #545454 + #414141 + #404040 + #3A3A3A + #363636 + #99333333 + #333333 + #2F2F2F + #2A2A2A + #262626 + #232323 + #181818 + #141414 + + #26FFFFFF + #33FFFFFF + #4CFFFFFF + #59FFFFFF + #7FFFFFFF + #B2FFFFFF + + #4C000000 + #99000000 + #E5000000 + + #232323 + #181818 + #141414 + #33FFFFFF + #26FFFFFF + diff --git a/app/src/main/res/values/cloneflix_dimens.xml b/app/src/main/res/values/cloneflix_dimens.xml new file mode 100644 index 00000000000..d5d4469efa3 --- /dev/null +++ b/app/src/main/res/values/cloneflix_dimens.xml @@ -0,0 +1,88 @@ + + + 2dp + 4dp + 8dp + 12dp + 16dp + 20dp + 24dp + 32dp + 40dp + 48dp + 64dp + 80dp + + 4dp + 8dp + 12dp + 16dp + 24dp + 999dp + + 80dp + 1.4dp + 56dp + 48dp + 40dp + 32dp + 52dp + 72dp + 1dp + + + 14dp + 16dp + 24dp + 32dp + 40dp + + + 0.5dp + 1dp + 2dp + + + 218dp + 123dp + 128dp + 72dp + 236dp + 204dp + 245dp + 152dp + 128dp + 72dp + + 11sp + 13sp + 14sp + 16sp + 17sp + 18sp + 20sp + 21sp + 24sp + 27sp + 50sp + + 12sp + 13sp + 14sp + 16sp + 20sp + 21sp + 22sp + 24sp + 28sp + 30sp + 33sp + + 20sp + 48sp + 55sp + 40sp + 80sp + + 32sp + diff --git a/app/src/main/res/values/cloneflix_strings.xml b/app/src/main/res/values/cloneflix_strings.xml new file mode 100644 index 00000000000..dd214c2e95a --- /dev/null +++ b/app/src/main/res/values/cloneflix_strings.xml @@ -0,0 +1,68 @@ + + + + Play + Pause + Stop + Replay 10 seconds + Forward 10 seconds + Next Episode + Episodes and Info + Audio and Subtitles + Playback Speed + Lock Screen Controls + Unlock Screen Controls + Add to My List + Remove from My List + Like / Rate Up + Love / Double Thumbs Up + Dislike / Rate Down + Mute Audio + Unmute Audio + Close + Back + Copy + Search + Clear Search + Notifications + Account Profile + Help & Support + Movie card %1$s + Play episode %1$d + Video progress %1$s of %2$s + Volume level %1$d percent + User Avatar %1$s + + + Recently Added + Top 10 + New Season + New Episodes + Leaving Soon + Must Watch + Cloneflix Original + Spatial Audio + + + Episodes + More Like This + Trailers & More + Audio + Subtitles + Speed + Season %1$d + EP %1$d + 1.0x (Normal) + Off + + + Next episode in %1$ds + Play + More Info + Watch Credits + Play Next + + + Enter text here… + Search movies, TV shows, genres… + diff --git a/app/src/main/res/values/cloneflix_styles.xml b/app/src/main/res/values/cloneflix_styles.xml new file mode 100644 index 00000000000..ac5b8b9abcd --- /dev/null +++ b/app/src/main/res/values/cloneflix_styles.xml @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 60ea7d51c786c00877daadff2adf01c4a9b287fa Mon Sep 17 00:00:00 2001 From: Spring Date: Mon, 24 Aug 2026 22:59:21 +0700 Subject: [PATCH 03/35] feat(design-system): implement Android Views and showcase for Cloneflix design system --- .../ui/revamp/RevampDesignSystemActivity.kt | 23 +++ .../ui/revamp/RevampDesignSystemFragment.kt | 60 +++++++ .../ui/revamp/about/CloneflixAboutCardView.kt | 43 +++++ .../ui/revamp/about/CloneflixAboutFragment.kt | 60 +++++++ .../revamp/colors/CloneflixColorsAdapter.kt | 43 +++++ .../revamp/colors/CloneflixColorsFragment.kt | 44 +++++ .../ui/revamp/components/CloneflixButton.kt | 133 ++++++++++++++ .../ui/revamp/components/CloneflixCardView.kt | 56 ++++++ .../components/CloneflixColorSwatchView.kt | 50 ++++++ .../components/CloneflixComponentsFragment.kt | 53 ++++++ .../ui/revamp/components/CloneflixDivider.kt | 25 +++ .../revamp/components/CloneflixHeaderView.kt | 58 +++++++ .../revamp/components/CloneflixInputField.kt | 29 ++++ .../ui/revamp/components/CloneflixTextView.kt | 69 ++++++++ .../icons/CloneflixIconsLabelsFragment.kt | 31 ++++ .../ui/revamp/theme/CloneflixColors.kt | 162 ++++++++++++++++++ .../ui/revamp/theme/CloneflixDimens.kt | 61 +++++++ .../ui/revamp/theme/CloneflixTheme.kt | 16 ++ .../ui/revamp/theme/CloneflixTypography.kt | 78 +++++++++ .../typography/CloneflixTypographyAdapter.kt | 55 ++++++ .../typography/CloneflixTypographyFragment.kt | 85 +++++++++ 21 files changed, 1234 insertions(+) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt new file mode 100644 index 00000000000..1849d06e4e9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt @@ -0,0 +1,23 @@ +package com.lagradost.cloudstream3.ui.revamp + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.RevampActivityDesignSystemBinding + +class RevampDesignSystemActivity : AppCompatActivity() { + + private lateinit var binding: RevampActivityDesignSystemBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = RevampActivityDesignSystemBinding.inflate(layoutInflater) + setContentView(binding.root) + + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .replace(R.id.container, RevampDesignSystemFragment.newInstance()) + .commit() + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt new file mode 100644 index 00000000000..0bb5fb7cebd --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt @@ -0,0 +1,60 @@ +package com.lagradost.cloudstream3.ui.revamp + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.tabs.TabLayoutMediator +import com.lagradost.cloudstream3.databinding.RevampFragmentDesignSystemBinding +import com.lagradost.cloudstream3.ui.revamp.about.CloneflixAboutFragment +import com.lagradost.cloudstream3.ui.revamp.colors.CloneflixColorsFragment +import com.lagradost.cloudstream3.ui.revamp.components.CloneflixComponentsFragment +import com.lagradost.cloudstream3.ui.revamp.icons.CloneflixIconsLabelsFragment +import com.lagradost.cloudstream3.ui.revamp.typography.CloneflixTypographyFragment + +class RevampDesignSystemFragment : Fragment() { + + private var _binding: RevampFragmentDesignSystemBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = RevampFragmentDesignSystemBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val fragments = listOf( + CloneflixAboutFragment.newInstance() to "About", + CloneflixTypographyFragment.newInstance() to "Typography", + CloneflixColorsFragment.newInstance() to "Colors", + CloneflixComponentsFragment.newInstance() to "Components", + CloneflixIconsLabelsFragment.newInstance() to "Icons & Labels" + ) + + binding.viewPager.adapter = object : FragmentStateAdapter(this) { + override fun getItemCount(): Int = fragments.size + override fun createFragment(position: Int): Fragment = fragments[position].first + } + + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = fragments[position].second + }.attach() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + fun newInstance() = RevampDesignSystemFragment() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt new file mode 100644 index 00000000000..36e6daed972 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt @@ -0,0 +1,43 @@ +package com.lagradost.cloudstream3.ui.revamp.about + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import android.widget.Toast +import com.lagradost.cloudstream3.databinding.RevampViewAboutCardBinding + +class CloneflixAboutCardView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + val binding: RevampViewAboutCardBinding = + RevampViewAboutCardBinding.inflate(LayoutInflater.from(context), this, true) + + init { + binding.btnConnectX.setOnClickListener { + openUrl("https://x.com/ivannaheraskina") + } + + binding.btnFeedback.setOnClickListener { + Toast.makeText( + context, + "Feedback submitted! Thank you for using Cloneflix Design System 2024.", + Toast.LENGTH_LONG + ).show() + } + } + + private fun openUrl(url: String) { + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + context.startActivity(intent) + } catch (_: Exception) { + Toast.makeText(context, "Could not open $url", Toast.LENGTH_SHORT).show() + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt new file mode 100644 index 00000000000..aff2f6a59ca --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt @@ -0,0 +1,60 @@ +package com.lagradost.cloudstream3.ui.revamp.about + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.lagradost.cloudstream3.databinding.RevampFragmentAboutBinding + +class CloneflixAboutFragment : Fragment() { + + private var _binding: RevampFragmentAboutBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = RevampFragmentAboutBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.aboutCard.btnConnectX.setOnClickListener { + openUrl("https://x.com/ivannaheraskina") + } + + binding.aboutCard.btnFeedback.setOnClickListener { + Toast.makeText( + requireContext(), + "Thank you for supporting Cloneflix Design System 2024!", + Toast.LENGTH_SHORT + ).show() + } + } + + private fun openUrl(url: String) { + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } catch (_: Exception) { + Toast.makeText(requireContext(), "Could not open $url", Toast.LENGTH_SHORT).show() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + fun newInstance() = CloneflixAboutFragment() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt new file mode 100644 index 00000000000..cffc7306424 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt @@ -0,0 +1,43 @@ +package com.lagradost.cloudstream3.ui.revamp.colors + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.databinding.RevampItemColorSectionBinding +import com.lagradost.cloudstream3.ui.revamp.components.CloneflixColorSwatchView +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors + +class CloneflixColorsAdapter( + private val sections: Map> +) : RecyclerView.Adapter() { + + private val sectionList = sections.toList() + + class ViewHolder(val binding: RevampItemColorSectionBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = RevampItemColorSectionBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val (categoryName, tokens) = sectionList[position] + holder.binding.sectionTitle.text = categoryName.uppercase() + holder.binding.swatchesContainer.removeAllViews() + + val context = holder.itemView.context + for (token in tokens) { + val swatchView = CloneflixColorSwatchView(context).apply { + bind(token) + } + holder.binding.swatchesContainer.addView(swatchView) + } + } + + override fun getItemCount(): Int = sectionList.size +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt new file mode 100644 index 00000000000..0cb873ce9ce --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.ui.revamp.colors + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.lagradost.cloudstream3.databinding.RevampFragmentColorsBinding +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors + +class CloneflixColorsFragment : Fragment() { + + private var _binding: RevampFragmentColorsBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = RevampFragmentColorsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val tokens = CloneflixColors.getAllTokens() + val sections = tokens.groupBy { it.category } + + binding.rvColorSections.layoutManager = LinearLayoutManager(requireContext()) + binding.rvColorSections.adapter = CloneflixColorsAdapter(sections) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + fun newInstance() = CloneflixColorsFragment() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt new file mode 100644 index 00000000000..997de0ba8e1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt @@ -0,0 +1,133 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.content.Context +import android.util.AttributeSet +import androidx.core.content.ContextCompat +import com.google.android.material.button.MaterialButton +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography + +class CloneflixButton @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = com.google.android.material.R.attr.materialButtonStyle +) : MaterialButton(context, attrs, defStyleAttr) { + + enum class Variant { + PRIMARY, + SECONDARY, + OUTLINE, + GHOST + } + + enum class Size { + LARGE, + MEDIUM, + SMALL + } + + private var currentVariant: Variant = Variant.PRIMARY + private var currentSize: Size = Size.MEDIUM + + init { + isAllCaps = false + typeface = CloneflixTypography.getFontTypeface(context, CloneflixTypography.Weight.BOLD) + + if (attrs != null) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixButton) + val variantIndex = typedArray.getInt(R.styleable.CloneflixButton_cloneflixButtonVariant, 0) + val sizeIndex = typedArray.getInt(R.styleable.CloneflixButton_cloneflixButtonSize, 1) + val iconRes = typedArray.getResourceId(R.styleable.CloneflixButton_cloneflixIcon, 0) + val btnText = typedArray.getString(R.styleable.CloneflixButton_cloneflixText) + typedArray.recycle() + + currentVariant = when (variantIndex) { + 1 -> Variant.SECONDARY + 2 -> Variant.OUTLINE + 3 -> Variant.GHOST + else -> Variant.PRIMARY + } + + currentSize = when (sizeIndex) { + 0 -> Size.LARGE + 2 -> Size.SMALL + else -> Size.MEDIUM + } + + if (iconRes != 0) { + setIconResource(iconRes) + } + if (btnText != null) { + text = btnText + } + } + + applyVariant(currentVariant) + applySize(currentSize) + } + + fun setVariant(variant: Variant) { + currentVariant = variant + applyVariant(variant) + } + + fun setButtonSize(size: Size) { + currentSize = size + applySize(size) + } + + private fun applyVariant(variant: Variant) { + when (variant) { + Variant.PRIMARY -> { + setBackgroundResource(R.drawable.cloneflix_btn_primary) + setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) + iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_white) + strokeWidth = 0 + } + Variant.SECONDARY -> { + setBackgroundResource(R.drawable.cloneflix_btn_secondary) + setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_black)) + iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_black) + strokeWidth = 0 + } + Variant.OUTLINE -> { + setBackgroundResource(R.drawable.cloneflix_btn_outline) + setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) + iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_white) + } + Variant.GHOST -> { + setBackgroundResource(R.drawable.cloneflix_btn_ghost) + setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) + iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_white) + strokeWidth = 0 + } + } + } + + private fun applySize(size: Size) { + when (size) { + Size.LARGE -> { + minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_button_height_large) + setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_headline1)) + val padH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_2xl) + val padV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_m) + setPadding(padH, padV, padH, padV) + } + Size.MEDIUM -> { + minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_button_height_medium) + setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_body)) + val padH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_l) + val padV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_s) + setPadding(padH, padV, padH, padV) + } + Size.SMALL -> { + minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_button_height_small) + setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_smallbody)) + val padH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_m) + val padV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_xs) + setPadding(padH, padV, padH, padV) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt new file mode 100644 index 00000000000..7dc78557ac0 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt @@ -0,0 +1,56 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout +import androidx.core.content.ContextCompat +import com.lagradost.cloudstream3.R + +class CloneflixCardView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + enum class ElevationLevel { + FLAT, + SURFACE, + ELEVATED + } + + init { + var elevationLevel = ElevationLevel.SURFACE + if (attrs != null) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixCardView) + val levelIndex = typedArray.getInt(R.styleable.CloneflixCardView_cardElevationLevel, 1) + typedArray.recycle() + + elevationLevel = when (levelIndex) { + 0 -> ElevationLevel.FLAT + 2 -> ElevationLevel.ELEVATED + else -> ElevationLevel.SURFACE + } + } + + applyElevationLevel(elevationLevel) + clipToOutline = true + } + + fun setElevationLevel(level: ElevationLevel) { + applyElevationLevel(level) + } + + private fun applyElevationLevel(level: ElevationLevel) { + when (level) { + ElevationLevel.FLAT -> { + setBackgroundColor(ContextCompat.getColor(context, R.color.cloneflix_canvas_background)) + } + ElevationLevel.SURFACE -> { + setBackgroundResource(R.drawable.cloneflix_bg_card) + } + ElevationLevel.ELEVATED -> { + setBackgroundResource(R.drawable.cloneflix_bg_card_elevated) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt new file mode 100644 index 00000000000..f3534b5aca8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt @@ -0,0 +1,50 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.content.Context +import android.graphics.drawable.GradientDrawable +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.core.content.ContextCompat +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.RevampItemColorSwatchBinding +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTheme + +class CloneflixColorSwatchView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + private val binding: RevampItemColorSwatchBinding = + RevampItemColorSwatchBinding.inflate(LayoutInflater.from(context), this, true) + + private var currentToken: CloneflixColors.ColorToken? = null + + fun bind(token: CloneflixColors.ColorToken) { + currentToken = token + binding.colorName.text = token.name + binding.colorHexAndRgb.text = "${token.hex} • ${token.rgbDescription}" + + val previewDrawable = GradientDrawable().apply { + shape = GradientDrawable.RECTANGLE + cornerRadius = resources.getDimension(R.dimen.cloneflix_radius_s) + setColor(token.colorInt) + setStroke( + resources.getDimensionPixelSize(R.dimen.cloneflix_divider_height), + ContextCompat.getColor(context, R.color.cloneflix_border_subtle) + ) + } + binding.colorPreview.background = previewDrawable + + setOnClickListener { + CloneflixTheme.copyToClipboard(context, token.name, token.hex) + binding.copyButton.setImageResource(R.drawable.cloneflix_ic_check) + postDelayed({ + binding.copyButton.setImageResource(R.drawable.cloneflix_ic_copy) + }, 1200) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt new file mode 100644 index 00000000000..37ccdea14b0 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt @@ -0,0 +1,53 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.lagradost.cloudstream3.databinding.RevampFragmentComponentsBinding + +class CloneflixComponentsFragment : Fragment() { + + private var _binding: RevampFragmentComponentsBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = RevampFragmentComponentsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.btnDemoPrimary.setOnClickListener { + Toast.makeText(requireContext(), "Primary Red Button Clicked!", Toast.LENGTH_SHORT).show() + } + + binding.btnDemoSecondary.setOnClickListener { + Toast.makeText(requireContext(), "Secondary White Button Clicked!", Toast.LENGTH_SHORT).show() + } + + binding.btnDemoOutline.setOnClickListener { + Toast.makeText(requireContext(), "Outline Button Clicked!", Toast.LENGTH_SHORT).show() + } + + binding.btnDemoGhost.setOnClickListener { + Toast.makeText(requireContext(), "Ghost Button Clicked!", Toast.LENGTH_SHORT).show() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + fun newInstance() = CloneflixComponentsFragment() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt new file mode 100644 index 00000000000..763ea39788b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt @@ -0,0 +1,25 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens + +class CloneflixDivider @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + init { + setBackgroundColor(ContextCompat.getColor(context, R.color.cloneflix_border_subtle)) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val height = resources.getDimensionPixelSize(R.dimen.cloneflix_divider_height) + val finalHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + super.onMeasure(widthMeasureSpec, finalHeightMeasureSpec) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt new file mode 100644 index 00000000000..23f9ea3c7c6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt @@ -0,0 +1,58 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.annotation.DrawableRes +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.RevampViewHeaderBinding + +class CloneflixHeaderView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + val binding: RevampViewHeaderBinding = + RevampViewHeaderBinding.inflate(LayoutInflater.from(context), this, true) + + init { + if (attrs != null) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixHeaderView) + val title = typedArray.getString(R.styleable.CloneflixHeaderView_headerTitle) + val subtitle = typedArray.getString(R.styleable.CloneflixHeaderView_headerSubtitle) + val iconRes = typedArray.getResourceId(R.styleable.CloneflixHeaderView_headerIcon, 0) + val showDivider = typedArray.getBoolean(R.styleable.CloneflixHeaderView_showDivider, true) + typedArray.recycle() + + title?.let { setTitle(it) } + subtitle?.let { setSubtitle(it) } + if (iconRes != 0) { + setIcon(iconRes) + } + setDividerVisible(showDivider) + } + } + + fun setTitle(title: CharSequence) { + binding.headerTitle.text = title + } + + fun setSubtitle(subtitle: CharSequence?) { + if (!subtitle.isNullOrBlank()) { + binding.headerSubtitle.text = subtitle + binding.headerSubtitle.visibility = VISIBLE + } else { + binding.headerSubtitle.visibility = GONE + } + } + + fun setIcon(@DrawableRes iconRes: Int) { + binding.headerIcon.setImageResource(iconRes) + } + + fun setDividerVisible(visible: Boolean) { + binding.headerDivider.visibility = if (visible) VISIBLE else GONE + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt new file mode 100644 index 00000000000..d33abd2930e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt @@ -0,0 +1,29 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatEditText +import androidx.core.content.ContextCompat +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography + +class CloneflixInputField @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = android.R.attr.editTextStyle +) : AppCompatEditText(context, attrs, defStyleAttr) { + + init { + setBackgroundResource(R.drawable.cloneflix_bg_input_field) + setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) + setHintTextColor(ContextCompat.getColor(context, R.color.cloneflix_grey_200)) + typeface = CloneflixTypography.getFontTypeface(context, CloneflixTypography.Weight.REGULAR) + setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_body)) + + val paddingH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_l) + val paddingV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_m) + setPadding(paddingH, paddingV, paddingH, paddingV) + minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_input_height) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt new file mode 100644 index 00000000000..b03198c0b8c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt @@ -0,0 +1,69 @@ +package com.lagradost.cloudstream3.ui.revamp.components + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.widget.TextViewCompat +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography + +class CloneflixTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AppCompatTextView(context, attrs, defStyleAttr) { + + init { + if (attrs != null) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixTextView) + val styleEnum = typedArray.getInt(R.styleable.CloneflixTextView_cloneflixTextStyle, -1) + typedArray.recycle() + + if (styleEnum >= 0) { + applyStyleEnum(styleEnum) + } + } + } + + fun setCloneflixStyle(level: CloneflixTypography.StyleLevel) { + TextViewCompat.setTextAppearance(this, level.styleResId) + typeface = CloneflixTypography.getFontTypeface(context, level.weight) + } + + private fun applyStyleEnum(index: Int) { + val level = when (index) { + 0 -> CloneflixTypography.StyleLevel.REGULAR_CAPTION2 + 1 -> CloneflixTypography.StyleLevel.REGULAR_CAPTION1 + 2 -> CloneflixTypography.StyleLevel.REGULAR_SMALL_BODY + 3 -> CloneflixTypography.StyleLevel.REGULAR_BODY + 4 -> CloneflixTypography.StyleLevel.REGULAR_HEADLINE2 + 5 -> CloneflixTypography.StyleLevel.REGULAR_HEADLINE1 + 6 -> CloneflixTypography.StyleLevel.REGULAR_TITLE4 + 7 -> CloneflixTypography.StyleLevel.REGULAR_TITLE3 + 8 -> CloneflixTypography.StyleLevel.REGULAR_TITLE2 + 9 -> CloneflixTypography.StyleLevel.REGULAR_TITLE1 + 10 -> CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE + + 11 -> CloneflixTypography.StyleLevel.MEDIUM_CAPTION2 + 12 -> CloneflixTypography.StyleLevel.MEDIUM_CAPTION1 + 13 -> CloneflixTypography.StyleLevel.MEDIUM_SMALL_BODY + 14 -> CloneflixTypography.StyleLevel.MEDIUM_BODY + 15 -> CloneflixTypography.StyleLevel.MEDIUM_HEADLINE2 + 16 -> CloneflixTypography.StyleLevel.MEDIUM_HEADLINE1 + 17 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE4 + 18 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE3 + 19 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE2 + 20 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE1 + 21 -> CloneflixTypography.StyleLevel.MEDIUM_LARGE_TITLE + + 22 -> CloneflixTypography.StyleLevel.BOLD_TITLE2 + 23 -> CloneflixTypography.StyleLevel.BOLD_TITLE1 + 24 -> CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE + + 25 -> CloneflixTypography.StyleLevel.LOGO_BEBAS + 26 -> CloneflixTypography.StyleLevel.HEADER_DISPLAY + else -> null + } + level?.let { setCloneflixStyle(it) } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt new file mode 100644 index 00000000000..e5f4d1742da --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt @@ -0,0 +1,31 @@ +package com.lagradost.cloudstream3.ui.revamp.icons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import com.lagradost.cloudstream3.ui.revamp.compose.screens.CloneflixIconsLabelsComposeScreen +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +class CloneflixIconsLabelsFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setContent { + CloneflixTheme { + CloneflixIconsLabelsComposeScreen() + } + } + } + } + + companion object { + fun newInstance() = CloneflixIconsLabelsFragment() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt new file mode 100644 index 00000000000..24facb00218 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt @@ -0,0 +1,162 @@ +package com.lagradost.cloudstream3.ui.revamp.theme + +import android.graphics.Color +import androidx.annotation.ColorInt + +object CloneflixColors { + + const val PRIMARY_RED_HEX = "#E50914" + const val PRIMARY_BLACK_HEX = "#000000" + const val PRIMARY_WHITE_HEX = "#FFFFFF" + + @ColorInt val PrimaryRed = Color.parseColor(PRIMARY_RED_HEX) + @ColorInt val PrimaryBlack = Color.parseColor(PRIMARY_BLACK_HEX) + @ColorInt val PrimaryWhite = Color.parseColor(PRIMARY_WHITE_HEX) + + const val RED_100_HEX = "#EB3942" + const val RED_200_HEX = "#C11119" + const val RED_300_HEX = "#F50723" + + @ColorInt val Red100 = Color.parseColor(RED_100_HEX) + @ColorInt val Red200 = Color.parseColor(RED_200_HEX) + @ColorInt val Red300 = Color.parseColor(RED_300_HEX) + + const val BLUE_100_HEX = "#0071EB" + const val BLUE_200_HEX = "#448EF4" + const val BLUE_300_HEX = "#54B9C5" + + @ColorInt val Blue100 = Color.parseColor(BLUE_100_HEX) + @ColorInt val Blue200 = Color.parseColor(BLUE_200_HEX) + @ColorInt val Blue300 = Color.parseColor(BLUE_300_HEX) + + const val GREEN_HEX = "#46D369" + @ColorInt val Green = Color.parseColor(GREEN_HEX) + + const val GREY_10_HEX = "#E5E5E5" + const val GREY_20_HEX = "#DCDCDC" + const val GREY_25_HEX = "#D2D2D2" + const val GREY_50_HEX = "#BCBCBC" + const val GREY_100_HEX = "#B3B3B3" + const val GREY_150_HEX = "#979797" + const val GREY_200_HEX = "#808080" + const val GREY_250_HEX = "#777777" + const val GREY_300_T40_HEX = "#666D6D6E" + const val GREY_300_T70_HEX = "#B26D6D6E" + const val GREY_350_HEX = "#545454" + const val GREY_400_HEX = "#414141" + const val GREY_450_HEX = "#404040" + const val GREY_500_HEX = "#3A3A3A" + const val GREY_550_HEX = "#363636" + const val GREY_600_T60_HEX = "#99333333" + const val GREY_600_HEX = "#333333" + const val GREY_650_HEX = "#2F2F2F" + const val GREY_700_HEX = "#2A2A2A" + const val GREY_750_HEX = "#262626" + const val GREY_800_HEX = "#232323" + const val GREY_850_HEX = "#181818" + const val GREY_900_HEX = "#141414" + + @ColorInt val Grey10 = Color.parseColor(GREY_10_HEX) + @ColorInt val Grey20 = Color.parseColor(GREY_20_HEX) + @ColorInt val Grey25 = Color.parseColor(GREY_25_HEX) + @ColorInt val Grey50 = Color.parseColor(GREY_50_HEX) + @ColorInt val Grey100 = Color.parseColor(GREY_100_HEX) + @ColorInt val Grey150 = Color.parseColor(GREY_150_HEX) + @ColorInt val Grey200 = Color.parseColor(GREY_200_HEX) + @ColorInt val Grey250 = Color.parseColor(GREY_250_HEX) + @ColorInt val Grey300T40 = Color.parseColor(GREY_300_T40_HEX) + @ColorInt val Grey300T70 = Color.parseColor(GREY_300_T70_HEX) + @ColorInt val Grey350 = Color.parseColor(GREY_350_HEX) + @ColorInt val Grey400 = Color.parseColor(GREY_400_HEX) + @ColorInt val Grey450 = Color.parseColor(GREY_450_HEX) + @ColorInt val Grey500 = Color.parseColor(GREY_500_HEX) + @ColorInt val Grey550 = Color.parseColor(GREY_550_HEX) + @ColorInt val Grey600T60 = Color.parseColor(GREY_600_T60_HEX) + @ColorInt val Grey600 = Color.parseColor(GREY_600_HEX) + @ColorInt val Grey650 = Color.parseColor(GREY_650_HEX) + @ColorInt val Grey700 = Color.parseColor(GREY_700_HEX) + @ColorInt val Grey750 = Color.parseColor(GREY_750_HEX) + @ColorInt val Grey800 = Color.parseColor(GREY_800_HEX) + @ColorInt val Grey850 = Color.parseColor(GREY_850_HEX) + @ColorInt val Grey900 = Color.parseColor(GREY_900_HEX) + + const val TRANSPARENT_WHITE_15_HEX = "#26FFFFFF" + const val TRANSPARENT_WHITE_20_HEX = "#33FFFFFF" + const val TRANSPARENT_WHITE_30_HEX = "#4CFFFFFF" + const val TRANSPARENT_WHITE_35_HEX = "#59FFFFFF" + const val TRANSPARENT_WHITE_50_HEX = "#7FFFFFFF" + const val TRANSPARENT_WHITE_70_HEX = "#B2FFFFFF" + + @ColorInt val TransparentWhite15 = Color.parseColor(TRANSPARENT_WHITE_15_HEX) + @ColorInt val TransparentWhite20 = Color.parseColor(TRANSPARENT_WHITE_20_HEX) + @ColorInt val TransparentWhite30 = Color.parseColor(TRANSPARENT_WHITE_30_HEX) + @ColorInt val TransparentWhite35 = Color.parseColor(TRANSPARENT_WHITE_35_HEX) + @ColorInt val TransparentWhite50 = Color.parseColor(TRANSPARENT_WHITE_50_HEX) + @ColorInt val TransparentWhite70 = Color.parseColor(TRANSPARENT_WHITE_70_HEX) + + const val TRANSPARENT_BLACK_30_HEX = "#4C000000" + const val TRANSPARENT_BLACK_60_HEX = "#99000000" + const val TRANSPARENT_BLACK_90_HEX = "#E5000000" + + @ColorInt val TransparentBlack30 = Color.parseColor(TRANSPARENT_BLACK_30_HEX) + @ColorInt val TransparentBlack60 = Color.parseColor(TRANSPARENT_BLACK_60_HEX) + @ColorInt val TransparentBlack90 = Color.parseColor(TRANSPARENT_BLACK_90_HEX) + + data class ColorToken( + val category: String, + val name: String, + val hex: String, + val rgbDescription: String, + @ColorInt val colorInt: Int, + val isDark: Boolean = true + ) + + fun getAllTokens(): List = listOf( + ColorToken("Primary", "Primary / Red", PRIMARY_RED_HEX, "RGB (229, 9, 20)", PrimaryRed), + ColorToken("Primary", "Primary / Black", PRIMARY_BLACK_HEX, "RGB (0, 0, 0)", PrimaryBlack), + ColorToken("Primary", "Primary / White", PRIMARY_WHITE_HEX, "RGB (255, 255, 255)", PrimaryWhite, isDark = false), + + ColorToken("Secondary", "Secondary / Red-100", RED_100_HEX, "RGB (235, 57, 66)", Red100), + ColorToken("Secondary", "Secondary / Red-200", RED_200_HEX, "RGB (193, 17, 25)", Red200), + ColorToken("Secondary", "Secondary / Red-300", RED_300_HEX, "RGB (245, 7, 35)", Red300), + ColorToken("Secondary", "Secondary / Blue-100", BLUE_100_HEX, "RGB (0, 113, 235)", Blue100), + ColorToken("Secondary", "Secondary / Blue-200", BLUE_200_HEX, "RGB (68, 142, 244)", Blue200), + ColorToken("Secondary", "Secondary / Blue-300", BLUE_300_HEX, "RGB (84, 185, 197)", Blue300), + ColorToken("Secondary", "Secondary / Green", GREEN_HEX, "RGB (70, 211, 105)", Green), + + ColorToken("Neutral Greys", "Grey-10", GREY_10_HEX, "RGB (229, 229, 229)", Grey10, isDark = false), + ColorToken("Neutral Greys", "Grey-20", GREY_20_HEX, "RGB (220, 220, 220)", Grey20, isDark = false), + ColorToken("Neutral Greys", "Grey-25", GREY_25_HEX, "RGB (210, 210, 210)", Grey25, isDark = false), + ColorToken("Neutral Greys", "Grey-50", GREY_50_HEX, "RGB (188, 188, 188)", Grey50, isDark = false), + ColorToken("Neutral Greys", "Grey-100", GREY_100_HEX, "RGB (179, 179, 179)", Grey100), + ColorToken("Neutral Greys", "Grey-150", GREY_150_HEX, "RGB (151, 151, 151)", Grey150), + ColorToken("Neutral Greys", "Grey-200", GREY_200_HEX, "RGB (128, 128, 128)", Grey200), + ColorToken("Neutral Greys", "Grey-250", GREY_250_HEX, "RGB (119, 119, 119)", Grey250), + ColorToken("Neutral Greys", "Grey-300T40", GREY_300_T40_HEX, "RGB (109, 109, 110, 40%)", Grey300T40), + ColorToken("Neutral Greys", "Grey-300T70", GREY_300_T70_HEX, "RGB (109, 109, 110, 70%)", Grey300T70), + ColorToken("Neutral Greys", "Grey-350", GREY_350_HEX, "RGB (84, 84, 84)", Grey350), + ColorToken("Neutral Greys", "Grey-400", GREY_400_HEX, "RGB (65, 65, 65)", Grey400), + ColorToken("Neutral Greys", "Grey-450", GREY_450_HEX, "RGB (64, 64, 64)", Grey450), + ColorToken("Neutral Greys", "Grey-500", GREY_500_HEX, "RGB (58, 58, 58)", Grey500), + ColorToken("Neutral Greys", "Grey-550", GREY_550_HEX, "RGB (54, 54, 54)", Grey550), + ColorToken("Neutral Greys", "Grey-600T60", GREY_600_T60_HEX, "RGB (51, 51, 51, 60%)", Grey600T60), + ColorToken("Neutral Greys", "Grey-600", GREY_600_HEX, "RGB (51, 51, 51)", Grey600), + ColorToken("Neutral Greys", "Grey-650", GREY_650_HEX, "RGB (47, 47, 47)", Grey650), + ColorToken("Neutral Greys", "Grey-700", GREY_700_HEX, "RGB (42, 42, 42)", Grey700), + ColorToken("Neutral Greys", "Grey-750", GREY_750_HEX, "RGB (38, 38, 38)", Grey750), + ColorToken("Neutral Greys", "Grey-800", GREY_800_HEX, "RGB (35, 35, 35)", Grey800), + ColorToken("Neutral Greys", "Grey-850", GREY_850_HEX, "RGB (24, 24, 24)", Grey850), + ColorToken("Neutral Greys", "Grey-900", GREY_900_HEX, "RGB (20, 20, 20)", Grey900), + + ColorToken("Transparent White", "TransparentWhite-15", TRANSPARENT_WHITE_15_HEX, "RGB (255, 255, 255, 15%)", TransparentWhite15), + ColorToken("Transparent White", "TransparentWhite-20", TRANSPARENT_WHITE_20_HEX, "RGB (255, 255, 255, 20%)", TransparentWhite20), + ColorToken("Transparent White", "TransparentWhite-30", TRANSPARENT_WHITE_30_HEX, "RGB (255, 255, 255, 30%)", TransparentWhite30), + ColorToken("Transparent White", "TransparentWhite-35", TRANSPARENT_WHITE_35_HEX, "RGB (255, 255, 255, 35%)", TransparentWhite35), + ColorToken("Transparent White", "TransparentWhite-50", TRANSPARENT_WHITE_50_HEX, "RGB (255, 255, 255, 50%)", TransparentWhite50), + ColorToken("Transparent White", "TransparentWhite-70", TRANSPARENT_WHITE_70_HEX, "RGB (255, 255, 255, 70%)", TransparentWhite70), + + ColorToken("Transparent Black", "TransparentBlack-30", TRANSPARENT_BLACK_30_HEX, "RGB (0, 0, 0, 30%)", TransparentBlack30), + ColorToken("Transparent Black", "TransparentBlack-60", TRANSPARENT_BLACK_60_HEX, "RGB (0, 0, 0, 60%)", TransparentBlack60), + ColorToken("Transparent Black", "TransparentBlack-90", TRANSPARENT_BLACK_90_HEX, "RGB (0, 0, 0, 90%)", TransparentBlack90) + ) +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt new file mode 100644 index 00000000000..5f457f45c13 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt @@ -0,0 +1,61 @@ +package com.lagradost.cloudstream3.ui.revamp.theme + +import android.content.Context +import android.util.TypedValue + +object CloneflixDimens { + const val SPACING_XXS = 2 + const val SPACING_XS = 4 + const val SPACING_S = 8 + const val SPACING_M = 12 + const val SPACING_L = 16 + const val SPACING_XL = 20 + const val SPACING_2XL = 24 + const val SPACING_3XL = 32 + const val SPACING_4XL = 40 + const val SPACING_5XL = 48 + const val SPACING_6XL = 64 + const val SPACING_7XL = 80 + + const val RADIUS_XS = 4f + const val RADIUS_S = 8f + const val RADIUS_M = 12f + const val RADIUS_L = 16f + const val RADIUS_XL = 24f + const val RADIUS_FULL = 999f + + const val HEADER_ICON_SIZE = 80 + const val HEADER_ICON_SIZE_COMPACT = 56 + const val HEADER_ICON_STROKE_WIDTH = 1.4f + + const val BUTTON_HEIGHT_LARGE = 48 + const val BUTTON_HEIGHT_MEDIUM = 40 + const val BUTTON_HEIGHT_SMALL = 32 + const val INPUT_HEIGHT = 52 + + const val ICON_XS = 14 + const val ICON_S = 16 + const val ICON_M = 24 + const val ICON_L = 32 + const val ICON_XL = 40 + + const val BORDER_SUBTLE = 0.5f + const val BORDER_DEFAULT = 1f + const val BORDER_FOCUS = 2f + + fun dpToPx(context: Context, dp: Float): Float { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp, + context.resources.displayMetrics + ) + } + + fun spToPx(context: Context, sp: Float): Float { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + sp, + context.resources.displayMetrics + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt new file mode 100644 index 00000000000..742ffd9000a --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt @@ -0,0 +1,16 @@ +package com.lagradost.cloudstream3.ui.revamp.theme + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.widget.Toast + +object CloneflixTheme { + + fun copyToClipboard(context: Context, label: String, text: String) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + val clip = ClipData.newPlainText(label, text) + clipboard?.setPrimaryClip(clip) + Toast.makeText(context, "Copied $label: $text", Toast.LENGTH_SHORT).show() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt new file mode 100644 index 00000000000..7c6830707d9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt @@ -0,0 +1,78 @@ +package com.lagradost.cloudstream3.ui.revamp.theme + +import android.content.Context +import android.graphics.Typeface +import androidx.annotation.StyleRes +import androidx.core.content.res.ResourcesCompat +import com.lagradost.cloudstream3.R + +object CloneflixTypography { + + const val FONT_FAMILY_NETFLIX_SANS = "Netflix Sans" + const val FONT_FAMILY_BEBAS_NEUE = "Bebas Neue" + + enum class Weight { + REGULAR, + MEDIUM, + BOLD + } + + enum class StyleLevel( + val label: String, + val sizeSp: Float, + val lineHeightSp: Float, + val weight: Weight, + @StyleRes val styleResId: Int + ) { + REGULAR_CAPTION2("Regular / Caption2 / 11px", 11f, 14f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Caption2), + REGULAR_CAPTION1("Regular / Caption1 / 13px", 13f, 16f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Caption1), + REGULAR_SMALL_BODY("Regular / SmallBody / 14px", 14f, 18f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_SmallBody), + REGULAR_BODY("Regular / Body / 16px", 16f, 20f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Body), + REGULAR_HEADLINE2("Regular / Headline2 / 17px", 17f, 22f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Headline2), + REGULAR_HEADLINE1("Regular / Headline1 / 18px", 18f, 22f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Headline1), + REGULAR_TITLE4("Regular / Title4 / 20px", 20f, 30f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title4), + REGULAR_TITLE3("Regular / Title3 / 21px", 21f, 25f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title3), + REGULAR_TITLE2("Regular / Title2 / 24px", 24f, 30f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title2), + REGULAR_TITLE1("Regular / Title1 / 27px", 27f, 36f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title1), + REGULAR_LARGE_TITLE("Regular / LargeTitle / 50px", 50f, 64f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_LargeTitle), + + MEDIUM_CAPTION2("Medium / Caption2 / 12px", 12f, 15f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Caption2), + MEDIUM_CAPTION1("Medium / Caption1 / 13px", 13f, 17f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Caption1), + MEDIUM_SMALL_BODY("Medium / SmallBody / 14px", 14f, 18f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_SmallBody), + MEDIUM_BODY("Medium / Body / 16px", 16f, 24f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Body), + MEDIUM_HEADLINE2("Medium / Headline2 / 20px", 20f, 30f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Headline2), + MEDIUM_HEADLINE1("Medium / Headline1 / 21px", 21f, 25f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Headline1), + MEDIUM_TITLE4("Medium / Title4 / 22px", 22f, 22f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title4), + MEDIUM_TITLE3("Medium / Title3 / 24px", 24f, 31f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title3), + MEDIUM_TITLE2("Medium / Title2 / 28px", 28f, 45f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title2), + MEDIUM_TITLE1("Medium / Title1 / 30px", 30f, 45f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title1), + MEDIUM_LARGE_TITLE("Medium / LargeTitle / 33px", 33f, 42f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_LargeTitle), + + BOLD_TITLE2("Bold / Title2 / 20px", 20f, 30f, Weight.BOLD, R.style.TextAppearance_Cloneflix_Bold_Title2), + BOLD_TITLE1("Bold / Title1 / 48px", 48f, 62f, Weight.BOLD, R.style.TextAppearance_Cloneflix_Bold_Title1), + BOLD_LARGE_TITLE("Bold / LargeTitle / 55px", 55f, 70f, Weight.BOLD, R.style.TextAppearance_Cloneflix_Bold_LargeTitle), + + LOGO_BEBAS("Logo – Bebas Neue / 32px", 32f, 38f, Weight.BOLD, R.style.TextAppearance_Cloneflix_LogoBebas), + HEADER_DISPLAY("Display Header / 40px", 40f, 48f, Weight.BOLD, R.style.TextAppearance_Cloneflix_HeaderDisplay); + + fun getCategoryName(): String = when (weight) { + Weight.REGULAR -> "Regular Styles" + Weight.MEDIUM -> "Medium Styles" + Weight.BOLD -> if (this == LOGO_BEBAS) "Logo & Display" else "Bold Styles" + } + } + + fun getFontTypeface(context: Context, weight: Weight): Typeface { + val base = try { + ResourcesCompat.getFont(context, R.font.netflix_sans) + } catch (_: Exception) { + null + } ?: Typeface.SANS_SERIF + + return when (weight) { + Weight.REGULAR -> Typeface.create(base, Typeface.NORMAL) + Weight.MEDIUM -> Typeface.create(base, Typeface.BOLD) + Weight.BOLD -> Typeface.create(base, Typeface.BOLD) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt new file mode 100644 index 00000000000..e8522d0e9b1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt @@ -0,0 +1,55 @@ +package com.lagradost.cloudstream3.ui.revamp.typography + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.widget.TextViewCompat +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.databinding.RevampItemTypographyStyleBinding +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography + +class CloneflixTypographyAdapter( + private val styles: List, + private val sampleTextProvider: ((CloneflixTypography.StyleLevel) -> String)? = null +) : RecyclerView.Adapter() { + + class ViewHolder(val binding: RevampItemTypographyStyleBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = RevampItemTypographyStyleBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val style = styles[position] + holder.binding.styleLabel.text = style.label + holder.binding.styleMetrics.text = "Size: ${style.sizeSp.toInt()}sp • LH: ${style.lineHeightSp.toInt()}sp" + + val sample = sampleTextProvider?.invoke(style) ?: when (style) { + CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE, + CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE -> "Unlimited movies, TV shows, and more" + CloneflixTypography.StyleLevel.LOGO_BEBAS -> "CLONEFLIX" + else -> "Watch anywhere. Cancel anytime." + } + + holder.binding.styleSampleText.text = sample + TextViewCompat.setTextAppearance(holder.binding.styleSampleText, style.styleResId) + holder.binding.styleSampleText.typeface = + CloneflixTypography.getFontTypeface(holder.itemView.context, style.weight) + + holder.itemView.setOnClickListener { + CloneflixTheme.copyToClipboard( + holder.itemView.context, + style.label, + "fontSize: ${style.sizeSp}sp, lineHeight: ${style.lineHeightSp}sp" + ) + } + } + + override fun getItemCount(): Int = styles.size +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt new file mode 100644 index 00000000000..00165a6bd25 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt @@ -0,0 +1,85 @@ +package com.lagradost.cloudstream3.ui.revamp.typography + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.lagradost.cloudstream3.databinding.RevampFragmentTypographyBinding +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography + +class CloneflixTypographyFragment : Fragment() { + + private var _binding: RevampFragmentTypographyBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = RevampFragmentTypographyBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val regularStyles = listOf( + CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE, + CloneflixTypography.StyleLevel.REGULAR_TITLE1, + CloneflixTypography.StyleLevel.REGULAR_TITLE2, + CloneflixTypography.StyleLevel.REGULAR_TITLE3, + CloneflixTypography.StyleLevel.REGULAR_TITLE4, + CloneflixTypography.StyleLevel.REGULAR_HEADLINE1, + CloneflixTypography.StyleLevel.REGULAR_HEADLINE2, + CloneflixTypography.StyleLevel.REGULAR_BODY, + CloneflixTypography.StyleLevel.REGULAR_SMALL_BODY, + CloneflixTypography.StyleLevel.REGULAR_CAPTION1, + CloneflixTypography.StyleLevel.REGULAR_CAPTION2 + ) + binding.rvRegularStyles.layoutManager = LinearLayoutManager(requireContext()) + binding.rvRegularStyles.adapter = CloneflixTypographyAdapter(regularStyles) + + val mediumStyles = listOf( + CloneflixTypography.StyleLevel.MEDIUM_LARGE_TITLE, + CloneflixTypography.StyleLevel.MEDIUM_TITLE1, + CloneflixTypography.StyleLevel.MEDIUM_TITLE2, + CloneflixTypography.StyleLevel.MEDIUM_TITLE3, + CloneflixTypography.StyleLevel.MEDIUM_TITLE4, + CloneflixTypography.StyleLevel.MEDIUM_HEADLINE1, + CloneflixTypography.StyleLevel.MEDIUM_HEADLINE2, + CloneflixTypography.StyleLevel.MEDIUM_BODY, + CloneflixTypography.StyleLevel.MEDIUM_SMALL_BODY, + CloneflixTypography.StyleLevel.MEDIUM_CAPTION1, + CloneflixTypography.StyleLevel.MEDIUM_CAPTION2 + ) + binding.rvMediumStyles.layoutManager = LinearLayoutManager(requireContext()) + binding.rvMediumStyles.adapter = CloneflixTypographyAdapter(mediumStyles) + + val boldStyles = listOf( + CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE, + CloneflixTypography.StyleLevel.BOLD_TITLE1, + CloneflixTypography.StyleLevel.BOLD_TITLE2 + ) + binding.rvBoldStyles.layoutManager = LinearLayoutManager(requireContext()) + binding.rvBoldStyles.adapter = CloneflixTypographyAdapter(boldStyles) + + val displayStyles = listOf( + CloneflixTypography.StyleLevel.LOGO_BEBAS, + CloneflixTypography.StyleLevel.HEADER_DISPLAY + ) + binding.rvDisplayStyles.layoutManager = LinearLayoutManager(requireContext()) + binding.rvDisplayStyles.adapter = CloneflixTypographyAdapter(displayStyles) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + fun newInstance() = CloneflixTypographyFragment() + } +} From bf6a3795a1444b7dc035edd51fb74fa0ae4a5959 Mon Sep 17 00:00:00 2001 From: Spring Date: Mon, 24 Aug 2026 22:59:27 +0700 Subject: [PATCH 04/35] feat(design-system): implement tokenized Jetpack Compose Cloneflix components, screens, and sample data --- .../compose/CloneflixComposeActivity.kt | 19 + .../CloneflixAudioSubtitlesSelector.kt | 310 +++++ .../components/CloneflixAvatarComponents.kt | 837 ++++++++++++ .../components/CloneflixBadgeComponents.kt | 438 ++++++ .../components/CloneflixButtonPatterns.kt | 302 +++++ .../components/CloneflixComposeComponents.kt | 830 ++++++++++++ .../CloneflixContentBlockComponents.kt | 1173 +++++++++++++++++ .../compose/components/CloneflixDefaults.kt | 124 ++ .../compose/components/CloneflixDropdown.kt | 315 +++++ .../components/CloneflixEpisodesDrawer.kt | 336 +++++ .../CloneflixHeroBannerComponents.kt | 1091 +++++++++++++++ .../CloneflixMediaControlButtons.kt | 371 ++++++ .../compose/components/CloneflixMovieCard.kt | 826 ++++++++++++ .../CloneflixMovieInfoComponents.kt | 307 +++++ .../CloneflixNextEpisodeComponents.kt | 296 +++++ .../CloneflixPlaybackSpeedSelector.kt | 266 ++++ .../compose/components/CloneflixSampleData.kt | 55 + .../components/CloneflixVideoPlayerPattern.kt | 582 ++++++++ .../CloneflixVideoProgressIndicator.kt | 322 +++++ .../components/CloneflixVolumeSlider.kt | 261 ++++ .../screens/CloneflixAboutComposeScreen.kt | 149 +++ .../screens/CloneflixAvatarsComposeScreen.kt | 484 +++++++ .../screens/CloneflixButtonsComposeScreen.kt | 494 +++++++ .../screens/CloneflixColorsComposeScreen.kt | 81 ++ .../CloneflixComponentsComposeScreen.kt | 195 +++ .../CloneflixContentBlocksComposeScreen.kt | 341 +++++ .../screens/CloneflixDropdownComposeScreen.kt | 295 +++++ .../CloneflixHeroBannersComposeScreen.kt | 435 ++++++ .../CloneflixIconsLabelsComposeScreen.kt | 502 +++++++ .../CloneflixInputFieldsComposeScreen.kt | 198 +++ .../CloneflixMoviePreviewComposeScreen.kt | 555 ++++++++ .../screens/CloneflixShowcaseComposeScreen.kt | 162 +++ .../CloneflixTypographyComposeScreen.kt | 222 ++++ .../CloneflixVideoPlayerComposeScreen.kt | 445 +++++++ .../compose/theme/CloneflixComposeTheme.kt | 247 ++++ 35 files changed, 13866 insertions(+) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt new file mode 100644 index 00000000000..5f4af267bd6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt @@ -0,0 +1,19 @@ +package com.lagradost.cloudstream3.ui.revamp.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import com.lagradost.cloudstream3.ui.revamp.compose.screens.CloneflixShowcaseComposeScreen +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +class CloneflixComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + CloneflixTheme { + CloneflixShowcaseComposeScreen() + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt new file mode 100644 index 00000000000..a6998a48555 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt @@ -0,0 +1,310 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.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.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +@Composable +fun CloneflixTrackOptionItem( + title: String, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.03f else 1f, + label = "trackItemScale" + ) + + val backgroundColor = when { + isFocused -> TransparentWhite20 + isSelected -> Color(0x1AE50914) + else -> Color.Transparent + } + + Row( + modifier = modifier + .fillMaxWidth() + .scale(scale) + .clip(RoundedCornerShape(8.dp)) + .background(backgroundColor) + .then( + if (isFocused) Modifier.border(1.5.dp, PrimaryWhite, RoundedCornerShape(8.dp)) + else Modifier + ) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(horizontal = 12.dp, vertical = 10.dp) + .semantics { + role = Role.RadioButton + contentDescription = "$title, ${if (isSelected) "selected" else "not selected"}" + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Box( + modifier = Modifier.size(18.dp), + contentAlignment = Alignment.Center + ) { + if (isSelected) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_check), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier.size(16.dp) + ) + } + } + + Text( + text = title, + style = CloneflixTheme.typography.regularBody, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + color = if (isSelected) PrimaryWhite else Grey200, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +fun CloneflixAudioSubtitlesSelector( + audioTracks: List, + selectedAudio: String, + onAudioSelected: (String) -> Unit, + subtitleTracks: List, + selectedSubtitle: String, + onSubtitleSelected: (String) -> Unit, + onCloseClick: () -> Unit, + modifier: Modifier = Modifier +) { + val audioListState = rememberLazyListState() + val subtitleListState = rememberLazyListState() + + Surface( + modifier = modifier + .width(640.dp) + .border(BorderStroke(1.dp, Color(0x33FFFFFF)), RoundedCornerShape(16.dp)), + shape = RoundedCornerShape(16.dp), + color = TransparentBlack90 + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp) + ) { + // Header with Close + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Audio & Subtitles", + style = CloneflixTheme.typography.mediumHeadline2, + fontSize = 20.sp, + color = PrimaryWhite + ) + + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(Color(0x33FFFFFF)) + .clickable(onClick = onCloseClick), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_close), + contentDescription = "Close", + tint = PrimaryWhite, + modifier = Modifier.size(16.dp) + ) + } + } + + Spacer(modifier = Modifier.height(20.dp)) + + // Two Columns: Audio (Left) and Subtitles (Right) + Row( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + // Column 1: Audio + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + ) { + Text( + text = "AUDIO", + style = CloneflixTheme.typography.mediumHeadline2, + fontSize = 14.sp, + color = Grey100, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + LazyColumn( + state = audioListState, + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(audioTracks) { track -> + CloneflixTrackOptionItem( + title = track, + isSelected = track == selectedAudio, + onClick = { onAudioSelected(track) } + ) + } + } + } + + // Vertical Divider line + Box( + modifier = Modifier + .width(1.dp) + .fillMaxHeight() + .background(Color(0x26FFFFFF)) + ) + + // Column 2: Subtitles + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + ) { + Text( + text = "SUBTITLES", + style = CloneflixTheme.typography.mediumHeadline2, + fontSize = 14.sp, + color = Grey100, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + LazyColumn( + state = subtitleListState, + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(subtitleTracks) { track -> + CloneflixTrackOptionItem( + title = track, + isSelected = track == selectedSubtitle, + onClick = { onSubtitleSelected(track) } + ) + } + } + } + } + } + } +} + +@Preview(name = "Audio Subtitles Dialog Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun AudioSubtitlesDialogPreview() { + CloneflixTheme { + val audioTracks = listOf( + "Japanese [Original]", + "English", + "Spanish (Latin America)", + "French", + "German" + ) + val subtitleTracks = listOf( + "Off", + "English (CC)", + "Japanese (CC)", + "Spanish", + "Simplified Chinese", + "Traditional Chinese", + "Arabic", + "French", + "German" + ) + + Box( + modifier = Modifier + .background(Color(0xFF141414)) + .padding(24.dp) + ) { + CloneflixAudioSubtitlesSelector( + audioTracks = audioTracks, + selectedAudio = "Japanese [Original]", + onAudioSelected = {}, + subtitleTracks = subtitleTracks, + selectedSubtitle = "English (CC)", + onSubtitleSelected = {}, + onCloseClick = {} + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt new file mode 100644 index 00000000000..1e1a349bc8e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt @@ -0,0 +1,837 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.GenericShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey350 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite + +enum class AvatarFaceStyle { + SMILEY, + WINK, + ANGRY, + KIDS, + MONSTER_GREY, + MONSTER_BLUE, + MONSTER_RED, + MONSTER_YELLOW, + CHICKEN, + ZOMBIE, + PANDA, + COOL, + SURPRISED, + GEOMETRIC +} + +data class CloneflixAvatarModel( + val id: String, + val name: String, + val topColor: Color, + val bottomColor: Color, + val faceStyle: AvatarFaceStyle = AvatarFaceStyle.SMILEY, + val accentColor: Color = PrimaryWhite +) + +object CloneflixOfficialAvatars { + val Red = CloneflixAvatarModel("red", "Red Classic", Color(0xFFE50914), Color(0xFF8B0000), AvatarFaceStyle.SMILEY) + val Green = CloneflixAvatarModel("green", "Green Classic", Color(0xFF2ECC71), Color(0xFF1B8246), AvatarFaceStyle.SMILEY) + val DarkBlue = CloneflixAvatarModel("dark blue", "Dark Blue", Color(0xFF1E3799), Color(0xFF0C2461), AvatarFaceStyle.SMILEY) + val Blue = CloneflixAvatarModel("blue", "Blue Classic", Color(0xFF3867D6), Color(0xFF1B3B6F), AvatarFaceStyle.SMILEY) + val Purple = CloneflixAvatarModel("purple", "Purple", Color(0xFF8854D0), Color(0xFF5B2C98), AvatarFaceStyle.SMILEY) + val Pink = CloneflixAvatarModel("pink", "Pink Classic", Color(0xFFFD79A8), Color(0xFFD63031), AvatarFaceStyle.SMILEY) + val Yellow = CloneflixAvatarModel("yellow", "Yellow Gold", Color(0xFFF1C40F), Color(0xFFD35400), AvatarFaceStyle.SMILEY) + val Kids = CloneflixAvatarModel("kids", "Kids", Color(0xFFF39C12), Color(0xFFE67E22), AvatarFaceStyle.KIDS) + val AngryMan = CloneflixAvatarModel("angryman", "Angry Man", Color(0xFFE74C3C), Color(0xFF7B1113), AvatarFaceStyle.ANGRY) + val FluffyGrey = CloneflixAvatarModel("fluffygrey", "Fluffy Grey", Color(0xFF7F8C8D), Color(0xFF34495E), AvatarFaceStyle.MONSTER_GREY) + val FluffyBlue = CloneflixAvatarModel("fluffyblue", "Fluffy Blue", Color(0xFF00A8FF), Color(0xFF0097E6), AvatarFaceStyle.MONSTER_BLUE) + val FluffyRed = CloneflixAvatarModel("fluffyred", "Fluffy Red", Color(0xFFEA2027), Color(0xFFB53471), AvatarFaceStyle.MONSTER_RED) + val FluffyYellow = CloneflixAvatarModel("fluffyyellow", "Fluffy Yellow", Color(0xFFFFC312), Color(0xFFF79F1F), AvatarFaceStyle.MONSTER_YELLOW) + val Chicken = CloneflixAvatarModel("chicken", "Chicken", Color(0xFFEE5253), Color(0xFFEA2027), AvatarFaceStyle.CHICKEN) + val Zombie = CloneflixAvatarModel("zombi", "Zombie", Color(0xFF10AC84), Color(0xFF1DD1A1), AvatarFaceStyle.ZOMBIE) + val Panda = CloneflixAvatarModel("panda", "Panda", Color(0xFFECEFF1), Color(0xFFCFD8DC), AvatarFaceStyle.PANDA) + + val Popular = listOf( + Red, Green, DarkBlue, Blue, + Purple, Pink, Yellow, Kids, + AngryMan, FluffyGrey, FluffyBlue, FluffyRed, + FluffyYellow, Chicken, Zombie, Panda + ) + + private val Palette = listOf( + Pair(Color(0xFFE50914), Color(0xFF9E0000)), + Pair(Color(0xFF0984E3), Color(0xFF1B3B6F)), + Pair(Color(0xFF00B894), Color(0xFF006266)), + Pair(Color(0xFF6C5CE7), Color(0xFF4834D4)), + Pair(Color(0xFFFD79A8), Color(0xFFE84393)), + Pair(Color(0xFFFDCB6E), Color(0xFFE17055)), + Pair(Color(0xFF00CEC9), Color(0xFF0984E3)), + Pair(Color(0xFFE17055), Color(0xFFD63031)), + Pair(Color(0xFF2C3E50), Color(0xFF1E272E)), + Pair(Color(0xFFFF7675), Color(0xFFD63031)), + Pair(Color(0xFF55EFC4), Color(0xFF00B894)), + Pair(Color(0xFFA29BFE), Color(0xFF6C5CE7)) + ) + + fun getAvatarByIndex(index: Int): CloneflixAvatarModel { + val numStr = String.format("%02d", index.coerceAtLeast(1)) + val colorPair = Palette[(index - 1) % Palette.size] + val styles = AvatarFaceStyle.values() + val faceStyle = styles[(index - 1) % styles.size] + return CloneflixAvatarModel( + id = numStr, + name = numStr, + topColor = colorPair.first, + bottomColor = colorPair.second, + faceStyle = faceStyle + ) + } + + fun getAllOthers(count: Int = 36): List { + return (1..count).map { getAvatarByIndex(it) } + } +} + +/** + * High-fidelity vector graphic renderer for Cloneflix avatar faces. + */ +@Composable +fun CloneflixAvatarGraphic( + avatar: CloneflixAvatarModel, + modifier: Modifier = Modifier +) { + Canvas(modifier = modifier.fillMaxSize()) { + val w = size.width + val h = size.height + + // Background gradient + drawRoundRect( + brush = Brush.verticalGradient( + colors = listOf(avatar.topColor, avatar.bottomColor), + startY = 0f, + endY = h + ), + size = Size(w, h), + cornerRadius = CornerRadius(w * 0.08f, h * 0.08f) + ) + + // Draw facial features based on AvatarFaceStyle + when (avatar.faceStyle) { + AvatarFaceStyle.SMILEY -> drawSmileyFace(w, h, PrimaryWhite) + AvatarFaceStyle.WINK -> drawWinkFace(w, h, PrimaryWhite) + AvatarFaceStyle.ANGRY -> drawAngryFace(w, h, PrimaryWhite) + AvatarFaceStyle.KIDS -> drawKidsFace(w, h, PrimaryWhite) + AvatarFaceStyle.MONSTER_GREY -> drawMonsterGrey(w, h) + AvatarFaceStyle.MONSTER_BLUE -> drawMonsterBlue(w, h) + AvatarFaceStyle.MONSTER_RED -> drawMonsterRed(w, h) + AvatarFaceStyle.MONSTER_YELLOW -> drawMonsterYellow(w, h) + AvatarFaceStyle.CHICKEN -> drawChickenFace(w, h) + AvatarFaceStyle.ZOMBIE -> drawZombieFace(w, h) + AvatarFaceStyle.PANDA -> drawPandaFace(w, h) + AvatarFaceStyle.COOL -> drawCoolFace(w, h, PrimaryWhite) + AvatarFaceStyle.SURPRISED -> drawSurprisedFace(w, h, PrimaryWhite) + AvatarFaceStyle.GEOMETRIC -> drawGeometricFace(w, h, avatar.id) + } + } +} + +private fun DrawScope.drawSmileyFace(w: Float, h: Float, color: Color) { + val eyeRadius = w * 0.065f + val eyeY = h * 0.38f + val leftEyeX = w * 0.35f + val rightEyeX = w * 0.65f + + // Eyes + drawCircle(color = color, radius = eyeRadius, center = Offset(leftEyeX, eyeY)) + drawCircle(color = color, radius = eyeRadius, center = Offset(rightEyeX, eyeY)) + + // Smile Arc + val mouthPath = Path().apply { + moveTo(w * 0.30f, h * 0.56f) + quadraticTo(w * 0.50f, h * 0.74f, w * 0.70f, h * 0.56f) + } + drawPath( + path = mouthPath, + color = color, + style = Stroke(width = w * 0.065f, cap = StrokeCap.Round) + ) +} + +private fun DrawScope.drawWinkFace(w: Float, h: Float, color: Color) { + val eyeRadius = w * 0.065f + val eyeY = h * 0.38f + + // Left eye (open) + drawCircle(color = color, radius = eyeRadius, center = Offset(w * 0.35f, eyeY)) + + // Right eye (wink) + val winkPath = Path().apply { + moveTo(w * 0.60f, eyeY) + quadraticTo(w * 0.67f, eyeY - h * 0.04f, w * 0.74f, eyeY) + } + drawPath(path = winkPath, color = color, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) + + // Smirk + val mouthPath = Path().apply { + moveTo(w * 0.34f, h * 0.58f) + quadraticTo(w * 0.52f, h * 0.72f, w * 0.72f, h * 0.52f) + } + drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.065f, cap = StrokeCap.Round)) +} + +private fun DrawScope.drawAngryFace(w: Float, h: Float, color: Color) { + // Brows + val leftBrow = Path().apply { + moveTo(w * 0.22f, h * 0.28f) + lineTo(w * 0.44f, h * 0.36f) + } + val rightBrow = Path().apply { + moveTo(w * 0.78f, h * 0.28f) + lineTo(w * 0.56f, h * 0.36f) + } + drawPath(leftBrow, color = color, style = Stroke(width = w * 0.07f, cap = StrokeCap.Round)) + drawPath(rightBrow, color = color, style = Stroke(width = w * 0.07f, cap = StrokeCap.Round)) + + // Eyes + drawCircle(color = color, radius = w * 0.055f, center = Offset(w * 0.34f, h * 0.42f)) + drawCircle(color = color, radius = w * 0.055f, center = Offset(w * 0.66f, h * 0.42f)) + + // Frown + val mouthPath = Path().apply { + moveTo(w * 0.32f, h * 0.68f) + quadraticTo(w * 0.50f, h * 0.56f, w * 0.68f, h * 0.68f) + } + drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.065f, cap = StrokeCap.Round)) +} + +private fun DrawScope.drawKidsFace(w: Float, h: Float, color: Color) { + // Big round eyes + drawCircle(color = color, radius = w * 0.10f, center = Offset(w * 0.35f, h * 0.38f)) + drawCircle(color = color, radius = w * 0.10f, center = Offset(w * 0.65f, h * 0.38f)) + drawCircle(color = PrimaryBlack, radius = w * 0.05f, center = Offset(w * 0.35f, h * 0.38f)) + drawCircle(color = PrimaryBlack, radius = w * 0.05f, center = Offset(w * 0.65f, h * 0.38f)) + drawCircle(color = PrimaryWhite, radius = w * 0.02f, center = Offset(w * 0.37f, h * 0.36f)) + drawCircle(color = PrimaryWhite, radius = w * 0.02f, center = Offset(w * 0.67f, h * 0.36f)) + + // Big happy smile with tongue + val mouthPath = Path().apply { + moveTo(w * 0.28f, h * 0.58f) + quadraticTo(w * 0.50f, h * 0.78f, w * 0.72f, h * 0.58f) + } + drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.065f, cap = StrokeCap.Round)) +} + +private fun DrawScope.drawMonsterGrey(w: Float, h: Float) { + // Horns + drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.25f, h * 0.20f)) + drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.75f, h * 0.20f)) + + // Eyes + drawCircle(color = PrimaryWhite, radius = w * 0.07f, center = Offset(w * 0.35f, h * 0.40f)) + drawCircle(color = PrimaryWhite, radius = w * 0.07f, center = Offset(w * 0.65f, h * 0.40f)) + drawCircle(color = PrimaryBlack, radius = w * 0.035f, center = Offset(w * 0.35f, h * 0.40f)) + drawCircle(color = PrimaryBlack, radius = w * 0.035f, center = Offset(w * 0.65f, h * 0.40f)) + + // Teeth smile + val mouthPath = Path().apply { + moveTo(w * 0.30f, h * 0.60f) + lineTo(w * 0.70f, h * 0.60f) + lineTo(w * 0.65f, h * 0.70f) + lineTo(w * 0.35f, h * 0.70f) + close() + } + drawPath(mouthPath, color = PrimaryWhite) +} + +private fun DrawScope.drawMonsterBlue(w: Float, h: Float) { + // Single Cyclops Eye + drawCircle(color = PrimaryWhite, radius = w * 0.13f, center = Offset(w * 0.50f, h * 0.36f)) + drawCircle(color = Color(0xFF0984E3), radius = w * 0.07f, center = Offset(w * 0.50f, h * 0.36f)) + drawCircle(color = PrimaryBlack, radius = w * 0.04f, center = Offset(w * 0.50f, h * 0.36f)) + drawCircle(color = PrimaryWhite, radius = w * 0.018f, center = Offset(w * 0.52f, h * 0.34f)) + + // Cute fangs mouth + val mouthPath = Path().apply { + moveTo(w * 0.32f, h * 0.60f) + quadraticTo(w * 0.50f, h * 0.76f, w * 0.68f, h * 0.60f) + } + drawPath(path = mouthPath, color = PrimaryWhite, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) + // Fang + val fang = Path().apply { + moveTo(w * 0.40f, h * 0.60f) + lineTo(w * 0.43f, h * 0.68f) + lineTo(w * 0.46f, h * 0.60f) + close() + } + drawPath(fang, color = PrimaryWhite) +} + +private fun DrawScope.drawMonsterRed(w: Float, h: Float) { + // Sharp eyes + val leftEye = Path().apply { + moveTo(w * 0.28f, h * 0.36f) + lineTo(w * 0.44f, h * 0.40f) + lineTo(w * 0.32f, h * 0.44f) + close() + } + val rightEye = Path().apply { + moveTo(w * 0.72f, h * 0.36f) + lineTo(w * 0.56f, h * 0.40f) + lineTo(w * 0.68f, h * 0.44f) + close() + } + drawPath(leftEye, color = Color(0xFFFFD32A)) + drawPath(rightEye, color = Color(0xFFFFD32A)) + + // Wide grin + val mouth = Path().apply { + moveTo(w * 0.26f, h * 0.56f) + quadraticTo(w * 0.50f, h * 0.78f, w * 0.74f, h * 0.56f) + quadraticTo(w * 0.50f, h * 0.64f, w * 0.26f, h * 0.56f) + close() + } + drawPath(mouth, color = PrimaryWhite) +} + +private fun DrawScope.drawMonsterYellow(w: Float, h: Float) { + drawSmileyFace(w, h, Color(0xFF2C3E50)) +} + +private fun DrawScope.drawChickenFace(w: Float, h: Float) { + // Comb on head + drawCircle(color = Color(0xFFFF4757), radius = w * 0.09f, center = Offset(w * 0.50f, h * 0.16f)) + drawCircle(color = Color(0xFFFF4757), radius = w * 0.07f, center = Offset(w * 0.38f, h * 0.19f)) + drawCircle(color = Color(0xFFFF4757), radius = w * 0.07f, center = Offset(w * 0.62f, h * 0.19f)) + + // Eyes + drawCircle(color = PrimaryWhite, radius = w * 0.06f, center = Offset(w * 0.35f, h * 0.40f)) + drawCircle(color = PrimaryWhite, radius = w * 0.06f, center = Offset(w * 0.65f, h * 0.40f)) + drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.35f, h * 0.40f)) + drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.65f, h * 0.40f)) + + // Beak + val beak = Path().apply { + moveTo(w * 0.42f, h * 0.48f) + lineTo(w * 0.58f, h * 0.48f) + lineTo(w * 0.50f, h * 0.66f) + close() + } + drawPath(beak, color = Color(0xFFFFA502)) +} + +private fun DrawScope.drawZombieFace(w: Float, h: Float) { + // Mismatched eyes + drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.35f, h * 0.38f)) + drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.35f, h * 0.38f)) + + // X eye + val xPath = Path().apply { + moveTo(w * 0.60f, h * 0.34f); lineTo(w * 0.70f, h * 0.42f) + moveTo(w * 0.70f, h * 0.34f); lineTo(w * 0.60f, h * 0.42f) + } + drawPath(xPath, color = PrimaryWhite, style = Stroke(width = w * 0.04f, cap = StrokeCap.Round)) + + // Stitched mouth + val mouthLine = Path().apply { + moveTo(w * 0.30f, h * 0.62f); lineTo(w * 0.70f, h * 0.62f) + moveTo(w * 0.38f, h * 0.56f); lineTo(w * 0.38f, h * 0.68f) + moveTo(w * 0.50f, h * 0.56f); lineTo(w * 0.50f, h * 0.68f) + moveTo(w * 0.62f, h * 0.56f); lineTo(w * 0.62f, h * 0.68f) + } + drawPath(mouthLine, color = PrimaryWhite, style = Stroke(width = w * 0.04f, cap = StrokeCap.Round)) +} + +private fun DrawScope.drawPandaFace(w: Float, h: Float) { + // Black ears + drawCircle(color = Color(0xFF2F3640), radius = w * 0.11f, center = Offset(w * 0.22f, h * 0.22f)) + drawCircle(color = Color(0xFF2F3640), radius = w * 0.11f, center = Offset(w * 0.78f, h * 0.22f)) + + // Eye patches + drawCircle(color = Color(0xFF2F3640), radius = w * 0.09f, center = Offset(w * 0.36f, h * 0.44f)) + drawCircle(color = Color(0xFF2F3640), radius = w * 0.09f, center = Offset(w * 0.64f, h * 0.44f)) + drawCircle(color = PrimaryWhite, radius = w * 0.035f, center = Offset(w * 0.36f, h * 0.43f)) + drawCircle(color = PrimaryWhite, radius = w * 0.035f, center = Offset(w * 0.64f, h * 0.43f)) + + // Nose & smile + drawCircle(color = Color(0xFF2F3640), radius = w * 0.04f, center = Offset(w * 0.50f, h * 0.56f)) + val mouthPath = Path().apply { + moveTo(w * 0.42f, h * 0.64f) + quadraticTo(w * 0.50f, h * 0.70f, w * 0.58f, h * 0.64f) + } + drawPath(path = mouthPath, color = Color(0xFF2F3640), style = Stroke(width = w * 0.04f, cap = StrokeCap.Round)) +} + +private fun DrawScope.drawCoolFace(w: Float, h: Float, color: Color) { + // Sunglasses + val glasses = Path().apply { + moveTo(w * 0.22f, h * 0.36f) + lineTo(w * 0.78f, h * 0.36f) + lineTo(w * 0.74f, h * 0.48f) + lineTo(w * 0.54f, h * 0.48f) + lineTo(w * 0.50f, h * 0.40f) + lineTo(w * 0.46f, h * 0.48f) + lineTo(w * 0.26f, h * 0.48f) + close() + } + drawPath(glasses, color = PrimaryBlack) + + // Smile + val mouthPath = Path().apply { + moveTo(w * 0.34f, h * 0.62f) + quadraticTo(w * 0.50f, h * 0.76f, w * 0.68f, h * 0.58f) + } + drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) +} + +private fun DrawScope.drawSurprisedFace(w: Float, h: Float, color: Color) { + drawCircle(color = color, radius = w * 0.065f, center = Offset(w * 0.35f, h * 0.36f)) + drawCircle(color = color, radius = w * 0.065f, center = Offset(w * 0.65f, h * 0.36f)) + drawCircle(color = color, radius = w * 0.08f, center = Offset(w * 0.50f, h * 0.62f)) +} + +private fun DrawScope.drawGeometricFace(w: Float, h: Float, text: String) { + drawSmileyFace(w, h, PrimaryWhite) +} + +/** + * Small Avatar component (UserProfilePicture or UserProfileMenu) + */ +@Composable +fun CloneflixSmallAvatar( + avatar: CloneflixAvatarModel, + modifier: Modifier = Modifier, + showMenuArrow: Boolean = false, + size: Dp = 32.dp, + onClick: (() -> Unit)? = null, + isExpanded: Boolean = false +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val rotationAngle by animateFloatAsState( + targetValue = if (isExpanded) 180f else 0f, + label = "SmallAvatarArrowRotation" + ) + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.08f else 1.0f, + label = "SmallAvatarScale" + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier + .scale(scale) + .clip(RoundedCornerShape(4.dp)) + .then( + if (isFocused) Modifier.border(BorderStroke(2.dp, PrimaryWhite), RoundedCornerShape(4.dp)) + else Modifier + ) + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier.focusable(interactionSource = interactionSource) + } + ) + .padding(if (showMenuArrow) 4.dp else 0.dp) + .semantics { + role = Role.Button + contentDescription = "User profile ${avatar.name}${if (showMenuArrow) ", menu dropdown" else ""}" + } + ) { + Box( + modifier = Modifier + .size(size) + .clip(RoundedCornerShape(3.dp)) + ) { + CloneflixAvatarGraphic(avatar = avatar) + } + + if (showMenuArrow) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_arrow_down), + contentDescription = null, + tint = if (isFocused) PrimaryWhite else Grey100, + modifier = Modifier + .size(16.dp) + .rotate(rotationAngle) + ) + } + } +} + +/** + * Large Avatar component (UserProfileAvatar or AddUserProfile) with hover/TV D-pad focus states. + */ +@Composable +fun CloneflixLargeAvatar( + name: String, + modifier: Modifier = Modifier, + avatar: CloneflixAvatarModel? = null, + isAddProfile: Boolean = false, + size: Dp = 144.dp, + isHoverState: Boolean = false, + onClick: () -> Unit = {} +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocusedByInteraction by interactionSource.collectIsFocusedAsState() + val isFocused = isFocusedByInteraction || isHoverState + + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.06f else 1.0f, + label = "LargeAvatarScale" + ) + + val borderWidth = if (isFocused) 2.5.dp else 0.dp + val borderColor = if (isFocused) PrimaryWhite else Color.Transparent + + val textColor = when { + isFocused -> PrimaryWhite + isAddProfile -> Grey200 + else -> colors.textPrimary + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .width(size + 16.dp) + .scale(scale) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(vertical = 6.dp) + .semantics { + role = Role.Button + contentDescription = if (isAddProfile) "Add Profile" else "Profile $name" + } + ) { + Box( + modifier = Modifier + .size(size) + .clip(RoundedCornerShape(6.dp)) + .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(6.dp)) + .background(if (isAddProfile) Grey800 else Color.Transparent), + contentAlignment = Alignment.Center + ) { + if (isAddProfile) { + // Circle Plus Icon for Add Profile + Box( + modifier = Modifier + .size(size * 0.54f) + .clip(CircleShape) + .background(if (isFocused) Grey700 else Grey850) + .border( + BorderStroke( + 1.5.dp, + if (isFocused) PrimaryWhite else Grey350 + ), + CircleShape + ), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_plus), + contentDescription = null, + tint = if (isFocused) PrimaryWhite else Grey200, + modifier = Modifier.size(size * 0.30f) + ) + } + } else if (avatar != null) { + CloneflixAvatarGraphic(avatar = avatar) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = name, + style = if (isFocused) typography.mediumBody.copy(fontWeight = FontWeight.Bold) else typography.mediumBody, + fontSize = 16.sp, + color = textColor, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +/** + * Sample "Who's Watching?" profile selector row with D-Pad traversal and selection state. + */ +@Composable +fun CloneflixUserProfilesRow( + profiles: List>, + selectedProfileName: String, + onProfileClick: (String, CloneflixAvatarModel) -> Unit, + onAddProfileClick: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + ) { + profiles.forEach { (profileName, avatar) -> + CloneflixLargeAvatar( + name = profileName, + avatar = avatar, + isAddProfile = false, + isHoverState = profileName == selectedProfileName, + onClick = { onProfileClick(profileName, avatar) } + ) + } + + CloneflixLargeAvatar( + name = "Add Profile", + avatar = null, + isAddProfile = true, + onClick = onAddProfileClick + ) + } +} + +private val PolygonShape = GenericShape { size, _ -> + moveTo(size.width / 2f, 0f) + lineTo(size.width, size.height) + lineTo(0f, size.height) + close() +} + +/** + * Interactive Profile Dropdown Menu matching the Figma design popover. + */ +@Composable +fun CloneflixProfileMenuDropdown( + currentProfiles: List>, + onProfileSelected: (String) -> Unit, + onManageProfilesClick: () -> Unit, + onTransferProfilesClick: () -> Unit, + onAccountClick: () -> Unit, + onHelpCenterClick: () -> Unit, + onSignOutClick: () -> Unit, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + + Column( + horizontalAlignment = Alignment.End, + modifier = modifier.width(224.dp) + ) { + // Pointer Polygon Arrow on top + Box( + modifier = Modifier + .padding(end = 24.dp) + .size(width = 16.dp, height = 10.dp) + .clip(PolygonShape) + .background(Grey800) + .border(BorderStroke(1.dp, Grey600), PolygonShape) + ) + + // Dropdown Menu Card + Surface( + color = Color(0xFF141414), + shape = RoundedCornerShape(4.dp), + border = BorderStroke(1.dp, Grey700), + shadowElevation = 12.dp, + modifier = Modifier + .fillMaxWidth() + .offset(y = (-1).dp) + ) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + // Profiles List + currentProfiles.forEach { (name, avatar) -> + ProfileMenuItem( + icon = { + Box( + modifier = Modifier + .size(32.dp) + .clip(RoundedCornerShape(3.dp)) + ) { + CloneflixAvatarGraphic(avatar = avatar) + } + }, + label = name, + onClick = { onProfileSelected(name) } + ) + } + + Spacer(modifier = Modifier.height(4.dp)) + CloneflixDivider(modifier = Modifier.padding(horizontal = 8.dp)) + Spacer(modifier = Modifier.height(4.dp)) + + // Action Items + ProfileMenuItem( + iconRes = R.drawable.cloneflix_ic_edit, + label = "Manage Profiles", + onClick = onManageProfilesClick + ) + + ProfileMenuItem( + iconRes = R.drawable.cloneflix_ic_person, + label = "Transfer Profiles", + onClick = onTransferProfilesClick + ) + + ProfileMenuItem( + iconRes = R.drawable.cloneflix_ic_account, + label = "Account", + onClick = onAccountClick + ) + + ProfileMenuItem( + iconRes = R.drawable.cloneflix_ic_help, + label = "Help Center", + onClick = onHelpCenterClick + ) + + Spacer(modifier = Modifier.height(4.dp)) + CloneflixDivider(modifier = Modifier.padding(horizontal = 8.dp)) + Spacer(modifier = Modifier.height(4.dp)) + + // Sign Out Link + Box( + modifier = Modifier + .fillMaxWidth() + .clickable(role = Role.Button, onClick = onSignOutClick) + .padding(horizontal = 16.dp, vertical = 12.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Sign out of Cloneflix", + style = typography.mediumSmallBody, + color = PrimaryWhite, + fontSize = 13.sp, + textAlign = TextAlign.Center + ) + } + } + } + } +} + +@Composable +private fun ProfileMenuItem( + label: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + iconRes: Int? = null, + icon: (@Composable () -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val typography = CloneflixTheme.typography + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .fillMaxWidth() + .background(if (isFocused) Grey800 else Color.Transparent) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(horizontal = 14.dp, vertical = 8.dp) + ) { + if (icon != null) { + icon() + } else if (iconRes != null) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = null, + tint = if (isFocused) PrimaryWhite else Grey100, + modifier = Modifier.size(20.dp) + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + Text( + text = label, + style = typography.regularBody, + fontSize = 13.sp, + color = if (isFocused) PrimaryWhite else Grey100, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt new file mode 100644 index 00000000000..637e09684e1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt @@ -0,0 +1,438 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.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.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.graphics.Color +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey10 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey25 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T40 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red300 + +/** + * Maturity Rating Badges from Figma Design System (TV-Y, TV-Y7, G, TV-G, PG, TV-PG, PG-13, TV-14, R, TV-MA, NC-17). + */ +@Composable +fun CloneflixMaturityRating( + rating: String, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val borderColor = if (isFocused) PrimaryWhite else Grey25 + val borderWidth = if (isFocused) 1.8.dp else 1.dp + val textColor = if (isFocused) PrimaryWhite else Grey25 + + Box( + modifier = modifier + .height(22.dp) + .clip(RoundedCornerShape(2.dp)) + .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(2.dp)) + .background(if (isFocused) Grey300T40 else Color.Transparent) + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier.focusable(interactionSource = interactionSource) + } + ) + .padding(horizontal = 6.5.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = rating, + color = textColor, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + lineHeight = 16.sp, + textAlign = TextAlign.Center, + modifier = Modifier.semantics { + contentDescription = "Maturity Rating $rating" + } + ) + } +} + +/** + * Video Quality Badges (HD, 4K, HDR, UltraHD 4K, Dolby Vision). + */ +@Composable +fun CloneflixVideoQualityBadge( + quality: String, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val borderColor = if (isFocused) PrimaryWhite else Grey200 + val borderWidth = if (isFocused) 1.5.dp else 1.dp + val textColor = if (isFocused) PrimaryWhite else Grey10 + + Box( + modifier = modifier + .height(18.dp) + .clip(RoundedCornerShape(4.dp)) + .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(4.dp)) + .background(if (isFocused) Grey300T40 else Color.Transparent) + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier.focusable(interactionSource = interactionSource) + } + ) + .padding(horizontal = 6.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = quality, + color = textColor, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + lineHeight = 14.sp, + textAlign = TextAlign.Center, + modifier = Modifier.semantics { + contentDescription = "Video Quality $quality" + } + ) + } +} + +enum class CloneflixTop10Size { + LARGE, + MEDIUM, + SMALL +} + +/** + * Iconic Top 10 Red Stacked Badge. + */ +@Composable +fun CloneflixTop10Badge( + modifier: Modifier = Modifier, + size: CloneflixTop10Size = CloneflixTop10Size.MEDIUM +) { + val badgeSize: Dp = when (size) { + CloneflixTop10Size.LARGE -> 32.dp + CloneflixTop10Size.MEDIUM -> 28.dp + CloneflixTop10Size.SMALL -> 24.dp + } + + val topTextSize = when (size) { + CloneflixTop10Size.LARGE -> 10.sp + CloneflixTop10Size.MEDIUM -> 9.sp + CloneflixTop10Size.SMALL -> 7.5.sp + } + + val numTextSize = when (size) { + CloneflixTop10Size.LARGE -> 16.sp + CloneflixTop10Size.MEDIUM -> 14.sp + CloneflixTop10Size.SMALL -> 11.5.sp + } + + Box( + modifier = modifier + .size(badgeSize) + .clip(RoundedCornerShape(4.dp)) + .background(PrimaryRed) + .semantics { contentDescription = "Top 10 Badge" }, + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "TOP", + color = PrimaryWhite, + fontSize = topTextSize, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + lineHeight = topTextSize + ) + Text( + text = "10", + color = PrimaryWhite, + fontSize = numTextSize, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + lineHeight = numTextSize + ) + } + } +} + +/** + * Top 10 with Rank text (e.g. "#2 in TV Shows Today"). + */ +@Composable +fun CloneflixTop10RankBanner( + rankText: String, + modifier: Modifier = Modifier, + size: CloneflixTop10Size = CloneflixTop10Size.MEDIUM, + onClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val textFontSize = when (size) { + CloneflixTop10Size.LARGE -> 22.sp + CloneflixTop10Size.MEDIUM -> 18.sp + CloneflixTop10Size.SMALL -> 15.sp + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .border( + if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(0.dp, Color.Transparent), + RoundedCornerShape(8.dp) + ) + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier.focusable(interactionSource = interactionSource) + } + ) + .padding(4.dp) + ) { + CloneflixTop10Badge(size = size) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = rankText, + color = PrimaryWhite, + fontSize = textFontSize, + fontWeight = FontWeight.Bold, + fontFamily = CloneflixTheme.typography.regularBody.fontFamily + ) + } +} + +enum class CloneflixBadgeType(@androidx.annotation.StringRes val stringRes: Int) { + RECENTLY_ADDED(com.lagradost.cloudstream3.R.string.cloneflix_badge_recently_added), + NEW_SEASON(com.lagradost.cloudstream3.R.string.cloneflix_badge_new_season), + TOP_10(com.lagradost.cloudstream3.R.string.cloneflix_badge_top_10), + LEAVING_SOON(com.lagradost.cloudstream3.R.string.cloneflix_badge_leaving_soon) +} + +/** + * Content Status Pill Badges (Recently Added, New Season, Top 10, Leaving Soon). + */ +@Composable +fun CloneflixContentBadge( + type: CloneflixBadgeType, + modifier: Modifier = Modifier, + customText: String? = null +) { + val text = customText ?: androidx.compose.ui.res.stringResource(id = type.stringRes) + + val backgroundColor = when (type) { + CloneflixBadgeType.RECENTLY_ADDED -> Red100 + CloneflixBadgeType.NEW_SEASON -> PrimaryRed + CloneflixBadgeType.TOP_10 -> Red300 + CloneflixBadgeType.LEAVING_SOON -> Red300 + } + + Box( + modifier = modifier + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(backgroundColor) + .padding(horizontal = CloneflixTheme.dimens.spacingS, vertical = CloneflixTheme.dimens.spacingXxs), + contentAlignment = Alignment.Center + ) { + Text( + text = text, + color = PrimaryWhite, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.SansSerif, + lineHeight = 13.sp, + textAlign = TextAlign.Center + ) + } +} + +enum class CloneflixLogoVariant { + WORDMARK_MEDIUM, + WORDMARK_SMALL, + LETTERMARK_LARGE, + LETTERMARK_MEDIUM, + LETTERMARK_SMALL +} + +/** + * Cloneflix Brand Logo Composable (Wordmark & Lettermark). + */ +@Composable +fun CloneflixLogoView( + variant: CloneflixLogoVariant = CloneflixLogoVariant.WORDMARK_MEDIUM, + modifier: Modifier = Modifier +) { + when (variant) { + CloneflixLogoVariant.WORDMARK_MEDIUM -> { + Text( + text = "CLONEFLIX", + color = PrimaryRed, + fontSize = 28.sp, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + letterSpacing = 1.5.sp, + modifier = modifier.semantics { contentDescription = "Cloneflix Logo" } + ) + } + CloneflixLogoVariant.WORDMARK_SMALL -> { + Text( + text = "CLONEFLIX", + color = PrimaryRed, + fontSize = 18.sp, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + letterSpacing = 1.sp, + modifier = modifier.semantics { contentDescription = "Cloneflix Logo Small" } + ) + } + CloneflixLogoVariant.LETTERMARK_LARGE -> { + Box( + modifier = modifier + .size(48.dp) + .clip(RoundedCornerShape(8.dp)) + .background(PrimaryBlack), + contentAlignment = Alignment.Center + ) { + Text( + text = "C", + color = PrimaryRed, + fontSize = 38.sp, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif + ) + } + } + CloneflixLogoVariant.LETTERMARK_MEDIUM -> { + Box( + modifier = modifier + .size(36.dp) + .clip(RoundedCornerShape(6.dp)) + .background(PrimaryBlack), + contentAlignment = Alignment.Center + ) { + Text( + text = "C", + color = PrimaryRed, + fontSize = 28.sp, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif + ) + } + } + CloneflixLogoVariant.LETTERMARK_SMALL -> { + Box( + modifier = modifier + .size(24.dp) + .clip(RoundedCornerShape(4.dp)) + .background(PrimaryBlack), + contentAlignment = Alignment.Center + ) { + Text( + text = "C", + color = PrimaryRed, + fontSize = 18.sp, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif + ) + } + } + } +} + +@Preview(name = "Badges & Logos Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun BadgesAndLogosPreview() { + CloneflixTheme { + Column(modifier = Modifier.padding(16.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + CloneflixMaturityRating(rating = "TV-MA") + CloneflixMaturityRating(rating = "PG-13") + CloneflixVideoQualityBadge(quality = "4K") + CloneflixVideoQualityBadge(quality = "HD") + CloneflixVideoQualityBadge(quality = "Dolby Vision") + } + Spacer(modifier = Modifier.height(16.dp)) + CloneflixTop10RankBanner(rankText = "#2 in TV Shows Today") + Spacer(modifier = Modifier.height(16.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + CloneflixContentBadge(type = CloneflixBadgeType.RECENTLY_ADDED) + CloneflixContentBadge(type = CloneflixBadgeType.NEW_SEASON) + CloneflixContentBadge(type = CloneflixBadgeType.TOP_10) + } + Spacer(modifier = Modifier.height(16.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) { + CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) + CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_MEDIUM) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt new file mode 100644 index 00000000000..fc268b5c65e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt @@ -0,0 +1,302 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +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.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.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +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 +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 + +@Composable +fun CloneflixHeroActionPattern( + onPlayClick: () -> Unit = {}, + onMoreInfoClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val dimens = CloneflixTheme.dimens + + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixButton( + text = stringResource(R.string.cloneflix_btn_play), + onClick = onPlayClick, + variant = CloneflixButtonVariant.SECONDARY, + size = CloneflixButtonSize.MEDIUM, + icon = painterResource(id = R.drawable.cloneflix_ic_play) + ) + + CloneflixButton( + text = stringResource(R.string.cloneflix_btn_more_info), + onClick = onMoreInfoClick, + variant = CloneflixButtonVariant.MORE_INFO, + size = CloneflixButtonSize.MEDIUM, + icon = painterResource(id = R.drawable.cloneflix_ic_info) + ) + } +} + +@Composable +fun CloneflixPreviewActionsPattern( + isAdded: Boolean = false, + onPlayClick: () -> Unit = {}, + onAddClick: () -> Unit = {}, + onThumbUpClick: () -> Unit = {}, + onExpandClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val dimens = CloneflixTheme.dimens + + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixCircleActionButton( + type = CloneflixCircleActionType.PLAY, + onClick = onPlayClick + ) + CloneflixCircleActionButton( + type = if (isAdded) CloneflixCircleActionType.ADDED else CloneflixCircleActionType.ADD, + onClick = onAddClick + ) + CloneflixCircleActionButton( + type = CloneflixCircleActionType.THUMB_UP, + onClick = onThumbUpClick + ) + } + + CloneflixCircleActionButton( + type = CloneflixCircleActionType.ARROW_DOWN, + onClick = onExpandClick + ) + } +} + +@Composable +fun CloneflixHeroRatingPattern( + isMuted: Boolean = true, + rating: String = "TV-MA", + onToggleMute: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val dimens = CloneflixTheme.dimens + + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixHeroSoundButton( + isMuted = isMuted, + onToggleMute = onToggleMute, + size = CloneflixMovieCardDefaults.PlayButtonOverlaySize + ) + + CloneflixMaturityRating( + rating = rating + ) + } +} + +@Composable +fun CloneflixVideoPlayerControlBar( + title: String = "S1:E3 • The Shadow Protocol", + currentTimeText: String = "14:20", + totalTimeText: String = "45:00", + progress: Float = 0.32f, + isPlaying: Boolean = true, + isMuted: Boolean = false, + onPlayPauseToggle: () -> Unit = {}, + onSeekBack: () -> Unit = {}, + onSeekForward: () -> Unit = {}, + onToggleMute: () -> Unit = {}, + onNextEpisode: () -> Unit = {}, + onSubtitlesClick: () -> Unit = {}, + onSpeedClick: () -> Unit = {}, + onEpisodesClick: () -> Unit = {}, + onFullscreenToggle: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + var currentProgress by remember { mutableFloatStateOf(progress) } + + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(dimens.radiusM)) + .background(Color(0xDF141414)) + .padding(dimens.spacingL) + ) { + // Scrubber / Progress Bar + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = currentTimeText, + style = typography.regularCaption1, + color = PrimaryWhite + ) + + Spacer(modifier = Modifier.width(dimens.spacingM)) + + Slider( + value = currentProgress, + onValueChange = { currentProgress = it }, + colors = SliderDefaults.colors( + thumbColor = PrimaryRed, + activeTrackColor = PrimaryRed, + inactiveTrackColor = Color(0x66FFFFFF) + ), + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.width(dimens.spacingM)) + + Text( + text = totalTimeText, + style = typography.regularCaption1, + color = Grey200 + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + // Control buttons row + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + // Left Group + Row( + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixPlayerControlButton( + action = if (isPlaying) CloneflixPlayerControlAction.PLAY else CloneflixPlayerControlAction.PAUSE, + onClick = onPlayPauseToggle, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.REPLAY_10, + onClick = onSeekBack, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.FORWARD_10, + onClick = onSeekForward, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + CloneflixPlayerControlButton( + action = if (isMuted) CloneflixPlayerControlAction.VOLUME_OFF else CloneflixPlayerControlAction.VOLUME_UP, + onClick = onToggleMute, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + } + + // Center Title + Text( + text = title, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + + // Right Group + Row( + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.NEXT_EPISODE, + onClick = onNextEpisode, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.EPISODES, + onClick = onEpisodesClick, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.SUBTITLES, + onClick = onSubtitlesClick, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.SPEED, + onClick = onSpeedClick, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.FULLSCREEN, + onClick = onFullscreenToggle, + size = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize = dimens.iconM + ) + } + } + } +} + +@Preview(name = "Button Patterns Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun ButtonPatternsPreview() { + CloneflixTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + CloneflixHeroActionPattern() + CloneflixPreviewActionsPattern() + CloneflixHeroRatingPattern() + CloneflixVideoPlayerControlBar() + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt new file mode 100644 index 00000000000..ad63cd8a532 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt @@ -0,0 +1,830 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface +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.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red300 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors + +enum class CloneflixButtonVariant { + PRIMARY, + SECONDARY, + DARK_SECONDARY, + MORE_INFO, + OUTLINE, + GHOST +} + +enum class CloneflixButtonSize { + LARGE, + MEDIUM, + SMALL +} + +@Composable +fun CloneflixButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + variant: CloneflixButtonVariant = CloneflixButtonVariant.PRIMARY, + size: CloneflixButtonSize = CloneflixButtonSize.MEDIUM, + icon: Painter? = null, + enabled: Boolean = true +) { + val dimens = CloneflixTheme.dimens + val colors = CloneflixTheme.colors + + val height = when (size) { + CloneflixButtonSize.LARGE -> dimens.buttonHeightLarge + CloneflixButtonSize.MEDIUM -> dimens.buttonHeightMedium + CloneflixButtonSize.SMALL -> dimens.buttonHeightSmall + } + + val textSize = when (size) { + CloneflixButtonSize.LARGE -> 18.sp + CloneflixButtonSize.MEDIUM -> 16.sp + CloneflixButtonSize.SMALL -> 14.sp + } + + val contentPadding = when (size) { + CloneflixButtonSize.LARGE -> PaddingValues(horizontal = dimens.spacing2Xl, vertical = dimens.spacingM) + CloneflixButtonSize.MEDIUM -> PaddingValues(horizontal = dimens.spacingL, vertical = dimens.spacingS) + CloneflixButtonSize.SMALL -> PaddingValues(horizontal = dimens.spacingM, vertical = dimens.spacingXs) + } + + val containerColor = when (variant) { + CloneflixButtonVariant.PRIMARY -> colors.primary + CloneflixButtonVariant.SECONDARY -> PrimaryWhite + CloneflixButtonVariant.DARK_SECONDARY -> TransparentWhite20 + CloneflixButtonVariant.MORE_INFO -> Color(0xB3333333) + CloneflixButtonVariant.OUTLINE -> Color.Transparent + CloneflixButtonVariant.GHOST -> Color.Transparent + } + + val contentColor = when (variant) { + CloneflixButtonVariant.PRIMARY -> colors.onPrimary + CloneflixButtonVariant.SECONDARY -> PrimaryBlack + CloneflixButtonVariant.DARK_SECONDARY -> PrimaryWhite + CloneflixButtonVariant.MORE_INFO -> PrimaryWhite + CloneflixButtonVariant.OUTLINE -> colors.textPrimary + CloneflixButtonVariant.GHOST -> colors.textPrimary + } + + val border = when (variant) { + CloneflixButtonVariant.OUTLINE -> BorderStroke(1.dp, Grey200) + else -> null + } + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val activeBorder = if (isFocused) { + BorderStroke(2.dp, PrimaryWhite) + } else { + border + } + + Button( + onClick = onClick, + enabled = enabled, + interactionSource = interactionSource, + modifier = modifier + .height(height) + .semantics { role = Role.Button }, + shape = CloneflixTheme.shapes.extraSmall, + colors = ButtonDefaults.buttonColors( + containerColor = containerColor, + contentColor = contentColor, + disabledContainerColor = containerColor.copy(alpha = 0.5f), + disabledContentColor = contentColor.copy(alpha = 0.5f) + ), + border = activeBorder, + contentPadding = contentPadding + ) { + if (icon != null) { + Icon( + painter = icon, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = contentColor + ) + Spacer(modifier = Modifier.width(dimens.spacingS)) + } + Text( + text = text, + fontSize = textSize, + fontWeight = FontWeight.Bold, + color = contentColor + ) + } +} + +@Composable +fun CloneflixCheckbox( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + label: String? = null, + enabled: Boolean = true +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val activeBorder = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else null + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .clip(CloneflixTheme.shapes.extraSmall) + .then(if (activeBorder != null) Modifier.border(activeBorder, CloneflixTheme.shapes.extraSmall) else Modifier) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + onClick = { onCheckedChange(!checked) } + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .padding(vertical = dimens.spacingXs, horizontal = dimens.spacingXs) + .semantics { role = Role.Checkbox } + ) { + Checkbox( + checked = checked, + onCheckedChange = null, + enabled = enabled, + colors = CheckboxDefaults.colors( + checkedColor = PrimaryWhite, + checkmarkColor = PrimaryBlack, + uncheckedColor = Grey200, + disabledCheckedColor = PrimaryWhite.copy(alpha = 0.5f), + disabledUncheckedColor = Grey200.copy(alpha = 0.5f) + ), + modifier = Modifier.size(20.dp) + ) + + if (!label.isNullOrBlank()) { + Spacer(modifier = Modifier.width(dimens.spacingS)) + Text( + text = label, + style = typography.regularBody, + color = if (isFocused) PrimaryWhite else colors.textPrimary + ) + } + } +} + +enum class CloneflixCardElevation { + SURFACE, + ELEVATED +} + +@Composable +fun CloneflixCard( + modifier: Modifier = Modifier, + elevation: CloneflixCardElevation = CloneflixCardElevation.SURFACE, + content: @Composable () -> Unit +) { + val colors = CloneflixTheme.colors + val shapes = CloneflixTheme.shapes + val bgColor = if (elevation == CloneflixCardElevation.SURFACE) colors.surface else colors.surfaceElevated + + Surface( + modifier = modifier.clip(shapes.large), + color = bgColor, + shape = shapes.large, + border = BorderStroke(1.dp, colors.border), + content = content + ) +} + +@Composable +fun CloneflixHeader( + title: String, + subtitle: String? = null, + iconRes: Int = R.drawable.cloneflix_ic_about, + modifier: Modifier = Modifier +) { + val dimens = CloneflixTheme.dimens + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + + Column(modifier = modifier.fillMaxWidth()) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = dimens.spacing2Xl) + ) { + Box( + modifier = Modifier + .size(dimens.headerIconSize) + .clip(CircleShape) + .background(colors.surface) + .border(dimens.headerIconStroke, PrimaryWhite, CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = "$title Icon", + tint = colors.textPrimary, + modifier = Modifier.size(36.dp) + ) + } + + Spacer(modifier = Modifier.width(dimens.spacing2Xl)) + + Column { + Text( + text = title, + style = typography.headerDisplay, + color = colors.textPrimary + ) + if (!subtitle.isNullOrBlank()) { + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = subtitle, + style = typography.regularSmallBody, + color = colors.textSecondary + ) + } + } + } + + CloneflixDivider() + } +} + +@Composable +fun CloneflixDivider( + modifier: Modifier = Modifier, + thickness: Dp = CloneflixTheme.dimens.dividerThickness, + color: Color = CloneflixTheme.colors.divider +) { + Box( + modifier = modifier + .fillMaxWidth() + .height(thickness) + .background(color) + ) +} + +@Composable +fun CloneflixColorSwatch( + token: CloneflixColors.ColorToken, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val typography = CloneflixTheme.typography + + var isFocused by remember { mutableStateOf(false) } + val activeBorder = if (isFocused) { + BorderStroke(2.dp, PrimaryWhite) + } else { + BorderStroke(1.dp, colors.border) + } + + Row( + modifier = modifier + .fillMaxWidth() + .clip(CloneflixTheme.shapes.large) + .background(colors.surface) + .border(activeBorder, CloneflixTheme.shapes.large) + .onFocusChanged { isFocused = it.isFocused } + .clickable( + onClick = onClick, + role = Role.Button + ) + .focusable() + .padding(dimens.spacingL) + .semantics { + contentDescription = "${token.name}, Hex: ${token.hex}, ${token.rgbDescription}" + }, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(dimens.swatchSize) + .clip(CloneflixTheme.shapes.small) + .background(Color(token.colorInt)) + .border(1.dp, colors.border, CloneflixTheme.shapes.small) + ) + + Spacer(modifier = Modifier.width(dimens.spacingL)) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = token.name, + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXxs)) + Text( + text = "${token.hex} • ${token.rgbDescription}", + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_copy), + contentDescription = "Copy Color Hex", + tint = colors.textSecondary, + modifier = Modifier.size(24.dp) + ) + } +} + +enum class CloneflixInputFieldSize { + LARGE, + MEDIUM +} + +enum class CloneflixInputFieldState { + DEFAULT, + FOCUSED, + ERROR +} + +@Composable +fun CloneflixInputField( + value: String, + onValueChange: (String) -> Unit, + label: String, + modifier: Modifier = Modifier, + size: CloneflixInputFieldSize = CloneflixInputFieldSize.LARGE, + isError: Boolean = false, + errorMessage: String? = null, + isPassword: Boolean = false, + enabled: Boolean = true, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + leadingIcon: (@Composable () -> Unit)? = null, + trailingIcon: (@Composable () -> Unit)? = null +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + var isFocused by remember { mutableStateOf(false) } + var passwordVisible by remember { mutableStateOf(!isPassword) } + + val hasFloating = isFocused || value.isNotEmpty() + + val containerHeight = when (size) { + CloneflixInputFieldSize.LARGE -> 56.dp + CloneflixInputFieldSize.MEDIUM -> 48.dp + } + + val labelOffsetY by animateDpAsState( + targetValue = if (hasFloating) 7.dp else if (size == CloneflixInputFieldSize.LARGE) 16.dp else 12.dp, + animationSpec = tween(durationMillis = 150), + label = "labelOffsetY" + ) + + val labelFontSize by animateFloatAsState( + targetValue = if (hasFloating) 12f else 16f, + animationSpec = tween(durationMillis = 150), + label = "labelFontSize" + ) + + val labelColor by animateColorAsState( + targetValue = when { + isError -> Red100 + isFocused -> Grey50 + else -> Grey50 + }, + animationSpec = tween(durationMillis = 150), + label = "labelColor" + ) + + val borderColor = when { + isError -> Red100 + isFocused -> PrimaryWhite + else -> Grey200 + } + + val borderWidth = if (isFocused) 2.dp else 1.dp + val shape = RoundedCornerShape(4.dp) + + val visualTransformation = if (isPassword && !passwordVisible) { + PasswordVisualTransformation() + } else { + VisualTransformation.None + } + + Column(modifier = modifier.fillMaxWidth()) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(containerHeight) + .clip(shape) + .background(Color(0x80000000)) + .border(BorderStroke(borderWidth, borderColor), shape) + .focusable() + .onFocusChanged { isFocused = it.isFocused } + .padding(horizontal = 16.dp), + contentAlignment = Alignment.CenterStart + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + if (leadingIcon != null) { + leadingIcon() + Spacer(modifier = Modifier.width(12.dp)) + } + + Box( + modifier = Modifier + .weight(1f) + .height(containerHeight) + ) { + Text( + text = label, + color = labelColor, + fontFamily = CloneflixFontFamily, + fontWeight = if (hasFloating) FontWeight.Medium else FontWeight.Normal, + fontSize = labelFontSize.sp, + modifier = Modifier + .offset(y = labelOffsetY) + ) + + BasicTextField( + value = value, + onValueChange = onValueChange, + enabled = enabled, + singleLine = true, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + textStyle = TextStyle( + fontFamily = CloneflixFontFamily, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + color = PrimaryWhite + ), + cursorBrush = SolidColor(PrimaryWhite), + modifier = Modifier + .fillMaxWidth() + .padding(top = if (hasFloating) 20.dp else 0.dp) + .align(if (hasFloating) Alignment.TopStart else Alignment.CenterStart) + ) + } + + if (isPassword) { + IconButton( + onClick = { passwordVisible = !passwordVisible }, + modifier = Modifier.size(24.dp) + ) { + Icon( + painter = painterResource( + id = if (passwordVisible) R.drawable.cloneflix_ic_visibility else R.drawable.cloneflix_ic_visibility_off + ), + contentDescription = if (passwordVisible) "Hide password" else "Show password", + tint = Grey50, + modifier = Modifier.size(20.dp) + ) + } + } else if (trailingIcon != null) { + trailingIcon() + } + } + } + + if (isError && !errorMessage.isNullOrBlank()) { + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(start = dimens.spacingXs) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_circle_error), + contentDescription = "Error", + tint = Red100, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = errorMessage, + style = typography.regularCaption1, + color = Red100 + ) + } + } + } +} + +@Composable +fun CloneflixTextField( + value: String, + onValueChange: (String) -> Unit, + label: String, + modifier: Modifier = Modifier, + isError: Boolean = false, + errorMessage: String? = null +) { + CloneflixInputField( + value = value, + onValueChange = onValueChange, + label = label, + modifier = modifier, + isError = isError, + errorMessage = errorMessage + ) +} + +@Composable +fun CloneflixGetStartedRow( + emailValue: String, + onEmailChange: (String) -> Unit, + onGetStartedClick: () -> Unit, + modifier: Modifier = Modifier, + isError: Boolean = false, + errorMessage: String? = null +) { + val dimens = CloneflixTheme.dimens + + Row( + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.Top + ) { + CloneflixInputField( + value = emailValue, + onValueChange = onEmailChange, + label = "Email address", + size = CloneflixInputFieldSize.LARGE, + isError = isError, + errorMessage = errorMessage, + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.width(dimens.spacingS)) + + CloneflixButton( + text = "Get Started", + onClick = onGetStartedClick, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.LARGE, + icon = painterResource(id = R.drawable.cloneflix_ic_chevron_right), + modifier = Modifier.height(56.dp) + ) + } +} + +@Composable +fun CloneflixSignInCard( + modifier: Modifier = Modifier, + initialEmail: String = "", + onSignInClick: (String, String) -> Unit = { _, _ -> }, + onUseCodeClick: () -> Unit = {}, + onForgotPasswordClick: () -> Unit = {}, + onSignUpClick: () -> Unit = {} +) { + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + + var emailOrPhone by remember { mutableStateOf(initialEmail) } + var password by remember { mutableStateOf("") } + var rememberMe by remember { mutableStateOf(true) } + var passwordError by remember { mutableStateOf(null) } + + CloneflixCard( + modifier = modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.ELEVATED + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacing3Xl) + ) { + Text( + text = "Sign In", + style = typography.boldTitle1, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + CloneflixInputField( + value = emailOrPhone, + onValueChange = { emailOrPhone = it }, + label = "Email or phone number", + size = CloneflixInputFieldSize.LARGE + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixInputField( + value = password, + onValueChange = { + password = it + if (it.isNotEmpty() && (it.length < 4 || it.length > 60)) { + passwordError = "Your password must contain between 4 and 60 characters." + } else { + passwordError = null + } + }, + label = "Password", + isPassword = true, + size = CloneflixInputFieldSize.LARGE, + isError = passwordError != null, + errorMessage = passwordError + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + CloneflixButton( + text = "Sign In", + onClick = { + if (password.length < 4 || password.length > 60) { + passwordError = "Your password must contain between 4 and 60 characters." + } else { + onSignInClick(emailOrPhone, password) + } + }, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.LARGE, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "OR", + style = typography.regularBody, + color = colors.textSecondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Button( + onClick = onUseCodeClick, + modifier = Modifier + .fillMaxWidth() + .height(44.dp), + shape = RoundedCornerShape(4.dp), + colors = ButtonDefaults.buttonColors( + containerColor = TransparentWhite20, + contentColor = PrimaryWhite + ) + ) { + Text( + text = "Use a Sign-In Code", + style = typography.mediumBody, + color = PrimaryWhite + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Forgot password?", + style = typography.mediumBody, + color = PrimaryWhite, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onForgotPasswordClick) + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Checkbox( + checked = rememberMe, + onCheckedChange = { rememberMe = it }, + colors = CheckboxDefaults.colors( + checkedColor = PrimaryWhite, + checkmarkColor = PrimaryBlack, + uncheckedColor = Grey200 + ) + ) + Text( + text = "Remember me", + style = typography.regularCaption1, + color = colors.textPrimary, + modifier = Modifier.clickable { rememberMe = !rememberMe } + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "New to Cloneflix? ", + style = typography.regularBody, + color = colors.textSecondary + ) + Text( + text = "Sign up now.", + style = typography.mediumBody, + color = PrimaryWhite, + modifier = Modifier.clickable(onClick = onSignUpClick) + ) + } + } + } +} + +@Preview(name = "Cloneflix Buttons Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun CloneflixButtonsPreview() { + CloneflixTheme { + Column(modifier = Modifier.padding(16.dp)) { + CloneflixButton(text = "Primary Button", onClick = {}, modifier = Modifier.fillMaxWidth()) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixButton(text = "Secondary Button", onClick = {}, variant = CloneflixButtonVariant.SECONDARY, modifier = Modifier.fillMaxWidth()) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixButton(text = "Outline Button", onClick = {}, variant = CloneflixButtonVariant.OUTLINE, modifier = Modifier.fillMaxWidth()) + } + } +} + diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt new file mode 100644 index 00000000000..78d31a1b23a --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt @@ -0,0 +1,1173 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.widthIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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.draw.rotate +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Blue100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey10 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey400 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +// ============================================================================ +// 1. HEADERS (Landing Page & Home Page) +// ============================================================================ + +/** + * Landing Page Header with Logo, Language selector dropdown, and Sign In action button. + */ +@Composable +fun CloneflixLandingHeader( + onSignInClick: () -> Unit = {}, + selectedLanguage: String = "English", + onLanguageSelected: (String) -> Unit = {}, + languageOptions: List = listOf("English", "Español", "Français", "Deutsch", "日本語"), + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + Row( + modifier = modifier + .fillMaxWidth() + .background(Color.Black.copy(alpha = 0.85f)) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + // Logo + Text( + text = "CLONEFLIX", + style = typography.logoBebas, + fontSize = 28.sp, + color = PrimaryRed + ) + + // Actions (Language dropdown + Sign In button) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CloneflixDropdown( + options = languageOptions, + selectedOption = selectedLanguage, + onOptionSelected = onLanguageSelected, + variant = CloneflixDropdownVariant.COMPACT, + width = 120.dp + ) + + CloneflixButton( + text = "Sign In", + onClick = onSignInClick, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.SMALL + ) + } + } +} + +/** + * Home Page Header with Logo, Navigation Links, Search, Notifications, and Profile Avatar. + */ +@Composable +fun CloneflixHomeHeader( + selectedNavIndex: Int = 0, + onNavItemSelected: (Int) -> Unit = {}, + onSearchClick: () -> Unit = {}, + onNotificationsClick: () -> Unit = {}, + onProfileClick: () -> Unit = {}, + navItems: List = listOf("Home", "TV Shows", "Movies", "New & Popular", "My List", "Browse by Languages"), + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + Row( + modifier = modifier + .fillMaxWidth() + .background(Color.Black.copy(alpha = 0.95f)) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingM), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + // Left side: Logo & Navigation Items + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingXl) + ) { + Text( + text = "CLONEFLIX", + style = typography.logoBebas, + fontSize = 24.sp, + color = PrimaryRed + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + navItems.forEachIndexed { index, item -> + val isSelected = selectedNavIndex == index + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + Text( + text = item, + style = if (isSelected) typography.mediumBody else typography.regularSmallBody, + color = when { + isSelected -> PrimaryWhite + isFocused -> PrimaryWhite + else -> Grey10 + }, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .focusable(interactionSource = interactionSource) + .clickable { onNavItemSelected(index) } + .padding(horizontal = 4.dp, vertical = 2.dp) + ) + } + } + } + + // Right side: Search, Notifications, Avatar with dropdown arrow + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + IconButton( + onClick = onSearchClick, + modifier = Modifier.size(36.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_search), + contentDescription = "Search", + tint = PrimaryWhite, + modifier = Modifier.size(20.dp) + ) + } + + IconButton( + onClick = onNotificationsClick, + modifier = Modifier.size(36.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_notification), + contentDescription = "Notifications", + tint = PrimaryWhite, + modifier = Modifier.size(20.dp) + ) + } + + // Profile Avatar with dropdown arrow + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .clickable { onProfileClick() } + .padding(4.dp) + ) { + Box( + modifier = Modifier + .size(32.dp) + .clip(RoundedCornerShape(4.dp)) + .background(PrimaryRed), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_person), + contentDescription = "Profile Avatar", + tint = PrimaryWhite, + modifier = Modifier.size(20.dp) + ) + } + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_arrow_down), + contentDescription = "Profile Menu", + tint = PrimaryWhite, + modifier = Modifier.size(14.dp) + ) + } + } + } +} + +// ============================================================================ +// 2. FAQ ACCORDION (Interactive Expandable Component) +// ============================================================================ + +/** + * Individual FAQ Question Item with smooth accordion expansion. + */ +@Composable +fun CloneflixFaqItem( + question: String, + answer: String, + isOpen: Boolean, + onToggle: () -> Unit, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val rotationAngle by animateFloatAsState( + targetValue = if (isOpen) 45f else 0f, + animationSpec = tween(durationMillis = 250), + label = "FaqIconRotation" + ) + + val itemBackgroundColor = when { + isFocused -> Color(0xFF414141) + isOpen -> Color(0xFF2D2D2D) + else -> Color(0xFF2D2D2D) + } + + val border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else null + + Column( + modifier = modifier + .fillMaxWidth() + .animateContentSize(animationSpec = tween(durationMillis = 250)) + ) { + // Question Header Card + Surface( + onClick = onToggle, + color = itemBackgroundColor, + shape = RoundedCornerShape(2.dp), + border = border, + interactionSource = interactionSource, + modifier = Modifier + .fillMaxWidth() + .focusable(interactionSource = interactionSource) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 22.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = question, + style = typography.mediumTitle3, + fontSize = 20.sp, + color = PrimaryWhite, + modifier = Modifier.weight(1f) + ) + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_plus), + contentDescription = if (isOpen) "Collapse answer" else "Expand answer", + tint = PrimaryWhite, + modifier = Modifier + .size(24.dp) + .rotate(rotationAngle) + ) + } + } + + // Answer Body Card + AnimatedVisibility(visible = isOpen) { + Column { + Spacer(modifier = Modifier.height(2.dp)) + Surface( + color = Color(0xFF2D2D2D), + shape = RoundedCornerShape(2.dp), + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = answer, + style = typography.regularBody, + fontSize = 18.sp, + lineHeight = 26.sp, + color = PrimaryWhite, + modifier = Modifier.padding(24.dp) + ) + } + } + } + } +} + +/** + * Full FAQ Section containing multiple interactive FAQ items. + */ +@Composable +fun CloneflixFaqSection( + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + + val faqList = remember { + listOf( + "What is Netflix?" to "Netflix is a streaming service that offers a wide variety of award-winning TV shows, movies, anime, documentaries, and more on thousands of internet-connected devices.\n\nYou can watch as much as you want, whenever you want – all for one low monthly price. There's always something new to discover and new TV shows and movies are added every week!", + "How much does Netflix cost?" to "Watch Netflix on your smartphone, tablet, Smart TV, laptop, or streaming device, all for one fixed monthly fee. Plans range from standard with ads to premium. No extra costs, no contracts.", + "Where can I watch?" to "Watch anywhere, anytime. Sign in with your Netflix account to watch instantly on the web at netflix.com from your personal computer or on any internet-connected device that offers the Netflix app, including smart TVs, smartphones, tablets, streaming media players and game consoles.\n\nYou can also download your favorite shows with the iOS or Android app. Use downloads to watch while you're on the go and without an internet connection.", + "How do I cancel?" to "Netflix is flexible. There are no pesky contracts and no commitments. You can easily cancel your account online in two clicks. There are no cancellation fees – start or stop your account anytime.", + "What can I watch on Netflix?" to "Netflix has an extensive library of feature films, documentaries, TV shows, anime, award-winning Netflix originals, and more. Watch as much as you want, anytime you want.", + "Is Netflix good for kids?" to "The Netflix Kids experience is included in your membership to give parents control while kids enjoy family-friendly TV shows and movies in their own space.\n\nKids profiles come with PIN-protected parental controls that let you restrict the maturity rating of content kids can watch and block specific titles you don’t want kids to see." + ) + } + + var openIndex by remember { mutableIntStateOf(0) } + + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Frequently Asked Questions", + style = typography.headerDisplay, + fontSize = 32.sp, + color = PrimaryWhite, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 24.dp) + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + faqList.forEachIndexed { index, (q, a) -> + CloneflixFaqItem( + question = q, + answer = a, + isOpen = openIndex == index, + onToggle = { + openIndex = if (openIndex == index) -1 else index + } + ) + } + } + } +} + +// ============================================================================ +// 3. SCREENS PREVIEWS (TV, Mac, iPhone with Download Card, Kids) +// ============================================================================ + +/** + * TV Screen Mockup Preview. + */ +@Composable +fun CloneflixTvPreviewCard( + modifier: Modifier = Modifier +) { + Surface( + color = Color(0xFF141414), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, TransparentWhite20), + modifier = modifier + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(16.dp) + ) { + // TV Bezel + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(16f / 9f) + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFF0F0F0F)) + .border(2.dp, Grey700, RoundedCornerShape(8.dp)), + contentAlignment = Alignment.Center + ) { + // TV Screen Content Simulation + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf(Color(0xFF2A1010), Color(0xFF141414), Color(0xFF0A0A0A)) + ) + ) + ) { + Column( + modifier = Modifier + .align(Alignment.Center) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "CLONEFLIX", + style = CloneflixTheme.typography.logoBebas, + fontSize = 36.sp, + color = PrimaryRed + ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = "Ultra HD 4K Streaming", + style = CloneflixTheme.typography.regularCaption1, + color = Grey10 + ) + } + } + } + + // TV Stand / Base + Box( + modifier = Modifier + .width(60.dp) + .height(6.dp) + .background(Grey600, RoundedCornerShape(bottomStart = 3.dp, bottomEnd = 3.dp)) + ) + Box( + modifier = Modifier + .width(100.dp) + .height(3.dp) + .background(Grey700, RoundedCornerShape(2.dp)) + ) + } + } +} + +/** + * Mac / Computer Screen Mockup Preview. + */ +@Composable +fun CloneflixMacPreviewCard( + modifier: Modifier = Modifier +) { + Surface( + color = Color(0xFF141414), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, TransparentWhite20), + modifier = modifier + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(16.dp) + ) { + // Screen Bezel + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(16f / 10f) + .clip(RoundedCornerShape(6.dp)) + .background(Color(0xFF1F1F1F)) + .border(1.5.dp, Grey700, RoundedCornerShape(6.dp)) + ) { + Column(modifier = Modifier.fillMaxSize()) { + // Top Bar (Window dots) + Row( + modifier = Modifier + .fillMaxWidth() + .height(16.dp) + .background(Color(0xFF2B2B2B)) + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Box(modifier = Modifier.size(6.dp).background(Color(0xFFFF5F56), CircleShape)) + Box(modifier = Modifier.size(6.dp).background(Color(0xFFFFBD2E), CircleShape)) + Box(modifier = Modifier.size(6.dp).background(Color(0xFF27C93F), CircleShape)) + } + + // Content + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .background( + Brush.linearGradient( + colors = listOf(Color(0xFF1E1B4B), Color(0xFF111827), Color(0xFF0F172A)) + ) + ), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "Stream on Web & Desktop", + style = CloneflixTheme.typography.boldTitle2, + fontSize = 18.sp, + color = PrimaryWhite + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Watch anywhere seamlessly", + style = CloneflixTheme.typography.regularCaption1, + color = Grey100 + ) + } + } + } + } + + // Laptop base + Box( + modifier = Modifier + .fillMaxWidth(0.95f) + .height(6.dp) + .background(Grey600, RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp)) + ) + } + } +} + +/** + * Standalone / Floating Download Progress Card (Stranger Things badge). + */ +@Composable +fun CloneflixDownloadProgressCard( + title: String = "Stranger Things", + statusText: String = "Downloading...", + modifier: Modifier = Modifier +) { + Surface( + color = PrimaryBlack, + shape = RoundedCornerShape(12.dp), + border = BorderStroke(2.dp, Color(0xFF404040)), + shadowElevation = 8.dp, + modifier = modifier + ) { + Row( + modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(14.dp) + ) { + // Movie Poster Thumbnail Placeholder + Box( + modifier = Modifier + .width(36.dp) + .height(50.dp) + .clip(RoundedCornerShape(4.dp)) + .background( + Brush.verticalGradient( + listOf(Color(0xFFE50914), Color(0xFF1F1F1F)) + ) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "ST", + fontWeight = FontWeight.Bold, + fontSize = 12.sp, + color = PrimaryWhite + ) + } + + // Title & Status + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = CloneflixTheme.typography.mediumBody, + fontSize = 14.sp, + color = PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = statusText, + style = CloneflixTheme.typography.regularCaption1, + fontSize = 12.sp, + color = Blue100 + ) + } + + // Download Animation / Indicator + Box( + modifier = Modifier.size(24.dp), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + color = Blue100, + strokeWidth = 2.5.dp + ) + } + } + } +} + +/** + * Mobile Phone Mockup Preview featuring the Stranger Things download card. + */ +@Composable +fun CloneflixMobilePreviewCard( + modifier: Modifier = Modifier +) { + Surface( + color = Color(0xFF141414), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, TransparentWhite20), + modifier = modifier + ) { + Box( + modifier = Modifier + .padding(16.dp) + .widthIn(max = 280.dp) + .aspectRatio(9f / 16f) + .clip(RoundedCornerShape(24.dp)) + .background(Color(0xFF1E1E1E)) + .border(2.dp, Grey700, RoundedCornerShape(24.dp)) + ) { + // Screen Wallpaper / Content + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf(Color(0xFF1A1A2E), Color(0xFF16213E), Color(0xFF0F3460)) + ) + ) + .padding(16.dp) + ) { + // Top status bar notch + Box( + modifier = Modifier + .align(Alignment.TopCenter) + .width(70.dp) + .height(14.dp) + .background(Color.Black, RoundedCornerShape(bottomStart = 8.dp, bottomEnd = 8.dp)) + ) + + // Downloading Card overlay + CloneflixDownloadProgressCard( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(bottom = 12.dp) + ) + } + } + } +} + +/** + * Kids Profile & Content Screen Preview. + */ +@Composable +fun CloneflixKidsPreviewCard( + modifier: Modifier = Modifier +) { + Surface( + color = Color(0xFF141414), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, TransparentWhite20), + modifier = modifier + ) { + Box( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .aspectRatio(16f / 9f) + .clip(RoundedCornerShape(8.dp)) + .background( + Brush.linearGradient( + listOf(Color(0xFF3B82F6), Color(0xFF8B5CF6), Color(0xFFEC4899)) + ) + ), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(16.dp) + ) { + // Kids Avatar Row + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(44.dp) + .background(Color(0xFFFFB703), CircleShape), + contentAlignment = Alignment.Center + ) { + Text(text = "🐯", fontSize = 22.sp) + } + Box( + modifier = Modifier + .size(52.dp) + .background(Color(0xFFFB8500), CircleShape) + .border(2.dp, PrimaryWhite, CircleShape), + contentAlignment = Alignment.Center + ) { + Text(text = "🚀", fontSize = 26.sp) + } + Box( + modifier = Modifier + .size(44.dp) + .background(Color(0xFF219EBC), CircleShape), + contentAlignment = Alignment.Center + ) { + Text(text = "🦄", fontSize = 22.sp) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = "Kids Profile Experience", + style = CloneflixTheme.typography.boldTitle2, + fontSize = 18.sp, + color = PrimaryWhite + ) + + Text( + text = "Family-friendly entertainment & parental controls", + style = CloneflixTheme.typography.regularCaption1, + color = PrimaryWhite.copy(alpha = 0.9f) + ) + } + } + } +} + +// ============================================================================ +// 4. LANDING PAGE BLOCKS (Headline + Image / Image + Headline) +// ============================================================================ + +enum class CloneflixBlockLayout { + HEADLINE_IMAGE, + IMAGE_HEADLINE +} + +/** + * Reusable Landing Page Block with Title, Description, and Device Preview Slot. + */ +@Composable +fun CloneflixFeatureBlock( + title: String, + description: String, + layout: CloneflixBlockLayout = CloneflixBlockLayout.HEADLINE_IMAGE, + previewContent: @Composable () -> Unit, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + Surface( + color = PrimaryBlack, + modifier = modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dimens.spacing2Xl) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacingL), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + val textContent: @Composable (Modifier) -> Unit = { mod -> + Column( + modifier = mod.padding(dimens.spacingL), + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + style = typography.headerDisplay, + fontSize = 32.sp, + lineHeight = 38.sp, + color = PrimaryWhite + ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + Text( + text = description, + style = typography.regularBody, + fontSize = 18.sp, + lineHeight = 24.sp, + color = Grey10 + ) + } + } + + val previewSlot: @Composable (Modifier) -> Unit = { mod -> + Box( + modifier = mod.padding(dimens.spacingL), + contentAlignment = Alignment.Center + ) { + previewContent() + } + } + + when (layout) { + CloneflixBlockLayout.HEADLINE_IMAGE -> { + textContent(Modifier.weight(1f)) + previewSlot(Modifier.weight(1f)) + } + CloneflixBlockLayout.IMAGE_HEADLINE -> { + previewSlot(Modifier.weight(1f)) + textContent(Modifier.weight(1f)) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + CloneflixDivider() + } + } +} + +// ============================================================================ +// 5. FOOTERS (Landing Page, Authentication Page, Home Page) +// ============================================================================ + +/** + * Landing Page Footer with Questions hotline, 4-column links grid, and Language selector. + */ +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun CloneflixLandingFooter( + onPhoneClick: () -> Unit = {}, + onLinkClick: (String) -> Unit = {}, + selectedLanguage: String = "English", + onLanguageSelected: (String) -> Unit = {}, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val links = listOf( + "FAQ", "Help Center", "Account", "Media Center", + "Investor Relations", "Jobs", "Ways to Watch", "Terms of Use", + "Privacy", "Cookie Preferences", "Corporate Information", "Contact Us", + "Speed Test", "Legal Notices", "Only on Netflix", "Ad Choices" + ) + + Column( + modifier = modifier + .fillMaxWidth() + .background(PrimaryBlack) + .padding(horizontal = dimens.spacing2Xl, vertical = 48.dp) + ) { + // Hotline + Text( + text = "Questions? Call 1-844-505-2993", + style = typography.regularBody, + color = Grey200, + modifier = Modifier + .clickable { onPhoneClick() } + .padding(bottom = dimens.spacingXl) + ) + + // 4-Column Responsive Grid of Links + FlowRow( + modifier = Modifier.fillMaxWidth(), + maxItemsInEachRow = 4, + horizontalArrangement = Arrangement.SpaceBetween, + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + links.forEach { link -> + Text( + text = link, + style = typography.regularCaption1, + fontSize = 13.sp, + color = Grey200, + modifier = Modifier + .widthIn(min = 140.dp, max = 220.dp) + .clickable { onLinkClick(link) } + .padding(vertical = 4.dp) + ) + } + } + + Spacer(modifier = Modifier.height(28.dp)) + + // Language Dropdown + CloneflixDropdown( + options = listOf("English", "Español", "Français", "Deutsch"), + selectedOption = selectedLanguage, + onOptionSelected = onLanguageSelected, + variant = CloneflixDropdownVariant.COMPACT, + width = 130.dp + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = "Netflix United States", + style = typography.regularCaption2, + color = Grey400 + ) + } +} + +/** + * Authentication Page Footer (Streamlined). + */ +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun CloneflixAuthFooter( + onPhoneClick: () -> Unit = {}, + onLinkClick: (String) -> Unit = {}, + selectedLanguage: String = "English", + onLanguageSelected: (String) -> Unit = {}, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val links = listOf( + "FAQ", "Help Center", "Terms of Use", + "Privacy", "Cookie Preferences", "Corporate Information" + ) + + Column( + modifier = modifier + .fillMaxWidth() + .background(PrimaryBlack.copy(alpha = 0.9f)) + .padding(horizontal = dimens.spacing2Xl, vertical = 32.dp) + ) { + Text( + text = "Questions? Call 1-844-505-2993", + style = typography.regularBody, + color = Grey200, + modifier = Modifier + .clickable { onPhoneClick() } + .padding(bottom = dimens.spacingL) + ) + + FlowRow( + modifier = Modifier.fillMaxWidth(), + maxItemsInEachRow = 4, + horizontalArrangement = Arrangement.SpaceBetween, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + links.forEach { link -> + Text( + text = link, + style = typography.regularCaption1, + fontSize = 13.sp, + color = Grey200, + modifier = Modifier + .widthIn(min = 140.dp, max = 220.dp) + .clickable { onLinkClick(link) } + .padding(vertical = 4.dp) + ) + } + } + + Spacer(modifier = Modifier.height(20.dp)) + + CloneflixDropdown( + options = listOf("English", "Español", "Français"), + selectedOption = selectedLanguage, + onOptionSelected = onLanguageSelected, + variant = CloneflixDropdownVariant.COMPACT, + width = 120.dp + ) + } +} + +/** + * Home Page Footer with Social Media Icons, Links Grid, and Service Code Button. + */ +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun CloneflixHomeFooter( + onSocialClick: (String) -> Unit = {}, + onLinkClick: (String) -> Unit = {}, + onServiceCodeClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val links = listOf( + "Audio Description", "Help Center", "Gift Cards", "Media Center", + "Investor Relations", "Jobs", "Netflix Shop", "Terms of Use", + "Privacy", "Legal Notices", "Cookie Preferences", "Corporate Information", + "Contact Us", "Do Not Sell or Share My Personal Information", "Ad Choices" + ) + + Column( + modifier = modifier + .fillMaxWidth() + .background(PrimaryBlack) + .padding(horizontal = dimens.spacing2Xl, vertical = 40.dp) + ) { + // Social Media Icons + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 20.dp) + ) { + val socialIcons = listOf( + "Facebook" to R.drawable.cloneflix_ic_social_fb, + "Instagram" to R.drawable.cloneflix_ic_social_ig, + "Twitter" to R.drawable.cloneflix_ic_social_x, + "YouTube" to R.drawable.cloneflix_ic_social_yt + ) + + socialIcons.forEach { (platform, iconRes) -> + IconButton( + onClick = { onSocialClick(platform) }, + modifier = Modifier.size(32.dp) + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = platform, + tint = PrimaryWhite, + modifier = Modifier.size(24.dp) + ) + } + } + } + + // Links Grid + FlowRow( + modifier = Modifier.fillMaxWidth(), + maxItemsInEachRow = 4, + horizontalArrangement = Arrangement.SpaceBetween, + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + links.forEach { link -> + Text( + text = link, + style = typography.regularCaption1, + fontSize = 13.sp, + color = Grey200, + modifier = Modifier + .widthIn(min = 140.dp, max = 220.dp) + .clickable { onLinkClick(link) } + .padding(vertical = 4.dp) + ) + } + } + + Spacer(modifier = Modifier.height(24.dp)) + + // Service Code Button + Surface( + onClick = onServiceCodeClick, + shape = RoundedCornerShape(2.dp), + border = BorderStroke(1.dp, Grey200), + color = Color.Transparent, + modifier = Modifier.padding(bottom = 20.dp) + ) { + Text( + text = "Service Code", + style = typography.regularCaption1, + fontSize = 13.sp, + color = Grey200, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp) + ) + } + + // Copyright + Text( + text = "© 1997 - 2024 Netflix, Inc.", + style = typography.regularCaption2, + fontSize = 11.sp, + color = Grey400 + ) + } +} + +// ============================================================================ +// PREVIEWS +// ============================================================================ + +@Preview(name = "Landing Header Preview", showBackground = true, backgroundColor = 0xFF000000) +@Composable +private fun LandingHeaderPreview() { + CloneflixTheme { + CloneflixLandingHeader() + } +} + +@Preview(name = "Home Header Preview", showBackground = true, backgroundColor = 0xFF000000) +@Composable +private fun HomeHeaderPreview() { + CloneflixTheme { + CloneflixHomeHeader() + } +} + +@Preview(name = "FAQ Section Preview", showBackground = true, backgroundColor = 0xFF000000) +@Composable +private fun FaqSectionPreview() { + CloneflixTheme { + CloneflixFaqSection(modifier = Modifier.padding(16.dp)) + } +} + +@Preview(name = "Feature Block Preview", showBackground = true, backgroundColor = 0xFF000000) +@Composable +private fun FeatureBlockPreview() { + CloneflixTheme { + CloneflixFeatureBlock( + title = "Enjoy on your TV", + description = "Watch on Smart TVs, Playstation, Xbox, Chromecast, Apple TV, Blu-ray players, and more.", + layout = CloneflixBlockLayout.HEADLINE_IMAGE, + previewContent = { + CloneflixTvPreviewCard(modifier = Modifier.fillMaxWidth(0.9f)) + } + ) + } +} + +@Preview(name = "Home Footer Preview", showBackground = true, backgroundColor = 0xFF000000) +@Composable +private fun HomeFooterPreview() { + CloneflixTheme { + CloneflixHomeFooter() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt new file mode 100644 index 00000000000..699f6e359df --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt @@ -0,0 +1,124 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +/** + * Central design tokens and defaults for Cloneflix components. + * Eliminates magic numbers across Jetpack Compose UI. + */ +object CloneflixTokens { + // Focus & Motion Tokens + const val FOCUS_SCALE_FACTOR = 1.05f + const val FOCUS_SCALE_FACTOR_LARGE = 1.08f + const val FOCUS_SCALE_FACTOR_SUBTLE = 1.02f + + // Alpha / Opacity Tokens + const val ALPHA_MUTED = 0.3f + const val ALPHA_SUBTLE = 0.4f + const val ALPHA_MEDIUM = 0.6f + const val ALPHA_HIGH = 0.7f + const val ALPHA_NEAR_OPAQUE = 0.9f + + // Common Radii + val RadiusPill: Dp = 999.dp + val RadiusCard: Dp = 4.dp + val RadiusCardMedium: Dp = 8.dp + val RadiusCardLarge: Dp = 12.dp +} + +/** + * Defaults and dimension formulas for Cloneflix Movie Cards. + */ +object CloneflixMovieCardDefaults { + val WidthMedium: Dp = 218.dp + val WidthSmall: Dp = 128.dp + val WidthTop10Medium: Dp = 215.dp + val WidthTop10Small: Dp = 160.dp + val WidthEpisodeMedium: Dp = 160.dp + val WidthEpisodeSmall: Dp = 128.dp + val WidthMoreLikeThis: Dp = 236.dp + val WidthTrailer: Dp = 236.dp + val WidthPlayerPreview: Dp = 245.dp + val WidthContinueWatchingMedium: Dp = 218.dp + val WidthContinueWatchingSmall: Dp = 160.dp + + val HeightMedium: Dp = 123.dp + val HeightSmall: Dp = 72.dp + val HeightTop10Medium: Dp = 154.dp + val HeightTop10Small: Dp = 115.dp + val HeightEpisodeMedium: Dp = 90.dp + val HeightEpisodeSmall: Dp = 72.dp + val HeightMoreLikeThis: Dp = 132.dp + val HeightTrailer: Dp = 204.dp + val HeightTrailerThumbnail: Dp = 132.dp + val HeightPlayerPreview: Dp = 152.dp + val HeightContinueWatchingMedium: Dp = 133.dp + val HeightContinueWatchingSmall: Dp = 98.dp + + val RankBoxWidth: Dp = 90.dp + val RankFontSize: TextUnit = 110.sp + val RankLineHeight: TextUnit = 110.sp + val RankShadowOffsetX: Dp = 10.dp + val RankForegroundOffsetX: Dp = 8.dp + + val PlayButtonOverlaySize: Dp = 36.dp + val PlayIconOverlaySize: Dp = 16.dp + val EpisodeItemPlayButtonSize: Dp = 32.dp + val EpisodeItemPlayIconSize: Dp = 14.dp + + val EpisodeThumbnailWidth: Dp = 128.dp + val EpisodeThumbnailHeight: Dp = 72.dp + + val ExpandedPreviewHeaderHeight: Dp = 260.dp + val ActionButtonSize: Dp = 40.dp + + fun cardWidth(type: CloneflixMovieCardType, size: CloneflixMovieCardSize): Dp = when (type) { + CloneflixMovieCardType.TOP10 -> if (size == CloneflixMovieCardSize.MEDIUM) WidthTop10Medium else WidthTop10Small + CloneflixMovieCardType.EPISODE -> if (size == CloneflixMovieCardSize.SMALL) WidthEpisodeSmall else WidthEpisodeMedium + CloneflixMovieCardType.MORE_LIKE_THIS, + CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY -> WidthMoreLikeThis + CloneflixMovieCardType.TRAILER -> WidthTrailer + CloneflixMovieCardType.PLAYER_PREVIEW -> WidthPlayerPreview + CloneflixMovieCardType.CONTINUE_WATCHING -> if (size == CloneflixMovieCardSize.MEDIUM) WidthContinueWatchingMedium else WidthContinueWatchingSmall + CloneflixMovieCardType.DEFAULT -> if (size == CloneflixMovieCardSize.MEDIUM) WidthMedium else WidthSmall + } + + fun cardHeight(type: CloneflixMovieCardType, size: CloneflixMovieCardSize): Dp = when (type) { + CloneflixMovieCardType.TOP10 -> if (size == CloneflixMovieCardSize.MEDIUM) HeightTop10Medium else HeightTop10Small + CloneflixMovieCardType.EPISODE -> if (size == CloneflixMovieCardSize.SMALL) HeightEpisodeSmall else HeightEpisodeMedium + CloneflixMovieCardType.MORE_LIKE_THIS, + CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY -> HeightMoreLikeThis + CloneflixMovieCardType.TRAILER -> HeightTrailer + CloneflixMovieCardType.PLAYER_PREVIEW -> HeightPlayerPreview + CloneflixMovieCardType.CONTINUE_WATCHING -> if (size == CloneflixMovieCardSize.MEDIUM) HeightContinueWatchingMedium else HeightContinueWatchingSmall + CloneflixMovieCardType.DEFAULT -> if (size == CloneflixMovieCardSize.MEDIUM) HeightMedium else HeightSmall + } +} + +/** + * Defaults for Video Player and Media Control Overlays. + */ +object CloneflixPlayerDefaults { + val ControlButtonSize: Dp = 48.dp + val ControlButtonSizeLarge: Dp = 64.dp + val ControlIconSize: Dp = 24.dp + val ControlIconSizeLarge: Dp = 32.dp + val BottomBarHeight: Dp = 80.dp + val TopBarHeight: Dp = 64.dp + val ProgressThumbSize: Dp = 12.dp + val ProgressTrackHeight: Dp = 4.dp + val VolumeSliderWidth: Dp = 140.dp + val SpeedItemWidth: Dp = 72.dp +} + +/** + * Defaults for Episodes and Drawer Panels. + */ +object CloneflixDrawerDefaults { + val DrawerWidth: Dp = 380.dp + val EpisodeItemCornerRadius: Dp = 6.dp + val EpisodeNumberWidth: Dp = 32.dp +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt new file mode 100644 index 00000000000..90b7e71ed77 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt @@ -0,0 +1,315 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +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.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.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.Surface +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.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite + +enum class CloneflixDropdownVariant { + DEFAULT, + OUTLINE, + COMPACT +} + +@Composable +fun CloneflixDropdown( + options: List, + selectedOption: String, + onOptionSelected: (String) -> Unit, + modifier: Modifier = Modifier, + label: String? = null, + variant: CloneflixDropdownVariant = CloneflixDropdownVariant.DEFAULT, + width: Dp? = null, + enabled: Boolean = true +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + val shapes = CloneflixTheme.shapes + + var expanded by remember { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val rotationAngle by animateFloatAsState( + targetValue = if (expanded) 180f else 0f, + label = "DropdownChevronRotation" + ) + + val borderStroke = when { + isFocused || expanded -> BorderStroke(2.dp, PrimaryWhite) + variant == CloneflixDropdownVariant.OUTLINE -> BorderStroke(1.dp, PrimaryWhite) + else -> BorderStroke(1.dp, colors.border) + } + + val backgroundColor = when { + expanded -> Color(0xFF2A2A2A) + isFocused -> Color(0xFF222222) + else -> PrimaryBlack + } + + val height = when (variant) { + CloneflixDropdownVariant.COMPACT -> 32.dp + else -> 40.dp + } + + val widthModifier = if (width != null) Modifier.width(width) else Modifier.fillMaxWidth() + + Column(modifier = modifier) { + if (!label.isNullOrBlank()) { + Text( + text = label, + style = typography.regularCaption1, + color = colors.textSecondary, + modifier = Modifier.padding(bottom = dimens.spacingXs) + ) + } + + Box(modifier = widthModifier) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(height) + .clip(shapes.extraSmall) + .background(backgroundColor) + .border(borderStroke, shapes.extraSmall) + .clickable( + enabled = enabled, + role = Role.DropdownList, + onClick = { expanded = !expanded } + ) + .focusable(interactionSource = interactionSource) + .padding(horizontal = dimens.spacingM, vertical = dimens.spacingS) + .semantics { + role = Role.DropdownList + contentDescription = "$label: $selectedOption, ${if (expanded) "Expanded" else "Collapsed"}" + } + ) { + Text( + text = selectedOption, + style = typography.regularBody, + fontSize = 14.sp, + color = colors.textPrimary, + modifier = Modifier.weight(1f), + maxLines = 1 + ) + + Spacer(modifier = Modifier.width(dimens.spacingS)) + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_dropdown), + contentDescription = null, + tint = colors.textPrimary, + modifier = Modifier + .size(16.dp) + .rotate(rotationAngle) + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .wrapContentHeight() + .heightIn(max = 280.dp) + .background(Color(0xE6000000)) + .border(BorderStroke(1.dp, PrimaryWhite), shapes.extraSmall) + ) { + options.forEach { option -> + val isSelected = option == selectedOption + DropdownMenuItem( + text = { + Text( + text = option, + style = typography.regularBody, + fontSize = 14.sp, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + color = if (isSelected) colors.primary else colors.textPrimary + ) + }, + trailingIcon = if (isSelected) { + { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_check), + contentDescription = "Selected", + tint = colors.primary, + modifier = Modifier.size(16.dp) + ) + } + } else null, + onClick = { + onOptionSelected(option) + expanded = false + }, + colors = MenuDefaults.itemColors( + textColor = colors.textPrimary, + trailingIconColor = colors.primary + ), + modifier = Modifier + .fillMaxWidth() + .height(36.dp) + ) + } + } + } + } +} + +@Composable +fun CloneflixDropdownStaticShowcase( + title: String, + selectedOption: String, + options: List, + modifier: Modifier = Modifier, + isHoverState: Boolean = false, + isExpandedState: Boolean = false, + onOptionSelected: (String) -> Unit = {} +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + val shapes = CloneflixTheme.shapes + + val borderStroke = when { + isExpandedState || isHoverState -> BorderStroke(1.dp, PrimaryWhite) + else -> BorderStroke(1.dp, colors.border) + } + + val backgroundColor = when { + isExpandedState -> Color(0xFF2A2A2A) + isHoverState -> Color(0xFF222222) + else -> PrimaryBlack + } + + Column(modifier = modifier) { + Text( + text = title, + style = typography.mediumCaption1, + color = colors.textSecondary, + modifier = Modifier.padding(bottom = dimens.spacingXs) + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(36.dp) + .clip(shapes.extraSmall) + .background(backgroundColor) + .border(borderStroke, shapes.extraSmall) + .padding(horizontal = dimens.spacingM, vertical = dimens.spacingS) + ) { + Text( + text = selectedOption, + style = typography.regularBody, + fontSize = 14.sp, + color = colors.textPrimary, + modifier = Modifier.weight(1f) + ) + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_dropdown), + contentDescription = null, + tint = colors.textPrimary, + modifier = Modifier + .size(14.dp) + .rotate(if (isExpandedState) 180f else 0f) + ) + } + + if (isExpandedState) { + Spacer(modifier = Modifier.height(2.dp)) + Surface( + modifier = Modifier + .fillMaxWidth() + .clip(shapes.extraSmall) + .border(BorderStroke(1.dp, colors.border), shapes.extraSmall), + color = Color(0xE6000000) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 200.dp) + .verticalScroll(rememberScrollState()) + .padding(dimens.spacingS) + ) { + options.forEach { option -> + val isSelected = option == selectedOption + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clip(shapes.extraSmall) + .clickable { onOptionSelected(option) } + .padding(horizontal = dimens.spacingS, vertical = 6.dp) + ) { + Text( + text = option, + style = typography.regularBody, + fontSize = 14.sp, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + color = if (isSelected) colors.primary else colors.textPrimary, + modifier = Modifier.weight(1f) + ) + if (isSelected) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_check), + contentDescription = "Selected", + tint = colors.primary, + modifier = Modifier.size(14.dp) + ) + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt new file mode 100644 index 00000000000..740bdc83380 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt @@ -0,0 +1,336 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.draw.scale +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.res.stringResource +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +data class CloneflixEpisodeItemData( + val episodeNumber: Int, + val title: String, + val durationText: String, + val synopsis: String, + val progress: Float = 0f, + val isCurrent: Boolean = false +) + +@Composable +fun CloneflixEpisodeCard( + item: CloneflixEpisodeItemData, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = CloneflixTheme.dimens + val colors = CloneflixTheme.colors + + val scale by animateFloatAsState( + targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, + label = "episodeCardScale" + ) + + val borderStroke = when { + isFocused -> BorderStroke(dimens.borderFocus, PrimaryWhite) + item.isCurrent -> BorderStroke(dimens.borderDefault, PrimaryRed) + else -> BorderStroke(dimens.borderSubtle, colors.border) + } + + Surface( + modifier = modifier + .width(CloneflixMovieCardDefaults.WidthTop10Medium) + .scale(scale) + .border(borderStroke, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .semantics { + role = Role.Button + contentDescription = item.title + }, + shape = RoundedCornerShape(CloneflixTokens.RadiusCardMedium), + color = if (isFocused) Color(0xFF282828) else Color(0xEB1C1C1C) + ) { + Column(modifier = Modifier.padding(dimens.spacingS)) { + // Thumbnail with progress bar + Box( + modifier = Modifier + .fillMaxWidth() + .height(CloneflixMovieCardDefaults.HeightTop10Small) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background( + Brush.verticalGradient( + colors = listOf(Color(0xFF333333), Color(0xFF141414)) + ) + ), + contentAlignment = Alignment.Center + ) { + if (item.isCurrent) { + Box( + modifier = Modifier + .size(CloneflixMovieCardDefaults.PlayButtonOverlaySize) + .clip(CircleShape) + .background(PrimaryRed), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = stringResource(R.string.cloneflix_cd_play), + tint = PrimaryWhite, + modifier = Modifier.size(CloneflixMovieCardDefaults.PlayIconOverlaySize) + ) + } + } else { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = stringResource(R.string.cloneflix_cd_play), + tint = Grey200.copy(alpha = CloneflixTokens.ALPHA_MEDIUM), + modifier = Modifier.size(dimens.iconM) + ) + } + + // Duration badge + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(dimens.spacingXs) + .background(TransparentBlack90, RoundedCornerShape(CloneflixTokens.RadiusCard)) + .padding(horizontal = dimens.spacingXs, vertical = dimens.spacingXxs) + ) { + Text( + text = item.durationText, + style = CloneflixTheme.typography.regularCaption2, + color = PrimaryWhite + ) + } + + // Progress line + if (item.progress > 0f) { + Box( + modifier = Modifier + .align(Alignment.BottomStart) + .fillMaxWidth() + .height(dimens.progressHeight) + .background(Color(0x66FFFFFF)) + ) { + Box( + modifier = Modifier + .fillMaxWidth(item.progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(PrimaryRed) + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + // Episode title + badge + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${item.episodeNumber}. ${item.title}", + style = CloneflixTheme.typography.mediumBody, + color = if (item.isCurrent) PrimaryRed else PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + Text( + text = item.synopsis, + style = CloneflixTheme.typography.regularCaption1, + color = Grey200, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Composable +fun CloneflixEpisodesDrawer( + seriesTitle: String, + seasonName: String, + episodes: List, + onEpisodeSelected: (CloneflixEpisodeItemData) -> Unit, + onCloseClick: () -> Unit, + modifier: Modifier = Modifier +) { + val listState = rememberLazyListState() + val dimens = CloneflixTheme.dimens + + Surface( + modifier = modifier + .fillMaxWidth() + .border(BorderStroke(dimens.borderDefault, dimens.dividerThickness.let { CloneflixTheme.colors.border }), RoundedCornerShape(topStart = dimens.radiusL, topEnd = dimens.radiusL)), + shape = RoundedCornerShape(topStart = dimens.radiusL, topEnd = dimens.radiusL), + color = TransparentBlack90 + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dimens.spacingL) + ) { + // Header + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + text = seriesTitle, + style = CloneflixTheme.typography.mediumCaption1, + color = PrimaryRed + ) + Text( + text = seasonName, + style = CloneflixTheme.typography.mediumTitle4, + color = PrimaryWhite + ) + } + + Box( + modifier = Modifier + .size(dimens.iconL) + .clip(CircleShape) + .background(Color(0x33FFFFFF)) + .clickable(onClick = onCloseClick), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_close), + contentDescription = stringResource(R.string.cloneflix_cd_close), + tint = PrimaryWhite, + modifier = Modifier.size(dimens.iconS) + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Episodes carousel + LazyRow( + state = listState, + contentPadding = PaddingValues(horizontal = dimens.spacing2Xl), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + modifier = Modifier.fillMaxWidth() + ) { + itemsIndexed(episodes) { _, episode -> + CloneflixEpisodeCard( + item = episode, + onClick = { onEpisodeSelected(episode) } + ) + } + } + } + } +} + +@Preview(name = "Episodes Drawer Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun EpisodesDrawerPreview() { + CloneflixTheme { + val sampleEpisodes = listOf( + CloneflixEpisodeItemData( + episodeNumber = 1, + title = "Sanctuary", + durationText = "54m", + synopsis = "A troubled teenager with sumo ancestry enters the sacred world of sumo wrestling.", + progress = 1.0f, + isCurrent = false + ), + CloneflixEpisodeItemData( + episodeNumber = 2, + title = "The Rebel", + durationText = "48m", + synopsis = "Kiyoshi refuses to follow tradition and sparks outrage among seasoned stable masters.", + progress = 0.45f, + isCurrent = true + ), + CloneflixEpisodeItemData( + episodeNumber = 3, + title = "The Crucible", + durationText = "51m", + synopsis = "An intensive training camp tests the physical limits of every rookie wrestler.", + progress = 0f, + isCurrent = false + ) + ) + + CloneflixEpisodesDrawer( + seriesTitle = "SANCTUARY", + seasonName = "Season 1 Episodes", + episodes = sampleEpisodes, + onEpisodeSelected = {}, + onCloseClick = {} + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt new file mode 100644 index 00000000000..4c52225efdf --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt @@ -0,0 +1,1091 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +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.draw.drawBehind +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey10 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T40 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T70 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600T60 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red300 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite70 + +enum class CloneflixTitlePreviewSize { + LARGE, + MEDIUM, + SMALL1, + SMALL2, + SMALL3 +} + +enum class CloneflixHeroBannerType { + LANDING_PAGE, + AUTHENTICATION_PAGE, + HOME_PAGE, + MOVIE_PREVIEW +} + +/** + * Standard Play Button used in Hero Banners and Title Previews. + */ +@Composable +fun CloneflixHeroPlayButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: String = "Play" +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val background = if (isFocused) PrimaryWhite else PrimaryWhite.copy(alpha = 0.95f) + val border = if (isFocused) BorderStroke(2.dp, PrimaryRed) else null + + Row( + modifier = modifier + .height(42.dp) + .clip(RoundedCornerShape(4.dp)) + .background(background) + .then(if (border != null) Modifier.border(border, RoundedCornerShape(4.dp)) else Modifier) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(horizontal = 24.dp, vertical = 8.dp) + .semantics { + contentDescription = "$text Video" + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryBlack, + modifier = Modifier.size(20.dp) + ) + Text( + text = text, + color = PrimaryBlack, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } +} + +/** + * Standard More Info Button used in Hero Banners and Title Previews. + */ +@Composable +fun CloneflixHeroMoreInfoButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: String = "More Info" +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val background = if (isFocused) Grey300T70 else Grey600T60 + val border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(1.dp, TransparentWhite20) + + Row( + modifier = modifier + .height(42.dp) + .clip(RoundedCornerShape(4.dp)) + .background(background) + .border(border, RoundedCornerShape(4.dp)) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(horizontal = 22.dp, vertical = 8.dp) + .semantics { + contentDescription = text + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_info), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier.size(22.dp) + ) + Text( + text = text, + color = PrimaryWhite, + fontSize = 16.sp, + fontWeight = FontWeight.Medium + ) + } +} + +/** + * Circular Action Button used in Hero Banners (Replay, Volume, Plus, ThumbUp, Close). + */ +@Composable +fun CloneflixHeroCircleButton( + iconRes: Int, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + size: Dp = 40.dp, + iconSize: Dp = 18.dp, + backgroundColor: Color = Grey700.copy(alpha = 0.8f) +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val activeBorder = if (isFocused) { + BorderStroke(2.dp, PrimaryWhite) + } else { + BorderStroke(1.dp, TransparentWhite20) + } + + Box( + modifier = modifier + .size(size) + .clip(CircleShape) + .background(if (isFocused) Grey600 else backgroundColor) + .border(activeBorder, CircleShape) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .semantics { + this.contentDescription = contentDescription + }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier.size(iconSize) + ) + } +} + +/** + * Hero Banner Right-Edge Maturity Rating Badge (e.g. "16+", "TV-MA"). + */ +@Composable +fun CloneflixHeroMaturityRating( + rating: String = "16+", + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .height(32.dp) + .clip(RoundedCornerShape(topStart = 2.dp, bottomStart = 2.dp)) + .background(Grey600T60) + .border( + width = 1.dp, + color = TransparentWhite20, + shape = RoundedCornerShape(topStart = 2.dp, bottomStart = 2.dp) + ) + .drawBehind { + // Red left accent bar + drawRect( + color = PrimaryRed, + size = androidx.compose.ui.geometry.Size(3.dp.toPx(), size.height) + ) + } + .padding(start = 12.dp, end = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = rating, + color = PrimaryWhite, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + lineHeight = 16.sp + ) + } +} + +/** + * Title Preview Component from Figma Section 15 (Sizes: Large, Medium, Small1, Small2, Small3). + */ +@Composable +fun CloneflixTitlePreview( + size: CloneflixTitlePreviewSize = CloneflixTitlePreviewSize.LARGE, + title: String = "HOUSE OF NINJAS", + subtitle: String = "忍びの家", + synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + top10RankText: String? = "#2 in TV Shows Today", + onPlayClick: () -> Unit = {}, + onMoreInfoClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + + when (size) { + CloneflixTitlePreviewSize.LARGE -> { + Column( + modifier = modifier.widthIn(max = 540.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Title Logo Box + CloneflixTitleLogoView(title = title, subtitle = subtitle) + + // Top 10 Rank Banner + if (!top10RankText.isNullOrBlank()) { + CloneflixTop10RankBanner( + rankText = top10RankText, + size = CloneflixTop10Size.LARGE + ) + } + + // Synopsis + Text( + text = synopsis, + style = typography.regularBody, + color = colors.textPrimary, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + lineHeight = 22.sp + ) + + // Action Buttons Row + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixHeroPlayButton(onClick = onPlayClick) + CloneflixHeroMoreInfoButton(onClick = onMoreInfoClick) + } + } + } + + CloneflixTitlePreviewSize.MEDIUM -> { + Column( + modifier = modifier.widthIn(max = 518.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + CloneflixTitleLogoView(title = title, subtitle = subtitle) + + Text( + text = synopsis, + style = typography.regularBody, + color = colors.textPrimary, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + lineHeight = 22.sp + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixHeroPlayButton(onClick = onPlayClick) + CloneflixHeroMoreInfoButton(onClick = onMoreInfoClick) + } + } + } + + CloneflixTitlePreviewSize.SMALL1 -> { + Column( + modifier = modifier.widthIn(max = 480.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + CloneflixTitleLogoView(title = title, subtitle = subtitle) + + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixHeroPlayButton(onClick = onPlayClick) + CloneflixHeroMoreInfoButton(onClick = onMoreInfoClick) + } + } + } + + CloneflixTitlePreviewSize.SMALL2 -> { + Box( + modifier = modifier + .width(518.dp) + .height(207.dp) + .clip(RoundedCornerShape(8.dp)) + .background( + Brush.linearGradient( + listOf(Color(0xFF1E2229), Color(0xFF0F1115)) + ) + ) + .border(BorderStroke(1.dp, TransparentWhite20), RoundedCornerShape(8.dp)), + contentAlignment = Alignment.Center + ) { + CloneflixTitleLogoView(title = title, subtitle = subtitle, isLarge = true) + } + } + + CloneflixTitlePreviewSize.SMALL3 -> { + Box( + modifier = modifier + .width(340.dp) + .height(136.dp) + .clip(RoundedCornerShape(6.dp)) + .background( + Brush.linearGradient( + listOf(Color(0xFF1E2229), Color(0xFF0F1115)) + ) + ) + .border(BorderStroke(1.dp, TransparentWhite20), RoundedCornerShape(6.dp)), + contentAlignment = Alignment.Center + ) { + CloneflixTitleLogoView(title = title, subtitle = subtitle, isLarge = false) + } + } + } +} + +/** + * Stylized Cinematic Title Logo View. + */ +@Composable +fun CloneflixTitleLogoView( + title: String, + subtitle: String? = null, + isLarge: Boolean = false, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.Start + ) { + Text( + text = title, + fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, + fontWeight = FontWeight.Bold, + fontSize = if (isLarge) 40.sp else 32.sp, + color = PrimaryWhite, + letterSpacing = 2.sp, + lineHeight = if (isLarge) 44.sp else 36.sp + ) + if (!subtitle.isNullOrBlank()) { + Text( + text = subtitle, + fontSize = if (isLarge) 16.sp else 13.sp, + fontWeight = FontWeight.Normal, + color = Grey200, + letterSpacing = 4.sp + ) + } + } +} + +/** + * Versatile Hero Banner Component supporting LandingPage, AuthenticationPage, HomePage, and MoviePreview. + */ +@Composable +fun CloneflixHeroBanner( + type: CloneflixHeroBannerType = CloneflixHeroBannerType.HOME_PAGE, + title: String = "HOUSE OF NINJAS", + synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + top10RankText: String? = "#2 in TV Shows Today", + maturityRating: String = "16+", + onPlayClick: () -> Unit = {}, + onMoreInfoClick: () -> Unit = {}, + onAudioToggle: (() -> Unit)? = null, + onCloseClick: (() -> Unit)? = null, + onAddToListClick: (() -> Unit)? = null, + onThumbsUpClick: (() -> Unit)? = null, + isMuted: Boolean = false, + modifier: Modifier = Modifier +) { + when (type) { + CloneflixHeroBannerType.HOME_PAGE -> { + Box( + modifier = modifier + .fillMaxWidth() + .height(480.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFF181C24)) + ) { + // Background artistic backdrop gradient simulating hero wallpaper + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf(Color(0xFF2C3E50), Color(0xFF0F141D), Color(0xFF0B0E14)), + radius = 1200f + ) + ) + ) + + // Top Vignette Gradient (for navigation bar contrast) + Box( + modifier = Modifier + .fillMaxWidth() + .height(140.dp) + .background( + Brush.verticalGradient( + listOf(TransparentBlack90, Color.Transparent) + ) + ) + ) + + // Left Scrim Gradient (for title readability) + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.horizontalGradient( + listOf( + Color(0xFF0B0E14).copy(alpha = 0.95f), + Color(0xFF0B0E14).copy(alpha = 0.6f), + Color.Transparent + ) + ) + ) + ) + + // Bottom Fade Scrim Gradient (seamless merge into movie rows) + Box( + modifier = Modifier + .fillMaxWidth() + .height(180.dp) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + listOf(Color.Transparent, Color(0xFF141414)) + ) + ) + ) + + // Title Preview Overlay (Left) + Box( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(start = 48.dp, bottom = 48.dp) + ) { + CloneflixTitlePreview( + size = CloneflixTitlePreviewSize.LARGE, + title = title, + synopsis = synopsis, + top10RankText = top10RankText, + onPlayClick = onPlayClick, + onMoreInfoClick = onMoreInfoClick + ) + } + + // Controls Overlay (Right: Audio toggle + Maturity Rating) + Row( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(bottom = 60.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (onAudioToggle != null) { + CloneflixHeroCircleButton( + iconRes = if (isMuted) R.drawable.cloneflix_ic_mute else R.drawable.cloneflix_ic_volume, + contentDescription = if (isMuted) "Unmute Audio" else "Mute Audio", + onClick = onAudioToggle, + size = 40.dp + ) + } + + CloneflixHeroMaturityRating(rating = maturityRating) + } + } + } + + CloneflixHeroBannerType.MOVIE_PREVIEW -> { + Box( + modifier = modifier + .fillMaxWidth() + .height(380.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFF181C24)) + ) { + // Backdrop Gradient + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf(Color(0xFF34495E), Color(0xFF1A252F), Color(0xFF11171E)), + radius = 900f + ) + ) + ) + + // Bottom Fade Scrim + Box( + modifier = Modifier + .fillMaxWidth() + .height(140.dp) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + listOf(Color.Transparent, Color(0xFF181818)) + ) + ) + ) + + // Top Right Close Button + if (onCloseClick != null) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(16.dp) + ) { + CloneflixHeroCircleButton( + iconRes = R.drawable.cloneflix_ic_close, + contentDescription = "Close Preview", + onClick = onCloseClick, + size = 36.dp, + iconSize = 16.dp, + backgroundColor = Grey850 + ) + } + } + + // Bottom-Left Title & Actions + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(start = 32.dp, bottom = 28.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + CloneflixTitleLogoView(title = title, subtitle = "忍びの家") + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + CloneflixHeroPlayButton(onClick = onPlayClick) + + if (onAddToListClick != null) { + CloneflixHeroCircleButton( + iconRes = R.drawable.cloneflix_ic_plus, + contentDescription = "Add to My List", + onClick = onAddToListClick, + size = 42.dp, + iconSize = 18.dp, + backgroundColor = Grey700 + ) + } + + if (onThumbsUpClick != null) { + CloneflixHeroCircleButton( + iconRes = R.drawable.cloneflix_ic_thumb_up, + contentDescription = "I like this", + onClick = onThumbsUpClick, + size = 42.dp, + iconSize = 18.dp, + backgroundColor = Grey700 + ) + } + } + } + + // Bottom-Right Audio Toggle + if (onAudioToggle != null) { + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(end = 32.dp, bottom = 28.dp) + ) { + CloneflixHeroCircleButton( + iconRes = if (isMuted) R.drawable.cloneflix_ic_mute else R.drawable.cloneflix_ic_volume, + contentDescription = if (isMuted) "Unmute Audio" else "Mute Audio", + onClick = onAudioToggle, + size = 40.dp + ) + } + } + } + } + + CloneflixHeroBannerType.LANDING_PAGE -> { + Box( + modifier = modifier + .fillMaxWidth() + .height(440.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFF0F1115)) + ) { + // Vignette background + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf(Color(0xFF2C3E50).copy(alpha = 0.7f), Color(0xFF0F1115), Color(0xFF000000)), + radius = 1000f + ) + ) + ) + + // Header Navigation + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp, vertical = 20.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "CLONEFLIX", + fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, + fontWeight = FontWeight.Bold, + color = PrimaryRed, + fontSize = 28.sp, + letterSpacing = 2.sp + ) + + CloneflixButton( + text = "Sign In", + onClick = onPlayClick, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.SMALL + ) + } + + // Central Hero Content + Column( + modifier = Modifier + .align(Alignment.Center) + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + Text( + text = "Unlimited movies, TV shows, and more", + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = PrimaryWhite, + textAlign = TextAlign.Center + ) + + Text( + text = "Watch anywhere. Cancel anytime.", + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = Grey10, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(6.dp)) + + Text( + text = "Ready to watch? Enter your email to create or restart your membership.", + fontSize = 15.sp, + color = Grey200, + textAlign = TextAlign.Center + ) + + Row( + modifier = Modifier.padding(top = 8.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixButton( + text = "Get Started >", + onClick = onPlayClick, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.LARGE + ) + } + } + } + } + + CloneflixHeroBannerType.AUTHENTICATION_PAGE -> { + Box( + modifier = modifier + .fillMaxWidth() + .height(440.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFF0F1115)) + ) { + // Vignette background + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf(Color(0xFF1E272E), Color(0xFF0F1115), Color(0xFF000000)), + radius = 900f + ) + ) + ) + + // Header + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp, vertical = 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "CLONEFLIX", + fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, + fontWeight = FontWeight.Bold, + color = PrimaryRed, + fontSize = 28.sp, + letterSpacing = 2.sp + ) + } + + // Auth Card in Center + Box( + modifier = Modifier + .align(Alignment.Center) + .width(360.dp) + .clip(RoundedCornerShape(8.dp)) + .background(TransparentBlack90) + .border(BorderStroke(1.dp, TransparentWhite20), RoundedCornerShape(8.dp)) + .padding(28.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Sign In", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + + CloneflixButton( + text = "Sign In with Account", + onClick = onPlayClick, + modifier = Modifier.fillMaxWidth(), + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.MEDIUM + ) + + Text( + text = "New to Cloneflix? Sign up now.", + fontSize = 13.sp, + color = Grey200 + ) + } + } + } + } + } +} + +/** + * Full Composed Home Page Hero Pattern from Figma Section 15 (Frame 314). + * Includes Top App Navigation Bar, Hero Backdrop with Title Preview, Rating, and Bottom Shelf Preview. + */ +@Composable +fun CloneflixHomePageHeroPattern( + onPlayClick: () -> Unit = {}, + onMoreInfoClick: () -> Unit = {}, + onSearchClick: () -> Unit = {}, + onNotificationClick: () -> Unit = {}, + onProfileClick: () -> Unit = {}, + onCardClick: ((String) -> Unit)? = null, + modifier: Modifier = Modifier +) { + val navItems = listOf("Home", "TV Shows", "Movies", "New & Popular", "My List", "Browse by Languages") + var selectedNavIndex by remember { mutableStateOf(0) } + var isMuted by remember { mutableStateOf(false) } + + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFF141414)) + ) { + // 1. Top Navigation Bar + Row( + modifier = Modifier + .fillMaxWidth() + .background( + Brush.verticalGradient( + listOf(Color(0xFF0F1115), Color(0xCC0F1115)) + ) + ) + .padding(horizontal = 32.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + // Left: Logo & Nav items + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + Text( + text = "CLONEFLIX", + fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, + fontWeight = FontWeight.Bold, + color = PrimaryRed, + fontSize = 24.sp, + letterSpacing = 2.sp + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + navItems.forEachIndexed { index, item -> + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val isSelected = selectedNavIndex == index + + Text( + text = item, + color = when { + isFocused -> PrimaryWhite + isSelected -> PrimaryWhite + else -> Grey200 + }, + fontWeight = if (isSelected || isFocused) FontWeight.Bold else FontWeight.Normal, + fontSize = 13.sp, + modifier = Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { selectedNavIndex = index } + ) + .focusable(interactionSource = interactionSource) + .then( + if (isFocused) Modifier.border( + BorderStroke(1.dp, PrimaryWhite), + RoundedCornerShape(2.dp) + ).padding(horizontal = 4.dp, vertical = 2.dp) else Modifier + ) + ) + } + } + } + + // Right: Actions (Search, Notification, Profile) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_search), + contentDescription = "Search", + tint = PrimaryWhite, + modifier = Modifier + .size(20.dp) + .clickable(onClick = onSearchClick) + ) + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_notification), + contentDescription = "Notifications", + tint = PrimaryWhite, + modifier = Modifier + .size(20.dp) + .clickable(onClick = onNotificationClick) + ) + + // Profile Avatar with Dropdown Arrow + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.clickable(onClick = onProfileClick) + ) { + Box( + modifier = Modifier + .size(28.dp) + .clip(RoundedCornerShape(4.dp)) + .background(Blue100), + contentAlignment = Alignment.Center + ) { + Text( + text = "C", + color = PrimaryWhite, + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + } + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_arrow_down), + contentDescription = "Profile Menu", + tint = PrimaryWhite, + modifier = Modifier.size(12.dp) + ) + } + } + } + + // 2. Hero Banner Body + CloneflixHeroBanner( + type = CloneflixHeroBannerType.HOME_PAGE, + title = "HOUSE OF NINJAS", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + top10RankText = "#2 in TV Shows Today", + maturityRating = "16+", + onPlayClick = onPlayClick, + onMoreInfoClick = onMoreInfoClick, + onAudioToggle = { isMuted = !isMuted }, + isMuted = isMuted + ) + + // 3. Bottom Trending Shelf Row Preview + Column( + modifier = Modifier + .fillMaxWidth() + .offset(y = (-40).dp) + .padding(horizontal = 32.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Trending Now", + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + listOf( + Triple("House of Ninjas", "Recently Added", "TOP 10"), + Triple("Avatar: The Last Airbender", "New Season", "TOP 10"), + Triple("One Piece", "Recently Added", "TOP 10"), + Triple("Stranger Things", "Leaving Soon", "TOP 10") + ).forEach { (movieTitle, tag, badge) -> + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + Box( + modifier = Modifier + .weight(1f) + .height(110.dp) + .clip(RoundedCornerShape(4.dp)) + .background( + Brush.linearGradient( + listOf(Color(0xFF2C3E50), Color(0xFF1A252F)) + ) + ) + .border( + BorderStroke( + if (isFocused) 2.dp else 1.dp, + if (isFocused) PrimaryWhite else TransparentWhite20 + ), + RoundedCornerShape(4.dp) + ) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { onCardClick?.invoke(movieTitle) } + ) + .focusable(interactionSource = interactionSource) + .padding(8.dp) + ) { + // Badge Tag + Box( + modifier = Modifier + .align(Alignment.TopStart) + .clip(RoundedCornerShape(2.dp)) + .background(Red300) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = tag, + color = PrimaryWhite, + fontSize = 10.sp, + fontWeight = FontWeight.Bold + ) + } + + Text( + text = movieTitle, + color = PrimaryWhite, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.align(Alignment.BottomStart) + ) + } + } + } + } + } +} + +val Blue100 = Color(0xFF0071EB) + +@Preview(name = "Hero Banners Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun HeroBannersPreview() { + CloneflixTheme { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) { + CloneflixTitlePreview(size = CloneflixTitlePreviewSize.LARGE) + CloneflixHeroBanner(type = CloneflixHeroBannerType.HOME_PAGE) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt new file mode 100644 index 00000000000..e0dd3691107 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt @@ -0,0 +1,371 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +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.Icon +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +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.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.res.stringResource +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +enum class CloneflixPlayerControlAction { + PLAY, + PAUSE, + REPLAY_10, + FORWARD_10, + VOLUME_UP, + VOLUME_OFF, + NEXT_EPISODE, + SUBTITLES, + SPEED, + EPISODES, + FULLSCREEN +} + +@Composable +fun CloneflixPlayerControlButton( + action: CloneflixPlayerControlAction, + onClick: () -> Unit, + modifier: Modifier = Modifier, + size: Dp = CloneflixPlayerDefaults.ControlButtonSize, + iconSize: Dp = CloneflixPlayerDefaults.ControlIconSize, + enabled: Boolean = true, + isSelected: Boolean = false +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = CloneflixTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, + label = "playerBtnScale" + ) + + val iconRes = when (action) { + CloneflixPlayerControlAction.PLAY -> R.drawable.cloneflix_ic_play + CloneflixPlayerControlAction.PAUSE -> R.drawable.cloneflix_ic_play + CloneflixPlayerControlAction.REPLAY_10 -> R.drawable.netflix_skip_back + CloneflixPlayerControlAction.FORWARD_10 -> R.drawable.netflix_skip_forward + CloneflixPlayerControlAction.VOLUME_UP -> R.drawable.ic_baseline_volume_up_24 + CloneflixPlayerControlAction.VOLUME_OFF -> R.drawable.ic_baseline_volume_mute_24 + CloneflixPlayerControlAction.NEXT_EPISODE -> R.drawable.ic_baseline_skip_next_24 + CloneflixPlayerControlAction.SUBTITLES -> R.drawable.ic_baseline_subtitles_24 + CloneflixPlayerControlAction.SPEED -> R.drawable.ic_baseline_speed_24 + CloneflixPlayerControlAction.EPISODES -> R.drawable.baseline_list_alt_24 + CloneflixPlayerControlAction.FULLSCREEN -> R.drawable.baseline_fullscreen_24 + } + + val contentDesc = when (action) { + CloneflixPlayerControlAction.PLAY -> stringResource(R.string.cloneflix_cd_play) + CloneflixPlayerControlAction.PAUSE -> stringResource(R.string.cloneflix_cd_pause) + CloneflixPlayerControlAction.REPLAY_10 -> stringResource(R.string.cloneflix_cd_replay_10) + CloneflixPlayerControlAction.FORWARD_10 -> stringResource(R.string.cloneflix_cd_forward_10) + CloneflixPlayerControlAction.VOLUME_UP -> stringResource(R.string.cloneflix_cd_unmute) + CloneflixPlayerControlAction.VOLUME_OFF -> stringResource(R.string.cloneflix_cd_mute) + CloneflixPlayerControlAction.NEXT_EPISODE -> stringResource(R.string.cloneflix_cd_next_episode) + CloneflixPlayerControlAction.SUBTITLES -> stringResource(R.string.cloneflix_cd_audio_subtitles) + CloneflixPlayerControlAction.SPEED -> stringResource(R.string.cloneflix_cd_playback_speed) + CloneflixPlayerControlAction.EPISODES -> stringResource(R.string.cloneflix_cd_episodes) + CloneflixPlayerControlAction.FULLSCREEN -> stringResource(R.string.cloneflix_cd_unlock_screen) + } + + val backgroundColor = when { + isFocused -> TransparentWhite20 + isSelected -> TransparentWhite20 + else -> Color.Transparent + } + + val borderStroke = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null + + Box( + modifier = modifier + .size(size) + .scale(scale) + .clip(CircleShape) + .background(backgroundColor) + .then(if (borderStroke != null) Modifier.border(borderStroke, CircleShape) else Modifier) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .semantics { + role = Role.Button + contentDescription = contentDesc + }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = contentDesc, + tint = if (enabled) PrimaryWhite else Grey200.copy(alpha = CloneflixTokens.ALPHA_MEDIUM), + modifier = Modifier.size(iconSize) + ) + } +} + +enum class CloneflixCircleActionType { + PLAY, + ADD, + ADDED, + THUMB_UP, + THUMB_DOWN, + DOUBLE_THUMB_UP, + MUTE, + UNMUTE, + ARROW_DOWN +} + +@Composable +fun CloneflixCircleActionButton( + type: CloneflixCircleActionType, + onClick: () -> Unit, + modifier: Modifier = Modifier, + size: Dp = CloneflixMovieCardDefaults.ActionButtonSize, + iconSize: Dp = CloneflixTheme.dimens.iconM, + enabled: Boolean = true +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = CloneflixTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, + label = "circleBtnScale" + ) + + val isPlay = type == CloneflixCircleActionType.PLAY + + val containerColor = when { + isPlay -> PrimaryWhite + isFocused -> TransparentWhite20 + else -> TransparentBlack60 + } + + val contentColor = when { + isPlay -> PrimaryBlack + else -> PrimaryWhite + } + + val borderStroke = when { + isPlay -> if (isFocused) BorderStroke(dimens.borderFocus, PrimaryBlack) else null + isFocused -> BorderStroke(dimens.borderFocus, PrimaryWhite) + else -> BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_HIGH)) + } + + val iconRes = when (type) { + CloneflixCircleActionType.PLAY -> R.drawable.cloneflix_ic_play + CloneflixCircleActionType.ADD -> R.drawable.cloneflix_ic_plus + CloneflixCircleActionType.ADDED -> R.drawable.cloneflix_ic_check + CloneflixCircleActionType.THUMB_UP -> R.drawable.cloneflix_ic_thumb_up + CloneflixCircleActionType.THUMB_DOWN -> R.drawable.cloneflix_ic_thumb_down + CloneflixCircleActionType.DOUBLE_THUMB_UP -> R.drawable.cloneflix_ic_double_thumb_up + CloneflixCircleActionType.MUTE -> R.drawable.ic_baseline_volume_mute_24 + CloneflixCircleActionType.UNMUTE -> R.drawable.ic_baseline_volume_up_24 + CloneflixCircleActionType.ARROW_DOWN -> R.drawable.cloneflix_ic_arrow_down + } + + val desc = when (type) { + CloneflixCircleActionType.PLAY -> stringResource(R.string.cloneflix_cd_play) + CloneflixCircleActionType.ADD -> stringResource(R.string.cloneflix_cd_add_to_list) + CloneflixCircleActionType.ADDED -> stringResource(R.string.cloneflix_cd_remove_from_list) + CloneflixCircleActionType.THUMB_UP -> stringResource(R.string.cloneflix_cd_like) + CloneflixCircleActionType.THUMB_DOWN -> stringResource(R.string.cloneflix_cd_dislike) + CloneflixCircleActionType.DOUBLE_THUMB_UP -> stringResource(R.string.cloneflix_cd_double_like) + CloneflixCircleActionType.MUTE -> stringResource(R.string.cloneflix_cd_mute) + CloneflixCircleActionType.UNMUTE -> stringResource(R.string.cloneflix_cd_unmute) + CloneflixCircleActionType.ARROW_DOWN -> stringResource(R.string.cloneflix_cd_episodes) + } + + Box( + modifier = modifier + .size(size) + .scale(scale) + .clip(CircleShape) + .background(containerColor) + .then(if (borderStroke != null) Modifier.border(borderStroke, CircleShape) else Modifier) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .semantics { + role = Role.Button + contentDescription = desc + }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = desc, + tint = contentColor, + modifier = Modifier.size(iconSize) + ) + } +} + +@Composable +fun CloneflixThumbsReactionPill( + onDislikeClick: () -> Unit, + onLikeClick: () -> Unit, + onLoveClick: () -> Unit, + modifier: Modifier = Modifier +) { + val dimens = CloneflixTheme.dimens + Surface( + modifier = modifier, + shape = RoundedCornerShape(CloneflixTokens.RadiusPill), + color = Color(0xEB232323), + border = BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_MEDIUM)) + ) { + Row( + modifier = Modifier.padding(horizontal = dimens.spacingM, vertical = dimens.spacingS), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixCircleActionButton( + type = CloneflixCircleActionType.THUMB_DOWN, + onClick = onDislikeClick, + size = CloneflixMovieCardDefaults.PlayButtonOverlaySize, + iconSize = dimens.iconS + ) + CloneflixCircleActionButton( + type = CloneflixCircleActionType.THUMB_UP, + onClick = onLikeClick, + size = CloneflixMovieCardDefaults.PlayButtonOverlaySize, + iconSize = dimens.iconS + ) + CloneflixCircleActionButton( + type = CloneflixCircleActionType.DOUBLE_THUMB_UP, + onClick = onLoveClick, + size = CloneflixMovieCardDefaults.PlayButtonOverlaySize, + iconSize = dimens.iconS + ) + } + } +} + +@Composable +fun CloneflixHeroSoundButton( + isMuted: Boolean, + onToggleMute: () -> Unit, + modifier: Modifier = Modifier, + size: Dp = CloneflixMovieCardDefaults.PlayButtonOverlaySize +) { + val dimens = CloneflixTheme.dimens + CloneflixCircleActionButton( + type = if (isMuted) CloneflixCircleActionType.MUTE else CloneflixCircleActionType.UNMUTE, + onClick = onToggleMute, + size = size, + iconSize = dimens.iconS, + modifier = modifier + ) +} + +@Composable +fun CloneflixHeroReplayButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + size: Dp = CloneflixMovieCardDefaults.PlayButtonOverlaySize +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = CloneflixTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, + label = "replayBtnScale" + ) + + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, PrimaryWhite) + } else { + BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_HIGH)) + } + + val replayDesc = stringResource(R.string.cloneflix_cd_replay_10) + + Box( + modifier = modifier + .size(size) + .scale(scale) + .clip(CircleShape) + .background(TransparentBlack60) + .border(border, CircleShape) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .semantics { + role = Role.Button + contentDescription = replayDesc + }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_replay_arrow), + contentDescription = replayDesc, + tint = PrimaryWhite, + modifier = Modifier.size(dimens.iconS) + ) + } +} + +@Preview(name = "Media Control Buttons Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun MediaControlButtonsPreview() { + CloneflixTheme { + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixCircleActionButton(type = CloneflixCircleActionType.PLAY, onClick = {}) + CloneflixCircleActionButton(type = CloneflixCircleActionType.ADD, onClick = {}) + CloneflixCircleActionButton(type = CloneflixCircleActionType.THUMB_UP, onClick = {}) + CloneflixThumbsReactionPill(onDislikeClick = {}, onLikeClick = {}, onLoveClick = {}) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt new file mode 100644 index 00000000000..3ca2f460592 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt @@ -0,0 +1,826 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.draw.scale +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.res.stringResource +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey500 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 + +enum class CloneflixMovieCardSize { + MEDIUM, + SMALL +} + +enum class CloneflixMovieCardType { + DEFAULT, + MORE_LIKE_THIS, + MORE_LIKE_THIS_WITH_PLAY, + EPISODE, + TRAILER, + PLAYER_PREVIEW, + TOP10, + CONTINUE_WATCHING +} + +/** + * Main Movie Card Composable from Figma Node 202:17654. + * Optimized for Google TV / Android TV D-Pad traversal and Mobile touch. + */ +@Composable +fun CloneflixMovieCard( + title: String, + modifier: Modifier = Modifier, + type: CloneflixMovieCardType = CloneflixMovieCardType.DEFAULT, + size: CloneflixMovieCardSize = CloneflixMovieCardSize.MEDIUM, + badge: CloneflixBadgeType? = null, + showLogo: Boolean = false, + top10Rank: Int? = null, + progress: Float? = null, + runtime: String? = null, + timestamp: String? = null, + subtitle: String? = null, + onClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, + label = "cardScale" + ) + + val cardWidth: Dp = CloneflixMovieCardDefaults.cardWidth(type, size) + val cardHeight: Dp = CloneflixMovieCardDefaults.cardHeight(type, size) + + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, PrimaryWhite) + } else { + BorderStroke(dimens.borderSubtle, colors.border) + } + + Column( + modifier = modifier + .width(cardWidth) + .scale(scale) + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier.focusable(interactionSource = interactionSource) + } + ) + .semantics { contentDescription = title } + ) { + if (type == CloneflixMovieCardType.TOP10 && top10Rank != null) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(cardHeight), + verticalAlignment = Alignment.Bottom + ) { + // Large Stylized Rank Number (1..10) + Box( + modifier = Modifier + .width(CloneflixMovieCardDefaults.RankBoxWidth) + .fillMaxHeight(), + contentAlignment = Alignment.BottomCenter + ) { + Text( + text = top10Rank.toString(), + fontSize = CloneflixMovieCardDefaults.RankFontSize, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + color = Grey800, + lineHeight = CloneflixMovieCardDefaults.RankLineHeight, + modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankShadowOffsetX) + ) + Text( + text = top10Rank.toString(), + fontSize = CloneflixMovieCardDefaults.RankFontSize, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + color = PrimaryBlack, + lineHeight = CloneflixMovieCardDefaults.RankLineHeight, + modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankForegroundOffsetX) + ) + } + + // Poster Card Thumbnail + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(Grey800) + .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) + ) { + MovieCardSurface( + title = title, + showLogo = showLogo, + badge = badge, + runtime = runtime, + timestamp = timestamp, + progress = progress, + showCenterPlay = false + ) + } + } + } else { + // Standard Card Surface + Box( + modifier = Modifier + .fillMaxWidth() + .height(if (type == CloneflixMovieCardType.TRAILER) CloneflixMovieCardDefaults.HeightTrailerThumbnail else cardHeight) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(Grey800) + .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) + ) { + MovieCardSurface( + title = title, + showLogo = showLogo, + badge = badge, + runtime = runtime, + timestamp = timestamp, + progress = progress, + showCenterPlay = type == CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY || + type == CloneflixMovieCardType.EPISODE + ) + } + + // Trailer Title/Subtitle below thumbnail + if (type == CloneflixMovieCardType.TRAILER && !subtitle.isNullOrBlank()) { + Spacer(modifier = Modifier.height(dimens.spacingS)) + Text( + text = subtitle, + style = typography.mediumSmallBody, + color = if (isFocused) PrimaryWhite else Grey100, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + } +} + +@Composable +private fun MovieCardSurface( + title: String, + showLogo: Boolean, + badge: CloneflixBadgeType?, + runtime: String?, + timestamp: String?, + progress: Float?, + showCenterPlay: Boolean +) { + val typography = CloneflixTheme.typography + + // Gradient Background Mockup + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf( + Grey700, + Grey850, + PrimaryBlack + ) + ) + ) + ) { + // Title Text in Mockup Center + Text( + text = title, + style = typography.regularCaption1, + fontWeight = FontWeight.SemiBold, + color = Grey50, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .align(Alignment.Center) + .padding(horizontal = 8.dp) + ) + + // Top-Left Logo / Lettermark + if (showLogo) { + Box( + modifier = Modifier + .align(Alignment.TopStart) + .padding(6.dp) + ) { + CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_SMALL) + } + } + + // Top-Right Badge (e.g. Recently Added, New Season) + if (badge != null) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(6.dp) + ) { + CloneflixContentBadge(type = badge) + } + } + + // Center Play Circle Button Overlay + if (showCenterPlay) { + Box( + modifier = Modifier + .size(CloneflixMovieCardDefaults.PlayButtonOverlaySize) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(CloneflixTheme.dimens.borderDefault, PrimaryWhite), CircleShape) + .align(Alignment.Center), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = stringResource(R.string.cloneflix_cd_play), + tint = PrimaryWhite, + modifier = Modifier.size(CloneflixMovieCardDefaults.PlayIconOverlaySize) + ) + } + } + + // Bottom-Right Runtime Tag (e.g. "2h 18m") + if (!runtime.isNullOrBlank()) { + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(CloneflixTheme.dimens.spacingS) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(TransparentBlack90) + .padding(horizontal = CloneflixTheme.dimens.spacingXs, vertical = CloneflixTheme.dimens.spacingXxs) + ) { + Text( + text = runtime, + style = typography.regularCaption2, + fontWeight = FontWeight.Medium, + color = PrimaryWhite + ) + } + } + + // Bottom-Right Timestamp Tag for Player Preview (e.g. "51:29") + if (!timestamp.isNullOrBlank()) { + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(CloneflixTheme.dimens.spacingS) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(TransparentBlack90) + .padding(horizontal = CloneflixTheme.dimens.spacingXs, vertical = CloneflixTheme.dimens.spacingXxs) + ) { + Text( + text = timestamp, + style = typography.regularCaption2, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + } + } + + // Bottom Progress Indicator (Continue Watching) + if (progress != null) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(CloneflixTheme.dimens.progressHeight) + .align(Alignment.BottomCenter) + .background(Grey500) + ) { + Box( + modifier = Modifier + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(PrimaryRed) + ) + } + } + } +} + +/** + * List of Episodes Item Pattern from Figma Section 120:3230. + * Supports Current (active/highlighted) vs Other episode states with D-pad navigation. + */ +@Composable +fun CloneflixEpisodeItem( + episodeNumber: Int, + title: String, + duration: String, + synopsis: String, + modifier: Modifier = Modifier, + isCurrent: Boolean = false, + onClick: (() -> Unit)? = null +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val bgColor = when { + isFocused -> Grey700 + isCurrent -> Grey800 + else -> colors.surface + } + + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, PrimaryWhite) + } else if (isCurrent) { + BorderStroke(dimens.borderDefault, PrimaryWhite.copy(alpha = CloneflixTokens.ALPHA_SUBTLE)) + } else { + BorderStroke(dimens.borderSubtle, colors.border) + } + + Row( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixDrawerDefaults.EpisodeItemCornerRadius)) + .background(bgColor) + .border(border, RoundedCornerShape(CloneflixDrawerDefaults.EpisodeItemCornerRadius)) + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier.focusable(interactionSource = interactionSource) + } + ) + .padding(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + // Episode Number (e.g. "1", "2") + Text( + text = episodeNumber.toString(), + style = typography.boldTitle2, + color = if (isFocused || isCurrent) PrimaryWhite else Grey200, + modifier = Modifier.width(CloneflixDrawerDefaults.EpisodeNumberWidth) + ) + + Spacer(modifier = Modifier.width(dimens.spacingM)) + + // Episode Thumbnail with Play Button Overlay (128x72dp) + Box( + modifier = Modifier + .width(CloneflixMovieCardDefaults.EpisodeThumbnailWidth) + .height(CloneflixMovieCardDefaults.EpisodeThumbnailHeight) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(Grey850) + .border(BorderStroke(dimens.borderSubtle, colors.border), RoundedCornerShape(CloneflixTokens.RadiusCard)), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.cloneflix_label_episode_prefix, episodeNumber), + style = typography.regularCaption2, + color = Grey50 + ) + + // Play circle button overlay + Box( + modifier = Modifier + .size(CloneflixMovieCardDefaults.EpisodeItemPlayButtonSize) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(dimens.borderDefault, PrimaryWhite), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = stringResource(R.string.cloneflix_cd_play_episode, episodeNumber), + tint = PrimaryWhite, + modifier = Modifier.size(CloneflixMovieCardDefaults.EpisodeItemPlayIconSize) + ) + } + } + + Spacer(modifier = Modifier.width(dimens.spacingL)) + + // Episode Title, Duration & Description + Column(modifier = Modifier.weight(1f)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + style = typography.mediumHeadline2, + color = if (isFocused || isCurrent) PrimaryWhite else colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.width(dimens.spacingS)) + + Text( + text = duration, + style = typography.regularCaption1, + color = Grey100 + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + Text( + text = synopsis, + style = typography.regularSmallBody, + color = if (isFocused) PrimaryWhite else Grey200, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +/** + * Movie Block Carousel / Row Pattern (Figma Section 202:19015). + */ +@Composable +fun CloneflixMovieBlockRow( + title: String, + items: List, + modifier: Modifier = Modifier, + onItemClick: ((CloneflixMovieCardItem) -> Unit)? = null +) { + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + + Column(modifier = modifier.fillMaxWidth()) { + Text( + text = title, + style = typography.mediumTitle3, + fontSize = 18.sp, + color = colors.textPrimary, + modifier = Modifier.padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingS) + ) + + LazyRow( + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(horizontal = dimens.spacing2Xl, vertical = dimens.spacingS), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + itemsIndexed(items) { index, item -> + CloneflixMovieCard( + title = item.title, + type = item.type, + size = item.size, + badge = item.badge, + showLogo = item.showLogo, + top10Rank = item.top10Rank, + progress = item.progress, + runtime = item.runtime, + timestamp = item.timestamp, + subtitle = item.subtitle, + onClick = { onItemClick?.invoke(item) } + ) + } + } + } +} + +data class CloneflixMovieCardItem( + val title: String, + val type: CloneflixMovieCardType = CloneflixMovieCardType.DEFAULT, + val size: CloneflixMovieCardSize = CloneflixMovieCardSize.MEDIUM, + val badge: CloneflixBadgeType? = null, + val showLogo: Boolean = false, + val top10Rank: Int? = null, + val progress: Float? = null, + val runtime: String? = null, + val timestamp: String? = null, + val subtitle: String? = null +) + +/** + * Expanded Movie Preview Banner / Card (Figma Section 109:9726). + */ +@Composable +fun CloneflixExpandedMoviePreview( + title: String = CloneflixSampleData.SAMPLE_TITLE_HOUSE_OF_NINJAS, + matchScore: String = CloneflixSampleData.SAMPLE_MATCH_NEW, + maturityRating: String = CloneflixSampleData.SAMPLE_MATURITY_RATING, + duration: String = CloneflixSampleData.SAMPLE_DURATION_SEASONS, + quality: String = CloneflixSampleData.SAMPLE_QUALITY_HD, + genres: List = CloneflixSampleData.SAMPLE_GENRES, + synopsis: String = CloneflixSampleData.SAMPLE_SYNOPSIS, + onPlayClick: () -> Unit = {}, + onAddClick: () -> Unit = {}, + onThumbUpClick: () -> Unit = {}, + onMuteClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + Box( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(Grey850) + .border(BorderStroke(dimens.borderDefault, colors.border), RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + // Backdrop Header with Action Buttons + Box( + modifier = Modifier + .fillMaxWidth() + .height(CloneflixMovieCardDefaults.ExpandedPreviewHeaderHeight) + .background( + Brush.verticalGradient( + colors = listOf( + Grey700, + Grey850, + PrimaryBlack + ) + ) + ) + ) { + // Backdrop Title & Logo + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(dimens.spacing2Xl) + ) { + CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = title, + style = typography.boldTitle1, + fontSize = 28.sp, + color = PrimaryWhite + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + // Circular Action Buttons (Play, Add, ThumbUp, Mute) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + // Play Button + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = stringResource(R.string.cloneflix_cd_play), + isPrimary = true, + onClick = onPlayClick + ) + + // Add to List Button (+) + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_plus), + contentDescription = stringResource(R.string.cloneflix_cd_add_to_list), + onClick = onAddClick + ) + + // Thumb Up Button + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), + contentDescription = stringResource(R.string.cloneflix_cd_like), + onClick = onThumbUpClick + ) + + // Mute / Volume Button + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_mute), + contentDescription = stringResource(R.string.cloneflix_cd_mute), + onClick = onMuteClick + ) + } + } + } + + // Info Body + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacing2Xl) + ) { + // Metadata Row (Match, Rating, Duration, Quality) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = matchScore, + color = GreenAccent, + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + + CloneflixMaturityRating(rating = maturityRating) + + Text( + text = duration, + color = PrimaryWhite, + style = typography.regularBody + ) + + CloneflixVideoQualityBadge(quality = quality) + } + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + // Genres separated by dots + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + genres.forEachIndexed { index, genre -> + Text( + text = genre, + style = typography.regularCaption1, + color = PrimaryWhite + ) + if (index < genres.size - 1) { + Box( + modifier = Modifier + .size(dimens.spacingXs) + .clip(CircleShape) + .background(Grey200) + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Text( + text = synopsis, + style = typography.regularBody, + color = Grey100, + lineHeight = 22.sp + ) + } + } + } +} + +/** + * Circular action button for media preview overlays. + */ +@Composable +fun CloneflixCircleActionButton( + icon: Painter, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + isPrimary: Boolean = false +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = CloneflixTheme.dimens + + val bgColor = when { + isPrimary -> PrimaryWhite + isFocused -> PrimaryWhite.copy(alpha = CloneflixTokens.ALPHA_MUTED) + else -> TransparentBlack60 + } + + val iconTint = if (isPrimary) PrimaryBlack else PrimaryWhite + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, PrimaryWhite) + } else { + BorderStroke(dimens.borderDefault, PrimaryWhite.copy(alpha = CloneflixTokens.ALPHA_HIGH)) + } + + Box( + modifier = modifier + .size(CloneflixMovieCardDefaults.ActionButtonSize) + .clip(CircleShape) + .background(bgColor) + .border(border, CircleShape) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource), + contentAlignment = Alignment.Center + ) { + Icon( + painter = icon, + contentDescription = contentDescription, + tint = iconTint, + modifier = Modifier.size(dimens.iconM) + ) + } +} + +@Preview(name = "Movie Cards Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun MovieCardsPreview() { + CloneflixTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + CloneflixMovieCard( + title = "House of Ninjas", + type = CloneflixMovieCardType.DEFAULT, + showLogo = true, + badge = CloneflixBadgeType.NEW_SEASON + ) + + CloneflixMovieCard( + title = "The Witcher", + type = CloneflixMovieCardType.CONTINUE_WATCHING, + progress = 0.65f, + showLogo = true + ) + } + + CloneflixEpisodeItem( + episodeNumber = 1, + title = "The Offer", + duration = "55m", + synopsis = "While Haru Tawara develops a crush on a mysterious young woman at work...", + isCurrent = true + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt new file mode 100644 index 00000000000..60ed79c8364 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt @@ -0,0 +1,307 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.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.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite + +/** + * Text / MovieInfo Inline Row Pattern from Figma Section 12. + */ +@Composable +fun CloneflixMovieInfoRow( + matchText: String = "New", + maturityRating: String = "TV-MA", + duration: String = "3 Seasons", + quality: String = "HD", + hasAudioDescription: Boolean = false, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text( + text = matchText, + color = GreenAccent, + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + + CloneflixMaturityRating(rating = maturityRating) + + Text( + text = duration, + color = PrimaryWhite, + style = typography.regularBody + ) + + CloneflixVideoQualityBadge(quality = quality) + + if (hasAudioDescription) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_ad), + contentDescription = "Audio Description", + tint = PrimaryWhite, + modifier = Modifier.size(20.dp) + ) + } + } +} + +/** + * MovieInfo Block Pattern from Figma Section 13 (Detailed header, advisories, Top 10 rank). + */ +@Composable +fun CloneflixMovieInfoBlock( + matchScore: String = "98% Match", + releaseYear: String = "2024", + duration: String = "3 Seasons", + maturityRating: String = "TV-MA", + advisories: String = "smoking, violence", + quality: String = "HD", + top10RankText: String? = "#2 in TV Shows Today", + hasAudioDescription: Boolean = true, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + // Line 1: Match Score, Year, Duration, Quality, AD Icon + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = matchScore, + color = GreenAccent, + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + + Text( + text = releaseYear, + color = PrimaryWhite, + style = typography.regularBody + ) + + Text( + text = duration, + color = PrimaryWhite, + style = typography.regularBody + ) + + CloneflixVideoQualityBadge(quality = quality) + + if (hasAudioDescription) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_ad), + contentDescription = "Audio Description Available", + tint = PrimaryWhite, + modifier = Modifier.size(20.dp) + ) + } + } + + // Line 2: Rating badge with advisory keywords + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + CloneflixMaturityRating(rating = maturityRating) + + if (advisories.isNotBlank()) { + Text( + text = advisories, + color = Grey200, + style = typography.regularSmallBody + ) + } + } + + // Line 3: Top 10 rank banner if present + if (!top10RankText.isNullOrBlank()) { + CloneflixTop10RankBanner( + rankText = top10RankText, + size = CloneflixTop10Size.MEDIUM + ) + } + } +} + +/** + * Complete MovieInfo Overview Card from Figma Section 14 (Synopsis, Block & Metadata columns). + */ +@Composable +fun CloneflixMovieInfoOverview( + title: String = "House of Ninjas", + synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + cast: String = "Kento Kaku, Yosuke Eguchi, Tae Kimura, more", + genres: String = "TV Dramas, Japanese, TV Thrillers", + moodTags: String = "Dark, Suspenseful, Exciting", + matchScore: String = "New", + releaseYear: String = "2024", + duration: String = "3 Seasons", + maturityRating: String = "TV-MA", + advisories: String = "smoking, violence", + quality: String = "HD", + top10RankText: String? = "#2 in TV Shows Today", + onClick: (() -> Unit)? = null, + modifier: Modifier = Modifier +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val activeBorder = if (isFocused) { + BorderStroke(2.dp, PrimaryWhite) + } else { + BorderStroke(1.dp, colors.border) + } + + Box( + modifier = modifier + .fillMaxWidth() + .clip(CloneflixTheme.shapes.large) + .background(colors.surface) + .border(activeBorder, CloneflixTheme.shapes.large) + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier.focusable(interactionSource = interactionSource) + } + ) + .padding(dimens.spacing2Xl) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + CloneflixMovieInfoBlock( + matchScore = matchScore, + releaseYear = releaseYear, + duration = duration, + maturityRating = maturityRating, + advisories = advisories, + quality = quality, + top10RankText = top10RankText, + hasAudioDescription = true + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = synopsis, + style = typography.regularBody, + color = colors.textPrimary, + lineHeight = 22.sp + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Metadata columns: Cast, Genres, Mood tags + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(color = Grey100, fontWeight = FontWeight.Normal)) { + append("Cast: ") + } + withStyle(SpanStyle(color = PrimaryWhite, fontWeight = FontWeight.Medium)) { + append(cast) + } + }, + style = typography.regularSmallBody + ) + + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(color = Grey100, fontWeight = FontWeight.Normal)) { + append("Genres: ") + } + withStyle(SpanStyle(color = PrimaryWhite, fontWeight = FontWeight.Medium)) { + append(genres) + } + }, + style = typography.regularSmallBody + ) + + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(color = Grey100, fontWeight = FontWeight.Normal)) { + append("This show is: ") + } + withStyle(SpanStyle(color = PrimaryWhite, fontWeight = FontWeight.Medium)) { + append(moodTags) + } + }, + style = typography.regularSmallBody + ) + } + } + } +} + +@Preview(name = "Movie Info Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun MovieInfoComponentsPreview() { + CloneflixTheme { + Column(modifier = Modifier.padding(16.dp)) { + CloneflixMovieInfoRow() + Spacer(modifier = Modifier.height(24.dp)) + CloneflixMovieInfoOverview() + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt new file mode 100644 index 00000000000..7702d93a8ce --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt @@ -0,0 +1,296 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.draw.scale +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +@Composable +fun CloneflixNextEpisodeCard( + episodeNumber: String, + episodeTitle: String, + durationText: String, + synopsis: String, + onPlayNextClick: () -> Unit, + modifier: Modifier = Modifier, + countdownSeconds: Int = 10, + totalCountdownSeconds: Int = 10, + onCancelClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.03f else 1f, + label = "nextEpisodeScale" + ) + + val borderStroke = if (isFocused) { + BorderStroke(2.dp, PrimaryWhite) + } else { + BorderStroke(1.dp, Color(0x33FFFFFF)) + } + + val countdownProgress = (countdownSeconds.toFloat() / totalCountdownSeconds.toFloat()).coerceIn(0f, 1f) + + Surface( + modifier = modifier + .width(380.dp) + .scale(scale) + .border(borderStroke, RoundedCornerShape(12.dp)), + shape = RoundedCornerShape(12.dp), + color = TransparentBlack90 + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + // Header row with countdown and close button + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Box(contentAlignment = Alignment.Center) { + CircularProgressIndicator( + progress = { countdownProgress }, + modifier = Modifier.size(26.dp), + color = PrimaryRed, + trackColor = Color(0x33FFFFFF), + strokeWidth = 2.5.dp + ) + Text( + text = "$countdownSeconds", + style = CloneflixTheme.typography.regularCaption2, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + } + + Text( + text = "Next Episode in ${countdownSeconds}s", + style = CloneflixTheme.typography.mediumHeadline2, + fontSize = 16.sp, + color = PrimaryWhite + ) + } + + if (onCancelClick != null) { + Box( + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + .clickable(onClick = onCancelClick), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_close), + contentDescription = "Dismiss", + tint = Grey200, + modifier = Modifier.size(16.dp) + ) + } + } + } + + // Thumbnail preview + Episode Info row + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.Top + ) { + // Thumbnail Box + Box( + modifier = Modifier + .width(130.dp) + .height(78.dp) + .clip(RoundedCornerShape(8.dp)) + .background( + Brush.linearGradient( + colors = listOf(Color(0xFF2C2C2C), Color(0xFF161616)) + ) + ), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(Color(0x99000000)) + .border(1.dp, Color(0x66FFFFFF), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = "Play", + tint = PrimaryWhite, + modifier = Modifier.size(16.dp) + ) + } + + // Duration pill + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(4.dp) + .background(Color(0xB2000000), RoundedCornerShape(4.dp)) + .padding(horizontal = 4.dp, vertical = 2.dp) + ) { + Text( + text = durationText, + style = CloneflixTheme.typography.regularCaption2, + fontSize = 10.sp, + color = PrimaryWhite + ) + } + } + + // Info Column + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = episodeNumber, + style = CloneflixTheme.typography.mediumCaption1, + color = PrimaryRed + ) + Text( + text = episodeTitle, + style = CloneflixTheme.typography.mediumBody, + color = PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = synopsis, + style = CloneflixTheme.typography.regularCaption1, + color = Grey200, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + + // Action button "Play Next" + val playNextInteraction = remember { MutableInteractionSource() } + val isPlayNextFocused by playNextInteraction.collectIsFocusedAsState() + + Row( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .clip(RoundedCornerShape(8.dp)) + .background(if (isPlayNextFocused) Color(0xFFD00812) else PrimaryRed) + .then( + if (isPlayNextFocused) Modifier.border(2.dp, PrimaryWhite, RoundedCornerShape(8.dp)) + else Modifier + ) + .clickable( + interactionSource = playNextInteraction, + indication = null, + onClick = onPlayNextClick + ) + .focusable(interactionSource = playNextInteraction) + .semantics { + role = Role.Button + contentDescription = "Play Next Episode" + }, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "Play Next Episode", + style = CloneflixTheme.typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + } + } + } +} + +@Preview(name = "Next Episode Card Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun NextEpisodeCardPreview() { + CloneflixTheme { + Box( + modifier = Modifier + .background(Color(0xFF141414)) + .padding(24.dp) + ) { + CloneflixNextEpisodeCard( + episodeNumber = "Episode 2", + episodeTitle = "The Way of Shadows", + durationText = "48m", + synopsis = "A cryptic message from the past sends Haru on a clandestine reconnaissance mission.", + countdownSeconds = 8, + totalCountdownSeconds = 10, + onPlayNextClick = {}, + onCancelClick = {} + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt new file mode 100644 index 00000000000..b8e6e41516f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt @@ -0,0 +1,266 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.draw.scale +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 + +enum class CloneflixPlaybackSpeed(val speedMultiplier: Float, val label: String) { + SPEED_0_5X(0.5f, "0.5x"), + SPEED_0_75X(0.75f, "0.75x"), + SPEED_1_0X(1.0f, "1x (Normal)"), + SPEED_1_25X(1.25f, "1.25x"), + SPEED_1_5X(1.5f, "1.5x") +} + +@Composable +fun CloneflixPlaybackSpeedSelector( + currentSpeed: CloneflixPlaybackSpeed, + onSpeedSelected: (CloneflixPlaybackSpeed) -> Unit, + modifier: Modifier = Modifier, + onCloseClick: (() -> Unit)? = null +) { + val speeds = CloneflixPlaybackSpeed.entries + val currentIndex = speeds.indexOf(currentSpeed).coerceAtLeast(0) + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + Surface( + modifier = modifier + .width(520.dp) + .border( + border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(1.dp, Color(0x33FFFFFF)), + shape = RoundedCornerShape(16.dp) + ) + .onKeyEvent { keyEvent -> + when (keyEvent.key) { + Key.DirectionLeft -> { + if (currentIndex > 0) { + onSpeedSelected(speeds[currentIndex - 1]) + true + } else false + } + Key.DirectionRight -> { + if (currentIndex < speeds.size - 1) { + onSpeedSelected(speeds[currentIndex + 1]) + true + } else false + } + else -> false + } + } + .focusable(interactionSource = interactionSource) + .semantics { + role = Role.Button + contentDescription = "Playback Speed: ${currentSpeed.label}" + }, + shape = RoundedCornerShape(16.dp), + color = TransparentBlack90 + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Top Row with Title & optional Close + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Playback Speed", + style = CloneflixTheme.typography.mediumHeadline2, + fontSize = 18.sp, + color = PrimaryWhite + ) + + if (onCloseClick != null) { + Box( + modifier = Modifier + .size(30.dp) + .clip(CircleShape) + .background(Color(0x33FFFFFF)) + .clickable(onClick = onCloseClick), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_close), + contentDescription = "Close", + tint = PrimaryWhite, + modifier = Modifier.size(14.dp) + ) + } + } + } + + Spacer(modifier = Modifier.height(28.dp)) + + // Speed step track with points + Box( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + contentAlignment = Alignment.Center + ) { + // Connecting line + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .padding(horizontal = 24.dp) + ) { + val y = size.height / 2f + drawLine( + color = Grey450, + start = Offset(0f, y), + end = Offset(size.width, y), + strokeWidth = 3.dp.toPx() + ) + } + + // Steps row + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + speeds.forEachIndexed { index, speed -> + val isSelected = speed == currentSpeed + val scale by animateFloatAsState( + targetValue = if (isSelected) 1.25f else 1f, + label = "speedPointScale" + ) + + Box( + modifier = Modifier + .size(40.dp) + .scale(scale) + .clip(CircleShape) + .clickable { onSpeedSelected(speed) }, + contentAlignment = Alignment.Center + ) { + if (isSelected) { + Box( + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + .background(PrimaryRed.copy(alpha = 0.3f)), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .size(16.dp) + .clip(CircleShape) + .background(PrimaryRed) + ) + } + } else { + Box( + modifier = Modifier + .size(12.dp) + .clip(CircleShape) + .background(Grey200) + ) + } + } + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Speed labels row + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + speeds.forEach { speed -> + val isSelected = speed == currentSpeed + Text( + text = speed.label, + style = if (isSelected) CloneflixTheme.typography.mediumBody else CloneflixTheme.typography.regularSmallBody, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + color = if (isSelected) PrimaryWhite else Grey200, + textAlign = TextAlign.Center, + modifier = Modifier + .clickable { onSpeedSelected(speed) } + .padding(horizontal = 4.dp, vertical = 2.dp) + ) + } + } + } + } +} + +@Preview(name = "Playback Speed Selector Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun PlaybackSpeedSelectorPreview() { + CloneflixTheme { + Box( + modifier = Modifier + .background(Color(0xFF141414)) + .padding(24.dp) + ) { + CloneflixPlaybackSpeedSelector( + currentSpeed = CloneflixPlaybackSpeed.SPEED_1_0X, + onSpeedSelected = {}, + onCloseClick = {} + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt new file mode 100644 index 00000000000..ff4561e8d7d --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt @@ -0,0 +1,55 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +/** + * Centralized mock/sample data for Cloneflix Previews and Showcases. + * Keeps production Composable parameter signatures clean from hardcoded sample strings. + */ +object CloneflixSampleData { + const val SAMPLE_TITLE_HOUSE_OF_NINJAS = "House of Ninjas" + const val SAMPLE_MATCH_SCORE = "98% Match" + const val SAMPLE_MATCH_NEW = "New" + const val SAMPLE_MATURITY_RATING = "TV-MA" + const val SAMPLE_DURATION_SEASONS = "3 Seasons" + const val SAMPLE_QUALITY_HD = "HD" + const val SAMPLE_QUALITY_4K = "4K Ultra HD" + + val SAMPLE_GENRES = listOf("Violent", "Dark", "Action") + const val SAMPLE_SYNOPSIS = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats." + + val SAMPLE_MOVIE_ITEMS = listOf( + CloneflixMovieCardItem( + title = "House of Ninjas", + badge = CloneflixBadgeType.RECENTLY_ADDED, + showLogo = true, + progress = 0.65f, + runtime = "2h 18m" + ), + CloneflixMovieCardItem( + title = "Stranger Things", + badge = CloneflixBadgeType.TOP_10, + showLogo = true, + top10Rank = 1, + runtime = "55m" + ), + CloneflixMovieCardItem( + title = "The Witcher", + badge = CloneflixBadgeType.NEW_SEASON, + showLogo = true, + progress = 0.20f, + runtime = "1h 02m" + ), + CloneflixMovieCardItem( + title = "Cyberpunk: Edgerunners", + showLogo = true, + progress = 0.85f, + runtime = "24m" + ), + CloneflixMovieCardItem( + title = "Squid Game", + badge = CloneflixBadgeType.TOP_10, + showLogo = true, + top10Rank = 2, + runtime = "1h 10m" + ) + ) +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt new file mode 100644 index 00000000000..7e42c07ed8c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt @@ -0,0 +1,582 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.aspectRatio +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.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +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.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +enum class CloneflixPlayerOverlayState { + DEFAULT, + SOUND_CHANGE, + SCROLL_PREVIEW, + NEXT_PREVIEW, + LIST_PREVIEW, + SPEED_CHANGE, + SUBTITLES_CHANGE +} + +@Composable +fun CloneflixVideoPlayerPattern( + title: String = "Sanctuary", + episodeLabel: String = "E1 Episode 1", + overlayState: CloneflixPlayerOverlayState = CloneflixPlayerOverlayState.DEFAULT, + onOverlayStateChange: ((CloneflixPlayerOverlayState) -> Unit)? = null, + onBackClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + var isPlaying by remember { mutableStateOf(true) } + var currentProgress by remember { mutableFloatStateOf(0.35f) } + var volume by remember { mutableFloatStateOf(0.75f) } + var isMuted by remember { mutableStateOf(false) } + var selectedSpeed by remember { mutableStateOf(CloneflixPlaybackSpeed.SPEED_1_0X) } + var selectedAudio by remember { mutableStateOf("Japanese [Original]") } + var selectedSubtitle by remember { mutableStateOf("English (CC)") } + var countdownSec by remember { mutableIntStateOf(9) } + + val sampleEpisodes = remember { + listOf( + CloneflixEpisodeItemData(1, "Sanctuary", "54m", "A troubled teenager with sumo ancestry enters the sacred world of sumo wrestling.", 1.0f, false), + CloneflixEpisodeItemData(2, "The Rebel", "48m", "Kiyoshi refuses to follow tradition and sparks outrage among seasoned stable masters.", 0.35f, true), + CloneflixEpisodeItemData(3, "The Crucible", "51m", "An intensive training camp tests the physical limits of every rookie wrestler.", 0f, false), + CloneflixEpisodeItemData(4, "Blood on the Clay", "50m", "A tense tournament match pushes Kiyoshi to the brink against a fierce veteran.", 0f, false), + CloneflixEpisodeItemData(5, "Honor Bound", "56m", "Family secrets emerge that threaten the future of the entire Ensho stable.", 0f, false) + ) + } + + val audioTracks = remember { + listOf("Japanese [Original]", "English", "Spanish (Latin America)", "French", "German") + } + + val subtitleTracks = remember { + listOf("Off", "English (CC)", "Japanese (CC)", "Spanish", "Simplified Chinese", "Traditional Chinese", "Arabic", "German", "French") + } + + Box( + modifier = modifier + .fillMaxWidth() + .aspectRatio(16f / 9f) + .clip(RoundedCornerShape(12.dp)) + .background(Color(0xFF0F0F0F)) + ) { + // 1. Mock Video Content / Cinematic Gradient Backdrop + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf(Color(0xFF2A1115), Color(0xFF0D0D0E)), + radius = 900f + ) + ) + ) { + // Subtle cinematic film grain / center title watermark + Text( + text = title.uppercase(), + style = CloneflixTheme.typography.logoBebas, + fontSize = 48.sp, + color = Color(0x15FFFFFF), + modifier = Modifier.align(Alignment.Center) + ) + } + + // 2. Scrim Gradients (Top and Bottom) + Box( + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .align(Alignment.TopCenter) + .background( + Brush.verticalGradient( + colors = listOf(Color(0xD9000000), Color.Transparent) + ) + ) + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .height(140.dp) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, Color(0xE6000000)) + ) + ) + ) + + // 3. TOP BAR + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopCenter) + .padding(horizontal = 20.dp, vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(14.dp) + ) { + // Back Button + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(TransparentWhite20) + .clickable(onClick = onBackClick), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_arrow_left), + contentDescription = "Back", + tint = PrimaryWhite, + modifier = Modifier.size(20.dp) + ) + } + + // Title + Episode Header + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = title, + style = CloneflixTheme.typography.mediumHeadline2, + fontSize = 18.sp, + color = PrimaryWhite + ) + Text( + text = "•", + style = CloneflixTheme.typography.mediumBody, + color = Grey200 + ) + Text( + text = episodeLabel, + style = CloneflixTheme.typography.regularBody, + color = Grey50 + ) + } + } + + // Flag / Options Button + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(Color(0x33FFFFFF)) + .clickable {}, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_flag), + contentDescription = "Report / Flag", + tint = PrimaryWhite, + modifier = Modifier.size(18.dp) + ) + } + } + + // 4. CENTER PLAY/PAUSE/REPLAY/FORWARD CONTROLS + Row( + modifier = Modifier.align(Alignment.Center), + horizontalArrangement = Arrangement.spacedBy(28.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Replay 10s + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.REPLAY_10, + onClick = { currentProgress = (currentProgress - 0.05f).coerceAtLeast(0f) }, + size = 48.dp, + iconSize = 24.dp + ) + + // Play / Pause main button + Box( + modifier = Modifier + .size(64.dp) + .clip(CircleShape) + .background(PrimaryWhite) + .clickable { isPlaying = !isPlaying }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = if (isPlaying) "Pause" else "Play", + tint = PrimaryBlack, + modifier = Modifier.size(28.dp) + ) + } + + // Forward 10s + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.FORWARD_10, + onClick = { currentProgress = (currentProgress + 0.05f).coerceAtMost(1f) }, + size = 48.dp, + iconSize = 24.dp + ) + } + + // 5. SEEK / SCROLL PREVIEW TOOLTIP OVERLAY + if (overlayState == CloneflixPlayerOverlayState.SCROLL_PREVIEW) { + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(start = 140.dp, bottom = 80.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Surface( + shape = RoundedCornerShape(8.dp), + color = Color(0xEB1A1A1A), + border = BorderStroke(1.dp, Color(0x40FFFFFF)) + ) { + Column( + modifier = Modifier.padding(6.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .size(width = 130.dp, height = 75.dp) + .clip(RoundedCornerShape(4.dp)) + .background( + Brush.linearGradient( + colors = listOf(Color(0xFF442222), Color(0xFF1E1E1E)) + ) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "Preview Frame", + style = CloneflixTheme.typography.regularCaption2, + color = Grey200 + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "00:32:15", + style = CloneflixTheme.typography.mediumCaption1, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + } + } + } + } + + // 6. VOLUME SLIDER POPOVER OVERLAY + if (overlayState == CloneflixPlayerOverlayState.SOUND_CHANGE) { + Box( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(start = 110.dp, bottom = 68.dp) + ) { + CloneflixVolumeControlPopup( + volume = volume, + onVolumeChange = { volume = it }, + isMuted = isMuted, + onToggleMute = { isMuted = !isMuted } + ) + } + } + + // 7. BOTTOM CONTROLS & PROGRESS BAR + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(horizontal = 20.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + // Progress Bar + CloneflixVideoProgressIndicator( + progress = currentProgress, + bufferedProgress = 0.65f, + durationMs = 54 * 60 * 1000L, + currentPositionMs = (currentProgress * 54 * 60 * 1000L).toLong(), + onProgressChange = { currentProgress = it }, + size = CloneflixProgressSize.LARGE, + showTimestamps = false + ) + + // Bottom Buttons Bar + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + // Left group: Play/Pause, 10s Replay, 10s Forward, Volume + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.PLAY, + onClick = { isPlaying = !isPlaying }, + size = 38.dp, + iconSize = 20.dp + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.REPLAY_10, + onClick = { currentProgress = (currentProgress - 0.05f).coerceAtLeast(0f) }, + size = 38.dp, + iconSize = 20.dp + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.FORWARD_10, + onClick = { currentProgress = (currentProgress + 0.05f).coerceAtMost(1f) }, + size = 38.dp, + iconSize = 20.dp + ) + CloneflixPlayerControlButton( + action = if (isMuted) CloneflixPlayerControlAction.VOLUME_OFF else CloneflixPlayerControlAction.VOLUME_UP, + onClick = { + onOverlayStateChange?.invoke( + if (overlayState == CloneflixPlayerOverlayState.SOUND_CHANGE) CloneflixPlayerOverlayState.DEFAULT + else CloneflixPlayerOverlayState.SOUND_CHANGE + ) + }, + isSelected = overlayState == CloneflixPlayerOverlayState.SOUND_CHANGE, + size = 38.dp, + iconSize = 20.dp + ) + } + + // Middle Info Label + Text( + text = "$title • $episodeLabel", + style = CloneflixTheme.typography.mediumBody, + fontSize = 13.sp, + color = Grey200, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + // Right group: Next Episode, Episodes, Subtitles, Speed, Fullscreen + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.NEXT_EPISODE, + onClick = { + onOverlayStateChange?.invoke( + if (overlayState == CloneflixPlayerOverlayState.NEXT_PREVIEW) CloneflixPlayerOverlayState.DEFAULT + else CloneflixPlayerOverlayState.NEXT_PREVIEW + ) + }, + isSelected = overlayState == CloneflixPlayerOverlayState.NEXT_PREVIEW, + size = 38.dp, + iconSize = 20.dp + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.EPISODES, + onClick = { + onOverlayStateChange?.invoke( + if (overlayState == CloneflixPlayerOverlayState.LIST_PREVIEW) CloneflixPlayerOverlayState.DEFAULT + else CloneflixPlayerOverlayState.LIST_PREVIEW + ) + }, + isSelected = overlayState == CloneflixPlayerOverlayState.LIST_PREVIEW, + size = 38.dp, + iconSize = 20.dp + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.SUBTITLES, + onClick = { + onOverlayStateChange?.invoke( + if (overlayState == CloneflixPlayerOverlayState.SUBTITLES_CHANGE) CloneflixPlayerOverlayState.DEFAULT + else CloneflixPlayerOverlayState.SUBTITLES_CHANGE + ) + }, + isSelected = overlayState == CloneflixPlayerOverlayState.SUBTITLES_CHANGE, + size = 38.dp, + iconSize = 20.dp + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.SPEED, + onClick = { + onOverlayStateChange?.invoke( + if (overlayState == CloneflixPlayerOverlayState.SPEED_CHANGE) CloneflixPlayerOverlayState.DEFAULT + else CloneflixPlayerOverlayState.SPEED_CHANGE + ) + }, + isSelected = overlayState == CloneflixPlayerOverlayState.SPEED_CHANGE, + size = 38.dp, + iconSize = 20.dp + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.FULLSCREEN, + onClick = {}, + size = 38.dp, + iconSize = 20.dp + ) + } + } + } + + // 8. NEXT EPISODE FLOATING PROMPT OVERLAY + if (overlayState == CloneflixPlayerOverlayState.NEXT_PREVIEW) { + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(end = 24.dp, bottom = 80.dp) + ) { + CloneflixNextEpisodeCard( + episodeNumber = "Episode 2", + episodeTitle = "The Way of Shadows", + durationText = "48m", + synopsis = "A cryptic message from the past sends Haru on a clandestine reconnaissance mission.", + countdownSeconds = countdownSec, + totalCountdownSeconds = 10, + onPlayNextClick = { + onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) + }, + onCancelClick = { + onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) + } + ) + } + } + + // 9. EPISODES DRAWER OVERLAY + if (overlayState == CloneflixPlayerOverlayState.LIST_PREVIEW) { + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + ) { + CloneflixEpisodesDrawer( + seriesTitle = title.uppercase(), + seasonName = "Season 1 Episodes", + episodes = sampleEpisodes, + onEpisodeSelected = { + onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) + }, + onCloseClick = { + onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) + } + ) + } + } + + // 10. PLAYBACK SPEED SELECTOR OVERLAY + if (overlayState == CloneflixPlayerOverlayState.SPEED_CHANGE) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0x80000000)) + .clickable { onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) }, + contentAlignment = Alignment.Center + ) { + CloneflixPlaybackSpeedSelector( + currentSpeed = selectedSpeed, + onSpeedSelected = { + selectedSpeed = it + onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) + }, + onCloseClick = { + onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) + } + ) + } + } + + // 11. AUDIO & SUBTITLES MODAL DIALOG OVERLAY + if (overlayState == CloneflixPlayerOverlayState.SUBTITLES_CHANGE) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0x80000000)) + .clickable { onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) }, + contentAlignment = Alignment.Center + ) { + CloneflixAudioSubtitlesSelector( + audioTracks = audioTracks, + selectedAudio = selectedAudio, + onAudioSelected = { selectedAudio = it }, + subtitleTracks = subtitleTracks, + selectedSubtitle = selectedSubtitle, + onSubtitleSelected = { selectedSubtitle = it }, + onCloseClick = { + onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) + } + ) + } + } + } +} + +@Preview(name = "Video Player Pattern Preview - Default", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun VideoPlayerPatternDefaultPreview() { + CloneflixTheme { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF141414)) + .padding(24.dp) + ) { + CloneflixVideoPlayerPattern( + overlayState = CloneflixPlayerOverlayState.DEFAULT + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt new file mode 100644 index 00000000000..dc541cc41a9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt @@ -0,0 +1,322 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.interaction.collectIsHoveredAsState +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +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.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.semantics.ProgressBarRangeInfo +import androidx.compose.ui.semantics.progressBarRangeInfo +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite30 + +enum class CloneflixProgressSize { + REGULAR, + LARGE +} + +@Composable +fun CloneflixVideoProgressIndicator( + progress: Float, + onProgressChange: (Float) -> Unit, + modifier: Modifier = Modifier, + bufferedProgress: Float = 0f, + durationMs: Long = 0L, + currentPositionMs: Long = 0L, + size: CloneflixProgressSize = CloneflixProgressSize.LARGE, + showTimestamps: Boolean = true, + showRemainingTime: Boolean = false, + enabled: Boolean = true, + isHoveredOrActive: Boolean? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val isHoveredInternal by interactionSource.collectIsHoveredAsState() + val isHovered = isHoveredOrActive ?: (isFocused || isHoveredInternal) + + val trackHeight by animateDpAsState( + targetValue = when { + isHovered && size == CloneflixProgressSize.LARGE -> 8.dp + isHovered -> 6.dp + size == CloneflixProgressSize.LARGE -> 5.dp + else -> 3.5.dp + }, + label = "trackHeight" + ) + + val thumbRadius by animateDpAsState( + targetValue = when { + isHovered && size == CloneflixProgressSize.LARGE -> 9.dp + isHovered -> 7.dp + size == CloneflixProgressSize.LARGE -> 7.dp + else -> 5.dp + }, + label = "thumbRadius" + ) + + val haloRadius by animateDpAsState( + targetValue = if (isHovered) (thumbRadius + 8.dp) else 0.dp, + label = "haloRadius" + ) + + var componentWidth by remember { mutableFloatStateOf(1f) } + var isDragging by remember { mutableStateOf(false) } + var dragProgress by remember { mutableFloatStateOf(progress) } + + val activeProgress = if (isDragging) dragProgress else progress.coerceIn(0f, 1f) + val animatedProgress by animateFloatAsState( + targetValue = activeProgress, + label = "animatedProgress" + ) + + val effectiveCurrentMs = if (isDragging && durationMs > 0L) { + (dragProgress * durationMs).toLong() + } else { + currentPositionMs + } + + val elapsedText = remember(effectiveCurrentMs) { + formatTimeMs(effectiveCurrentMs) + } + + val endText = remember(effectiveCurrentMs, durationMs, showRemainingTime) { + if (showRemainingTime && durationMs > effectiveCurrentMs) { + "-${formatTimeMs(durationMs - effectiveCurrentMs)}" + } else { + formatTimeMs(durationMs) + } + } + + Column( + modifier = modifier + .fillMaxWidth() + .semantics { + progressBarRangeInfo = ProgressBarRangeInfo( + current = activeProgress, + range = 0f..1f + ) + } + .onKeyEvent { keyEvent -> + if (!enabled) return@onKeyEvent false + when (keyEvent.key) { + Key.DirectionLeft -> { + val step = if (durationMs > 0L) (10_000f / durationMs).coerceAtLeast(0.01f) else 0.05f + val newProgress = (activeProgress - step).coerceIn(0f, 1f) + onProgressChange(newProgress) + true + } + Key.DirectionRight -> { + val step = if (durationMs > 0L) (10_000f / durationMs).coerceAtLeast(0.01f) else 0.05f + val newProgress = (activeProgress + step).coerceIn(0f, 1f) + onProgressChange(newProgress) + true + } + else -> false + } + } + .focusable(enabled = enabled, interactionSource = interactionSource) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(32.dp) + .onSizeChanged { componentWidth = it.width.toFloat().coerceAtLeast(1f) } + .pointerInput(enabled) { + if (!enabled) return@pointerInput + detectTapGestures { offset -> + val tapProgress = (offset.x / componentWidth).coerceIn(0f, 1f) + onProgressChange(tapProgress) + } + } + .pointerInput(enabled) { + if (!enabled) return@pointerInput + detectHorizontalDragGestures( + onDragStart = { offset -> + isDragging = true + dragProgress = (offset.x / componentWidth).coerceIn(0f, 1f) + }, + onDragEnd = { + isDragging = false + onProgressChange(dragProgress) + }, + onDragCancel = { + isDragging = false + }, + onHorizontalDrag = { _, dragAmount -> + val deltaProgress = dragAmount / componentWidth + dragProgress = (dragProgress + deltaProgress).coerceIn(0f, 1f) + onProgressChange(dragProgress) + } + ) + }, + contentAlignment = Alignment.CenterStart + ) { + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(32.dp) + ) { + val centerY = this.size.height / 2f + val totalWidth = this.size.width + val trackHeightPx = trackHeight.toPx() + val cornerRadius = CornerRadius(trackHeightPx / 2f, trackHeightPx / 2f) + + // 1. Background (Remaining) track + drawRoundRect( + color = Grey450, + topLeft = Offset(0f, centerY - trackHeightPx / 2f), + size = Size(totalWidth, trackHeightPx), + cornerRadius = cornerRadius + ) + + // 2. Buffered progress track + val bufferedWidth = (bufferedProgress.coerceIn(0f, 1f) * totalWidth) + if (bufferedWidth > 0f) { + drawRoundRect( + color = Grey50.copy(alpha = 0.65f), + topLeft = Offset(0f, centerY - trackHeightPx / 2f), + size = Size(bufferedWidth, trackHeightPx), + cornerRadius = cornerRadius + ) + } + + // 3. Played progress track + val playedWidth = (animatedProgress * totalWidth) + if (playedWidth > 0f) { + drawRoundRect( + color = PrimaryRed, + topLeft = Offset(0f, centerY - trackHeightPx / 2f), + size = Size(playedWidth, trackHeightPx), + cornerRadius = cornerRadius + ) + } + + // 4. Halo ring on hover/focus + if (haloRadius.value > 0f) { + drawCircle( + color = PrimaryRed.copy(alpha = 0.25f), + radius = haloRadius.toPx(), + center = Offset(playedWidth, centerY) + ) + } + + // 5. Scrubber thumb handle + drawCircle( + color = PrimaryRed, + radius = thumbRadius.toPx(), + center = Offset(playedWidth, centerY) + ) + + // 6. Center dot highlight in thumb + if (isHovered) { + drawCircle( + color = PrimaryWhite, + radius = (thumbRadius * 0.4f).toPx(), + center = Offset(playedWidth, centerY) + ) + } + } + } + + if (showTimestamps) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 2.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = elapsedText, + style = CloneflixTheme.typography.regularSmallBody, + color = PrimaryWhite + ) + Text( + text = endText, + style = CloneflixTheme.typography.regularSmallBody, + color = Grey200 + ) + } + } + } +} + +private fun formatTimeMs(ms: Long): String { + val totalSeconds = (ms / 1000).coerceAtLeast(0) + val hours = totalSeconds / 3600 + val minutes = (totalSeconds % 3600) / 60 + val seconds = totalSeconds % 60 + return if (hours > 0) { + "%d:%02d:%02d".format(hours, minutes, seconds) + } else { + "%02d:%02d".format(minutes, seconds) + } +} + +@Preview(name = "Progress Indicator Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun VideoProgressIndicatorPreview() { + CloneflixTheme { + Column( + modifier = Modifier + .background(Color(0xFF141414)) + .padding(24.dp), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + CloneflixVideoProgressIndicator( + progress = 0.05f, + bufferedProgress = 0.25f, + durationMs = 3600_000L, + currentPositionMs = 180_000L, + onProgressChange = {} + ) + CloneflixVideoProgressIndicator( + progress = 0.60f, + bufferedProgress = 0.85f, + durationMs = 5400_000L, + currentPositionMs = 3240_000L, + isHoveredOrActive = true, + onProgressChange = {} + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt new file mode 100644 index 00000000000..2b018ee0b1a --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt @@ -0,0 +1,261 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectVerticalDragGestures +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.interaction.collectIsHoveredAsState +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +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.draw.scale +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.ProgressBarRangeInfo +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.progressBarRangeInfo +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.SurfaceDark +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +enum class CloneflixVolumeLevel { + HIGH, + MIDDLE, + MUTE +} + +@Composable +fun CloneflixVerticalVolumeBar( + volume: Float, + onVolumeChange: (Float) -> Unit, + modifier: Modifier = Modifier, + height: Float = 100f, + width: Float = 6f, + enabled: Boolean = true +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + var trackHeightPx by remember { mutableFloatStateOf(1f) } + val animatedVolume by animateFloatAsState( + targetValue = volume.coerceIn(0f, 1f), + label = "animatedVolume" + ) + + Box( + modifier = modifier + .width(36.dp) + .height(110.dp) + .onSizeChanged { trackHeightPx = it.height.toFloat().coerceAtLeast(1f) } + .semantics { + progressBarRangeInfo = ProgressBarRangeInfo( + current = volume, + range = 0f..1f + ) + } + .onKeyEvent { keyEvent -> + if (!enabled) return@onKeyEvent false + when (keyEvent.key) { + Key.DirectionUp -> { + onVolumeChange((volume + 0.1f).coerceIn(0f, 1f)) + true + } + Key.DirectionDown -> { + onVolumeChange((volume - 0.1f).coerceIn(0f, 1f)) + true + } + else -> false + } + } + .pointerInput(enabled) { + if (!enabled) return@pointerInput + detectVerticalDragGestures { change, _ -> + val y = change.position.y + val newVol = (1f - (y / trackHeightPx)).coerceIn(0f, 1f) + onVolumeChange(newVol) + } + } + .focusable(enabled = enabled, interactionSource = interactionSource), + contentAlignment = Alignment.Center + ) { + Canvas(modifier = Modifier.size(width = 36.dp, height = 100.dp)) { + val totalH = size.height + val totalW = size.width + val barW = 6.dp.toPx() + val barX = (totalW - barW) / 2f + val corner = CornerRadius(barW / 2f, barW / 2f) + + // Background bar (grey) + drawRoundRect( + color = Grey450, + topLeft = Offset(barX, 0f), + size = Size(barW, totalH), + cornerRadius = corner + ) + + // Active bar (red) from bottom upwards + val fillH = animatedVolume * totalH + if (fillH > 0f) { + drawRoundRect( + color = PrimaryRed, + topLeft = Offset(barX, totalH - fillH), + size = Size(barW, fillH), + cornerRadius = corner + ) + } + + // Scrubber thumb circle + val thumbY = totalH - fillH + val thumbRadius = 7.dp.toPx() + drawCircle( + color = PrimaryWhite, + radius = thumbRadius, + center = Offset(totalW / 2f, thumbY.coerceIn(thumbRadius, totalH - thumbRadius)) + ) + } + } +} + +@Composable +fun CloneflixVolumeControlPopup( + volume: Float, + onVolumeChange: (Float) -> Unit, + isMuted: Boolean, + onToggleMute: () -> Unit, + modifier: Modifier = Modifier, + isExpanded: Boolean = true +) { + val volumeLevel = when { + isMuted || volume <= 0.01f -> CloneflixVolumeLevel.MUTE + volume < 0.5f -> CloneflixVolumeLevel.MIDDLE + else -> CloneflixVolumeLevel.HIGH + } + + Surface( + modifier = modifier, + shape = RoundedCornerShape(24.dp), + color = TransparentBlack90, + border = BorderStroke(1.dp, Color(0x33FFFFFF)) + ) { + Column( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 14.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + CloneflixVerticalVolumeBar( + volume = if (isMuted) 0f else volume, + onVolumeChange = { + if (isMuted) onToggleMute() + onVolumeChange(it) + } + ) + + val iconRes = when (volumeLevel) { + CloneflixVolumeLevel.HIGH -> R.drawable.ic_baseline_volume_up_24 + CloneflixVolumeLevel.MIDDLE -> R.drawable.ic_baseline_volume_up_24 + CloneflixVolumeLevel.MUTE -> R.drawable.ic_baseline_volume_mute_24 + } + + val iconColor = if (volumeLevel == CloneflixVolumeLevel.MUTE) Grey200 else PrimaryWhite + + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .clickable(onClick = onToggleMute) + .padding(4.dp), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = "Toggle Mute", + tint = iconColor, + modifier = Modifier.size(22.dp) + ) + } + } + } +} + +@Preview(name = "Volume Slider Preview", showBackground = true, backgroundColor = 0xFF141414) +@Composable +private fun VolumeSliderPreview() { + CloneflixTheme { + Row( + modifier = Modifier + .background(Color(0xFF141414)) + .padding(24.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + CloneflixVolumeControlPopup( + volume = 0.85f, + onVolumeChange = {}, + isMuted = false, + onToggleMute = {} + ) + CloneflixVolumeControlPopup( + volume = 0.40f, + onVolumeChange = {}, + isMuted = false, + onToggleMute = {} + ) + CloneflixVolumeControlPopup( + volume = 0f, + onVolumeChange = {}, + isMuted = true, + onToggleMute = {} + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt new file mode 100644 index 00000000000..04e474a52d6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt @@ -0,0 +1,149 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import androidx.compose.foundation.background +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonSize +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonVariant +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +@Composable +fun CloneflixAboutComposeScreen( + onFollowClick: () -> Unit = {}, + onFeedbackClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val dimens = CloneflixTheme.dimens + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val scrollState = rememberScrollState() + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + CloneflixHeader( + title = "About", + subtitle = "Cloneflix Design System 2024 (Website Version)", + iconRes = R.drawable.cloneflix_ic_about + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacing2Xl)) { + Text( + text = "Hello! I'm Ivanna Heraskina, a Product Designer from San Francisco 🌉.", + style = typography.regularHeadline1, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "The Cloneflix Design System 2024 (Website Version) was created with love and dedication. Whether you're a designer, developer, or just interested in UI/UX, feel free to use this toolkit. Use it to bring your creative ideas to life.", + style = typography.regularBody, + color = colors.textPrimary, + lineHeight = 22.sp + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "If you have any thoughts or feedback, or simply want to say hi, add me on X.com (@ivannaheraskina).", + style = typography.regularSmallBody, + color = colors.textDimmed, + lineHeight = 20.sp + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Text( + text = "If it was useful, your support would mean a lot!", + style = typography.regularSmallBody, + color = colors.textMuted + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + Row(modifier = Modifier.fillMaxWidth()) { + CloneflixButton( + text = "Follow @ivannaheraskina", + onClick = onFollowClick, + modifier = Modifier.weight(1f), + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.MEDIUM, + icon = painterResource(id = R.drawable.cloneflix_ic_open_in_new) + ) + + Spacer(modifier = Modifier.width(dimens.spacingM)) + + CloneflixButton( + text = "Feedback", + onClick = onFeedbackClick, + variant = CloneflixButtonVariant.OUTLINE, + size = CloneflixButtonSize.MEDIUM, + icon = painterResource(id = R.drawable.cloneflix_ic_feedback) + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.ELEVATED + ) { + Column(modifier = Modifier.padding(dimens.spacing2Xl)) { + Text( + text = "Design System Architecture", + style = typography.mediumHeadline2, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + Text( + text = "• Pure Jetpack Compose UI with reactive theme\n• 33+ Figma color tokens\n• Netflix Sans & Bebas Neue typography hierarchy\n• Reusable modular components with D-pad & touch compatibility", + style = typography.regularSmallBody, + color = colors.textMuted, + lineHeight = 22.sp + ) + } + } + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixAboutScreenPreview() { + CloneflixTheme { + CloneflixAboutComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt new file mode 100644 index 00000000000..d395d7afe21 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt @@ -0,0 +1,484 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.Toast +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +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.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +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.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAvatarGraphic +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAvatarModel +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLargeAvatar +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixOfficialAvatars +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixProfileMenuDropdown +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixSmallAvatar +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixUserProfilesRow +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun CloneflixAvatarsComposeScreen( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var selectedProfileName by remember { mutableStateOf("Jennifer") } + var menuExpanded by remember { mutableStateOf(false) } + var activeOfficialAvatar by remember { mutableStateOf(CloneflixOfficialAvatars.Red) } + + val sampleProfiles = remember { + listOf( + "Jennifer" to CloneflixOfficialAvatars.Red, + "Bill" to CloneflixOfficialAvatars.FluffyBlue, + "Alise" to CloneflixOfficialAvatars.Green, + "James" to CloneflixOfficialAvatars.Purple + ) + } + + val dropdownProfiles = remember { + listOf( + "Jennifer" to CloneflixOfficialAvatars.Red, + "Max" to CloneflixOfficialAvatars.Blue, + "Beyoncé" to CloneflixOfficialAvatars.Pink + ) + } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + CloneflixHeader( + title = "User Profile Avatar", + subtitle = "Small & Large Variants • Who's Watching Sample • Profile Menu • Official Avatars", + iconRes = R.drawable.cloneflix_ic_account + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // Section 1: Small Avatar Variants + Text( + text = "SMALL AVATAR (32DP)", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Header & Compact Navigation Avatars", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Type=UserProfilePicture (Standalone) and Type=UserProfileMenu (With Dropdown Arrow).", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + horizontalArrangement = Arrangement.spacedBy(32.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixSmallAvatar( + avatar = CloneflixOfficialAvatars.Purple, + showMenuArrow = false, + onClick = { + Toast.makeText(context, "Small Avatar Clicked", Toast.LENGTH_SHORT).show() + } + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "UserProfilePicture", + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixSmallAvatar( + avatar = CloneflixOfficialAvatars.Red, + showMenuArrow = true, + isExpanded = menuExpanded, + onClick = { menuExpanded = !menuExpanded } + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "UserProfileMenu", + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // Section 2: Large Avatar Variants & States + Text( + text = "LARGE AVATAR & FOCUS STATES (144DP)", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Profile Avatar & Add Profile (Default vs Hover/Focused)", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Google TV / Android TV D-Pad focus traversal with white border and scale effect.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixLargeAvatar( + name = "James", + avatar = CloneflixOfficialAvatars.Purple, + isHoverState = false, + onClick = { Toast.makeText(context, "Selected James (Default)", Toast.LENGTH_SHORT).show() } + ) + Text(text = "Default", style = typography.regularCaption1, color = colors.textSecondary) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixLargeAvatar( + name = "James", + avatar = CloneflixOfficialAvatars.Purple, + isHoverState = true, + onClick = { Toast.makeText(context, "Selected James (Hover/Focus)", Toast.LENGTH_SHORT).show() } + ) + Text(text = "Hover / Focus", style = typography.regularCaption1, color = colors.primary) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixLargeAvatar( + name = "Add Profile", + avatar = null, + isAddProfile = true, + isHoverState = false, + onClick = { Toast.makeText(context, "Add Profile (Default)", Toast.LENGTH_SHORT).show() } + ) + Text(text = "Default", style = typography.regularCaption1, color = colors.textSecondary) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixLargeAvatar( + name = "Add Profile", + avatar = null, + isAddProfile = true, + isHoverState = true, + onClick = { Toast.makeText(context, "Add Profile (Hover/Focus)", Toast.LENGTH_SHORT).show() } + ) + Text(text = "Hover / Focus", style = typography.regularCaption1, color = colors.primary) + } + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // Section 3: Sample "Who's Watching?" Profile Selector + Text( + text = "SAMPLE: WHO'S WATCHING? (PROFILE SELECTOR)", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dimens.spacing2Xl, horizontal = dimens.spacingL) + ) { + Text( + text = "Who's Watching?", + style = typography.boldTitle1, + color = PrimaryWhite, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Active Profile: $selectedProfileName", + style = typography.mediumSmallBody, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + CloneflixUserProfilesRow( + profiles = sampleProfiles, + selectedProfileName = selectedProfileName, + onProfileClick = { name, _ -> + selectedProfileName = name + Toast.makeText(context, "Switched to profile $name", Toast.LENGTH_SHORT).show() + }, + onAddProfileClick = { + Toast.makeText(context, "Add new profile clicked", Toast.LENGTH_SHORT).show() + } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // Section 4: Sample Profile Menu Popover Dropdown + Text( + text = "SAMPLE: PROFILE MENU (HEADER POPOVER)", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Interactive Profile Dropdown Menu", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Top-right navigation popover with profile switching, management actions, and sign out.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacingL), + contentAlignment = Alignment.CenterStart + ) { + CloneflixProfileMenuDropdown( + currentProfiles = dropdownProfiles, + onProfileSelected = { name -> + Toast.makeText(context, "Selected $name", Toast.LENGTH_SHORT).show() + }, + onManageProfilesClick = { + Toast.makeText(context, "Manage Profiles", Toast.LENGTH_SHORT).show() + }, + onTransferProfilesClick = { + Toast.makeText(context, "Transfer Profiles", Toast.LENGTH_SHORT).show() + }, + onAccountClick = { + Toast.makeText(context, "Account Settings", Toast.LENGTH_SHORT).show() + }, + onHelpCenterClick = { + Toast.makeText(context, "Help Center", Toast.LENGTH_SHORT).show() + }, + onSignOutClick = { + Toast.makeText(context, "Sign out of Cloneflix", Toast.LENGTH_SHORT).show() + } + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // Section 5: Official Avatars Catalog + Text( + text = "OFFICIAL AVATARS CATALOG", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Main / Popular (16 Official Avatars)", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Selected Avatar: ${activeOfficialAvatar.name}", + style = typography.regularCaption1, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(14.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + CloneflixOfficialAvatars.Popular.forEach { avatar -> + AvatarGridItem( + avatar = avatar, + isSelected = avatar.id == activeOfficialAvatar.id, + onClick = { activeOfficialAvatar = avatar } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + Text( + text = "All Others (Extended Catalog 01..36)", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Algorithmic avatar variations for user profile customization.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + CloneflixOfficialAvatars.getAllOthers(36).forEach { avatar -> + AvatarGridItem( + avatar = avatar, + isSelected = avatar.id == activeOfficialAvatar.id, + size = 64.dp, + onClick = { activeOfficialAvatar = avatar } + ) + } + } + } + } + } +} + +@Composable +private fun AvatarGridItem( + avatar: CloneflixAvatarModel, + isSelected: Boolean, + modifier: Modifier = Modifier, + size: androidx.compose.ui.unit.Dp = 80.dp, + onClick: () -> Unit +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val scale by androidx.compose.animation.core.animateFloatAsState( + targetValue = if (isFocused || isSelected) 1.08f else 1.0f, + label = "GridAvatarScale" + ) + + val borderStroke = when { + isFocused || isSelected -> androidx.compose.foundation.BorderStroke(2.5.dp, PrimaryWhite) + else -> androidx.compose.foundation.BorderStroke(1.dp, Color(0x33FFFFFF)) + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .scale(scale) + .clip(RoundedCornerShape(6.dp)) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(2.dp) + .semantics { + role = Role.Button + contentDescription = "Avatar ${avatar.name}${if (isSelected) ", selected" else ""}" + } + ) { + Box( + modifier = Modifier + .size(size) + .clip(RoundedCornerShape(6.dp)) + .border(borderStroke, RoundedCornerShape(6.dp)) + ) { + CloneflixAvatarGraphic(avatar = avatar) + } + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = avatar.name, + style = CloneflixTheme.typography.regularCaption2, + color = if (isFocused || isSelected) PrimaryWhite else Grey200, + fontSize = 11.sp, + maxLines = 1 + ) + } +} + +@Preview(name = "Avatars Screen Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "Avatars Screen TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixAvatarsScreenPreview() { + CloneflixTheme { + CloneflixAvatarsComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt new file mode 100644 index 00000000000..77590b3e6ee --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt @@ -0,0 +1,494 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.Toast +import androidx.compose.foundation.background +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonSize +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonVariant +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCheckbox +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCircleActionButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCircleActionType +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDivider +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroActionPattern +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroRatingPattern +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroReplayButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroSoundButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlayerControlAction +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlayerControlButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPreviewActionsPattern +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixThumbsReactionPill +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoPlayerControlBar +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite + +@Composable +fun CloneflixButtonsComposeScreen( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var rememberMeChecked by remember { mutableStateOf(true) } + var isAddedToList by remember { mutableStateOf(false) } + var isMutedHero by remember { mutableStateOf(true) } + var isPlayerPlaying by remember { mutableStateOf(true) } + var isPlayerMuted by remember { mutableStateOf(false) } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + // Top Header + CloneflixHeader( + title = "Buttons", + subtitle = "Action Buttons • Media Control Buttons • Compositions & Patterns", + iconRes = R.drawable.cloneflix_ic_play + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // ========================================== + // SECTION 1: ACTION BUTTONS + // ========================================== + Text( + text = "ACTION BUTTONS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + // Sign In Buttons (Large & Small) + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Sign In (Primary Red • Large & Small)", + style = typography.mediumBody, + color = PrimaryWhite + ) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixButton( + text = "Sign In", + onClick = { Toast.makeText(context, "Sign In Large Clicked", Toast.LENGTH_SHORT).show() }, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.LARGE + ) + CloneflixButton( + text = "Sign In", + onClick = { Toast.makeText(context, "Sign In Small Clicked", Toast.LENGTH_SHORT).show() }, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.SMALL + ) + } + } + + CloneflixDivider() + + // Use Sign-In Code + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Secondary / Dark Action", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixButton( + text = "Use a Sign-In Code", + onClick = { Toast.makeText(context, "Use a Sign-In Code Clicked", Toast.LENGTH_SHORT).show() }, + variant = CloneflixButtonVariant.DARK_SECONDARY, + size = CloneflixButtonSize.MEDIUM + ) + } + + CloneflixDivider() + + // Play & More Info + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Hero CTA Actions (Play & More Info)", + style = typography.mediumBody, + color = PrimaryWhite + ) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixButton( + text = "Play", + onClick = { Toast.makeText(context, "Play Clicked", Toast.LENGTH_SHORT).show() }, + variant = CloneflixButtonVariant.SECONDARY, + size = CloneflixButtonSize.MEDIUM, + icon = painterResource(id = R.drawable.cloneflix_ic_play) + ) + CloneflixButton( + text = "More Info", + onClick = { Toast.makeText(context, "More Info Clicked", Toast.LENGTH_SHORT).show() }, + variant = CloneflixButtonVariant.MORE_INFO, + size = CloneflixButtonSize.MEDIUM, + icon = painterResource(id = R.drawable.cloneflix_ic_info) + ) + } + } + + CloneflixDivider() + + // Get Started + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Get Started Action (Onboarding)", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixButton( + text = "Get Started", + onClick = { Toast.makeText(context, "Get Started Clicked", Toast.LENGTH_SHORT).show() }, + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.LARGE, + icon = painterResource(id = R.drawable.cloneflix_ic_chevron_right), + modifier = Modifier.height(56.dp) + ) + } + + CloneflixDivider() + + // Manage Profiles (Outlined) + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Manage Profiles (Outlined Border)", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixButton( + text = "Manage Profiles", + onClick = { Toast.makeText(context, "Manage Profiles Clicked", Toast.LENGTH_SHORT).show() }, + variant = CloneflixButtonVariant.OUTLINE, + size = CloneflixButtonSize.MEDIUM + ) + } + + CloneflixDivider() + + // Checkbox (Remember Me) + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Checkbox Button (Remember Me)", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixCheckbox( + checked = rememberMeChecked, + onCheckedChange = { rememberMeChecked = it }, + label = "Remember me" + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // ========================================== + // SECTION 2: MEDIA CONTROL BUTTONS + // ========================================== + Text( + text = "MEDIA CONTROL BUTTONS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + // Video Player Action Bar Controls + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text( + text = "Video Player Controls (48dp / D-pad focusable)", + style = typography.mediumBody, + color = PrimaryWhite + ) + Text( + text = "Play, 10s Rewind, 10s Forward, Sound, Mute, Next Ep, Subtitles, Speed, Episodes, Fullscreen", + style = typography.regularCaption1, + color = Grey200 + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.PLAY, + onClick = { Toast.makeText(context, "Play", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.REPLAY_10, + onClick = { Toast.makeText(context, "10s Back", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.FORWARD_10, + onClick = { Toast.makeText(context, "10s Forward", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.VOLUME_UP, + onClick = { Toast.makeText(context, "Volume Up", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.VOLUME_OFF, + onClick = { Toast.makeText(context, "Mute", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.NEXT_EPISODE, + onClick = { Toast.makeText(context, "Next Episode", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.SUBTITLES, + onClick = { Toast.makeText(context, "Subtitles", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.SPEED, + onClick = { Toast.makeText(context, "Speed", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.EPISODES, + onClick = { Toast.makeText(context, "Episodes", Toast.LENGTH_SHORT).show() } + ) + CloneflixPlayerControlButton( + action = CloneflixPlayerControlAction.FULLSCREEN, + onClick = { Toast.makeText(context, "Fullscreen", Toast.LENGTH_SHORT).show() } + ) + } + } + + CloneflixDivider() + + // Movie Preview Circular Actions + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text( + text = "Movie Preview Action Buttons (40dp)", + style = typography.mediumBody, + color = PrimaryWhite + ) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixCircleActionButton( + type = CloneflixCircleActionType.PLAY, + onClick = { Toast.makeText(context, "Play Circle", Toast.LENGTH_SHORT).show() } + ) + CloneflixCircleActionButton( + type = if (isAddedToList) CloneflixCircleActionType.ADDED else CloneflixCircleActionType.ADD, + onClick = { + isAddedToList = !isAddedToList + Toast.makeText(context, if (isAddedToList) "Added to list" else "Removed from list", Toast.LENGTH_SHORT).show() + } + ) + CloneflixCircleActionButton( + type = CloneflixCircleActionType.THUMB_UP, + onClick = { Toast.makeText(context, "Liked", Toast.LENGTH_SHORT).show() } + ) + CloneflixCircleActionButton( + type = CloneflixCircleActionType.ARROW_DOWN, + onClick = { Toast.makeText(context, "Details", Toast.LENGTH_SHORT).show() } + ) + } + } + + CloneflixDivider() + + // Multi-reaction Reaction Pill + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text( + text = "Thumbs Multi-Reaction Pill (Dislike • Like • Love)", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixThumbsReactionPill( + onDislikeClick = { Toast.makeText(context, "Dislike Clicked", Toast.LENGTH_SHORT).show() }, + onLikeClick = { Toast.makeText(context, "Like Clicked", Toast.LENGTH_SHORT).show() }, + onLoveClick = { Toast.makeText(context, "Love Clicked", Toast.LENGTH_SHORT).show() } + ) + } + + CloneflixDivider() + + // Hero Banner Controls + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text( + text = "Hero Banner Video Controls (35dp Sound Toggle & Replay)", + style = typography.mediumBody, + color = PrimaryWhite + ) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixHeroSoundButton( + isMuted = isMutedHero, + onToggleMute = { + isMutedHero = !isMutedHero + Toast.makeText(context, if (isMutedHero) "Audio Muted" else "Audio Playing", Toast.LENGTH_SHORT).show() + } + ) + CloneflixHeroReplayButton( + onClick = { Toast.makeText(context, "Replay Video", Toast.LENGTH_SHORT).show() } + ) + } + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // ========================================== + // SECTION 3: PATTERNS & COMPOSITIONS + // ========================================== + Text( + text = "PATTERNS & COMPOSITIONS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + // Pattern 1: Hero Actions + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Pattern: Hero CTAs (Play + More Info)", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixHeroActionPattern( + onPlayClick = { Toast.makeText(context, "Hero Play", Toast.LENGTH_SHORT).show() }, + onMoreInfoClick = { Toast.makeText(context, "Hero More Info", Toast.LENGTH_SHORT).show() } + ) + } + + CloneflixDivider() + + // Pattern 2: Preview Card Actions Row + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Pattern: Movie Card Quick Actions", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixPreviewActionsPattern( + isAdded = isAddedToList, + onPlayClick = { Toast.makeText(context, "Play Preview", Toast.LENGTH_SHORT).show() }, + onAddClick = { isAddedToList = !isAddedToList }, + onThumbUpClick = { Toast.makeText(context, "Thumb Up", Toast.LENGTH_SHORT).show() }, + onExpandClick = { Toast.makeText(context, "Expand Details", Toast.LENGTH_SHORT).show() } + ) + } + + CloneflixDivider() + + // Pattern 3: Hero Banner Rating & Sound + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Pattern: Hero Banner Rating + Sound", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixHeroRatingPattern( + isMuted = isMutedHero, + rating = "TV-MA", + onToggleMute = { isMutedHero = !isMutedHero } + ) + } + + CloneflixDivider() + + // Pattern 4: Full Video Player Control Bar + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "Pattern: Video Player Bottom Control Bar", + style = typography.mediumBody, + color = PrimaryWhite + ) + CloneflixVideoPlayerControlBar( + isPlaying = isPlayerPlaying, + isMuted = isPlayerMuted, + onPlayPauseToggle = { isPlayerPlaying = !isPlayerPlaying }, + onToggleMute = { isPlayerMuted = !isPlayerMuted }, + onSeekBack = { Toast.makeText(context, "-10s", Toast.LENGTH_SHORT).show() }, + onSeekForward = { Toast.makeText(context, "+10s", Toast.LENGTH_SHORT).show() }, + onNextEpisode = { Toast.makeText(context, "Next Episode", Toast.LENGTH_SHORT).show() }, + onEpisodesClick = { Toast.makeText(context, "Episode List Drawer", Toast.LENGTH_SHORT).show() }, + onSubtitlesClick = { Toast.makeText(context, "Subtitles Dialog", Toast.LENGTH_SHORT).show() }, + onSpeedClick = { Toast.makeText(context, "Speed Selector", Toast.LENGTH_SHORT).show() }, + onFullscreenToggle = { Toast.makeText(context, "Fullscreen Toggle", Toast.LENGTH_SHORT).show() } + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } +} + +@Preview(name = "Buttons Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "Buttons TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixButtonsScreenPreview() { + CloneflixTheme { + CloneflixButtonsComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt new file mode 100644 index 00000000000..0cc13dc5932 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt @@ -0,0 +1,81 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import androidx.compose.foundation.background +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.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixColorSwatch +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors + +@Composable +fun CloneflixColorsComposeScreen( + onColorClick: (CloneflixColors.ColorToken) -> Unit = {}, + modifier: Modifier = Modifier +) { + val tokens = CloneflixColors.getAllTokens() + val sections = tokens.groupBy { it.category } + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + + LazyColumn( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .padding(horizontal = dimens.spacing2Xl) + ) { + item { + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + CloneflixHeader( + title = "Colors", + subtitle = "33+ Tokens • Primary, Secondary, Greys, Transparent", + iconRes = R.drawable.cloneflix_ic_colors + ) + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + } + + sections.forEach { (categoryName, categoryTokens) -> + item { + Text( + text = categoryName.uppercase(), + style = typography.sectionHeader, + color = colors.primary, + modifier = Modifier.padding(top = dimens.spacingM, bottom = dimens.spacingL) + ) + } + + items(categoryTokens) { token -> + CloneflixColorSwatch( + token = token, + onClick = { onColorClick(token) } + ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + } + } + + item { + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixColorsScreenPreview() { + CloneflixTheme { + CloneflixColorsComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt new file mode 100644 index 00000000000..2f9d667f8ff --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt @@ -0,0 +1,195 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import androidx.compose.foundation.background +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonSize +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonVariant +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixInputField +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTextField +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +@Composable +fun CloneflixComponentsComposeScreen( + onPrimaryClick: () -> Unit = {}, + onSecondaryClick: () -> Unit = {}, + onOutlineClick: () -> Unit = {}, + onGhostClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var emailText by remember { mutableStateOf("") } + var passwordText by remember { mutableStateOf("") } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + CloneflixHeader( + title = "Components", + subtitle = "Buttons • Cards • Inputs • Custom Views", + iconRes = R.drawable.cloneflix_ic_about + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + Text( + text = "BUTTON VARIANTS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixButton( + text = "Primary Action (Red)", + onClick = onPrimaryClick, + modifier = Modifier.fillMaxWidth(), + variant = CloneflixButtonVariant.PRIMARY, + size = CloneflixButtonSize.LARGE, + icon = painterResource(id = R.drawable.cloneflix_ic_open_in_new) + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixButton( + text = "Secondary Action (White)", + onClick = onSecondaryClick, + modifier = Modifier.fillMaxWidth(), + variant = CloneflixButtonVariant.SECONDARY, + size = CloneflixButtonSize.MEDIUM + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixButton( + text = "Outlined Action (Border)", + onClick = onOutlineClick, + modifier = Modifier.fillMaxWidth(), + variant = CloneflixButtonVariant.OUTLINE, + size = CloneflixButtonSize.MEDIUM, + icon = painterResource(id = R.drawable.cloneflix_ic_copy) + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixButton( + text = "Ghost Action (Transparent)", + onClick = onGhostClick, + variant = CloneflixButtonVariant.GHOST, + size = CloneflixButtonSize.SMALL + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + Text( + text = "INPUT FIELDS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixInputField( + value = emailText, + onValueChange = { emailText = it }, + label = "Email address or phone number" + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixInputField( + value = passwordText, + onValueChange = { passwordText = it }, + label = "Password", + isPassword = true + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + Text( + text = "CARD CONTAINERS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Surface Container (#232323)", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Standard content card with 16dp rounded corners and subtle border.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.ELEVATED + ) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Elevated Container (#181818)", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Elevated surface card for modals, tooltips, and bottom sheets.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixComponentsScreenPreview() { + CloneflixTheme { + CloneflixComponentsComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt new file mode 100644 index 00000000000..a19dc7f27ee --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt @@ -0,0 +1,341 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.Toast +import androidx.compose.foundation.background +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAuthFooter +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBlockLayout +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDivider +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDownloadProgressCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixFaqSection +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixFeatureBlock +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHomeFooter +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHomeHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixKidsPreviewCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLandingFooter +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLandingHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMacPreviewCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMobilePreviewCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTvPreviewCard +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +@Composable +fun CloneflixContentBlocksComposeScreen( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var selectedLandingLang by remember { mutableStateOf("English") } + var selectedHomeNavIndex by remember { mutableIntStateOf(0) } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + CloneflixHeader( + title = "Content Blocks", + subtitle = "Headers • FAQ Accordion • Screen Previews • Landing Blocks • Footers", + iconRes = R.drawable.cloneflix_ic_about + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // -------------------------------------------------------------------- + // 1. HEADERS + // -------------------------------------------------------------------- + Text( + text = "HEADERS", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Text( + text = "Landing Page Header", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + CloneflixLandingHeader( + selectedLanguage = selectedLandingLang, + onLanguageSelected = { selectedLandingLang = it }, + onSignInClick = { + Toast.makeText(context, "Sign In clicked", Toast.LENGTH_SHORT).show() + } + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Home Page Header", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + CloneflixHomeHeader( + selectedNavIndex = selectedHomeNavIndex, + onNavItemSelected = { selectedHomeNavIndex = it }, + onSearchClick = { + Toast.makeText(context, "Search action", Toast.LENGTH_SHORT).show() + }, + onNotificationsClick = { + Toast.makeText(context, "Notifications action", Toast.LENGTH_SHORT).show() + }, + onProfileClick = { + Toast.makeText(context, "Profile menu action", Toast.LENGTH_SHORT).show() + } + ) + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + CloneflixDivider() + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // -------------------------------------------------------------------- + // 2. LANDING PAGE BLOCKS + // -------------------------------------------------------------------- + Text( + text = "LANDING PAGE BLOCKS", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + // Block 1: Headline + Image (TV) + CloneflixFeatureBlock( + title = "Enjoy on your TV", + description = "Watch on Smart TVs, Playstation, Xbox, Chromecast, Apple TV, Blu-ray players, and more.", + layout = CloneflixBlockLayout.HEADLINE_IMAGE, + previewContent = { + CloneflixTvPreviewCard(modifier = Modifier.fillMaxWidth(0.95f)) + } + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Block 2: Image + Headline (Devices) + CloneflixFeatureBlock( + title = "Watch everywhere", + description = "Stream unlimited movies and TV shows on your phone, tablet, laptop, and TV.", + layout = CloneflixBlockLayout.IMAGE_HEADLINE, + previewContent = { + CloneflixMacPreviewCard(modifier = Modifier.fillMaxWidth(0.95f)) + } + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Block 3: Headline + Image (Kids) + CloneflixFeatureBlock( + title = "Create profiles for kids", + description = "Send kids on adventures with their favorite characters in a space made just for them — free with your membership.", + layout = CloneflixBlockLayout.HEADLINE_IMAGE, + previewContent = { + CloneflixKidsPreviewCard(modifier = Modifier.fillMaxWidth(0.95f)) + } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + CloneflixDivider() + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // -------------------------------------------------------------------- + // 3. SCREENS PREVIEWS + // -------------------------------------------------------------------- + Text( + text = "SCREENS PREVIEWS", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "TV Screen Preview", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixTvPreviewCard(modifier = Modifier.fillMaxWidth()) + } + + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Mac Computer Screen Preview", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixMacPreviewCard(modifier = Modifier.fillMaxWidth()) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Kids Profile Preview", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixKidsPreviewCard(modifier = Modifier.fillMaxWidth()) + } + + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Download Status Preview", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixDownloadProgressCard( + title = "Stranger Things", + statusText = "Downloading...", + modifier = Modifier.fillMaxWidth() + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "iPhone Screen Preview", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixMobilePreviewCard() + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + CloneflixDivider() + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // -------------------------------------------------------------------- + // 4. FAQ ACCORDION + // -------------------------------------------------------------------- + Text( + text = "FAQ ACCORDION", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + CloneflixFaqSection(modifier = Modifier.padding(dimens.spacingL)) + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + CloneflixDivider() + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // -------------------------------------------------------------------- + // 5. FOOTERS + // -------------------------------------------------------------------- + Text( + text = "FOOTERS", + style = typography.sectionHeader, + color = colors.primary + ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Text( + text = "Landing Page Footer", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + CloneflixLandingFooter( + onPhoneClick = { Toast.makeText(context, "Call Support clicked", Toast.LENGTH_SHORT).show() }, + onLinkClick = { link -> Toast.makeText(context, "Link clicked: $link", Toast.LENGTH_SHORT).show() } + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Authentication Page Footer", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + CloneflixAuthFooter( + onPhoneClick = { Toast.makeText(context, "Call Support clicked", Toast.LENGTH_SHORT).show() }, + onLinkClick = { link -> Toast.makeText(context, "Link clicked: $link", Toast.LENGTH_SHORT).show() } + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Home Page Footer", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + CloneflixHomeFooter( + onSocialClick = { platform -> Toast.makeText(context, "$platform clicked", Toast.LENGTH_SHORT).show() }, + onLinkClick = { link -> Toast.makeText(context, "Link clicked: $link", Toast.LENGTH_SHORT).show() }, + onServiceCodeClick = { Toast.makeText(context, "Service Code: 839-204", Toast.LENGTH_SHORT).show() } + ) + } + } +} + +@Preview(name = "Content Blocks Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "Content Blocks TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixContentBlocksScreenPreview() { + CloneflixTheme { + CloneflixContentBlocksComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt new file mode 100644 index 00000000000..6f32bae97da --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt @@ -0,0 +1,295 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import androidx.compose.foundation.background +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.Modifier +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdown +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdownStaticShowcase +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdownVariant +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +@Composable +fun CloneflixDropdownComposeScreen( + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + val viewingPreferencesOptions = listOf("Original Language", "Dubbing", "Subtitles") + var selectedViewingPref by remember { mutableStateOf("Original Language") } + + val languageOptions = listOf( + "Arabic", "Cantonese", "Danish", "Dutch", "English", + "Filipino", "Flemish", "French", "French Canadian", + "German", "Greek", "Hebrew", "Hindi", "Hungarian", + "Indonesian", "Italian", "Japanese", "Korean", + "Malay", "Mandarin", "Polish", "Portuguese", + "Romanian", "Russian", "Spanish", "Swedish", + "Thai", "Turkish", "Ukrainian", "Vietnamese" + ) + var selectedLanguage by remember { mutableStateOf("English") } + + val suggestionOptions = listOf( + "Suggestions For you", + "Year Released", + "A-Z", + "Z-A" + ) + var selectedSuggestion by remember { mutableStateOf("Suggestions For you") } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + CloneflixHeader( + title = "Dropdowns", + subtitle = "Viewing Preferences • Language Options • Suggestions For You", + iconRes = R.drawable.cloneflix_ic_dropdown + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + Text( + text = "VIEWING PREFERENCES", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Interactive Dropdown", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Select audio/subtitle preference mode.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + CloneflixDropdown( + options = viewingPreferencesOptions, + selectedOption = selectedViewingPref, + onOptionSelected = { selectedViewingPref = it }, + variant = CloneflixDropdownVariant.OUTLINE, + width = 220.dp + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Figma Design States (Default • Hover • Click/Expanded)", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CloneflixDropdownStaticShowcase( + title = "Default", + selectedOption = "Original Language", + options = viewingPreferencesOptions, + modifier = Modifier.weight(1f) + ) + CloneflixDropdownStaticShowcase( + title = "Hover", + selectedOption = "Original Language", + options = viewingPreferencesOptions, + isHoverState = true, + modifier = Modifier.weight(1f) + ) + CloneflixDropdownStaticShowcase( + title = "Click / Expanded", + selectedOption = "Original Language", + options = viewingPreferencesOptions, + isExpandedState = true, + modifier = Modifier.weight(1f) + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + Text( + text = "LANGUAGE OPTIONS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Interactive Language Selector", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Comprehensive language picker with scrollable option menu.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + CloneflixDropdown( + options = languageOptions, + selectedOption = selectedLanguage, + onOptionSelected = { selectedLanguage = it }, + variant = CloneflixDropdownVariant.OUTLINE, + width = 220.dp + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Figma Design States (Default • Hover • Click/Expanded)", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CloneflixDropdownStaticShowcase( + title = "Default", + selectedOption = "English", + options = languageOptions.take(5), + modifier = Modifier.weight(1f) + ) + CloneflixDropdownStaticShowcase( + title = "Hover", + selectedOption = "English", + options = languageOptions.take(5), + isHoverState = true, + modifier = Modifier.weight(1f) + ) + CloneflixDropdownStaticShowcase( + title = "Click / Expanded", + selectedOption = "English", + options = languageOptions.take(6), + isExpandedState = true, + modifier = Modifier.weight(1f) + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + Text( + text = "SUGGESTIONS FOR YOU", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Text( + text = "Interactive Sort & Filter Filter", + style = typography.mediumBody, + color = colors.textPrimary + ) + Spacer(modifier = Modifier.height(dimens.spacingXs)) + Text( + text = "Browse feed filter dropdown with sorting options.", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + CloneflixDropdown( + options = suggestionOptions, + selectedOption = selectedSuggestion, + onOptionSelected = { selectedSuggestion = it }, + variant = CloneflixDropdownVariant.OUTLINE, + width = 240.dp + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Figma Design States (Default • Hover • Click/Expanded)", + style = typography.regularCaption1, + color = colors.textSecondary + ) + Spacer(modifier = Modifier.height(dimens.spacingS)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CloneflixDropdownStaticShowcase( + title = "Default", + selectedOption = "Suggestions For you", + options = suggestionOptions, + modifier = Modifier.weight(1f) + ) + CloneflixDropdownStaticShowcase( + title = "Hover", + selectedOption = "Suggestions For you", + options = suggestionOptions, + isHoverState = true, + modifier = Modifier.weight(1f) + ) + CloneflixDropdownStaticShowcase( + title = "Click / Expanded", + selectedOption = "Suggestions For you", + options = suggestionOptions, + isExpandedState = true, + modifier = Modifier.weight(1f) + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixDropdownScreenPreview() { + CloneflixTheme { + CloneflixDropdownComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt new file mode 100644 index 00000000000..6f8d699ac2c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt @@ -0,0 +1,435 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.Toast +import androidx.compose.foundation.background +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroBanner +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroBannerType +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHomePageHeroPattern +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoOverview +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTitlePreview +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTitlePreviewSize +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +@Composable +fun CloneflixHeroBannersComposeScreen( + onActionClick: ((String) -> Unit)? = null, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + val notifyAction: (String) -> Unit = onActionClick ?: { actionName -> + Toast.makeText(context, actionName, Toast.LENGTH_SHORT).show() + } + + var isHomeAudioMuted by remember { mutableStateOf(false) } + var isModalAudioMuted by remember { mutableStateOf(false) } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + CloneflixHeader( + title = "Hero Banners", + subtitle = "Title Preview • Hero Banners • Page Patterns", + iconRes = R.drawable.cloneflix_ic_play + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // ========================================== + // SECTION 1: TITLE PREVIEW SIZES + // ========================================== + Text( + text = "TITLE PREVIEW SIZES", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Size: Large + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Size = Large (518 × 406)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + CloneflixTitlePreview( + size = CloneflixTitlePreviewSize.LARGE, + title = "HOUSE OF NINJAS", + top10RankText = "#2 in TV Shows Today", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + onPlayClick = { notifyAction("Play: House of Ninjas") }, + onMoreInfoClick = { notifyAction("More Info: House of Ninjas") } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Size: Medium + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Size = Medium (518 × 360)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + CloneflixTitlePreview( + size = CloneflixTitlePreviewSize.MEDIUM, + title = "HOUSE OF NINJAS", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + onPlayClick = { notifyAction("Play: House of Ninjas") }, + onMoreInfoClick = { notifyAction("More Info: House of Ninjas") } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Size: Small 1, Small 2, Small 3 + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Size = Small Variants (Small1, Small2, Small3)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + Text( + text = "Small 1: Logo + Action Buttons", + style = typography.mediumBody, + color = colors.textPrimary + ) + CloneflixTitlePreview( + size = CloneflixTitlePreviewSize.SMALL1, + title = "HOUSE OF NINJAS", + onPlayClick = { notifyAction("Play: House of Ninjas") }, + onMoreInfoClick = { notifyAction("More Info: House of Ninjas") } + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Small 2: Title Banner Box (518 × 207)", + style = typography.mediumBody, + color = colors.textPrimary + ) + CloneflixTitlePreview( + size = CloneflixTitlePreviewSize.SMALL2, + title = "HOUSE OF NINJAS" + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Small 3: Compact Title Box (340 × 136)", + style = typography.mediumBody, + color = colors.textPrimary + ) + CloneflixTitlePreview( + size = CloneflixTitlePreviewSize.SMALL3, + title = "HOUSE OF NINJAS" + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // ========================================== + // SECTION 2: HERO BANNER VARIANTS + // ========================================== + Text( + text = "HERO BANNER VARIANTS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 1. Home Page Hero Banner + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Type = HomePage (Full Backdrop Scrims, Title Preview, Rating, Audio Toggle)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + CloneflixHeroBanner( + type = CloneflixHeroBannerType.HOME_PAGE, + title = "HOUSE OF NINJAS", + top10RankText = "#2 in TV Shows Today", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + maturityRating = "16+", + onPlayClick = { notifyAction("Playing House of Ninjas") }, + onMoreInfoClick = { notifyAction("Opening House of Ninjas details") }, + onAudioToggle = { + isHomeAudioMuted = !isHomeAudioMuted + notifyAction(if (isHomeAudioMuted) "Audio Muted" else "Audio Unmuted") + }, + isMuted = isHomeAudioMuted + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 2. Movie Preview Hero Banner + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Type = MoviePreview (Modal Header with Close, Title Logo, Play, +, Like, Volume)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + CloneflixHeroBanner( + type = CloneflixHeroBannerType.MOVIE_PREVIEW, + title = "HOUSE OF NINJAS", + onPlayClick = { notifyAction("Playing Movie Preview") }, + onCloseClick = { notifyAction("Closed Movie Preview Modal") }, + onAddToListClick = { notifyAction("Added House of Ninjas to My List") }, + onThumbsUpClick = { notifyAction("Liked House of Ninjas") }, + onAudioToggle = { + isModalAudioMuted = !isModalAudioMuted + notifyAction(if (isModalAudioMuted) "Preview Audio Muted" else "Preview Audio Unmuted") + }, + isMuted = isModalAudioMuted + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 3. Landing Page Hero + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Type = LandingPage (Vignette, Header, Central Headline, CTA Button)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + CloneflixHeroBanner( + type = CloneflixHeroBannerType.LANDING_PAGE, + onPlayClick = { notifyAction("Get Started / Sign In clicked") } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 4. Authentication Page Hero + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Type = AuthenticationPage (Vignette, Center Sign-In Box)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + CloneflixHeroBanner( + type = CloneflixHeroBannerType.AUTHENTICATION_PAGE, + onPlayClick = { notifyAction("Sign In clicked") } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // ========================================== + // SECTION 3: COMPOSED PATTERNS (HeroBanner+) + // ========================================== + Text( + text = "COMPOSED PATTERNS (HERO BANNER+)", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 1. Full Composed Home Page Pattern + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Pattern = HomePage (Top Navigation Bar + Hero Banner + Trending Shelf Row)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + CloneflixHomePageHeroPattern( + onPlayClick = { notifyAction("Home Pattern: Play Video") }, + onMoreInfoClick = { notifyAction("Home Pattern: More Info") }, + onSearchClick = { notifyAction("Home Pattern: Open Search") }, + onNotificationClick = { notifyAction("Home Pattern: Open Notifications") }, + onProfileClick = { notifyAction("Home Pattern: Open Profile Menu") }, + onCardClick = { movie -> notifyAction("Selected Trending: $movie") } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 2. Full Composed Movie Preview Modal Pattern + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Pattern = MoviePreview (Header Hero + Integrated Details Overview)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(colors.surfaceElevated) + ) { + CloneflixHeroBanner( + type = CloneflixHeroBannerType.MOVIE_PREVIEW, + title = "HOUSE OF NINJAS", + onPlayClick = { notifyAction("Modal: Play Video") }, + onCloseClick = { notifyAction("Modal: Closed") }, + onAddToListClick = { notifyAction("Modal: Added to My List") }, + onThumbsUpClick = { notifyAction("Modal: Liked") }, + onAudioToggle = { + isModalAudioMuted = !isModalAudioMuted + notifyAction(if (isModalAudioMuted) "Audio Muted" else "Audio Unmuted") + }, + isMuted = isModalAudioMuted + ) + + CloneflixMovieInfoOverview( + title = "House of Ninjas", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + cast = "Kento Kaku, Yosuke Eguchi, Tae Kimura, more", + genres = "TV Dramas, Japanese, TV Thrillers", + moodTags = "Dark, Suspenseful, Exciting", + matchScore = "98% Match", + releaseYear = "2024", + duration = "1 Season", + maturityRating = "TV-MA", + advisories = "smoking, violence", + quality = "HD", + top10RankText = "#2 in TV Shows Today", + onClick = { notifyAction("Overview clicked") }, + modifier = Modifier.padding(16.dp) + ) + } + } + } + } +} + +@Preview(name = "Hero Banners Screen Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun HeroBannersScreenPreview() { + CloneflixTheme { + CloneflixHeroBannersComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt new file mode 100644 index 00000000000..17f6e1e7dbb --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt @@ -0,0 +1,502 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.widget.Toast +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +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.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixContentBadge +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoVariant +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoView +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMaturityRating +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoBlock +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoOverview +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoRow +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTop10Badge +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTop10RankBanner +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTop10Size +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoQualityBadge +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T40 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite + +data class CloneflixIconItem( + val name: String, + val resId: Int +) + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun CloneflixIconsLabelsComposeScreen( + onItemClick: ((String) -> Unit)? = null, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + val copyAction: (String) -> Unit = onItemClick ?: { textToCopy -> + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Cloneflix Asset", textToCopy) + clipboard.setPrimaryClip(clip) + Toast.makeText(context, "Copied: $textToCopy", Toast.LENGTH_SHORT).show() + } + + val iconList = remember { + listOf( + CloneflixIconItem("Info", R.drawable.cloneflix_ic_info), + CloneflixIconItem("Notification", R.drawable.cloneflix_ic_notification), + CloneflixIconItem("Search", R.drawable.cloneflix_ic_search), + CloneflixIconItem("Play", R.drawable.cloneflix_ic_play), + CloneflixIconItem("ArrowRight", R.drawable.cloneflix_ic_arrow_right), + CloneflixIconItem("ArrowLeft", R.drawable.cloneflix_ic_arrow_left), + CloneflixIconItem("ArrowUp", R.drawable.cloneflix_ic_arrow_up), + CloneflixIconItem("ArrowDown", R.drawable.cloneflix_ic_arrow_down), + CloneflixIconItem("ThumbUp", R.drawable.cloneflix_ic_thumb_up), + CloneflixIconItem("ThumbDown", R.drawable.cloneflix_ic_thumb_down), + CloneflixIconItem("PlusWide", R.drawable.cloneflix_ic_plus), + CloneflixIconItem("Cross", R.drawable.cloneflix_ic_close), + CloneflixIconItem("Flag", R.drawable.cloneflix_ic_flag), + CloneflixIconItem("ArrowRightNarrow", R.drawable.cloneflix_ic_arrow_right_narrow), + CloneflixIconItem("CircleError", R.drawable.cloneflix_ic_error_circle), + CloneflixIconItem("Question", R.drawable.cloneflix_ic_help), + CloneflixIconItem("Pensil", R.drawable.cloneflix_ic_edit), + CloneflixIconItem("Person", R.drawable.cloneflix_ic_person), + CloneflixIconItem("Account", R.drawable.cloneflix_ic_account), + CloneflixIconItem("AudioDesc", R.drawable.cloneflix_ic_ad), + CloneflixIconItem("Facebook", R.drawable.cloneflix_ic_social_fb), + CloneflixIconItem("Twitter/X", R.drawable.cloneflix_ic_social_x), + CloneflixIconItem("Instagram", R.drawable.cloneflix_ic_social_ig), + CloneflixIconItem("YouTube", R.drawable.cloneflix_ic_social_yt) + ) + } + + val maturityRatings = remember { + listOf("TV-Y", "TV-Y7", "G", "TV-G", "PG", "TV-PG", "PG-13", "TV-14", "R", "TV-MA", "NC-17") + } + + val videoQualities = remember { + listOf("HD", "4K", "HDR", "UltraHD 4K", "Dolby Vision") + } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + // Section 0: Header + CloneflixHeader( + title = "Icons & Labels", + subtitle = "Design System 2024 • Website & TV Reference", + iconRes = R.drawable.cloneflix_ic_labels + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // Section 1: Icons + Text( + text = "ICONS (24)", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + iconList.forEach { iconItem -> + CloneflixIconCard( + iconItem = iconItem, + onClick = { copyAction(iconItem.name) } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // Section 2: Maturity Ratings + Text( + text = "MATURITY RATINGS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + maturityRatings.forEach { rating -> + CloneflixMaturityRating( + rating = rating, + onClick = { copyAction(rating) } + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // Section 3: Video Quality + Text( + text = "VIDEO QUALITY", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + videoQualities.forEach { quality -> + CloneflixVideoQualityBadge( + quality = quality, + onClick = { copyAction(quality) } + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // Section 4: Labels & Badges + Text( + text = "LABELS & BADGES", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + Text( + text = "Top 10 Badges", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixTop10Badge(size = CloneflixTop10Size.LARGE) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Large (32dp)", style = typography.regularCaption2, color = colors.textSecondary) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixTop10Badge(size = CloneflixTop10Size.MEDIUM) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Medium (28dp)", style = typography.regularCaption2, color = colors.textSecondary) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CloneflixTop10Badge(size = CloneflixTop10Size.SMALL) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Small (24dp)", style = typography.regularCaption2, color = colors.textSecondary) + } + } + + CloneflixTop10RankBanner( + rankText = "#2 in TV Shows Today", + size = CloneflixTop10Size.LARGE, + onClick = { copyAction("#2 in TV Shows Today") } + ) + + CloneflixTop10RankBanner( + rankText = "#1 in Movies Today", + size = CloneflixTop10Size.MEDIUM, + onClick = { copyAction("#1 in Movies Today") } + ) + + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + Text( + text = "Content Status Pills", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + CloneflixContentBadge(type = CloneflixBadgeType.RECENTLY_ADDED) + CloneflixContentBadge(type = CloneflixBadgeType.NEW_SEASON) + CloneflixContentBadge(type = CloneflixBadgeType.LEAVING_SOON) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // Section 5: Logos + Text( + text = "LOGO VARIANTS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + Text( + text = "Wordmark", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) + CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_SMALL) + } + + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + Text( + text = "Lettermark (Icon / Emblem)", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_LARGE) + CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_MEDIUM) + CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_SMALL) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // Section 6: Movie Info Patterns & Live Blocks + Text( + text = "PATTERNS & PREVIEWS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Text( + text = "Inline Metadata Pattern (Figma Section 12)", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier.padding(dimens.spacingL)) { + CloneflixMovieInfoRow( + matchText = "New", + maturityRating = "TV-MA", + duration = "3 Seasons", + quality = "HD", + hasAudioDescription = true + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Movie Info Block Pattern (Figma Section 13)", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.ELEVATED + ) { + Box(modifier = Modifier.padding(dimens.spacingL)) { + CloneflixMovieInfoBlock( + matchScore = "98% Match", + releaseYear = "2024", + duration = "3 Seasons", + maturityRating = "TV-MA", + advisories = "smoking, violence", + quality = "HD", + top10RankText = "#2 in TV Shows Today", + hasAudioDescription = true + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "Full Movie Info Overview (Figma Section 14)", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + CloneflixMovieInfoOverview( + title = "House of Ninjas", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + cast = "Kento Kaku, Yosuke Eguchi, Tae Kimura, more", + genres = "TV Dramas, Japanese, TV Thrillers", + moodTags = "Dark, Suspenseful, Exciting", + matchScore = "New", + releaseYear = "2024", + duration = "3 Seasons", + maturityRating = "TV-MA", + advisories = "smoking, violence", + quality = "HD", + top10RankText = "#2 in TV Shows Today", + onClick = { copyAction("House of Ninjas Overview") } + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } +} + +@Composable +private fun CloneflixIconCard( + iconItem: CloneflixIconItem, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val activeBorder = if (isFocused) { + BorderStroke(2.dp, PrimaryWhite) + } else { + BorderStroke(1.dp, colors.border) + } + + Column( + modifier = modifier + .width(100.dp) + .clip(RoundedCornerShape(8.dp)) + .background(if (isFocused) Grey300T40 else colors.surface) + .border(activeBorder, RoundedCornerShape(8.dp)) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(vertical = dimens.spacingM, horizontal = dimens.spacingS), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painter = painterResource(id = iconItem.resId), + contentDescription = iconItem.name, + tint = if (isFocused) colors.primary else PrimaryWhite, + modifier = Modifier.size(28.dp) + ) + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + Text( + text = iconItem.name, + style = typography.regularCaption2, + color = if (isFocused) PrimaryWhite else Grey200, + textAlign = TextAlign.Center, + maxLines = 1 + ) + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixIconsLabelsPreview() { + CloneflixTheme { + CloneflixIconsLabelsComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt new file mode 100644 index 00000000000..f2fe79bbd6a --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt @@ -0,0 +1,198 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.Toast +import androidx.compose.foundation.background +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixGetStartedRow +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixInputField +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixInputFieldSize +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixSignInCard +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme + +@Composable +fun CloneflixInputFieldsComposeScreen( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var emailLarge by remember { mutableStateOf("") } + var emailOrPhoneMedium by remember { mutableStateOf("user@example.com") } + var passwordMedium by remember { mutableStateOf("") } + + var defaultField by remember { mutableStateOf("") } + var focusedField by remember { mutableStateOf("active.typing@netflix.com") } + var errorField by remember { mutableStateOf("invalid-email") } + + var getStartedEmail by remember { mutableStateOf("") } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + CloneflixHeader( + title = "Input Fields", + subtitle = "Netflix Design System 2024 • Floating labels, sizes & form components", + iconRes = R.drawable.cloneflix_ic_input_field + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + Text( + text = "FIELD SIZES & TYPES", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixInputField( + value = emailLarge, + onValueChange = { emailLarge = it }, + label = "Email address (Large - 56dp)", + size = CloneflixInputFieldSize.LARGE + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixInputField( + value = emailOrPhoneMedium, + onValueChange = { emailOrPhoneMedium = it }, + label = "Email or phone number (Medium - 48dp)", + size = CloneflixInputFieldSize.MEDIUM + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixInputField( + value = passwordMedium, + onValueChange = { passwordMedium = it }, + label = "Password (Password Type with Reveal)", + isPassword = true, + size = CloneflixInputFieldSize.MEDIUM + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + Text( + text = "FIELD STATES (DEFAULT, FOCUSED, ERROR)", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixInputField( + value = defaultField, + onValueChange = { defaultField = it }, + label = "Default State (Empty / Centered Placeholder)", + size = CloneflixInputFieldSize.LARGE + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixInputField( + value = focusedField, + onValueChange = { focusedField = it }, + label = "Focused / Active Typing State (Floating Top Label)", + size = CloneflixInputFieldSize.LARGE + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixInputField( + value = errorField, + onValueChange = { errorField = it }, + label = "Error State with Validation Message", + size = CloneflixInputFieldSize.LARGE, + isError = true, + errorMessage = "Please enter a valid email address or phone number." + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + Text( + text = "GET STARTED CALLOUT ROW", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixGetStartedRow( + emailValue = getStartedEmail, + onEmailChange = { getStartedEmail = it }, + onGetStartedClick = { + if (getStartedEmail.isBlank() || !getStartedEmail.contains("@")) { + Toast.makeText(context, "Please enter a valid email address", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(context, "Get Started: $getStartedEmail", Toast.LENGTH_SHORT).show() + } + } + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + Text( + text = "SIGN-IN COMPOUND FORM", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + CloneflixSignInCard( + onSignInClick = { email, _ -> + Toast.makeText(context, "Signing in with: $email", Toast.LENGTH_SHORT).show() + }, + onUseCodeClick = { + Toast.makeText(context, "Sign-in Code requested", Toast.LENGTH_SHORT).show() + }, + onForgotPasswordClick = { + Toast.makeText(context, "Forgot Password clicked", Toast.LENGTH_SHORT).show() + }, + onSignUpClick = { + Toast.makeText(context, "Sign Up clicked", Toast.LENGTH_SHORT).show() + } + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixInputFieldsScreenPreview() { + CloneflixTheme { + CloneflixInputFieldsComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt new file mode 100644 index 00000000000..344ef04153e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt @@ -0,0 +1,555 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.Toast +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixEpisodeItem +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixExpandedMoviePreview +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMaturityRating +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieBlockRow +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardItem +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoQualityBadge +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite + +@Composable +fun CloneflixMoviePreviewComposeScreen( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var currentEpisode by remember { mutableIntStateOf(1) } + + fun showToast(msg: String) { + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() + } + + val trendingMovies = remember { + listOf( + CloneflixMovieCardItem("House of Ninjas", showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), + CloneflixMovieCardItem("Rurouni Kenshin", showLogo = true, badge = CloneflixBadgeType.RECENTLY_ADDED), + CloneflixMovieCardItem("Avatar: The Last Airbender", showLogo = true), + CloneflixMovieCardItem("One Piece", showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), + CloneflixMovieCardItem("Stranger Things", showLogo = true), + CloneflixMovieCardItem("Squid Game", showLogo = true, badge = CloneflixBadgeType.LEAVING_SOON) + ) + } + + val continueWatchingMovies = remember { + listOf( + CloneflixMovieCardItem("House of Ninjas", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.75f, showLogo = true), + CloneflixMovieCardItem("The Witcher", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.40f, showLogo = true), + CloneflixMovieCardItem("Cyberpunk: Edgerunners", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.90f, showLogo = true), + CloneflixMovieCardItem("Arcane", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.55f, showLogo = true) + ) + } + + val top10Movies = remember { + listOf( + CloneflixMovieCardItem("House of Ninjas", type = CloneflixMovieCardType.TOP10, top10Rank = 1, showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), + CloneflixMovieCardItem("Avatar", type = CloneflixMovieCardType.TOP10, top10Rank = 2, showLogo = true), + CloneflixMovieCardItem("Damsel", type = CloneflixMovieCardType.TOP10, top10Rank = 3, showLogo = true, badge = CloneflixBadgeType.RECENTLY_ADDED), + CloneflixMovieCardItem("Spaceman", type = CloneflixMovieCardType.TOP10, top10Rank = 4, showLogo = true), + CloneflixMovieCardItem("The Gentlemen", type = CloneflixMovieCardType.TOP10, top10Rank = 5, showLogo = true), + CloneflixMovieCardItem("Code 8 Part II", type = CloneflixMovieCardType.TOP10, top10Rank = 6, showLogo = true), + CloneflixMovieCardItem("Shogun", type = CloneflixMovieCardType.TOP10, top10Rank = 7, showLogo = true) + ) + } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + // Header + CloneflixHeader( + title = "Movie Preview", + subtitle = "Movie Cards • Samples • Carousels • Episode Lists • Detail Previews", + iconRes = R.drawable.cloneflix_ic_play + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // SECTION 1: Movie Cards + Text( + text = "MOVIE CARDS (VARIANTS)", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + Text( + text = "Standard & More Like This", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column { + CloneflixMovieCard( + title = "Default Medium", + type = CloneflixMovieCardType.DEFAULT, + size = CloneflixMovieCardSize.MEDIUM, + onClick = { showToast("Default Medium Clicked") } + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Default (218x123dp)", style = typography.regularCaption2, color = Grey200) + } + + Column { + CloneflixMovieCard( + title = "More Like This", + type = CloneflixMovieCardType.MORE_LIKE_THIS, + runtime = "2h 18m", + showLogo = true, + onClick = { showToast("More Like This Clicked") } + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "More Like This (236x132dp)", style = typography.regularCaption2, color = Grey200) + } + + Column { + CloneflixMovieCard( + title = "With Play Icon", + type = CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY, + runtime = "2h 18m", + showLogo = true, + onClick = { showToast("Play Clicked") } + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "With Play Button", style = typography.regularCaption2, color = Grey200) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + Text( + text = "Episode, Player & Trailer Previews", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column { + CloneflixMovieCard( + title = "Episode Thumbnail", + type = CloneflixMovieCardType.EPISODE, + size = CloneflixMovieCardSize.SMALL, + onClick = { showToast("Episode Clicked") } + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Episode (128x72dp)", style = typography.regularCaption2, color = Grey200) + } + + Column { + CloneflixMovieCard( + title = "Player Timestamp", + type = CloneflixMovieCardType.PLAYER_PREVIEW, + timestamp = "51:29", + onClick = { showToast("Player Preview Clicked") } + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Player Preview (51:29)", style = typography.regularCaption2, color = Grey200) + } + + Column { + CloneflixMovieCard( + title = "Trailer", + subtitle = "Season 1 Trailer 1: House of Ninjas", + type = CloneflixMovieCardType.TRAILER, + onClick = { showToast("Trailer Clicked") } + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Trailer Preview", style = typography.regularCaption2, color = Grey200) + } + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // SECTION 2: Samples - Home Page Movie Cards + Text( + text = "SAMPLES (HOME PAGE CARDS)", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + Text( + text = "Badges & Branding Variations", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloneflixMovieCard( + title = "Default", + type = CloneflixMovieCardType.DEFAULT, + onClick = { showToast("Default Card") } + ) + + CloneflixMovieCard( + title = "With Logo", + type = CloneflixMovieCardType.DEFAULT, + showLogo = true, + onClick = { showToast("Card With Logo") } + ) + + CloneflixMovieCard( + title = "Recently Added", + type = CloneflixMovieCardType.DEFAULT, + showLogo = true, + badge = CloneflixBadgeType.RECENTLY_ADDED, + onClick = { showToast("Recently Added Card") } + ) + + CloneflixMovieCard( + title = "New Season", + type = CloneflixMovieCardType.DEFAULT, + showLogo = true, + badge = CloneflixBadgeType.NEW_SEASON, + onClick = { showToast("New Season Card") } + ) + + CloneflixMovieCard( + title = "Leaving Soon", + type = CloneflixMovieCardType.DEFAULT, + showLogo = true, + badge = CloneflixBadgeType.LEAVING_SOON, + onClick = { showToast("Leaving Soon Card") } + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // SECTION 3: Patterns - Movie Blocks (Carousels) + Text( + text = "PATTERNS: MOVIE BLOCKS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.ELEVATED + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + CloneflixMovieBlockRow( + title = "Trending Now", + items = trendingMovies, + onItemClick = { showToast("Selected: ${it.title}") } + ) + + CloneflixMovieBlockRow( + title = "Continue Watching", + items = continueWatchingMovies, + onItemClick = { showToast("Resuming: ${it.title}") } + ) + + CloneflixMovieBlockRow( + title = "Top 10 TV Shows Today", + items = top10Movies, + onItemClick = { showToast("Top 10: #${it.top10Rank} ${it.title}") } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // SECTION 4: Patterns - List of Episodes + Text( + text = "PATTERNS: LIST OF EPISODES", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixCard(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingL), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + CloneflixEpisodeItem( + episodeNumber = 1, + title = "The Offer", + duration = "55m", + synopsis = "While Haru Tawara develops a crush on a mysterious young woman at work, an unusual opportunity arises at his father's financially struggling brewery.", + isCurrent = currentEpisode == 1, + onClick = { + currentEpisode = 1 + showToast("Playing Episode 1") + } + ) + + CloneflixEpisodeItem( + episodeNumber = 2, + title = "The Trail", + duration = "52m", + synopsis = "Haru accompanies Karen to investigate a whistleblower's apartment. Meanwhile, several other Tawaras are tempted to step out of their ordinary lives.", + isCurrent = currentEpisode == 2, + onClick = { + currentEpisode = 2 + showToast("Playing Episode 2") + } + ) + + CloneflixEpisodeItem( + episodeNumber = 3, + title = "The Flower", + duration = "50m", + synopsis = "As Yoko investigates the Gentenkai group, Sohei receives an unexpected visitor. Haru uncovers a hidden truth about the recent incident.", + isCurrent = currentEpisode == 3, + onClick = { + currentEpisode = 3 + showToast("Playing Episode 3") + } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // SECTION 5: Patterns - Expanded Movie Preview & Detail Card + Text( + text = "PATTERNS: MOVIE PREVIEW (EXPANDED & DETAIL)", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + CloneflixExpandedMoviePreview( + title = "House of Ninjas", + matchScore = "New", + maturityRating = "TV-MA", + duration = "3 Seasons", + quality = "HD", + genres = listOf("Violent", "Dark", "Action"), + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + onPlayClick = { showToast("Play Clicked") }, + onAddClick = { showToast("Added to My List") }, + onThumbUpClick = { showToast("Liked") }, + onMuteClick = { showToast("Mute Toggled") } + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Text( + text = "More Like This Detail Card (Figma Type=Movie Preview Other)", + style = typography.mediumBody, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + CloneflixMoreLikeThisDetailCard( + title = "Rurouni Kenshin: The Beginning", + runtime = "2h 18m", + maturityRating = "TV-MA", + quality = "HD", + year = "2021", + synopsis = "Before he was a protector, Kenshin was a fearsome assassin known as Battosai. But when he meets the gentle Tomoe Yukishiro, his story begins to change.", + onAddClick = { showToast("Added to List") } + ) + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } +} + +/** + * More Like This Card with metadata and Add button from Figma 121:3161. + */ +@Composable +fun CloneflixMoreLikeThisDetailCard( + title: String, + runtime: String, + maturityRating: String, + quality: String, + year: String, + synopsis: String, + onAddClick: () -> Unit, + modifier: Modifier = Modifier +) { + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + Box( + modifier = modifier + .width(260.dp) + .clip(RoundedCornerShape(6.dp)) + .background(Grey850) + .border(BorderStroke(0.5.dp, colors.border), RoundedCornerShape(6.dp)) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + // Thumbnail with Logo & Runtime + CloneflixMovieCard( + title = title, + type = CloneflixMovieCardType.MORE_LIKE_THIS, + runtime = runtime, + showLogo = true, + modifier = Modifier.fillMaxWidth() + ) + + // Metadata row with + button + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingM) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + CloneflixMaturityRating(rating = maturityRating) + CloneflixVideoQualityBadge(quality = quality) + Text( + text = year, + style = typography.regularCaption1, + color = Grey100 + ) + } + + // Circle Add Button + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(Grey800) + .border(BorderStroke(1.dp, PrimaryWhite.copy(alpha = 0.6f)), CircleShape) + .clickable(onClick = onAddClick), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_plus), + contentDescription = "Add", + tint = PrimaryWhite, + modifier = Modifier.size(16.dp) + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = synopsis, + style = typography.regularSmallBody, + fontSize = 12.sp, + lineHeight = 16.sp, + color = Grey200, + maxLines = 3 + ) + } + } + } +} + +@Preview(name = "Phone Movie Preview Screen", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Movie Preview Screen", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixMoviePreviewScreenPreview() { + CloneflixTheme { + CloneflixMoviePreviewComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt new file mode 100644 index 00000000000..2d246fef45c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt @@ -0,0 +1,162 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.content.Intent +import android.net.Uri +import android.widget.Toast +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +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.platform.LocalContext +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDivider +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTheme as LegacyCloneflixTheme + +@Composable +fun CloneflixShowcaseComposeScreen() { + val context = LocalContext.current + val tabs = listOf("About", "Typography", "Colors", "Buttons", "Components", "Content Blocks", "Icons & Labels", "Dropdowns", "Input Fields", "Hero Banners", "Avatars", "Video Player", "Movie Preview") + var selectedTabIndex by remember { mutableIntStateOf(0) } + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + + Column( + modifier = Modifier + .fillMaxSize() + .background(colors.background) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(colors.surfaceElevated) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "CLONEFLIX", + style = typography.logoBebas, + fontSize = 24.sp + ) + Spacer(modifier = Modifier.width(dimens.spacingM)) + Text( + text = "Design System 2024", + style = typography.mediumBody, + color = colors.textPrimary + ) + } + + ScrollableTabRow( + selectedTabIndex = selectedTabIndex, + containerColor = colors.surfaceElevated, + contentColor = colors.textPrimary, + edgePadding = dimens.spacingL, + indicator = { tabPositions -> + if (selectedTabIndex < tabPositions.size) { + TabRowDefaults.SecondaryIndicator( + modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]), + color = colors.primary, + height = 3.dp + ) + } + } + ) { + tabs.forEachIndexed { index, title -> + Tab( + selected = selectedTabIndex == index, + onClick = { selectedTabIndex = index }, + text = { + Text( + text = title, + style = if (selectedTabIndex == index) typography.mediumBody else typography.regularBody, + color = if (selectedTabIndex == index) colors.textPrimary else colors.textSecondary + ) + } + ) + } + } + + CloneflixDivider() + + Box(modifier = Modifier.weight(1f)) { + when (selectedTabIndex) { + 0 -> CloneflixAboutComposeScreen( + onFollowClick = { + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://x.com/ivannaheraskina")) + context.startActivity(intent) + } catch (_: Exception) { + Toast.makeText(context, "Could not open link", Toast.LENGTH_SHORT).show() + } + }, + onFeedbackClick = { + Toast.makeText(context, "Feedback submitted! Thank you!", Toast.LENGTH_SHORT).show() + } + ) + 1 -> CloneflixTypographyComposeScreen( + onStyleClick = { styleLevel -> + LegacyCloneflixTheme.copyToClipboard( + context, + styleLevel.label, + "fontSize: ${styleLevel.sizeSp}sp, lineHeight: ${styleLevel.lineHeightSp}sp" + ) + } + ) + 2 -> CloneflixColorsComposeScreen( + onColorClick = { token -> + LegacyCloneflixTheme.copyToClipboard( + context, + token.name, + token.hex + ) + } + ) + 3 -> CloneflixButtonsComposeScreen() + 4 -> CloneflixComponentsComposeScreen( + onPrimaryClick = { Toast.makeText(context, "Primary Action Clicked", Toast.LENGTH_SHORT).show() }, + onSecondaryClick = { Toast.makeText(context, "Secondary Action Clicked", Toast.LENGTH_SHORT).show() }, + onOutlineClick = { Toast.makeText(context, "Outline Action Clicked", Toast.LENGTH_SHORT).show() }, + onGhostClick = { Toast.makeText(context, "Ghost Action Clicked", Toast.LENGTH_SHORT).show() } + ) + 5 -> CloneflixContentBlocksComposeScreen() + 6 -> CloneflixIconsLabelsComposeScreen() + 7 -> CloneflixDropdownComposeScreen() + 8 -> CloneflixInputFieldsComposeScreen() + 9 -> CloneflixHeroBannersComposeScreen() + 10 -> CloneflixAvatarsComposeScreen() + 11 -> CloneflixVideoPlayerComposeScreen() + 12 -> CloneflixMoviePreviewComposeScreen() + } + } + } +} + +@Preview(name = "Phone Showcase Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Showcase Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixShowcasePreview() { + CloneflixTheme { + CloneflixShowcaseComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt new file mode 100644 index 00000000000..54c632ad304 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt @@ -0,0 +1,222 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography + +@Composable +fun CloneflixTypographyComposeScreen( + onStyleClick: (CloneflixTypography.StyleLevel) -> Unit = {}, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + val colors = CloneflixTheme.colors + + val regularStyles = listOf( + CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE to typography.regularLargeTitle, + CloneflixTypography.StyleLevel.REGULAR_TITLE1 to typography.regularTitle1, + CloneflixTypography.StyleLevel.REGULAR_TITLE2 to typography.regularTitle2, + CloneflixTypography.StyleLevel.REGULAR_TITLE3 to typography.regularTitle3, + CloneflixTypography.StyleLevel.REGULAR_TITLE4 to typography.regularTitle4, + CloneflixTypography.StyleLevel.REGULAR_HEADLINE1 to typography.regularHeadline1, + CloneflixTypography.StyleLevel.REGULAR_HEADLINE2 to typography.regularHeadline2, + CloneflixTypography.StyleLevel.REGULAR_BODY to typography.regularBody, + CloneflixTypography.StyleLevel.REGULAR_SMALL_BODY to typography.regularSmallBody, + CloneflixTypography.StyleLevel.REGULAR_CAPTION1 to typography.regularCaption1, + CloneflixTypography.StyleLevel.REGULAR_CAPTION2 to typography.regularCaption2 + ) + + val mediumStyles = listOf( + CloneflixTypography.StyleLevel.MEDIUM_LARGE_TITLE to typography.mediumLargeTitle, + CloneflixTypography.StyleLevel.MEDIUM_TITLE1 to typography.mediumTitle1, + CloneflixTypography.StyleLevel.MEDIUM_TITLE2 to typography.mediumTitle2, + CloneflixTypography.StyleLevel.MEDIUM_TITLE3 to typography.mediumTitle3, + CloneflixTypography.StyleLevel.MEDIUM_TITLE4 to typography.mediumTitle4, + CloneflixTypography.StyleLevel.MEDIUM_HEADLINE1 to typography.mediumHeadline1, + CloneflixTypography.StyleLevel.MEDIUM_HEADLINE2 to typography.mediumHeadline2, + CloneflixTypography.StyleLevel.MEDIUM_BODY to typography.mediumBody, + CloneflixTypography.StyleLevel.MEDIUM_SMALL_BODY to typography.mediumSmallBody, + CloneflixTypography.StyleLevel.MEDIUM_CAPTION1 to typography.mediumCaption1, + CloneflixTypography.StyleLevel.MEDIUM_CAPTION2 to typography.mediumCaption2 + ) + + val boldStyles = listOf( + CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE to typography.boldLargeTitle, + CloneflixTypography.StyleLevel.BOLD_TITLE1 to typography.boldTitle1, + CloneflixTypography.StyleLevel.BOLD_TITLE2 to typography.boldTitle2 + ) + + val displayStyles = listOf( + CloneflixTypography.StyleLevel.LOGO_BEBAS to typography.logoBebas, + CloneflixTypography.StyleLevel.HEADER_DISPLAY to typography.headerDisplay + ) + + LazyColumn( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .padding(horizontal = dimens.spacing2Xl) + ) { + item { + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + CloneflixHeader( + title = "Typography", + subtitle = "Netflix Sans • Bebas Neue", + iconRes = R.drawable.cloneflix_ic_typography + ) + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + } + + item { + Text( + text = "REGULAR STYLES", + style = typography.sectionHeader, + color = colors.primary, + modifier = Modifier.padding(bottom = dimens.spacingL) + ) + } + items(regularStyles) { (level, style) -> + TypographyStyleCard(level, style, onStyleClick) + Spacer(modifier = Modifier.height(dimens.spacingM)) + } + + item { + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + Text( + text = "MEDIUM STYLES", + style = typography.sectionHeader, + color = colors.primary, + modifier = Modifier.padding(bottom = dimens.spacingL) + ) + } + items(mediumStyles) { (level, style) -> + TypographyStyleCard(level, style, onStyleClick) + Spacer(modifier = Modifier.height(dimens.spacingM)) + } + + item { + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + Text( + text = "BOLD STYLES", + style = typography.sectionHeader, + color = colors.primary, + modifier = Modifier.padding(bottom = dimens.spacingL) + ) + } + items(boldStyles) { (level, style) -> + TypographyStyleCard(level, style, onStyleClick) + Spacer(modifier = Modifier.height(dimens.spacingM)) + } + + item { + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + Text( + text = "LOGO & DISPLAY", + style = typography.sectionHeader, + color = colors.primary, + modifier = Modifier.padding(bottom = dimens.spacingL) + ) + } + items(displayStyles) { (level, style) -> + TypographyStyleCard(level, style, onStyleClick) + Spacer(modifier = Modifier.height(dimens.spacingM)) + } + + item { + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } + } +} + +@Composable +private fun TypographyStyleCard( + level: CloneflixTypography.StyleLevel, + style: TextStyle, + onStyleClick: (CloneflixTypography.StyleLevel) -> Unit +) { + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val typography = CloneflixTheme.typography + + val sample = when (level) { + CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE, + CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE -> "Unlimited movies, TV shows, and more" + CloneflixTypography.StyleLevel.LOGO_BEBAS -> "CLONEFLIX" + else -> "Watch anywhere. Cancel anytime." + } + + CloneflixCard( + modifier = Modifier + .fillMaxWidth() + .clickable( + onClick = { onStyleClick(level) }, + role = Role.Button + ) + .focusable() + .semantics { + contentDescription = "${level.label}, Size: ${level.sizeSp}sp, Sample: $sample" + } + ) { + Column(modifier = Modifier.padding(dimens.spacingL)) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = level.label, + style = typography.regularCaption1, + color = colors.textMuted, + modifier = Modifier.weight(1f) + ) + Text( + text = "Size: ${level.sizeSp.toInt()}sp • LH: ${level.lineHeightSp.toInt()}sp", + style = typography.regularCaption2, + color = colors.textSecondary + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingS)) + + Text( + text = sample, + style = style, + color = colors.textPrimary + ) + } + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun CloneflixTypographyScreenPreview() { + CloneflixTheme { + CloneflixTypographyComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt new file mode 100644 index 00000000000..00ba233f558 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt @@ -0,0 +1,445 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.Toast +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +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 +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAudioSubtitlesSelector +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixNextEpisodeCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlaybackSpeed +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlaybackSpeedSelector +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlayerOverlayState +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixProgressSize +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoPlayerPattern +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoProgressIndicator +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVolumeControlPopup +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 + +@Composable +fun CloneflixVideoPlayerComposeScreen( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val typography = CloneflixTheme.typography + val colors = CloneflixTheme.colors + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var interactiveProgress by remember { mutableFloatStateOf(0.42f) } + var interactiveVolume by remember { mutableFloatStateOf(0.70f) } + var isMuted by remember { mutableStateOf(false) } + var demoSpeed by remember { mutableStateOf(CloneflixPlaybackSpeed.SPEED_1_0X) } + var demoAudio by remember { mutableStateOf("Japanese [Original]") } + var demoSubtitle by remember { mutableStateOf("English (CC)") } + + var selectedPatternState by remember { mutableStateOf(CloneflixPlayerOverlayState.DEFAULT) } + + val patternTabs = listOf( + "Default" to CloneflixPlayerOverlayState.DEFAULT, + "Sound Change" to CloneflixPlayerOverlayState.SOUND_CHANGE, + "Scroll Preview" to CloneflixPlayerOverlayState.SCROLL_PREVIEW, + "Next Preview" to CloneflixPlayerOverlayState.NEXT_PREVIEW, + "List Preview" to CloneflixPlayerOverlayState.LIST_PREVIEW, + "Speed Change" to CloneflixPlayerOverlayState.SPEED_CHANGE, + "Subtitles Change" to CloneflixPlayerOverlayState.SUBTITLES_CHANGE + ) + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(dimens.spacing2Xl) + ) { + // Section Header matching Figma + CloneflixHeader( + title = "Video Player", + subtitle = "Video Progress Indicator • Volume Slider • Speed • Subtitles • Patterns", + iconRes = R.drawable.cloneflix_ic_videoplayer + ) + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // ========================================== + // SECTION 1: COMPONENTS + // ========================================== + Text( + text = "COMPONENTS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 1. Video Progress Indicator + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Video Progress Indicator (Beginning, Default, Hover)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + Text( + text = "Size = Large, Progress = Beginning (00:12 / 1:45:00)", + style = typography.mediumBody, + color = colors.textPrimary + ) + CloneflixVideoProgressIndicator( + progress = 0.02f, + bufferedProgress = 0.15f, + durationMs = 105 * 60 * 1000L, + currentPositionMs = 12 * 1000L, + size = CloneflixProgressSize.LARGE, + onProgressChange = {} + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Size = Large, Progress = Middle Default (1:00:41 / 1:45:00)", + style = typography.mediumBody, + color = colors.textPrimary + ) + CloneflixVideoProgressIndicator( + progress = 0.58f, + bufferedProgress = 0.78f, + durationMs = 105 * 60 * 1000L, + currentPositionMs = 3641 * 1000L, + size = CloneflixProgressSize.LARGE, + onProgressChange = {} + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Size = Large, Progress = Middle Hover (Glow Ring & Scrubber)", + style = typography.mediumBody, + color = colors.textPrimary + ) + CloneflixVideoProgressIndicator( + progress = 0.58f, + bufferedProgress = 0.78f, + durationMs = 105 * 60 * 1000L, + currentPositionMs = 3641 * 1000L, + size = CloneflixProgressSize.LARGE, + isHoveredOrActive = true, + onProgressChange = {} + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Interactive Live Scrubber (Touch / D-Pad Seekable)", + style = typography.mediumBody, + color = colors.primary + ) + CloneflixVideoProgressIndicator( + progress = interactiveProgress, + bufferedProgress = 0.85f, + durationMs = 60 * 60 * 1000L, + currentPositionMs = (interactiveProgress * 60 * 60 * 1000L).toLong(), + size = CloneflixProgressSize.LARGE, + onProgressChange = { interactiveProgress = it } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 2. Volume Slider (High, Middle, Mute) + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Volume Slider (High / Middle / Mute)", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(32.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text(text = "High (85%)", style = typography.mediumCaption1, color = colors.textSecondary) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixVolumeControlPopup( + volume = 0.85f, + onVolumeChange = {}, + isMuted = false, + onToggleMute = {} + ) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text(text = "Middle (40%)", style = typography.mediumCaption1, color = colors.textSecondary) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixVolumeControlPopup( + volume = 0.40f, + onVolumeChange = {}, + isMuted = false, + onToggleMute = {} + ) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text(text = "Mute (0%)", style = typography.mediumCaption1, color = colors.textSecondary) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixVolumeControlPopup( + volume = 0f, + onVolumeChange = {}, + isMuted = true, + onToggleMute = {} + ) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text(text = "Interactive", style = typography.mediumCaption1, color = colors.primary) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixVolumeControlPopup( + volume = interactiveVolume, + onVolumeChange = { interactiveVolume = it }, + isMuted = isMuted, + onToggleMute = { isMuted = !isMuted } + ) + } + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 3. Next Episode Preview & Playback Speed + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Next Episode Preview & Speed Selector", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.Top + ) { + Column(modifier = Modifier.weight(1f)) { + Text(text = "Next Episode Floating Card", style = typography.mediumCaption1, color = colors.textSecondary) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixNextEpisodeCard( + episodeNumber = "Episode 2", + episodeTitle = "The Way of Shadows", + durationText = "48m", + synopsis = "A cryptic message from the past sends Haru on a clandestine reconnaissance mission.", + countdownSeconds = 8, + totalCountdownSeconds = 10, + onPlayNextClick = { Toast.makeText(context, "Playing Next Episode", Toast.LENGTH_SHORT).show() }, + onCancelClick = { Toast.makeText(context, "Dismissed Prompt", Toast.LENGTH_SHORT).show() } + ) + } + + Column(modifier = Modifier.weight(1.2f)) { + Text(text = "Playback Speed Indicator & Stepper", style = typography.mediumCaption1, color = colors.textSecondary) + Spacer(modifier = Modifier.height(8.dp)) + CloneflixPlaybackSpeedSelector( + currentSpeed = demoSpeed, + onSpeedSelected = { demoSpeed = it } + ) + } + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 4. Subtitles & Audio Selector + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Subtitles & Audio Dialog", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + val audioTracks = listOf("Japanese [Original]", "English", "Spanish (Latin America)", "French", "German") + val subtitleTracks = listOf("Off", "English (CC)", "Japanese (CC)", "Spanish", "Simplified Chinese", "Traditional Chinese", "Arabic", "German", "French") + + CloneflixAudioSubtitlesSelector( + audioTracks = audioTracks, + selectedAudio = demoAudio, + onAudioSelected = { demoAudio = it }, + subtitleTracks = subtitleTracks, + selectedSubtitle = demoSubtitle, + onSubtitleSelected = { demoSubtitle = it }, + onCloseClick = { Toast.makeText(context, "Closed Dialog", Toast.LENGTH_SHORT).show() } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // ========================================== + // SECTION 2: PATTERNS (FULL VIDEO PLAYER) + // ========================================== + Text( + text = "PATTERNS", + style = typography.sectionHeader, + color = colors.primary + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Interactive pattern state switcher + ScrollableTabRow( + selectedTabIndex = patternTabs.indexOfFirst { it.second == selectedPatternState }.coerceAtLeast(0), + containerColor = colors.surfaceElevated, + contentColor = colors.textPrimary, + edgePadding = 0.dp, + indicator = { tabPositions -> + val curIndex = patternTabs.indexOfFirst { it.second == selectedPatternState }.coerceAtLeast(0) + if (curIndex < tabPositions.size) { + TabRowDefaults.SecondaryIndicator( + modifier = Modifier.tabIndicatorOffset(tabPositions[curIndex]), + color = colors.primary, + height = 3.dp + ) + } + } + ) { + patternTabs.forEach { (label, state) -> + val isSelected = selectedPatternState == state + Tab( + selected = isSelected, + onClick = { selectedPatternState = state }, + text = { + Text( + text = label, + style = if (isSelected) typography.mediumBody else typography.regularBody, + color = if (isSelected) colors.textPrimary else colors.textSecondary + ) + } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Full Composed Video Player Pattern Card + CloneflixCard( + modifier = Modifier.fillMaxWidth(), + elevation = CloneflixCardElevation.SURFACE + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingXl), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Type = ${patternTabs.find { it.second == selectedPatternState }?.first ?: "Default"}", + style = typography.sectionHeader, + color = colors.textSecondary + ) + + CloneflixVideoPlayerPattern( + title = "Sanctuary", + episodeLabel = "E1 Episode 1", + overlayState = selectedPatternState, + onOverlayStateChange = { selectedPatternState = it }, + onBackClick = { Toast.makeText(context, "Back Pressed", Toast.LENGTH_SHORT).show() } + ) + } + } + } +} + +@Preview(name = "Video Player Screen Preview - TV", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Preview(name = "Video Player Screen Preview - Phone", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Composable +private fun VideoPlayerScreenPreview() { + CloneflixTheme { + CloneflixVideoPlayerComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt new file mode 100644 index 00000000000..757ed69f754 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt @@ -0,0 +1,247 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +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.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R + +val CloneflixFontFamily = FontFamily( + Font(R.font.netflix_sans, FontWeight.Normal), + Font(R.font.netflix_sans, FontWeight.Medium), + Font(R.font.netflix_sans, FontWeight.Bold) +) + +val PrimaryRed = Color(0xFFE50914) +val PrimaryBlack = Color(0xFF000000) +val PrimaryWhite = Color(0xFFFFFFFF) + +val Red100 = Color(0xFFEB3942) +val Red200 = Color(0xFFC11119) +val Red300 = Color(0xFFF50723) + +val Blue100 = Color(0xFF0071EB) +val Blue200 = Color(0xFF448EF4) +val Blue300 = Color(0xFF54B9C5) + +val GreenAccent = Color(0xFF46D369) + +val Grey10 = Color(0xFFE5E5E5) +val Grey20 = Color(0xFFDCDCDC) +val Grey25 = Color(0xFFD2D2D2) +val Grey50 = Color(0xFFBCBCBC) +val Grey100 = Color(0xFFB3B3B3) +val Grey150 = Color(0xFF979797) +val Grey200 = Color(0xFF808080) +val Grey250 = Color(0xFF777777) +val Grey300T40 = Color(0x666D6D6E) +val Grey300T70 = Color(0xB26D6D6E) +val Grey350 = Color(0xFF545454) +val Grey400 = Color(0xFF414141) +val Grey450 = Color(0xFF404040) +val Grey500 = Color(0xFF3A3A3A) +val Grey550 = Color(0xFF363636) +val Grey600T60 = Color(0x99333333) +val Grey600 = Color(0xFF333333) +val Grey650 = Color(0xFF2F2F2F) +val Grey700 = Color(0xFF2A2A2A) +val Grey750 = Color(0xFF262626) +val Grey800 = Color(0xFF232323) +val Grey850 = Color(0xFF181818) +val Grey900 = Color(0xFF141414) + +val TransparentWhite15 = Color(0x26FFFFFF) +val TransparentWhite20 = Color(0x33FFFFFF) +val TransparentWhite30 = Color(0x4CFFFFFF) +val TransparentWhite35 = Color(0x59FFFFFF) +val TransparentWhite50 = Color(0x7FFFFFFF) +val TransparentWhite70 = Color(0xB2FFFFFF) + +val TransparentBlack30 = Color(0x4C000000) +val TransparentBlack60 = Color(0x99000000) +val TransparentBlack90 = Color(0xE5000000) + +val SurfaceDark = Color(0xFF232323) +val SurfaceElevated = Color(0xFF181818) +val CanvasBackground = Color(0xFF141414) +val BorderSubtle = Color(0x33FFFFFF) +val DividerColor = Color(0x26FFFFFF) + +@Immutable +data class CloneflixColors( + val primary: Color = PrimaryRed, + val onPrimary: Color = PrimaryWhite, + val background: Color = CanvasBackground, + val onBackground: Color = PrimaryWhite, + val surface: Color = SurfaceDark, + val onSurface: Color = PrimaryWhite, + val surfaceElevated: Color = SurfaceElevated, + val border: Color = BorderSubtle, + val divider: Color = DividerColor, + val textPrimary: Color = PrimaryWhite, + val textSecondary: Color = Grey200, + val textMuted: Color = Grey100, + val textDimmed: Color = Grey50, + val redAccent100: Color = Red100, + val redAccent200: Color = Red200, + val redAccent300: Color = Red300, + val blueAccent100: Color = Blue100, + val blueAccent200: Color = Blue200, + val blueAccent300: Color = Blue300, + val greenAccent: Color = GreenAccent +) + +@Immutable +data class CloneflixTypography( + val regularCaption2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 11.sp, lineHeight = 14.sp), + val regularCaption1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 16.sp), + val regularSmallBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 18.sp), + val regularBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 20.sp), + val regularHeadline2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 17.sp, lineHeight = 22.sp), + val regularHeadline1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 18.sp, lineHeight = 22.sp), + val regularTitle4: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 20.sp, lineHeight = 30.sp), + val regularTitle3: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 21.sp, lineHeight = 25.sp), + val regularTitle2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 24.sp, lineHeight = 30.sp), + val regularTitle1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 27.sp, lineHeight = 36.sp), + val regularLargeTitle: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 50.sp, lineHeight = 64.sp), + + val mediumCaption2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 15.sp), + val mediumCaption1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 13.sp, lineHeight = 17.sp), + val mediumSmallBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 18.sp), + val mediumBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 16.sp, lineHeight = 24.sp), + val mediumHeadline2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 20.sp, lineHeight = 30.sp), + val mediumHeadline1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 21.sp, lineHeight = 25.sp), + val mediumTitle4: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 22.sp, lineHeight = 22.sp), + val mediumTitle3: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 24.sp, lineHeight = 31.sp), + val mediumTitle2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 28.sp, lineHeight = 45.sp), + val mediumTitle1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 30.sp, lineHeight = 45.sp), + val mediumLargeTitle: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 33.sp, lineHeight = 42.sp), + + val boldTitle2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 20.sp, lineHeight = 30.sp), + val boldTitle1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 48.sp, lineHeight = 62.sp), + val boldLargeTitle: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 55.sp, lineHeight = 70.sp), + + val headerDisplay: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 36.sp, lineHeight = 44.sp), + val sectionHeader: TextStyle = TextStyle(fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Bold, fontSize = 24.sp, lineHeight = 30.sp, color = PrimaryRed), + val logoBebas: TextStyle = TextStyle(fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Bold, fontSize = 32.sp, lineHeight = 38.sp, color = PrimaryRed) +) + +@Immutable +data class CloneflixDimens( + val spacingXxs: Dp = 2.dp, + val spacingXs: Dp = 4.dp, + val spacingS: Dp = 8.dp, + val spacingM: Dp = 12.dp, + val spacingL: Dp = 16.dp, + val spacingXl: Dp = 20.dp, + val spacing2Xl: Dp = 24.dp, + val spacing3Xl: Dp = 32.dp, + val spacing4Xl: Dp = 40.dp, + val spacing5Xl: Dp = 48.dp, + val spacing6Xl: Dp = 64.dp, + val spacing7Xl: Dp = 80.dp, + + val radiusXs: Dp = 4.dp, + val radiusS: Dp = 8.dp, + val radiusM: Dp = 12.dp, + val radiusL: Dp = 16.dp, + val radiusXl: Dp = 24.dp, + val radiusFull: Dp = 999.dp, + + val headerIconSize: Dp = 80.dp, + val headerIconStroke: Dp = 1.4.dp, + val buttonHeightLarge: Dp = 48.dp, + val buttonHeightMedium: Dp = 40.dp, + val buttonHeightSmall: Dp = 32.dp, + val inputHeight: Dp = 52.dp, + val swatchSize: Dp = 56.dp, + val dividerThickness: Dp = 1.dp, + + val iconXs: Dp = 14.dp, + val iconS: Dp = 16.dp, + val iconM: Dp = 24.dp, + val iconL: Dp = 32.dp, + val iconXl: Dp = 40.dp, + + val borderSubtle: Dp = 0.5.dp, + val borderDefault: Dp = 1.dp, + val borderFocus: Dp = 2.dp, + + val progressHeight: Dp = 3.5.dp +) + +val LocalCloneflixColors = staticCompositionLocalOf { CloneflixColors() } +val LocalCloneflixTypography = staticCompositionLocalOf { CloneflixTypography() } +val LocalCloneflixDimens = staticCompositionLocalOf { CloneflixDimens() } + +val CloneflixShapes = Shapes( + extraSmall = RoundedCornerShape(4.dp), + small = RoundedCornerShape(8.dp), + medium = RoundedCornerShape(12.dp), + large = RoundedCornerShape(16.dp), + extraLarge = RoundedCornerShape(24.dp) +) + +object CloneflixTheme { + val colors: CloneflixColors + @Composable + @ReadOnlyComposable + get() = LocalCloneflixColors.current + + val typography: CloneflixTypography + @Composable + @ReadOnlyComposable + get() = LocalCloneflixTypography.current + + val dimens: CloneflixDimens + @Composable + @ReadOnlyComposable + get() = LocalCloneflixDimens.current + + val shapes: Shapes + @Composable + @ReadOnlyComposable + get() = MaterialTheme.shapes +} + +@Composable +fun CloneflixTheme( + colors: CloneflixColors = CloneflixColors(), + typography: CloneflixTypography = CloneflixTypography(), + dimens: CloneflixDimens = CloneflixDimens(), + content: @Composable () -> Unit +) { + val materialColors = darkColorScheme( + primary = colors.primary, + onPrimary = colors.onPrimary, + background = colors.background, + onBackground = colors.onBackground, + surface = colors.surface, + onSurface = colors.onSurface + ) + + CompositionLocalProvider( + LocalCloneflixColors provides colors, + LocalCloneflixTypography provides typography, + LocalCloneflixDimens provides dimens + ) { + MaterialTheme( + colorScheme = materialColors, + shapes = CloneflixShapes, + content = content + ) + } +} From 773fae7fcb46bf07227b41330a40c3d11a92d47d Mon Sep 17 00:00:00 2001 From: Spring Date: Mon, 24 Aug 2026 23:00:10 +0700 Subject: [PATCH 05/35] test(design-system): add unit tests for Cloneflix design tokens and component defaults --- .../ui/revamp/CloneflixDesignSystemTest.kt | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt diff --git a/app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt b/app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt new file mode 100644 index 00000000000..6cdac886328 --- /dev/null +++ b/app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt @@ -0,0 +1,75 @@ +package com.lagradost.cloudstream3.ui.revamp + +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens +import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class CloneflixDesignSystemTest { + + @Test + fun testColorTokensCountAndIntegrity() { + val tokens = CloneflixColors.getAllTokens() + assertTrue(tokens.isNotEmpty()) + assertTrue(tokens.size >= 30) + + val primaryRed = tokens.first { it.name == "Primary / Red" } + assertEquals("#E50914", primaryRed.hex) + + val categories = tokens.map { it.category }.distinct() + assertTrue(categories.contains("Primary")) + assertTrue(categories.contains("Secondary")) + assertTrue(categories.contains("Neutral Greys")) + assertTrue(categories.contains("Transparent White")) + assertTrue(categories.contains("Transparent Black")) + } + + @Test + fun testTypographyHierarchy() { + val levels = CloneflixTypography.StyleLevel.values() + assertTrue(levels.size >= 25) + + val regularLarge = CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE + assertEquals(50f, regularLarge.sizeSp) + assertEquals(64f, regularLarge.lineHeightSp) + + val boldLarge = CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE + assertEquals(55f, boldLarge.sizeSp) + assertEquals(70f, boldLarge.lineHeightSp) + } + + @Test + fun testDimensTokens() { + assertEquals(16f, CloneflixDimens.RADIUS_L) + assertEquals(80, CloneflixDimens.HEADER_ICON_SIZE) + assertEquals(1.4f, CloneflixDimens.HEADER_ICON_STROKE_WIDTH) + assertEquals(48, CloneflixDimens.BUTTON_HEIGHT_LARGE) + assertEquals(40, CloneflixDimens.BUTTON_HEIGHT_MEDIUM) + assertEquals(32, CloneflixDimens.BUTTON_HEIGHT_SMALL) + } + + @Test + fun testMovieCardDefaultsAndTokens() { + assertEquals(1.05f, com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTokens.FOCUS_SCALE_FACTOR) + assertEquals( + 218.dp, + com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardDefaults.cardWidth( + com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType.DEFAULT, + com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize.MEDIUM + ) + ) + assertEquals( + 128.dp, + com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardDefaults.cardWidth( + com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType.DEFAULT, + com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize.SMALL + ) + ) + assertTrue(com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType.TOP_10.stringRes != 0) + assertTrue(com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType.RECENTLY_ADDED.stringRes != 0) + assertTrue(com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType.NEW_SEASON.stringRes != 0) + } +} From 433c8ccdfa3d3a76087ec7d1a4afec484353ef6a Mon Sep 17 00:00:00 2001 From: Spring Date: Mon, 24 Aug 2026 23:28:48 +0700 Subject: [PATCH 06/35] feat(ui): revamp Movie Details screen to Cloneflix and integrate into TV navigation --- .gitignore | 7 +- .../ui/result/ResultFragmentTv.kt | 144 ++- .../compose/components/CloneflixMovieCard.kt | 57 +- .../CloneflixMovieDetailsComposeScreen.kt | 1037 +++++++++++++++++ .../screens/CloneflixShowcaseComposeScreen.kt | 3 +- .../compose/theme/CloneflixComposeTheme.kt | 16 +- app/src/main/res/font/netflix_sans_black.ttf | Bin 0 -> 147116 bytes .../res/font/netflix_sans_black_italic.ttf | Bin 0 -> 129048 bytes app/src/main/res/font/netflix_sans_bold.ttf | Bin 0 -> 146248 bytes .../res/font/netflix_sans_bold_italic.ttf | Bin 0 -> 143256 bytes app/src/main/res/font/netflix_sans_italic.ttf | Bin 0 -> 150520 bytes app/src/main/res/font/netflix_sans_light.ttf | Bin 0 -> 146064 bytes .../res/font/netflix_sans_light_italic.ttf | Bin 0 -> 149336 bytes app/src/main/res/font/netflix_sans_medium.ttf | Bin 0 -> 146636 bytes .../res/font/netflix_sans_medium_italic.ttf | Bin 0 -> 147660 bytes .../main/res/font/netflix_sans_regular.ttf | Bin 0 -> 147476 bytes app/src/main/res/font/netflix_sans_thin.ttf | Bin 0 -> 146064 bytes .../res/font/netflix_sans_thin_italic.ttf | Bin 0 -> 148228 bytes .../main/res/layout/fragment_result_tv.xml | 6 + 19 files changed, 1242 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt create mode 100644 app/src/main/res/font/netflix_sans_black.ttf create mode 100644 app/src/main/res/font/netflix_sans_black_italic.ttf create mode 100644 app/src/main/res/font/netflix_sans_bold.ttf create mode 100644 app/src/main/res/font/netflix_sans_bold_italic.ttf create mode 100644 app/src/main/res/font/netflix_sans_italic.ttf create mode 100644 app/src/main/res/font/netflix_sans_light.ttf create mode 100644 app/src/main/res/font/netflix_sans_light_italic.ttf create mode 100644 app/src/main/res/font/netflix_sans_medium.ttf create mode 100644 app/src/main/res/font/netflix_sans_medium_italic.ttf create mode 100644 app/src/main/res/font/netflix_sans_regular.ttf create mode 100644 app/src/main/res/font/netflix_sans_thin.ttf create mode 100644 app/src/main/res/font/netflix_sans_thin_italic.ttf diff --git a/.gitignore b/.gitignore index 39baccaee70..a96e2c4d8a7 100644 --- a/.gitignore +++ b/.gitignore @@ -226,9 +226,8 @@ fabric.properties !/gradle/wrapper/gradle-wrapper.jar # End of https://www.toptal.com/developers/gitignore/api/kotlin,java,android,androidstudio,visualstudiocode -.agents/rules/antigravity-rtk-rules.md -.agents/skills/cloudstream/SKILL.md -.codegraph/.gitignore -.rtk/filters.toml +.agents/ +.codegraph/ +.rtk/ CLAUDE.md gradle/gradle-daemon-jvm.properties diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index c316fae1715..f77ca1e1a21 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -66,6 +66,15 @@ import com.lagradost.cloudstream3.utils.getImageFromDrawable import com.lagradost.cloudstream3.utils.setText import com.lagradost.cloudstream3.utils.setTextHtml import com.lagradost.cloudstream3.utils.txt +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.ui.revamp.compose.screens.CloneflixMovieDetailsComposeScreen +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD +import com.lagradost.cloudstream3.ui.search.SearchClickCallback +import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage class ResultFragmentTv : BaseFragment( BindingCreator.Inflate(FragmentResultTvBinding::inflate) @@ -73,6 +82,16 @@ class ResultFragmentTv : BaseFragment( private lateinit var viewModel: ResultViewModel2 + private var composePageState by mutableStateOf?>(null) + private var composeEpisodesState by mutableStateOf>(emptyList()) + private var composeRecommendationsState by mutableStateOf>(emptyList()) + private var composeActorsState by mutableStateOf>(emptyList()) + private var composeWatchStatusState by mutableStateOf(WatchType.NONE) + private var composeSeasonsState by mutableStateOf>(emptyList()) + private var composeSelectedSeasonIndexState by mutableStateOf(0) + private var composeHasTrailersState by mutableStateOf(false) + private var composeResumeWatchingState by mutableStateOf(null) + override fun onDestroyView() { updateUIEvent -= ::updateUI activity?.detachBackPressedCallback(this@ResultFragmentTv.toString()) @@ -273,6 +292,110 @@ class ResultFragmentTv : BaseFragment( // ===== ===== ===== var comingSoon = false + binding.resultComposeView.setContent { + CloneflixTheme { + val page = composePageState + if (page is Resource.Success) { + val d = page.value + val title = d.titleText.asStringNull(context) ?: d.title + val plot = d.plotText.asStringNull(context) ?: "" + val year = d.yearText?.asStringNull(context) ?: "" + val duration = d.durationText?.asStringNull(context) ?: "" + val rating = d.ratingText?.asStringNull(context) ?: "98% Match" + val contentRating = d.contentRatingText?.asStringNull(context) ?: "TV-MA" + val genres = d.tags + val actors = composeActorsState.ifEmpty { d.actors ?: emptyList() } + + CloneflixMovieDetailsComposeScreen( + title = title, + backdropUrl = d.posterBackgroundImage ?: d.posterImage, + posterUrl = d.posterImage, + logoUrl = d.logoUrl, + matchScore = rating, + releaseYear = year, + seasonsCount = duration, + maturityRating = contentRating, + synopsis = plot, + genres = genres, + dynamicActors = actors, + dynamicEpisodes = composeEpisodesState, + dynamicRecommendations = composeRecommendationsState, + dynamicSeasons = composeSeasonsState, + selectedSeasonIndex = composeSelectedSeasonIndexState, + isInWatchList = composeWatchStatusState != WatchType.NONE, + hasTrailers = composeHasTrailersState, + onPlayClick = { + val resume = composeResumeWatchingState + if (resume != null) { + viewModel.handleAction( + EpisodeClickEvent( + storedData.playerAction, + resume.result + ) + ) + } else { + val ep = composeEpisodesState.firstOrNull() + if (ep != null) { + viewModel.handleAction( + EpisodeClickEvent( + storedData.playerAction, + ep + ) + ) + } else { + binding.resultPlayMovieButton.performClick() + } + } + }, + onEpisodeClick = { ep -> + viewModel.handleAction( + EpisodeClickEvent( + storedData.playerAction, + ep + ) + ) + }, + onEpisodeDownloadClick = { ep -> + viewModel.handleAction( + EpisodeClickEvent( + ACTION_DOWNLOAD_EPISODE, + ep + ) + ) + }, + onSeasonSelect = { idx -> + viewModel.changeSeason(idx) + }, + onAddToListClick = { + binding.resultBookmarkButton.performClick() + }, + onLikeClick = { + binding.resultFavoriteButton.performClick() + }, + onTrailerClick = { + binding.resultPlayTrailerButton.performClick() + }, + onActorClick = { actorName -> + QuickSearchFragment.pushSearch(activity, actorName) + }, + onRecommendationClick = { rec -> + SearchHelper.handleSearchClickCallback( + SearchClickCallback( + SEARCH_ACTION_LOAD, + binding.root, + 0, + rec + ) + ) + }, + onCloseClick = { + activity?.popCurrentPage() + } + ) + } + } + } + binding.apply { //episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f @@ -477,6 +600,7 @@ class ResultFragmentTv : BaseFragment( } observeNullable(viewModel.resumeWatching) { resume -> + composeResumeWatchingState = resume binding.apply { if (resume == null) { return@observeNullable @@ -547,9 +671,10 @@ class ResultFragmentTv : BaseFragment( observe(viewModel.trailers) { trailersLinks -> context?.updateHasTrailers() - if (!LoadResponse.isTrailersEnabled) return@observe val extractedTrailerLinks = trailersLinks.flatMap { it.mirros } .map { (extractedTrailerLink, _) -> extractedTrailerLink } + composeHasTrailersState = extractedTrailerLinks.isNotEmpty() + if (!LoadResponse.isTrailersEnabled) return@observe binding.apply { resultPlayTrailer.isGone = extractedTrailerLinks.isEmpty() resultPlayTrailerButton.setOnClickListener { @@ -567,6 +692,7 @@ class ResultFragmentTv : BaseFragment( } observe(viewModel.watchStatus) { watchType -> + composeWatchStatusState = watchType binding.apply { resultBookmarkText.setText(watchType.stringRes) @@ -768,6 +894,7 @@ class ResultFragmentTv : BaseFragment( binding.resultRangeSelection.select(selected) } observe(viewModel.selectedSeasonIndex) { selected -> + composeSelectedSeasonIndexState = selected binding.resultSeasonSelection.select(selected) } observe(viewModel.selectedDubStatusIndex) { selected -> @@ -780,9 +907,11 @@ class ResultFragmentTv : BaseFragment( binding.resultDubSelection.update(it) } observe(viewModel.seasonSelections) { + composeSeasonsState = it.map { s -> s.first?.asStringNull(context) ?: "" } binding.resultSeasonSelection.update(it) } observe(viewModel.recommendations) { recommendations -> + composeRecommendationsState = recommendations setRecommendations(recommendations, null) } @@ -805,6 +934,9 @@ class ResultFragmentTv : BaseFragment( var hasLoadedEpisodesOnce = false observeNullable(viewModel.episodes) { episodes -> if (episodes == null) return@observeNullable + if (episodes is Resource.Success) { + composeEpisodesState = episodes.value + } binding.apply { if (comingSoon) resultBookmarkButton.requestFocus() @@ -859,10 +991,13 @@ class ResultFragmentTv : BaseFragment( observeNullable(viewModel.page) { data -> if (data == null) return@observeNullable + composePageState = data binding.apply { when (data) { is Resource.Success -> { val d = data.value + composeActorsState = d.actors ?: emptyList() + resultComposeView.isVisible = true resultVpn.setText(d.vpnText) resultInfo.setText(d.metaText) resultNoEpisodes.setText(d.noEpisodesFoundText) @@ -945,15 +1080,18 @@ class ResultFragmentTv : BaseFragment( } } - is Resource.Loading -> {} + is Resource.Loading -> { + resultComposeView.isGone = true + } is Resource.Failure -> { + resultComposeView.isGone = true resultErrorText.text = storedData.url.plus("\n") + data.errorString } } - resultFinishLoading.isVisible = data is Resource.Success + resultFinishLoading.isGone = true resultLoading.isVisible = data is Resource.Loading diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt index 3ca2f460592..f95d8c56c7c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt @@ -38,6 +38,9 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.viewinterop.AndroidView +import android.widget.ImageView +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription @@ -99,6 +102,7 @@ fun CloneflixMovieCard( runtime: String? = null, timestamp: String? = null, subtitle: String? = null, + posterUrl: String? = null, onClick: (() -> Unit)? = null ) { val interactionSource = remember { MutableInteractionSource() } @@ -191,7 +195,8 @@ fun CloneflixMovieCard( runtime = runtime, timestamp = timestamp, progress = progress, - showCenterPlay = false + showCenterPlay = false, + posterUrl = posterUrl ) } } @@ -213,7 +218,8 @@ fun CloneflixMovieCard( timestamp = timestamp, progress = progress, showCenterPlay = type == CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY || - type == CloneflixMovieCardType.EPISODE + type == CloneflixMovieCardType.EPISODE, + posterUrl = posterUrl ) } @@ -240,11 +246,12 @@ private fun MovieCardSurface( runtime: String?, timestamp: String?, progress: Float?, - showCenterPlay: Boolean + showCenterPlay: Boolean, + posterUrl: String? = null ) { val typography = CloneflixTheme.typography - // Gradient Background Mockup + // Background Box( modifier = Modifier .fillMaxSize() @@ -258,18 +265,32 @@ private fun MovieCardSurface( ) ) ) { - // Title Text in Mockup Center - Text( - text = title, - style = typography.regularCaption1, - fontWeight = FontWeight.SemiBold, - color = Grey50, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .align(Alignment.Center) - .padding(horizontal = 8.dp) - ) + if (!posterUrl.isNullOrBlank()) { + AndroidView( + factory = { ctx -> + ImageView(ctx).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + } + }, + update = { imageView -> + imageView.loadImage(posterUrl) + }, + modifier = Modifier.fillMaxSize() + ) + } else { + // Title Text in Mockup Center + Text( + text = title, + style = typography.regularCaption1, + fontWeight = FontWeight.SemiBold, + color = Grey50, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .align(Alignment.Center) + .padding(horizontal = 8.dp) + ) + } // Top-Left Logo / Lettermark if (showLogo) { @@ -553,6 +574,7 @@ fun CloneflixMovieBlockRow( runtime = item.runtime, timestamp = item.timestamp, subtitle = item.subtitle, + posterUrl = item.posterUrl, onClick = { onItemClick?.invoke(item) } ) } @@ -570,7 +592,8 @@ data class CloneflixMovieCardItem( val progress: Float? = null, val runtime: String? = null, val timestamp: String? = null, - val subtitle: String? = null + val subtitle: String? = null, + val posterUrl: String? = null ) /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt new file mode 100644 index 00000000000..e66e3f39314 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -0,0 +1,1037 @@ +package com.lagradost.cloudstream3.ui.revamp.compose.screens + +import android.widget.ImageView +import android.widget.Toast +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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.draw.scale +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.getWatchProgress +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCircleActionButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdown +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroPlayButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoVariant +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoView +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMaturityRating +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCard +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardItem +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixSampleData +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTokens +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoQualityBadge +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage + +/** + * Data representation for trailer / extra clips in Figma Node 121:4925. + */ +data class CloneflixTrailerData( + val title: String, + val runtime: String, + val rawTrailer: Any? = null +) + +/** + * Movie Details Page / Details Only Screen based on Figma Node 121:4892. + * Fully dynamic and optimized for Google TV / Android TV D-Pad traversal and Mobile touch. + */ +@Composable +fun CloneflixMovieDetailsComposeScreen( + modifier: Modifier = Modifier, + title: String = CloneflixSampleData.SAMPLE_TITLE_HOUSE_OF_NINJAS, + backdropUrl: String? = null, + posterUrl: String? = null, + logoUrl: String? = null, + matchScore: String = CloneflixSampleData.SAMPLE_MATCH_NEW, + releaseYear: String = "2024", + seasonsCount: String = CloneflixSampleData.SAMPLE_DURATION_SEASONS, + quality: String = CloneflixSampleData.SAMPLE_QUALITY_HD, + maturityRating: String = CloneflixSampleData.SAMPLE_MATURITY_RATING, + advisories: String = "smoking, violence", + top10RankText: String? = "#2 in TV Shows Today", + synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + cast: List = listOf("Kento Kaku", "Yosuke Eguchi", "Tae Kimura", "Kengo Kora", "Aju Makita", "Nobuko Miyamoto"), + genres: List = listOf("TV Dramas", "Japanese", "TV Thrillers", "Action & Adventure TV"), + moodTags: List = listOf("Dark", "Suspenseful", "Exciting"), + creator: String = "Dave Boyle", + writers: List = listOf("Dave Boyle", "Masahiro Yamaura", "Kota Oura", "Kanna Kimura"), + dynamicEpisodes: List? = null, + dynamicRecommendations: List? = null, + dynamicActors: List? = null, + dynamicSeasons: List? = null, + selectedSeasonIndex: Int = 0, + isInWatchList: Boolean = false, + hasTrailers: Boolean = false, + onPlayClick: () -> Unit = {}, + onEpisodeClick: ((ResultEpisode) -> Unit)? = null, + onEpisodeDownloadClick: ((ResultEpisode) -> Unit)? = null, + onSeasonSelect: ((Int) -> Unit)? = null, + onAddToListClick: () -> Unit = {}, + onLikeClick: () -> Unit = {}, + onMuteToggle: () -> Unit = {}, + onTrailerClick: () -> Unit = {}, + onActorClick: ((String) -> Unit)? = null, + onRecommendationClick: ((SearchResponse) -> Unit)? = null, + onCloseClick: () -> Unit = {} +) { + val context = LocalContext.current + val colors = CloneflixTheme.colors + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + val scrollState = rememberScrollState() + + var inMyListState by remember(isInWatchList) { mutableStateOf(isInWatchList) } + + val seasonOptions = remember(dynamicSeasons) { + if (!dynamicSeasons.isNullOrEmpty()) dynamicSeasons else listOf("Season 1", "Season 2", "Season 3") + } + + var selectedSeasonText by remember(selectedSeasonIndex, seasonOptions) { + mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "Season 1" }) + } + + fun showToast(message: String) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + + // Default fallback sample episodes if none passed + val sampleEpisodes = remember { + listOf( + ResultEpisode( + headerName = "Season 1", + name = "The Offer", + poster = null, + episode = 1, + seasonIndex = 1, + season = 1, + data = "", + apiName = "", + id = 1, + index = 0, + position = 2754000, + duration = 3240000, + score = null, + description = "Haru Tawara lives a quiet life as a vending machine restocker, until an unexpected encounter reopens the door to his past.", + isFiller = false, + tvType = com.lagradost.cloudstream3.TvType.TvSeries, + parentId = 0, + videoWatchState = com.lagradost.cloudstream3.ui.result.VideoWatchState.None, + runTime = 54 + ), + ResultEpisode( + headerName = "Season 1", + name = "The Trail", + poster = null, + episode = 2, + seasonIndex = 1, + season = 1, + data = "", + apiName = "", + id = 2, + index = 1, + position = 624000, + duration = 3120000, + score = null, + description = "As the BNM uncovers suspicious activity surrounding a religious group, Haru's grandmother makes an intriguing discovery of her own.", + isFiller = false, + tvType = com.lagradost.cloudstream3.TvType.TvSeries, + parentId = 0, + videoWatchState = com.lagradost.cloudstream3.ui.result.VideoWatchState.None, + runTime = 52 + ), + ResultEpisode( + headerName = "Season 1", + name = "The Flower", + poster = null, + episode = 3, + seasonIndex = 1, + season = 1, + data = "", + apiName = "", + id = 3, + index = 2, + position = 0, + duration = 3300000, + score = null, + description = "Karen tracks down a lead connected to the murder case. Meanwhile, Soichi grapples with family secrets and hidden identities.", + isFiller = false, + tvType = com.lagradost.cloudstream3.TvType.TvSeries, + parentId = 0, + videoWatchState = com.lagradost.cloudstream3.ui.result.VideoWatchState.None, + runTime = 55 + ) + ) + } + + val episodesToDisplay = dynamicEpisodes ?: sampleEpisodes + + val trailersList = remember { + listOf( + CloneflixTrailerData("Trailer: Season 1", "2m 18s"), + CloneflixTrailerData("Official Teaser", "1m 12s"), + CloneflixTrailerData("Sneak Peek: The Shinobi Legacy", "3m 05s"), + CloneflixTrailerData("Behind The Scenes Stunts", "4m 42s") + ) + } + + val castList = remember(dynamicActors, cast) { + dynamicActors?.map { it.actor.name }?.filter { it.isNotBlank() }?.ifEmpty { cast } ?: cast + } + + Column( + modifier = modifier + .fillMaxSize() + .background(colors.background) + .verticalScroll(scrollState) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) + ) { + // ========================================== + // 1. HERO BANNER SECTION (Figma 121:4893) + // ========================================== + Box( + modifier = Modifier + .fillMaxWidth() + .height(440.dp) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background( + Brush.verticalGradient( + colors = listOf( + Grey700, + Grey850, + PrimaryBlack + ) + ) + ) + .border( + BorderStroke(dimens.borderDefault, colors.border), + RoundedCornerShape(CloneflixTokens.RadiusCardMedium) + ) + ) { + // Live Backdrop Image (if available) + if (!backdropUrl.isNullOrBlank()) { + AndroidView( + factory = { ctx -> + ImageView(ctx).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + } + }, + update = { imageView -> + imageView.loadImage(backdropUrl) + }, + modifier = Modifier.fillMaxSize() + ) + } + + // Top-Right Close Button + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_close), + contentDescription = "Close", + onClick = { + onCloseClick() + }, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(dimens.spacingL) + ) + + // Hero Bottom Gradient Overlay & Title/Action Controls + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.70f) + .align(Alignment.BottomStart) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + TransparentBlack60, + PrimaryBlack + ) + ) + ) + ) + + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(dimens.spacing2Xl) + ) { + // Wordmark / Logo / Title + if (!logoUrl.isNullOrBlank()) { + Box( + modifier = Modifier + .height(64.dp) + .width(220.dp) + ) { + AndroidView( + factory = { ctx -> + ImageView(ctx).apply { + scaleType = ImageView.ScaleType.FIT_START + } + }, + update = { imageView -> + imageView.loadImage(logoUrl) + }, + modifier = Modifier.fillMaxSize() + ) + } + } else { + CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) + Spacer(modifier = Modifier.height(dimens.spacingS)) + Text( + text = title, + style = typography.boldTitle1, + fontSize = 32.sp, + color = PrimaryWhite, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Action Buttons Row (Play, Add/List, Thumbs Up, Volume/Audio, Trailer) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + // White Primary Play Button + CloneflixHeroPlayButton( + onClick = onPlayClick, + text = "Play" + ) + + // Add to My List (+) + CloneflixCircleActionButton( + icon = painterResource( + id = if (inMyListState) R.drawable.cloneflix_ic_check else R.drawable.cloneflix_ic_plus + ), + contentDescription = if (inMyListState) "In My List" else "Add to My List", + onClick = { + inMyListState = !inMyListState + onAddToListClick() + } + ) + + // Thumbs Up / Like + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), + contentDescription = "Like", + onClick = onLikeClick + ) + + // Trailer Toggle (if available) + if (hasTrailers) { + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = "Trailer", + onClick = onTrailerClick + ) + } + + // Mute / Volume Toggle + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_mute), + contentDescription = "Audio Toggle", + onClick = onMuteToggle + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing2Xl)) + + // ========================================== + // 2. MOVIE INFO & METADATA SECTION (Figma 121:4894) + // ========================================== + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) + ) { + // Left Column: Badges, Top 10, Synopsis + Column( + modifier = Modifier + .weight(0.65f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + // Line 1: Match Score, Year, Duration, Quality, AD Icon + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = matchScore, + color = GreenAccent, + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + + Text( + text = releaseYear, + color = PrimaryWhite, + style = typography.regularBody + ) + + Text( + text = seasonsCount, + color = PrimaryWhite, + style = typography.regularBody + ) + + CloneflixVideoQualityBadge(quality = quality) + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_ad), + contentDescription = "Audio Description", + tint = PrimaryWhite, + modifier = Modifier.size(20.dp) + ) + } + + // Line 2: Rating & Content Advisories + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + CloneflixMaturityRating(rating = maturityRating) + Text( + text = advisories, + color = Grey100, + style = typography.regularCaption1 + ) + } + + // Line 3: Top 10 Rank Badge (if available) + if (!top10RankText.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + modifier = Modifier.padding(vertical = dimens.spacingXs) + ) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(PrimaryRed) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = "TOP 10", + style = typography.regularCaption2, + color = PrimaryWhite, + fontWeight = FontWeight.Black + ) + } + Text( + text = top10RankText, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite + ) + } + } + + // Line 4: Synopsis + Text( + text = synopsis.ifBlank { "No synopsis available." }, + style = typography.regularBody, + color = Grey50, + lineHeight = 22.sp + ) + } + + // Right Column: Cast, Genres, Mood Tags + Column( + modifier = Modifier + .weight(0.35f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + // Cast Row + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Cast", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = castList.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + + // Genres Row + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Genres", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = genres.joinToString(", ").ifBlank { "TV Series, Movies" }, + style = typography.regularCaption1, + color = PrimaryWhite + ) + } + + // This show is (Moods) + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "This show is", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = moodTags.joinToString(", ").ifBlank { "Entertaining, Exciting" }, + style = typography.regularCaption1, + color = PrimaryWhite + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // ========================================== + // 3. EPISODES & SEASONS SECTION (Figma 121:4895) + // ========================================== + if (episodesToDisplay.isNotEmpty()) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Episodes", + style = typography.boldTitle2, + fontSize = 24.sp, + color = PrimaryWhite + ) + + // Season Selection Dropdown + if (seasonOptions.size > 1) { + CloneflixDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { seasonStr -> + selectedSeasonText = seasonStr + val idx = seasonOptions.indexOf(seasonStr) + if (idx >= 0) onSeasonSelect?.invoke(idx) + }, + width = 180.dp + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // Episodes List + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + episodesToDisplay.forEach { ep -> + CloneflixDynamicEpisodeRow( + episode = ep, + onClick = { + if (onEpisodeClick != null) onEpisodeClick(ep) + else showToast("Playing ${ep.headerName}: ${ep.name}") + }, + onDownloadClick = { + if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) + else showToast("Downloading Episode ${ep.episode}") + } + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } + + // ========================================== + // 4. MORE LIKE THIS SECTION (Figma 121:4909) + // ========================================== + val recommendationCards = remember(dynamicRecommendations) { + dynamicRecommendations?.map { rec -> + CloneflixMovieCardItem( + title = rec.name, + type = CloneflixMovieCardType.MORE_LIKE_THIS, + posterUrl = rec.posterUrl, + showLogo = true + ) + } ?: listOf( + CloneflixMovieCardItem("Rurouni Kenshin", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "2h 14m", showLogo = true, badge = CloneflixBadgeType.RECENTLY_ADDED), + CloneflixMovieCardItem("Shogun", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "1h 10m", showLogo = true, badge = CloneflixBadgeType.TOP_10), + CloneflixMovieCardItem("Alice in Borderland", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "50m", showLogo = true), + CloneflixMovieCardItem("Kingdom", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "2h 14m", showLogo = true), + CloneflixMovieCardItem("One Piece", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "58m", showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), + CloneflixMovieCardItem("Cyberpunk: Edgerunners", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "25m", showLogo = true) + ) + } + + if (recommendationCards.isNotEmpty()) { + Text( + text = "More Like This", + style = typography.boldTitle2, + fontSize = 22.sp, + color = PrimaryWhite + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + // 3-Column Grid of Recommendation Cards + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + val chunkedRecommendations = recommendationCards.chunked(3) + chunkedRecommendations.forEach { rowItems -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + rowItems.forEachIndexed { idx, cardItem -> + Box(modifier = Modifier.weight(1f)) { + CloneflixMovieCard( + title = cardItem.title, + type = cardItem.type, + size = CloneflixMovieCardSize.MEDIUM, + badge = cardItem.badge, + runtime = cardItem.runtime, + posterUrl = cardItem.posterUrl, + showLogo = cardItem.showLogo, + onClick = { + val rec = dynamicRecommendations?.find { it.name == cardItem.title } + if (rec != null && onRecommendationClick != null) { + onRecommendationClick(rec) + } else { + showToast("Opened recommendation: ${cardItem.title}") + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + } + if (rowItems.size < 3) { + repeat(3 - rowItems.size) { + Spacer(modifier = Modifier.weight(1f)) + } + } + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } + + // ========================================== + // 5. TRAILERS & MORE SECTION (Figma 121:4925) + // ========================================== + Text( + text = "Trailers & More", + style = typography.boldTitle2, + fontSize = 22.sp, + color = PrimaryWhite + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + trailersList.forEach { trailer -> + Box(modifier = Modifier.weight(1f)) { + CloneflixTrailerItemCard( + trailer = trailer, + onClick = { + if (hasTrailers) onTrailerClick() + else showToast("Playing ${trailer.title}") + }, + modifier = Modifier.fillMaxWidth() + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + + // ========================================== + // 6. ABOUT SECTION (Figma 121:4932) + // ========================================== + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(Grey850) + .padding(dimens.spacing2Xl), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = "About $title", + style = typography.boldTitle2, + fontSize = 20.sp, + color = PrimaryWhite + ) + + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + CloneflixAboutMetadataRow(label = "Creator", value = creator) + CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) + CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) + CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) + CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) + CloneflixAboutMetadataRow( + label = "Maturity Rating", + value = "$maturityRating Recommended for ages 16 and up. Contains $advisories." + ) + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } +} + +/** + * Dynamic Episode item row inside the Details Page (Figma Node 121:4895). + */ +@Composable +fun CloneflixDynamicEpisodeRow( + episode: ResultEpisode, + onClick: () -> Unit, + onDownloadClick: () -> Unit, + modifier: Modifier = Modifier +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.02f else 1f, + label = "episodeScale" + ) + + val background = if (isFocused) Grey800 else Grey850 + val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null + val progress = remember(episode) { episode.getWatchProgress() } + + Row( + modifier = modifier + .fillMaxWidth() + .scale(scale) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(background) + .then(if (border != null) Modifier.border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) else Modifier) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + // Episode Number Index + Text( + text = episode.episode.toString(), + style = typography.boldTitle1, + fontSize = 24.sp, + color = Grey200, + modifier = Modifier.width(32.dp) + ) + + // 16:9 Episode Thumbnail with Play Overlay & Progress Bar + Box( + modifier = Modifier + .width(160.dp) + .height(90.dp) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(Grey700) + ) { + if (!episode.poster.isNullOrBlank()) { + AndroidView( + factory = { ctx -> + ImageView(ctx).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + } + }, + update = { imageView -> + imageView.loadImage(episode.poster) + }, + modifier = Modifier.fillMaxSize() + ) + } + + // Play Icon Overlay + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier + .size(16.dp) + .align(Alignment.Center) + ) + } + + // Watched Progress Bar (if watched partially) + if (progress > 0.05f) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .align(Alignment.BottomStart) + .background(Grey800) + ) { + Box( + modifier = Modifier + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(PrimaryRed) + ) + } + } + } + + // Title, Duration, and Description + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${episode.episode}. ${episode.name ?: episode.headerName}", + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = if (episode.runTime != null) "${episode.runTime}m" else "", + style = typography.regularCaption1, + color = Grey200 + ) + } + + Text( + text = episode.description ?: "", + style = typography.regularCaption1, + color = Grey100, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + lineHeight = 18.sp + ) + } + + // Download Action Button + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.netflix_download), + contentDescription = "Download Episode ${episode.episode}", + onClick = onDownloadClick, + modifier = Modifier.size(36.dp) + ) + } +} + +/** + * Trailer item card in Trailers & More (Figma Node 121:4925). + */ +@Composable +fun CloneflixTrailerItemCard( + trailer: CloneflixTrailerData, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val typography = CloneflixTheme.typography + val dimens = CloneflixTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, + label = "trailerScale" + ) + + val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(dimens.borderSubtle, Grey700) + + Column( + modifier = modifier + .scale(scale) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(Grey850) + .border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(130.dp) + .background(Grey700) + ) { + // Play overlay + Box( + modifier = Modifier + .size(42.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier + .size(20.dp) + .align(Alignment.Center) + ) + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingM), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = trailer.title, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = trailer.runtime, + style = typography.regularCaption2, + color = Grey200 + ) + } + } +} + +/** + * Metadata row inside the About section (Figma Node 121:4932). + */ +@Composable +fun CloneflixAboutMetadataRow( + label: String, + value: String, + modifier: Modifier = Modifier +) { + val typography = CloneflixTheme.typography + + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "$label:", + style = typography.regularCaption1, + color = Grey200, + modifier = Modifier.width(130.dp) + ) + Text( + text = value, + style = typography.regularCaption1, + color = PrimaryWhite, + modifier = Modifier.weight(1f) + ) + } +} + +@Preview(device = Devices.TV_1080p, showBackground = true) +@Composable +private fun CloneflixMovieDetailsComposeScreenPreview() { + CloneflixTheme { + CloneflixMovieDetailsComposeScreen() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt index 2d246fef45c..1d6076ac7a8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt @@ -36,7 +36,7 @@ import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTheme as LegacyClonef @Composable fun CloneflixShowcaseComposeScreen() { val context = LocalContext.current - val tabs = listOf("About", "Typography", "Colors", "Buttons", "Components", "Content Blocks", "Icons & Labels", "Dropdowns", "Input Fields", "Hero Banners", "Avatars", "Video Player", "Movie Preview") + val tabs = listOf("About", "Typography", "Colors", "Buttons", "Components", "Content Blocks", "Icons & Labels", "Dropdowns", "Input Fields", "Hero Banners", "Avatars", "Video Player", "Movie Preview", "Movie Details") var selectedTabIndex by remember { mutableIntStateOf(0) } val typography = CloneflixTheme.typography val colors = CloneflixTheme.colors @@ -147,6 +147,7 @@ fun CloneflixShowcaseComposeScreen() { 10 -> CloneflixAvatarsComposeScreen() 11 -> CloneflixVideoPlayerComposeScreen() 12 -> CloneflixMoviePreviewComposeScreen() + 13 -> CloneflixMovieDetailsComposeScreen() } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt index 757ed69f754..c1b9d122367 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.Color 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.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -20,9 +21,18 @@ import androidx.compose.ui.unit.sp import com.lagradost.cloudstream3.R val CloneflixFontFamily = FontFamily( - Font(R.font.netflix_sans, FontWeight.Normal), - Font(R.font.netflix_sans, FontWeight.Medium), - Font(R.font.netflix_sans, FontWeight.Bold) + Font(R.font.netflix_sans_thin, FontWeight.Thin), + Font(R.font.netflix_sans_thin_italic, FontWeight.Thin, FontStyle.Italic), + Font(R.font.netflix_sans_light, FontWeight.Light), + Font(R.font.netflix_sans_light_italic, FontWeight.Light, FontStyle.Italic), + Font(R.font.netflix_sans_regular, FontWeight.Normal), + Font(R.font.netflix_sans_italic, FontWeight.Normal, FontStyle.Italic), + Font(R.font.netflix_sans_medium, FontWeight.Medium), + Font(R.font.netflix_sans_medium_italic, FontWeight.Medium, FontStyle.Italic), + Font(R.font.netflix_sans_bold, FontWeight.Bold), + Font(R.font.netflix_sans_bold_italic, FontWeight.Bold, FontStyle.Italic), + Font(R.font.netflix_sans_black, FontWeight.Black), + Font(R.font.netflix_sans_black_italic, FontWeight.Black, FontStyle.Italic) ) val PrimaryRed = Color(0xFFE50914) diff --git a/app/src/main/res/font/netflix_sans_black.ttf b/app/src/main/res/font/netflix_sans_black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3ec41a44b9bdac5834f5f574e8f7dfdbbe2001d7 GIT binary patch literal 147116 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 z z`=m+AI+9xp#`q_J*DT}~eN-LeYng@1i+aP=XyHS?^km!MrOR&ZW^a$}sGd^rSlsMu zyO-bA^91ap+8W&B*z_fL_D(y#&h2h2t65k*>qie)H8co5qkqsnRMoBy^b6gNZw~S@ zmg_9Ud?XY-LUD07pPE>qrtOjVT`ntddUa`x?xJv^2ld1ZTIa;Td=lmiCg{>OOu2ju zn-<-kkQNM?SqO>Al~QiKw&TiH5|D_niPM!?(`>k_2mLAD$YyP97}p25y_c<%?`tG4A8;s;h5k>sC5RnmScF`^cnSlU_W| zVv2WRV=rTj{|(McKEd8JLud}G^UT7`1X~X5aK-5;wjFjO?*G7y6RiS6Dmp*5%k|wj zCBcbL`#Ae8zOPd13kfNXo)sz1?!wd;%C0K8zK2dsl4^b5*l+5Z%UE4-xw5}}Zf$d! z@;LEP+T9a@|j2Bi*Im6qWc+hmOM)8o3wU)eSF%0~AkjX6!p(ItDD zOUFHxk#eBJxkDXO*p*#Ne>h>>hPKW%wMi-N>f8mfIXN+L`3vI4DJP1HYijT!{tG&U z4XJAB;M1D$;RW-ZXkavkf$LC&>udgpFudgrXZH^Ti?1sH|t2*00(a(H3)Cn~;r#EiY=}}7w+fve{ zzQmGv?|Pr=!3>)Ih}${KI4M^RRaM;)oxTcNR&00J*ctfaTBM2igHA~rGgz`Aj1)=f z8%nrdiEswvtqpXlYfyeS+6Ai;v!BARkBx3yDmE9PZ-()XKcx%84%xIBQ5KkJYq)GQe7iPY!%mpj&u( zeQAZR=DLOTFl8;6BiH0Q8|J0Jnh4eO97MirM#*RIODm|jSU!vok0@WWqsV8(4e(4t z1zdM39~l;SUcs8oFmkh2pFR*BAD)!y$Sx@VA}8J!A7o3;C@itt{{Q5=LmF?GFE>zm z(umDkg>Ar;8j+M)o>NdRoAMG1lGFkbbJ(*^`PMKWJrMa~#Nxwc<|aCJ-kft07eo5N$+HV$_yAHi=n zrT?)CU+snC>>u!-sPJ)KIL_?>CnH27ViH{Ah2v})aIsH{VM{qY0{HcG#xRY}kNoJ2 zLF^%_SBUa0+0p>dQI0=O((r#vwjqGurNS9cAB(nv-2~wG3OLaVJQcu$vtGb|#^Ldh zeu5`~KRD|H{O24V!Lm6#5&Z+Yb-;h4!eco+8t|p?K%E8exK!T=!(Rm+I@^*P0Dp|b zV;My(^BA0{FZPvy|BAz-sU-m~MPK`p!=DmxafZaxV-17!'KR1oRud;suY`=w7* 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

PtaufQ4K!%*fIH0?%xvB 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 W{Hs@E6+v literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/netflix_sans_bold.ttf b/app/src/main/res/font/netflix_sans_bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6a8eb27c9ed127dc21e23196988492aa2ec135e8 GIT binary patch literal 146248 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 Drq@u( literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/netflix_sans_italic.ttf b/app/src/main/res/font/netflix_sans_italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..681b8e4f346bf50a5e8e83d7d13dcccdce4ea607 GIT binary patch literal 150520 zcmc${2Y6If8aIBSZGifBGO=dEgnIw};+NAg1dk>I8LP$dIy%!-MBCCkVA}S)5 z#ej&&x(Y0^7DPm3Er5zFx~?KBi!Q><|99?8LiF!`?fIVP3%<9Ud+s^sE$40L+>ua1 zh&K)*$GeV-!pVQ4Lo%5&5LxpP8 z-;4U{shv|Mq#l{9Cbaqj-q|&M+N{~ujieg=FXqpu&zvy*=dV`j35hs^`_j3*zxzj* z5SAwOHy8c+JsdNDd$i|X3Ca;7klezReLn5V^nVi<;VYDo4?X?_90|F2e)CPsu{*8u zi{ih4z7xRqhxdd}ESCt8pRpXX9F@c9CS#NgVK|JIN;R0}}`0 z3f@s7vL3n_PY8R3f#Rgwc1BqhH8?sj7n#5qb7JBy!mNZO3q$Sa=TziSv~In_4qJ7! zU-*QEbP)RJr+-IVgdIjbHIcLVWWDspKXC+5B?-2H^)gjjFSBeimX0P`R!1Ch_9W*> zA6-ZKfJwGg{|0abHR#Z}0`1%aJcVAmrS=GU1NT_~{GrSMco{GnpaZA@WMFY;)0T#gbxOWA}0pYm6 z0kGn_4zLeU0%!u10ww@D04o6%fMx)HcOrlR+yMeW1@Hr;0b~FVKtAA6Kq{aZz@Ie& zY5=nUnSc_q(dsB%Al(j|NVo7DS#5_@oVNjrN8I!4H2Zy{{mjew^>olg52yvW1BwAY zfG|KFfYZ=O+pMqQ>Z43LKZ~+Ckdp_t=K4%P>myX{(NAi*dPW_hST#`+5zL$tsv zm|lfk)Z@JblE5g&_%D*h9ITg^jKt9ItiK5Nf;Pt>A77GMA)KT$XHq5@NV>2HWiDhY zpq{!|uhLVvJ_Q<{vR)OwVqTa>%P^1j0LGAqh#%%|@bH*k1E2cQXOsq`jnLEj&9 zK>z1(-T>H$>jjd$aaoi4LEoIVkSjixU&#{WNRlDD{BU}5dPsG+jPX9XEJ^)BM}Y2} zUqdo8BrB4fa2Xkr5AcE0Pm(L!I63{Ez}UW#V3-E>@r)eDFrMg#%O#gVd)sKE1JH~% z4&XcqFag&C625j^hsHjP7y6Rs*AVVQc-e6B`)wZ_XW$qxgrl9#YtYvM^tBu3+kn>q z%K-~f_9)<;U(Ibht$)RLK0kj4?~!wLh_*v?<#UkFH;E5+9t@2aeXjt_Mc=z{UTPo9 zAAhfpk+hI>59E{6WQbP3*ZvUAhn{iSl*VW03HpPbwSI{H;kQ_y<}fU8T=w`J;5b9S zNwUXj0vsfK(Uydlq?2I3)M&MXz!SW1LqT`7ncVv1Dr3!ItMThuoc}_Q1)K{^PJCh4&cEQz%js4u%aC>9@p?gtut_40+p9UVMr7py!1^>o$1v7D>(AWo^RWnVc&{0Dipx0nCpdia8{jr>Df;HH20j&+@$uG2rTNI`75;V)#7OuS?BMYs(cvSTCs5()1sc*gO!(_We%N6;p>bv6iqpI-gCY#X>S@ z-VOOV16k{a57JG3K|BA(*#!6_>!v)u;ITo79eA7;0O04HsQVSpt^gia@VLRqDy+9~ zo`v{qDQwMVz_$SSwB%91KLF>zKPQ~G;mo7wP{0{n2jV;r@DAWfKtEsy`ndUPpmD4j z{We3UnDPY3y+-IU} z6krLU)qdWE@qKCoa{BHyFXQ(k%l_Ln#aM>g73~)bS z1z-$72`B(;2Rsefi+Y)W5MkB9^h~W^NC;etm>g za1h2L9^x_Kv!n*~qJ&$BlcG_V+Z>ER&>*+55M}MwYhoA5xXt8ke1P~CJ|PDliwYTJ zqKNm|PV09q;ON86y$dW9Xqbvc0RUmmaW*hG$4 zd&nO-1v%z%Y5-^sn;}F1N>D!s-~^bAx_?7izx9eR5#xYOW&UVGM>O0AumOAk#DRhq z^@FY7vRuT3H=s|I{GNHe!|=1F)8r&htA`E zQrEEU=5p{VaNzdY?k97;O7!P8cc=9qLwNw$2)AGH9MPx5bMRq^-vTAof-`hCm$gN} z;V0{Zh%p}&l-5_-LhE?J+4`!~Hs>Apg>std6*vLvdXPLg6e<`;%lqb^DOwby- zpQzzo@(iX80+Ch3pWAel@T))~y4537w;MNd9>I}ll0vdcE9oE$$x^zEuApn_0Np|# zpbyh0=*#r4^dkKiy-xp4tt^14nSn*Kc$UO+Sw1Ua6|9EUvlcdnwX;sv#roMyHis=? ztJns%g+0JtVlN|y`5JqhT@*yYP4E;(2?2su2p3F3tdK0^3(dkDVW}`6Y!o&Nj|oo; zgTkA_Y0+QQitXYb#D~O3#3#fT#FxZBi$}$G#Sg@f#4p5uir+chCv#C`1^rDWS9z)Y zR63PWm88m2m8r(4+Erbu8LD}zHLCTht*V`>|EOKm26d)7Up-yDK)p=8UcE`ZTm8KH zfci!C5%p^tLF1rt(s*fnH6a?k#-yp&v}qpJ?A5%iIjcFR`7(rs$U>AMo*|<`{6o|s z;UUJ5;*ide30kIg*J^Y~YwBEe?mBPXXkDN#R2Qi;>(X_Tb<1_Db?bB+b$`%3rh88J zg6<{V5#3*O$8;xjZ|mOEeWbgp`%W*@JL#4BK)p(zq0iNK>Zj&o^Aq{w$eY+=jl=U4!uIJ(Hrz|8pj5>>9M(JHknOckd}RpqK$ zRc)&As>!Oks>P~xsx9EsE;UiRt79ZC%~mf|FINw!A5-s9?^hpGA6CCI%q1UgxwKigOZSZKfbI~u^os5%xO7r?TKA#u zGPvXbE-54~rRuZw9r`Ximo_}L`4?g-uyY9$%K8mB)d6S)5IUNUlFmwg{4altEXdyQ zj2h{(*C--SV;1M&PEX9_k4WER9f0(b8DzP=hIFzp%Q9<*scrr57$ef9YA28!l-ssV)Ux^1BqsYoi3& z4;&r%@#00m(ThJ^{PyC%E`ECP(#5}CJa%#S#V0O4etzM(?+6*Ab-3;zWB;L80Ru%I z2fPZvDg`}5&+`_bcedcTP1p&kepNUjToHZ{ZVEq(GSO9Z7ePm?+5m4Gh@PU2s=QQs zCOwx%BwabqtLoAsZ1#ZXtgl_d@5%$bDENdjP9r za?FIKWCd}=yxB?~MD~0kSwp+XHmq(vOjaTWb0L$+WU_!v5xvM-dYF7j&SNI6f`9N4 zeVA-Q)Rj&$Xo_$@xd*z`gQStalbxgu+O?fH(+M<#E}+ZlEA(6P6ZNM(G>a~zf1wj; zHeEztr2<_^ducvhOjps@$ZG0H`)Cne0v%j}X!;JFOsnV`x{-W>uE5Jf+cyDUZ($~a_UC&XaiMK6WvLl zV;hN;Qo4s8q<^F5Xa#L2Ka<|g(^eOrxeS>}g3-vv%q#e{i6X{a;8O!JfdYqQhvGh}Vl|D`f*=G0)e`JqR!X87; zXBImJ-{DENhdm{Dvc0fvMeGy77k)?;`v?1yJ;R=5-v|cw7x)-I3%T$o(u6!Vi>+Yu z*m|~*ZDNbr{cJgV5I)Hkwv_#WErVC`94wuc{gwT~P7BWLW5EZu?X%d}9=fZ-&OwZFptc89} zQ|bLeHua=RI!c%T+Z!of6TcT<5x*1vBEBlVCLR-yi=T;q6;Fs)#V=uDFTor76t?z? z___EGF=A3l`^7eKr&uR$5$oaaHHZ(0jpBo16MTwhcz-S8HnBt8CANwWi(}wrjD&*1TQ)JuVi;qwOn@&& zSTw!G!s%^hq<2^(wXi7q3yWYbES0&kH0H+AnT#bcM|f>cESWj86eeehOvy5tf@Lrt zR=|8&A@g9_%nRPPH_KzA;N5w`$Mb`a7r;tcAS+`*@b>)SnXBNN2eV41f$yhfwJa1K zpboxz2&1Szl^85cA&-z9La9(B6bmK77-6h1PG}KYg#w{bXo81d zkL=-0VV*D@5#VfLHknK2k@<+|rV2CR{m&fKlR>f{>xD0ngXBeWh`dA&lb6YnUs=&VlUK-J$gAWva+Dk+$H`yG z3GzBQN&beN9dDAi$lK%;d54@P?~*g*J!E;`C+CJOFX1*57BdSL@-^7QD%ip`u!9>B zKZd~yPLb?jEi56ogPULjr@;!If*ovu4TJ@Rq-~OH_gb)+&fIZ2v0ZJ~B&KCv1ex--(qnB`o@Kx{BK@ zF5{9^a|vH29D%hxF03F^x%`svk*nGuEGL)9=cE-Lz(V)|e}VP4S^bIJ;)B7xlCAF} zUqP?WM~rOK=pNALeiSR>*`v;$ICk{NfhTr7eBXwpOQv=-9Rrf_!n*v_M1*@#UhG--CfxPUjIiT z-~sO$L#=BbJYpPd(eL-UFQP>%X=olK?s!kTFm*6K&tuS>=c!H^JxEH_6FVoU2Tgb* zyK_QIfJQTD#T$cU)F9C&4-(Ho;yp-5sRx&%#`4ZVxw%C>m@U;Fl<{jduGO7`4*c33 z*Y2HzBENRTwPWX?z^_GIi=BgvUo%{@&OyqrDXwYfAThUSq9PAqbCieYC@o6VgB=l? zkf=zldVt>*4xnmn^Wafqiv|@7v;`f5%Qp;WYI8AAUR%(Wa`vDjU8!0F!>#Gew-q!!PU^UzGPHg_;dn+sH10=m_MX}l~AWvR9@(4YlORP(l}&tQ@fEz-`C zZjQBDt@gl;r_}3#2u%wns}CwB8To}xZa9wzb0j_px|-(B0Ide7{!cE^288ebjlA%l z-;5lR)c*@&KQCdZv2R0dg8uFQ;T!^UQ=%fXn@M%w{0Wl_`{wUc=hf%rs2gKUv2muD zsQ9?p*p#>l6Vzd~bEeMj!!|>8TufYSRBT*SOtLyQG1VNKYEB4OKP?rj^E)Tco;Foo z(b+jsT|T>;thC8b9=KD7xiKhKkKqET&Xa^NZ@|M7Pf|34Gyo!d*!q4TpBWB1^+4_* z?XVpOAe*GRSrbsu(ULnTba0_97!>t`f?hgu@yjYu4-{xS)!laRd4uVBTrxKf zs%z10Kuz=hjHH158L2IKxh;cnd4m~wceMrXp)ca{qEXrsMfN+;!*uh0QP?#2dcol4 zf(?U5>zelynj5g6u-umYYE5q0ptZJa(5ZUNAYC^I0^^un(>f?yH%J;<$24Q4n_AY~ zdoQeP0At97%wc}k*Xa}1AF(cRhKz!D7KHt!+Yq<7Aolr`wObFdL0Uvi*2yfEd}S?$ z6`VuYkso1^USw|8GWr;K9qXnmSvLKMOl9kc()t#eK#sDj>}~5))^AbMkv+jawf+rR z)3%oHC)sY{W2mJ;9{tc{=Y(Yy|)`RQ>a(v0yA5czSvUXSt zVWZ!oj2GkbwnvIEEwe?VShglIgkNLn_YmEh-wd7lC5&gz` z-Wtue(jM!ZRtwfBM#1(q(+V;UQD!QON8MY*YJDH^R)-&AH%Wta0DHz5JwtX|>kwZW zt$A!59na3O66>=h0+iST8~XssOlosXzC%DTt8 z9(UaYX)^k-HJ%J&U1Kdq6A!vQ%L@2DFydqX7lWw3mGr?L}dT{-%iBX zS6L){=(QNzSMZFt!1sJkI4HaV5AhUy$_vO9eFJauwkRU&;|+f}6j7BK5mh1Lr&h#H zQxPGpMC7v-G0Z9P5^_1E4!sWB9WFRrmlev^$+jRbvqv_FoXH8qqs^nIj$SwVjnO~&`TNEARr>Y%E%n>xcht|~ALqZt z|4Kl5z~+F#fHMIf2V4#KA&>+n2Tl*%6L=}`tH2+ESdf2EZct@VTTp+{(x6R2+k>77 zIu!I)(8od7R6eR~)qv_%)ve&M!H0u?R(q;*)l1d;)K@g4kvFc?OxA4FTnN#J^oDE? zITiAiHcnfsovB@{-L5^XJ+J*C)HO6Rv^=yibbRR4(1oFEL$`$P41Ff_Q0Vc{)1e=S zUJd;r)S`3N`RGD)CS9toP*Q?GD>2@N6dv|D6V}HF~AE(dM zSL$2!J^GpYrTPK=R{i7peflH%llrszOZu<%Hx107F!&oxhEzkLq1Mo5=r_zYtTb#g zY&YyN95B3Uc+2pi;i}=f;Z~R|%ri_C77>;hmK#C;h;(EkwqodK=s5V9#la2YtYU5aAuW`0< zxpAX$n{l^s(D;h+4daK#E5_@_+mViu-jV9a(#YA7OCz^M9*lf7@~z18k(VQ{Mc#^% zMR`W4q9URaqjIAvqgtbSqGm=djT(sB8ufV8zNjNnC!@|rU5ffT>ZVCy(wkyT#ikC^ zWYc`pYSU)Z4%1%KLDNywDbt6hE2itF+tH5E-qG37lcVQGuZ-Rly)*im=tI#bqd$z1 z#f*;0jTwu7yJHTN~tMNC@GP93aZ;mr(nk&t1=BehT=1u19<~`;E=Huqm=8w%^n{OpJ zC-^5sBqS!}CJZEOO?W(EU&4`ulL==NE+u*8qq=BSuNqdqGCLK@uI_c+RS#nHrd2&zk{N#b;gUQE}&m~_?aZgdDM5H99 z;h+S0V$Y3I@d(>v0)rk_l=WMpTwWo*m%IOB4rIMMd{A#@xZ=sh^NUv(?=2oIex>-0;&Ua=B|aq~CFLc}CEX>{ zOBR>xEP18mRLO;sZ%V~dRcT~tYH4w4b7^ns{L*!$+e-JA9x8pc^o`Q9r8mmF%gklv zW!+^n%a)gIE;~^6YT4zoYh|~}^UEvCpDBN({NwVg<(7)TiqwkwitdWV6{{;YRy9ms&Ty?r^=V-o4(xKDEBKzQ2A`{r37j^#>Zn z27Nt=p9OFt`aR_3hcS-rDn&ssfe z%dDNV_RYF7>zmm=v#V!!&z?Se?d$`ykIX(f`|KPtCx1@coc=j;=R7cH*PNqs-k5V{ z&WCfZ&ABH@aFw4i&z$_0BCys_ZSg6j*t7p5=lUO0K-{Dp@W9$$E4;q67@BG*NYi#9LX zy=ZXJ$wk)|-CWETD;Ap<&t1HE@s7oN7oS*sX7TkUiX~x7VwNN?$zIa9q-{z6lEEd1 zmt0zMYpKuD-oJeJ@}tW?TR~Qg zUXi?_dBy4#8&^EAV%LiED=x42d1dIz$d%@mOIL1Nd0^#{mB&}Uwesr9Yb$TAw5-Zr zRlKTtRnMxatLCoSx@zaDJ*)PuI<@NDstc>GtomlP|LWG&J*#J~Ub=c<_14vouim%% z$m)}86llq8Jts6e1mif&w++0Ia!MrFXtZepZil zDZZOR#Fs(a2(@0eHX!3R6PYJ*|B8o=zS>NhWTN&KWyy`PiEe&c2T|i6sd03BHn~DA zXw>jq^d(6D@ON!(%0{%w$XM*`OJygK`7@%`)Q62(G?Sspq(r?oL?+i}viLX^#dzfk zrlhuk>!mKwJIef%ViICyj_F0&)rzq$1^s!Y6JuPQ$}+u~YXGDu#>Q4BkOu$N;xWD~jqgTA3mDzfedVacNT)mf# zVA7Etwz!_*WV4Zu^TyGdM*o5ROfEzCP&>7GGb}MFUcs~>648<}vHc|;XmR9ZKJm2P z&I{hOVi=)lXXjENKM^oGS1v05M2lmCf0O#oHgI924@`jl?A7u> z&R5)@zsu+entW>SN@SL@QEobF6d56>~u1%27wuOv0DDcwyu zx+FEeF@r`ojo)0AKJ@|H>FIInO0#BFNJn%DT+lQ9g1wI1vbdk^GHSG5@+2MY88`7I zckHRHN*mK?t2Y`0tisrWP%n6wkqf0Xnjw+oTvJkwT$4n{#-#*`j)7TGX_fBdoC+uB z#5H9GxVThRU!`42i@O`mHh>{v+4`)6wAT30oaTh2&h&kCrP6qGpg}qMi9|msyNrYj z1DAIYKyQfWcqj6XjWo%Ar46x73LjSP=2V!GnCa?N*q7aPk9WI!OJHbT%s-Y%mTC?roD!P5~qN%Q-!bcxdPUPji~lzOwLlS}!O?1b^99^*YL<8zyw zodWU`OjUl3>~P)X?5OgN`0SRbkm3}5cwTaBWwc&{G3TJacF-?`#G*$tWQg}heYIS4 zWOA9D>$@R|!u(708ImW7uG*JYSEe+jxOFPhW1ALu3ryXYnb?rx$F^16s&1vgNys@mi`$(#Wq*ru~+UN_tVjCe&f;w?G1cLm6-gK#m{ns! z9YRc!7UY^@SOZFWkooYbFeHq!-`vuGn6`4AlT%$$Op=RJ?xg(eNvW<*CCO>|Zf^HJ zldlWSG07aYd10x|j`Dy?V^~>&%qhAiGNaAeN$H;8rr9D|W3@R8q@+azvgI3m)l z_J&k@*(Z^GMpKWr>7zOoS?1aqUUzBlT0S`ywD)yVHZEQx56+7L z#?=eFJ=4=hQojA(a(c-Gr!~z~6|KvL?kT~zmSbEp5*WhgeLW8ScjtY)H1EG1*Sp(; zIEggfW2M&QruaX(Y4AL7OD zu~!9am;AZ*3cruQ^A_oOsDj^T>!9m{rqb?@Vvpg1N$Rma9_ah zt=DniC_OKf?k%WaE!`JM_uu1rEe%KgV(EQXJg<}POQd@_?#D{?OQrj8)NhlXmr3v6 z!1HG5dAW3N#eJo8Um?}6!|K+{f({xypA^x}l8(o>$aIPjin4;?GJKm%pO@+t4wp6I z-KV9pqTw=pyG)-Ig79wfaM=XZdrm4V87}KU*^APq0F^o(C*)oU50UsW`Tw(stVK zUjr=x^dph@~9Xtd}UrdCC{6uUL%MdWDYvRK24*IX^{IKS=f z=N=sHACOR>%L>z({Tg!9C0pVSyvCz#Sla!WPZ(_$!|p~6H7*$!%t}mBAP8KI^g(lo zR+Q^QH3sKWMN66?H&pHrt_s%3kVsLc z)A@xfa_GIeJ*tQTLvm)a$xkj#WPc5$W%MJv}VR+g~;Vjvtt}&5jst@%+aW$6X#+z|ocC!sOb4ElJ&@!BmQ}u^{%_}8X}B{j$wvil z4`di?za%4kr=+;Q{t2V6-DkAB+cu9_IvQ*af@JJ`?4!2Zz9(Z#LuIlAQ-D_HSW^(0 ztC2fkaaJ#PY!A^IMF+1CPhA9j&CD=wtyw1b4I8CRbZ~IeriH3won2zoVVRobkic+f zx8yKW%J)Iu-hql>-_e@gZvMW0Dn)=Fcaew(#@aEGuD*Bmzoh+b@quK4J#B-O2)i@A z(51{#;}ei{UyVt>8nS921(GZn@CzQ;kpXHIvOum9 zcpd9c!qVTZQ#4#BRjTs=+Of$uZ)dyI&fPlO(AVg-i~cf@OF0p zrVby^ir=(j8*|BUJA6DVq;~F(=Rv8RyLF0(>)h?jHlDk6(xf`?U_7?@$j9^BI($3> zzdIi69z&m;hK*7k-i~b!-)-k^9o`OB#Zc$ByeJ=ThmU#fZ|Gwi&)qt_ueHzv|Np-3 z)|ozB$EGXzczPxJ*y}(O=&cX1W_*vOLxdQQc~ZwcY#x%CC=aE4lMJ?*kQ5Kg&dr%Y z?#pu(0(@^6F<-<#F!D4~6g>((%R1d!oYEqUE@KN8~L%4DV~;f48f=V;fCUXPB#@OYOSg8!^4bBUpMs#{B;(dhV%JmJZjarB7`g4qz6 zwd9{P)sT<@TTqKHhSK3<2V(tJ+@HJ4sFU&?N{Z3)Ab_Vk?1SV6*4Il$E9zCvip-`2 zWxjiAO|Zf(zb_Y;x%KiE2V=--7nkVhyp-+E)uu9&&@@iju%s-vFGt?k>^d$(6Xn=n zR5-(nE=JOU5_cFQ9!J~b&cp<5Qaq;&{GMSek1pYofG#|2Pux2}S?MT-pTD;v-zRRwTE(mJuY{^j@GR(m_q1Bl)qUwrcxIj@zBVpU1 zuYG{+y@++=1E74l(_?phlH!vvir$U|*ZM5hm9Bf;o_5>oJI?nhw>@r8xvfO-ypPse z9xo~>TD1xXTbn+J2^Inm6WUDLWlS`ng+$(rEesrTSkMCxZgH}u2&A;qROd>!Z04Bm zn(IGiUT$iAg7X+frYWmG=v&{f8P?A~@;xRmPERQE9^;Hu~QT2?p=<>+W z=;rDoXBTx)QDt*Ph_NJ+;}-_}GJqext=g~JWlWN1G5i-VPf{}c$D>emYose%75ra| zeynQ0Vvzp}shLya-s;`;NMGOeuqx@_C;O_as%k2#D&7np(=vTU0&hPL?ax5_{%C(M z&&Al190-!(mBKU9+WBRq^!QB6k8V558(b}~yU{rJmG>*?mjMGIPSXP#m8+-b($I2K zM0s<0xjwGE1fO#_5H;paALh*{c-m_0CbZ{QyyB&aGL&UOUIkMLetLY8kKVxNii7=< zJ1N`V9aT80eVo#{(41y=al5C}6N|&|ysn>Z*cX?iPt13=91NcoaWX3>Ld>ahW{2i1 zaP6r!7rXm*z)mI?Sy9`XKA*1|qL ze0(uJ2&`Zuu2!1r7@0OnPYeR4jBQqX#VWkoR~AJSM}`C^78e^7t$&Tm_86nc>&k>f zhti38&4^?Z1&#UMrm8%j)_lbpTQy_`y~ zI^4PK=Zqb>tNV-{5%duJk=Ly8*bVUk@)F|yWytp`hSE@8kVj0dG!QRD#={luV{Xlj z21P)4j8p9xWvjDWO9Sr0Vx1hPC@063)>cKt(z7)wxCoCk)^PstSeo-b0A7CxGKAsi zBON&sF6t7*`*K$CnZ!YB(8|3G=*pl)xz;yH8yT7JlJ5qHtgpH+vVQoku4;4SqCof2 z(|yYil(|hSRvLfGF3tW4f3_1n*3vRiT3Y%UPxOiCmte0um+=r>M`IL;7=?r&9|_kU z=-)>!8-)`mfZPisGEpA`lt>~M7YK?uyUeA`72tQEw%@zfC4GBAmrqgeZr>ZsI-sx<;vuhx!Pb14fZO^ z`n|XiW7G3r!hwZ;2jBbhGrpR^p8mt|0XgL&petf+aRNpzVANb^7$o|Qq#-^|)C!Pv z2smhHqoae?SEu!zCXREdp?NMzVm+JN+T=XHuBI{3InTe^W$d^y&WpOvYC^B%2DWWz z7qaim^J};K6i#VJxJYSsN%pmC-+XiJ8ZIpOlEPwT!42z6_}}`zzJ8FFuOQ&dg2RZl zB`M-s5`i{G$s?XnLhPwi?V_ zUO3(IpF4FWcXrY5Efdp==>?15tTyza#jga6XVZ{_iUR+V(MOW6P4OrB@^}}qjoe@h zCFKUbVv(2xh7JXIAx)VjeralL=IAbSm#3Sfo4)L~1$sxvth~fZxnpL2BFl_zQMh}1 zwx+bIO2adv$Nc)XFd?-8>Vl`6NEf{id*;8h#e3YJ_tLt+r61R@qHg>v?d&_tF6wEy z29~036KQAREX%e|gSwLN*+idu98nwzsR?;m@v^pthE=}#X62i>TTz*19dozBc#6T8X-?4m%aV&DlM0p-|TxDWMlb$RdH3OxKzqt?X2^hmz zj3HNAE3k8mBuaS^9)>5$p_0WB_td2E^$!q5jjvyz_`ZVoe0D?$GX#YC`2hY|S-HS| zX~VUF)FIDYHbfV%B%R!i${XD1cOI5`v|0fiKN$1m-fB*lXw)^{txM$&Nl`S38Qm-% z9(28BPyt+Rl_@fi|6z^vuS1f?Gr>N-+&)U{EK(|gN5ws&$RNZIg10tv#mn)3UL_srt?1o5(s%Baj=*3XgCZ?HZYF49oWL%h#n<>++(4BJ@G}a2F5P zsDc<{x+11Axr(-{O<}I1M`h?DoC6#=Z;Pz6@y*iVp_M5Itrt|3mwkCGUc+eZ+NQO& z1!CF!3^(ONGidlm_R}3Fx-NNo?U;$62^zsyP%nbt!Jvu4Fw%NJ%Z;!eNm@CSxz>=? z=GLDB$g0cQ1sEsYP0FgtdOWM;v7E;amcCqi5P!ClLIZjYr8a}{{T&wyDGi^PL}htBu`Vuc#Q{1e*C`b#Em1Dc{gq3{ zPubNqDIsqBMi@C`vV&afDNC+Bl#uNxSEcErio%s?y?Hrf0z>OfIrY&EX=!Dnp6z{V zZE>WL1xKfvwCYShNB5}veRCr9xrSgph75X5LEiRD_RG*3G98{B2F#Rrnzue+TTM}B zd)lOBqgN`cSWi-^ZmD|3qL``L(nS24C4Q4|KivtR#GboF)YdgIu$ApcMNkjsDVHa~jH2&P@UHjWccpbgzW$!b6S>se$ER=jDJYZy@va?IV4?^Y^4e&iFyDt3S6?G;={hN9k! zs27U%avnC`UEjpo|FBcZRhE*twrQ0@?x8lq37y*jly7^}tnD2*GxonfMh_@Y)? zR{;H`>74Xv$L_IpRCe;+cR7!|P{(sb9r%B8?Mq|o$L}6P<*%PU!~Fr?&Mnl@VenCf&b0(^;*Gp)@%1tWl?9O zvtw>bdWw_t#sSM0Z|k0{Xq0=?Ooc^x}!9+Mtj=;_muU(wFG-;a-+ZTaY* z3DLQE;Cl*ez)9o_?EQ^g{l3c}+olsNY#iM-wqjGxi&Tw145cuae0g2ht4q~6UvYFgdWaqMu)gnKGG|4>t(F{QY9dEuSwXp>59u94cr z{v=K>?)ljM?WGr$mX$g>OKG_`oUT$@RP1p5x%#gp-0oyXmMPXIyRbGO_V zMtj^Xk0c;-UjL7J%LnKReWij{e+R8}cqiL1wA;kKS73-*nfS<$FU#1rprpEc^gJ@Y zVn(sD+hhatiICU6|I%z6OsX<#@MXg?II?E=f(sqaH^qgwf`u4w1 zouAd6lvYwzh!5r^y6~~Rgf{Hbjy`C-69N_9m6>*}u#Be}^DS4C`Pe?BVU}ksJ82dd zc{~3UcvsR*()hr=z3r1Z*G!lmJHRb_J)sNrqqK8ckF%q6Ymc3I-YVe@ZmL6W9PU<&6e|>ZRqRoEFABJ-T6?` zUEi~x!D}yDE>hyQT|Eih!~E^uq7$|ZH0;VTxSjOi~coKfnkh@Mo6GmWe( zn%5GWG3EZc!ukCCO#Qm*f?0+9IGf{Xn~Q9c@-aQPX?0doX}#qe-d>9JWeE>o_;}*}w2^pFn^PxY zue^|g^ek8UWa@EVC%N_@`LG=6NCv!ZtF{n6}j+N6@) zPH)RKDzn_8t?7v+RhFAvM)-br zg{zj#rptLd9q98i+L6|W4akQIe3sCr&aLC;(X4(*_@7w;>cJxvenLI`w*LMg&IRP1 zdFq*G7y8>X?=+^}>b1Psl3{7VD<3i4ollt-6C>!(7ZPsPFVWB2c;7x3Vb%ECw&X<2 zi_-=!EV7(`bK1^d7U5SM3i!4d%YMs4G}Lkdm*ep65xnb&zMr)BjhW~*u4nx(vn+>V z+Q47S)3nLbWci*drT+KWG}Fr;bBk%D;oCLFb)10U*?l`hx*7x?vy}l^Zj1*@mfV-U0ial&dFs` z*JhXa^r$2(dYGHjdpbQWug7Tf;+z~-XQw#GD@;+b4GEd0c||&ZeRkNqo*y*1vHsC& zgaYVaW&HuaQ}6-)UjfqIL}@Q5&na;`F73^eYeR=TA#{r@!_Jl%bxm~P_z0zIabH$S z<7oF5YAQB6xdi0Jr;k@S2js=F4=mq>gw+f*6!xd5#E#7~l$kTzlbR|sJCZp6HRw|S z&QiWziVy6v&S$o*lQtZ7vO3(H{Ig@mYQ}oa8B^T%xBB8K znWpk+L+se*0H0AqWe7)j{UxIOftUo^U?>tYJJA{1^# z{aMLPqZ3*zi{sL}+<;Y_LBcLOG@@o*eaWP>+5$HE%(vV?1&`gc<)(9-03JWJvB>F zc~3*(l&t#}t4o`L0#d5z!tA0lPP-T!Rs%OFrhb_3z2O2W!DO^E1E)T7iv*}$5PC1;M{qtzwtg<41ePKVNg!MOR^>-r4Vz@HVFjn&X(BV`l#gcVAsSJvs-Lxa=n|m z1+w-pU3hX=j^#AfmsZpkVfw<~MSIwUbP{{JzXHB;?CjmmV*u-e$o1RG|9Q9E-oFa{ zOTK~BKb(0lEmkCUwv_)e-}3PnQ{Oi7&g%bu({l614cli;N2i>k}(ESEXv{ETjG{eqT?yR?-0#f%=&<@_dBb%SXHEgM|p z(RRMU_-j&fS>f|F6z$Z}c`9jNCzFm9YB zucbPpEzwY#kkMi15uMEhn{AV1BXKB3*UoQJW_5XbIG2p`3BKiE0JMG|1s9vw1XYAMMdbJ zYo5d;p1c=e7^LBYWCKt>K~d4_X^Lr4q7WFYRp=9Pd>o;Ho}lgrt1MRE=-?_7HV#W| zCP23Qku$K*`@5QYgj+$6Hn{{PFKlcNbM;7XrkrQv5dgS-)Z4hfln6ZrxWT*{)N3Rsteq|$ZC!`SZ#cv z!%`FRQVm|4PuE&j(Pb6_KrYF83H|usUHkVYUdTfrT_Guq)2n*MJ~Z><)``>qKla`` zyshKd8^)eP5+p%@q(BnvBnXmV1q%p*z4u-vQlvzQDpa?u?iyFwa+TzwSdMME+i|WF zIeFbv?3=j6Rf&7zxI~Ft;-18I%!BVYyXOFhl$hi`-}AiB`^S|{;9$<~?Ck7po0*-x zYa5IF?7sWHR=$4vY0f`4><*-n;QA$-!^>AB*R0vSc}343);#wt%w{Xt5u(laPkelg zw0tjcT|Qq%2siu<%9|E;omshw#X7IIJIo#g`IbO#~6UgjH!DAFcpH1Ny^lYxoX*mLuLbACAD7;N z-CEsAVs)tX3p96mU}+MG`9WQn#Ib#?;kMT7{Du@`*i^^n=;axC;eDaGZSKJV%WoZR zrqH$7P?O}cnu?5!oYuUunwj{R?x*oaXRa`eydf7HP1VDh%riW8 zU~$8#SBbA%T6Qfh?K!(ge4SRYq@=$x30Awt;f9*-+Kr$;$k2_GJW-&Z_>?C+XRgbK zpO`v!@ex5kT9%eOVJ0LqGkP!dezZ*^9S!y;t*i1Q!s}O7w@nwA!|P+hGrLM!mz%;I z^i>v`msgQ(H+!4|1+MBKYg}r0PE-sFYuVmfvBcf7t<5`j9e6y}2o{fR%d_-$bo{7Zg@q2+Hg|`0 zMK&hq1_h;L*~_cLLr3eR7V3Ssh@QxD>%9rd@i7UoP(?JAxGL*Qi{oPBlR}NA+KQ4& zb|v-8w?!YN@oNhr4i>|Q%dc;R9Qys9z2*03jpmnH5>kSLGLkYKMx*qDoZ_a`V5co6 z-IJG#{3FO8r^}ChhTozB!}0U;^0U9`^5kpuCmXgvlTY&~!|RUGtYm~a^+L~MRWz7~-* z{OgXOc@)OY`z>8QkK~Ky{Wj}sFsA=OW6pOFOHGoC0_7RF*+QFDq0*T5n_WB)|NQb~ zqppnHCHfyI?+(OFyjQLYln2pr@(J8i#%;RM^M12?Jq;-`oRhP1Gjf;I&iNr7!HKDa zR7XmBRz|M!sqP0KPbYBW+BDk&orbp0gNGVlK*Yv`&~V|5oe!8GX-SJo%FMB6wAJBg zzWx{60@<=9Ig`>H8O3p%^gp;9BtYMMtI#*&&Nt|r(bj3Kz<0L_GjJWWSVJGfJf~Yj z?-^f;RS%z}8Lih1^j=+;+d|Y6VpjK-q-%05$(e=5mE)f8_B-<>_;O*f1RcdsF$YG8 z6~#$3h0sJ=rFGUzH+Sr83!XAAZEv424^Jr#4O0!Tu0P8&-CWx~*u`E^_Oq>!TkN+a z@@dWvLKIxc?Jrm=uOGqd{BS3tiPh12*caaQzxVjx8x2p;dx1}$^uwJ&{~&yj{`aLx zkzdA_nr`@nbihOD{vW0*75l4C;P)){T~4v@Lj68K{CL41#8^W+kuDalfv^#BuR_wW zU>XVx`eLxnXLn7-U8Cv{@3Ku@aJA4O!kT_2*CC$s5~&ceZ+HWfVK(qZVig~BbQ;c02BO{gbGugIC<%%43UZj#{S3bRVn>4L_^t!5Zf8-Hqy>cf=1dSE& zOS%hlYqHoQLzYOR!&gAU2!x%nT1uw66w$UO($Cm=79};hbVqalStIF@5sfR)>{xPk zW&(RNN;x08{wU=NC9caHS-c4f*Y1eO;*H&HBO?=DckSl=!_B)V%9eEOhY{e@9c{y; zbfr(w1e+oVBfS(qLjtaqyX}#}^Fi$x*rn%lB@uReJ90l^F3Yj1#W^O+x!cc*obDbA z56|v&m0Mo;$>HR_DfY}?2VXSStgP=}Gg4k!T9?xM{Ow93)g5QF+&HiNFZ4nu?89zY zT9iKM`q%rgmz7U5oC|HlP_Aq+!}os}dmI-m{S9cze9XS8{!0v3dTk1Ab#x0-4=D}um&<*{S;^f?NhQ-*(NKvKr45}@YiC#IVBM!}eF&hE zgF0ZpEZ=}Skf$9#=SbS10Hs_7#fM&fU1Vs!pc@Jjz6zQgg-C~+rW145wsdbU=G*F~ z<;htHo-kKyhdq*zpF0wzqxq~WIxfb7^Ad(lkGib7LpV@Y*|*>dK@8J6|c zy9EqSlh1ooURo_Ka1o?J&2);t1@=O-I?x>t25 zuxLq!TFfVp@-cv0@iDH%aElq7yzQPc7vFlThxZL)iD;J|buT-wuWxHxQkX1RU1Rl~ zld+qu!>9KQoYy=1QTAZ{nzLh+A+~qy>diIv>n~_c32NU^Q=3~hTGqSYXINWv_G;8C z4gHUFII2&osyotE%&SuxX{wz1q-({LSVP*eIU1eoXo_S*s)CGkI-eoBt;G

2w&l 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

65&qNuE-=MtnsrheD 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 Wn$3g@{NO8UJx^k9pk2Lzm;FC%fL9d& literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/netflix_sans_light_italic.ttf b/app/src/main/res/font/netflix_sans_light_italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b836c37816b15aef505c0d62894d0c5a71c298b0 GIT binary patch literal 149336 zcmc$`2Y6If`agcoxi>Q@lSv~znapG|lQNS&nMv=xcM_UONTY`qdM{EUB9^rvBBHX2 zY(PbH5s_7NSqtleq9TH_h%O?sh^)fQ?{n@4^KRlk0>#}WwKWYZKg@cA(53-GKK!Vpp(($h z?`9}rx;8?*R@OAcCp|eUXAJ7Ti}%|mPHXSW`O`BS2=VhI#AD&a{yFNU=QoZc#DMl0 z>F(>9mi55H#|Y7(J*R@6_St<@PW0$cf%iRorY`8dIr*nWgjhxq8l>*+YVQpGDn#4z=$lBo_U-T%XoHzfWE% z)S~__)K^b$pVpO?GkrXvbtb&Czi-CuIko{(OK8I-{=9EiSKp0`-A@t{a~${O{k*?{ zfHK0&=Xrnq=r8C!%Y4Et&KPbrC`X7u@_&8zr?$I1GJhg&!bOyjQ(iAT&(B|cy!o{C z$gOem)8cnP-vwa*!+XN#)-#02k6VveKa!uudxMT=(ntn z*a4f4P8L3=;cbLI@#QCI-O3K2o|?#61p(ji_njT68`0Xqc8Mf=HUR!JxuPeM@U$GT8&IjLpAq@67y2DXCaf(~*(3*Bt{H_im-pKw+< z%Hwf=9oG!d1{efX0$KspfGz+pUk9iGj0N}tCIOrP-T>ad3J?g$12_Xl0qOux0*zJxi!7X&*fvszdj-uPY4llA1aM+UNdo_)fupN@IzM8f~A`<+iu! zQ)D6?O@irY+n01X(bEMuk0$xFfTRLO(O+@?m5dUjKs#45mOW|vhV909-zS;uQ=At8 z+5kV2Y<8U_LWUAqJ@Dc<)saMDD$dt%b|+bEz;S*CWiufs_mL^=45?vHkPhK6nTEE% zWi!ZKtOIi74cax4a27x!SToTvJ<-u3;656%WhUX|A_v=pjM=Y+M$pv)$VUCqHXG{c zfrplU1-YmoNh}F;xrQ-+4Y{}u9B+^$`kn2Ta1WV64}o4^lSUy1ILJw*5KXd$Wuydp za2}wA%5C4#*Kz$iu3xu(CtPHapyL{}_cCAt*+_yhccX^K^g3x`<1khgAdDmnDF8FB zf2WLo2pl(&MA6swx%d>xU~gmoHruSiT;eJG8MyRdj>%Bx5@_}r#>a7tM)`M;6M*1P z^4S*9v6uLO#__BO&szcMz`ufcQjy454)MeI?%}}F$dfof40r_aF#xv0)(Y4NScm8L zNW2~559h0cmz`>o4EZ`c%nQz2@Du!diUa2j z`aTOdhyFjs`9Ti2UME>(JPT@j_qH{2H21LwMP73jJ*#9B1GdGK8ap&I9Oc1^W6U z&Nl&v0BZnCQMLnc{EoTpp!FSm=kxP-@E$o=hiE%QS3U>%e3SU#;K9&%(f8ee<>-4S z&T9e7aQ)Qp^)ZqblJ0?ga+(a$>i60o;^)vaE}PQ$96UjPu=}79gyZW$KmpS0Y2x3faCao z1DuZk13*9g!g(d>ch0Z>24#5md%)?GQHO$TtDz-0*jnQbKi^=%6QZGaVknE+ni3s?_WF7b9qwno~`p}EcV zFQ@r$fz!4RH1h_}xxP)ZJt67Z5q6Pg+ipp=@g4Xe++KiYlJ1p54-L^{zC=Cfc|m2nBq(r? zzJv_O!+zLYZl@p@oVM&0=-j((zi_+H$0BI(UIWI)WqdcTIdFesBybzI1on`_N)j!} zc$@7>X+H8fIfFDv*Eey^=cWX_eN}w%8$z{EakA{tEY5?jv>Z5p0M&r;0A8L9FaorIWq?_L_1xEDY19PJ(sB}tGY4lp zy8+zziL%dVYI&DAVECU1qcwFV>^ep77lQaTq0I}kE z+fO)019+@eC8Q!I=p^3kxK!rHIw?atH>3cpUehIOhW*06aG1u@{do`~llg_g^@B z1LQ0fbCbVUX0zfv4?1Ki@LCDrk?t3OO^);5aJ~lE0JskD0sIv_b;r2_unWNJHUsWP zAK%;wRLfdyp90W=hd(-i$51l? z;{eTo-`;Z?a2odlY8~fgX#cn!$Q!q>c^SVSS@z$qCAvG>7@4uIW zwhXjf2bd4w@>U4oWeI?_fCubA)u@{U(BuAVz)rw6`!n(o&d)pA;_qz6^*BHruAi_2 zU4i>};SkA%Zsc+NLh&qof{SF1I0|`!K{6F(mEd@LM#WpEr@vL0LI_r@U88N6c<8Ib6v|WqHF-b?IG8% z_hUW)gmH+6cucs9j21BG1?Xpy+l>nV#7sgEnITL>*;v~ZaRSP?&E#!-K$5w?!-2=5 zf`v>G-EsYp?ca#;X271sbNeRfP{#e1nSl4v&&SANJdd(3Nxn2@ZqG0dvCj4_Z;Qti zl5dlX@;KO+w;k{f31>Z!_Yr=K;9)yWufX;*Blyc-*I~g`q|?G9K)q<(x8ohe!;+2+K|j#xl1v(0ToU z>BF|0%fTy{ublr5KbiAYqCdB}f3STslm~zv^SFcO$U#Xw2Ooy{{WGZ-n>5&UExlqpf_#iopd^a70nl=gp7S~! z&<1}lje+|_=#yN*{JCoT2j~MI(smATH%q7f$em1+a&(BVIBtT!lq2GLz6D^Q7UTz@ z`w1)}{&;4PLkQ0~65`&6JAM^HME6Ey&UWHP&Z8v~k3MtZr?!!WWGP)nSJ1U|1KmO& zppVd}=&STE^b7hgdWHT(Z7i6nStN^R$;`s?Ss^Q9RjiIRvQ{>h-NoA3L^hetVsqIN zwu)_JTi65a74|A}m~XJZu`dKsa2LDx3=%n5x*C0F^V z0#!PdL1j_ps47%rRd=Z-s%EO@sn)30s~%EqQ~gKnrjAr+s|(eA>ILd$>hYeI8 zsrRX0R==ixLnCOMG%gw+jlU*bqu0b~8Z{F%k7#yjUe$c0IidL`oQ2E6J;J@ieZzyo z)#1_MhVatx_V6w((|T$(I*5->q4U&@(go;3bP+nE&ZNuKP1P;et=6s64e0LIJ*nHR zdr|j_?ls-(x;J%i>HensyY5rn1>FyNnchY3p%2lk^jZ3ReY<{|eul9#F8$eoXYcz} zv=&=!wp%tE=O0m%1d>kj$XH~K7m@7}m)6rw;L=0%ar!5En7&WX)64W4y}<&Qis@M# zO9Gejz@<`F$!b{xYaZrO?=Y9v4|D0y><~N3P6r`97rR{2>_EaZGT$-a^s9vt#png*QjC!y7CG`RI!C@}>Y19&z z8Z=Fs&EV33JGkTpE(M0G99(J(@07Tt{x4jr9p=(z-FDq`x_!F+;L<_eVQ}fF?gQN^ z-B;j}6S$<5xRkEX)wk&_)Q341Kz*gN2S5IozeU!4 zc=7~bqN7F$@+x|8n&4<6k4x(d@JGl@vfNQaI$4=@nJvqT+ztO%U@8F2jEhv!~Dx9Qy4bBKn|@hF&_Yd_a^ zE?2tpKf8*MGhdxKf9B+w<7fVU=DjoTpLy%di)a3H25T;7BF|{fsLq6(2|N?RYokOd z9pNuyJ?0C*;V-U!@%*%ENp{R9}?aY&I?zC>%t9DCMrZv5p=|=4e+*u=nYTD@n?S~ zJ(or#UAf{dr5GTFi!sCX5=8URwNwX@i@k&&#ayuz$A#h*@w#|J{6)O!By&Q{g4Hyf zom`z1PL9^4Yi}pDQ@E4SDak3*so1H-39{xi#;Mb(+iAWNG6qh|36UkpCh+%hH3zU* zwpzA9wpF%Gwp}We4zfpOTlob)O0fI(Gn9Z%vK=yRAMiJH#Y=zhPqt}fsq8h`A=zQP zby#);$0M@$a2z3TBkS^O8Tc{cPr#a*J6T62lU6c76l4pzpWFkTCnNV_jqCxej>$0- zmXa038S~~L@*w#GEY})3kvvS+lSjx(#GY^c>0-KyzCl(~XF7?N&?V5pW#l@!MW@mlx`qzWzrug}l}6ENv<^1)7@bb* z>D}~i)CGEb25qM6=)LqkvWzNeA8n=Up~1(JU+9Ohx_e=Dk5lAkXf~~7bJ%Lw;PdoR zx{E$W|42pZ3j4i}zD55|Kc`<)9ZjZN>2t8VYw6qcBYKAZlZH|Q>u2kz6Lo{FxQ9MX zU!+IrKd7FXDEGy_BR{eEY$H`sBeBwa+DOA_EG)_M^eg%;l~Z?GK%1zV#?ftbH+*Xw zrSuv45`BlBpjGrPa)aEYMRYXP(0KX;eSsF!7Pg4pOM_`N-A?z=5E?_Dgk6$R1zk-y z(`V_+^j&%q7V1Y@P1|TBHPfZ=GnUbf^aw4d ze21smGwfNxo9%*aD`B4t{_sO;*gx4f>^b&4yCg)i*WqK_5c1(qWC#UpHe12wvGr^r z+r$>L``B{!AbgT7Y$>~+ErVCG8`mF?*SP&ps7=*<0)udtY#YpRQ|u9Y#VO)cahf=S2+v&A{$T=8+SUz{h-7Z-?& z#Kq!Laf!H0TrRE>SBopf_2MS+UU8B*8NNZ6xK6xV+#qfg2gEhvTJaunrZ@xIZiT29 zyTy9(K5>G$O>7Xih>h^~n#2dhX7NFBG<=E{cz>@xhzAK_tMVXv{P>;U_L{h9rn9cKSwZ!;@9CJ6K_{f7M&i{|eLgncNuvEzb* zo}(Ap-vxK}k)UK)n_>SDJlF}rlbsa2*e8Mye4tV6v=9J~HISVZLfAPWlzkRALlKplMb za8^gB&{^;ho8ZegvnV!NxJzgg+R0{epD;lv7OKf<@&!2~Oe9|-l0PSOk_}j)d{meu zOctt$5*gkx!c=(Z!D5IQDyqaVVH$aiY!%9d5}{Nm6UGYTgz-YFFh(d6nuXEu@Ee6D zGE0~z^a-=c9AOUWC-ac0m_eosv*7*DCv$}b!c1YIu!!`L8KN)wjC?Mt$w};17%w~~ zJShA@ctm&{_Ia!D5bX4$h!eI8o5*5exp1$rUf3c$AgmEq3U>=@;i0Y){w|yp&Iq3i z$AvG1&x8+!AB0Q73E{HvFX11;Pq6K$gloc&!Y9IM;ZxxQ;d|jb;UnSS!WH3TSpPSK zKMRN91H37`EgTV!3V#va5nd4v2(JqJgx7>U!i&NSh*0(le-qvl-WQGuFR?F$U}TJ3 zxdr16#t2Jx=Ow%I_P^Zy@2p=42}S%IM%0MX!x8I65Z!Mq;*(?tc?yx;)8rZK%h&~P z@;UN6*-c&`d&r;2AlXaykr&BJ25WmnSV5+9`6WM+tJt5hoSY%&$QXD43*iU64(o5X`aRs@gTcL$t)E0L zLa)z9jBMBFUeM=(*e5}9eSf)r<%jPt9p1n9k54~!-^xXOQ+slvRmQ=ohJADD78&-E z1353!!(ac=pm~vLUgIYrH3ld3%LggQT%#kiNRiX0z=hw8u~}sP1@CLs*X? zc&Jz$E*4Qd$aDu#S0p`#R;{L@i`PFu1U%?7#2N=_yE+Xg;yXyRse{COkc=9nzUsl{ z4K0Jq+Xv;QR`p;mu5;T5W&B!=Yjyje6TkMvwP*XF$giDo?c6>n@M{s*V*4QD*9_OJ zeUS2Nifh_FNKCC7bm^p1Z^$2{ZT91a4M457MH5`q)|x*kwDF-)zrn)>HQsA4>*Rw_ ztJPk(@#cdbjE>Q?LSp@5jr%AiUf#Z1srY(~COpnw-)5L4ki^0W)<9~Fz!L)q=I;qf~Z<`(V(aw6!g+jhgNcm)EkPl?ds0Kg!TiJ zOdKQ|2XhA|w)KKzo`W9R3~i>ioeKikE$GU*=tH=4J(^#D2TqnQM2< zusi3KG?_302-5e4!B+iVzo%ljAYteQ7+nKu4~n&8x%{gOBuOpU;N^`cX_`SAj2Uvk z_Ky&}klR9PC(ZAgT0Cj~9(6%uUY@!+Aucf~E+IBKDKRlEsjEvJRX=z7oJrHV($z@` zNr|zENwEp3>O^z8DG~pp)qj+V)rIX-=ggR{u4-@ZQCH6CB%j)G%}x9N2vMI6aYR3-Jb%UTij=6PX24(A@2*!+UfxvHS zU2_k1EU~Nf3OfmFw1*tC-A_-m&kzfZv%L-!pir0dx#oP0nLQ@|-QpmQ4m(FY?CoZI4T9=k>JLmPM}uqfz8vq>mjzhVWbJY>UP! z<@<=(L^hM1$Er&f`I`KbX0v>{fHjgawu^L$?IAju++%y)c9g9rZ_txii63M$X&~0% z1lv*Dzey=gK%TyboQ4JOw7ml_(TJRqnq9`)s2egTzKAs6wfzd*uA=9=*&F10M3L99 za?n7d;7uxRX6gb9na?x4@Il!s>{YO(YsyKAmSf#0XY%zC_71+v_ZYT*FSb_t`qE^KT`! zwng*_+g@7*J;?O7#qK}(p+8$6o0FhQX{EzPc z;olCrpMD3=y%AC5DMXp2@H4l<2iy-o@hCjUkKturfM0oCup(Ea6#d`}>k(0z5lfXK zvKfc?rVo+MYD7a1BR~3)c;3m|sn%(_({`t`PS<4RvH{scvhA|xWG^9O@~-S7*=gAY z*%djF_sVZNE1lKONzR4N&Cb2f3!Mj?w>b|wA8|h6e8KsKi>phA%TAXwuFkIYt}9*l zxmw+t-B!9CRg6-o75$3m6gSOBiR8$CNc=X(x#zUw7=g?pL2ioNQ+CU_n9`poOPcY=4f z_h#?i-bZ~zAGJ?{PrXl{&uX6?J_miSk5Y~59QDAcQ==~Xj`EH2E%oj7UFo~k_q3m% zU$Ng(zkPlu{8j$N{`LM7{O9|x^MA|#Qh+*OOu$0{g8^3p{Q|QC8v|zr4g~H9JQ#RB zC?cpO=s>V*a7Xa|;E#i^hma6ONI-}_WJ1V8A@7BXp~}#J(5TSt(C*M#p({f-h3*L5 z7kVi4gV4`HFNa!H-pDO?s`jYPhpEGshn-f3sLkrh$n~GlC^Wg6Qcb_+A&V`bi~Do zYseuhkmHZgB_O|Fs%zA>AS^mwst-4*hmyk0)t~;&!PIp7k^nUsny$RXkN_~sI zQ$I()TtA?HSie(0s6VKGSARl(R)0xJyc$SaZ7C`FWClsd{7l^RtTRU0)fsyAv*)bgl-sE4C=Mh!+CjCwcf zv#3i^*Q3LutD{?@JEQxe7e}v)ejs{#^zP^b(Qic`k3JoJG5T5ziBZG^#6-j-#AL>l z#x%yX#Y~NvAG0B5Ys@n-FU1^*IUaK+=3>m%7^}h6;AaRo#2L~J#fEyr1jA%QzhR|e zli^XrGlqSJLxy99j}2cLE*q>ySEHXX+!$v}Hx?WFjE@?38V?#z7|$5LGhUBnvC7z> zSbc0#Y<6sUY;$Z!?DW`$v1?)kj?%}wdaf5LOFJmN+$We&V{shZA=t9!PvQ@kHX; z#7l`clEfs>ByLZ0lWLRNlBOojPdb|PQPP>D?~<-3vt(s*P;zB*OLAv&U-IJQb;%DT zZ%^Kxd@%XFv-d)M#om^_iBNHk-Da22BS|@0w1S&YP~7Zl)+w zLQ;$=nJJYiEh(KTyHgINyp?i1<#fu$lxt>UPBLek%gxQ^4)b*LLi1Yl7V|Uaqvi`1 znMH4Dv23zzwLE7zU^!|zVfiZ6GgXypOl?k`p1L7*YwDiVQ>hnHZ=|`VnbPvps?)}# zb*IfrTbi~Z?V+?CX?xOMOFNeKS=x7Lzog63mFd;#W73zVZ%E&gzAgQ^^taNFr=L#0 zn0_sTWCUbHWSBAvGa55GGqz_O&A6VKo4GpkK<33Pbyi2#@~r(?)@)gJVfM=Gec2!9 zxaN501mzUwOwE~-vpi=Y=Sa?loU1w3Tvcv)ZgXx&?)2P+xodN`FF0LrvEW*vu`so8e&OoEJ%z6o9xeQ+@JtaaQWgaj>5GzzvWv=# znv431UMjj!bhS95IH5SRxU_h2@nG@663>#5lBg0>Np4AHNlQsv$<&g8l7~xnmJF60 zEP1!&M9JBbOC>i-)uqPL)Y8Jz+R_cB50&mHJzDxf>1U-EO0Sm1l$p!&%chqtEL&T) zrEFW-;j%Mjm&>f>%JQi4{PNoJ3FTAE7ncu|Z!Ou2%h0&8l6iy{m(&)2oZC>#N6AcURA;URk}N`hlAIn$DU7wPLNl zc7E-)+N*VabzAFB)ce)%tpBbdrlGi@zu~!tYmNDhm5t4f{f(O%pJ_bS_;KS`jhCCU zo7$RIHf?Kqsp+Gpi_J;R>CKhR6Po8V4>a#=KG=Ms`SNJlXw_)*=$6roNADPYV)V6^ zQ7ySGZ7qE*%Ud?LY;PGH7jW0|j*Amz6VoTonz(J^u})>@!Yr(x~_IRcdNRSx{JG;ySuyRbg%B-(!Hbmt?skkH+#H$%surz z(|cC;?CUwvbG_HIH>x+ccUA zvl-ztCd}A1hp-nQQHr z^(*`R`i=dj{>=Wu{?7is{+Id>_Mh#)IWK5l=De19OXuyH_wjti{M`Aa^Hv*`%Qh`LyzJU?vRtt|V0roS4a;9zetCuKikKDU zD>_!JT(M)t2P-~aac0GLE7dEFEAv-QU)jHM>B_fOo?dxeQ;Ut1hm(vRb*?Z?$T*es$dHajUnk-nIIr)rVKVxBArT^Q*6{ zzPZMEP3@XT*Bo6tYHj1%b!!i-y>_?a?xefN-92#kOLyN~m$0sR-N3rH*8Q?RXnp1S z2i6~5|I3EL4YeD_ZRp)FXT$Oh0~;RRuye!UhJzd4-EeBdg$>s>vW-a_GdC7*tlv0p zW9P=58_y1e4D=3c82I2Gtkw|*UysmB@X-aV7!e zFMVf4iPrWLR+AdaO(F-4r6e4BA1c?HqP3K0&&$4khm0uQK?_wb|4XM;%Sd#wL~}rnvj5odi{Y zK2$E7m6}$eW~z`;!6r>4+Lhk7w<{abE+cK&-Is!Ia>9rKE#IdyWxk{4t266MZ<2;{;f zL=Rdt<^=zs#IVe=EK5pbqUEkEQ;~X9M1-Gl9x&H|0-$Xq=w)L_G7VwrlPqXu_THqP z|5Q7^*w5agL^^iBD!#*Cwv&(Z$Jv&Kcno{Xhwz~eit}ce*^;be+Hi?(mTZRAqx?nE$%?5JEwpnRGe=xcnQwsE>1y~L`xD!n7Q#Tb%BAZTXTU?lLI4P ztz_!t2(n3PS^8y<3Ma=%#|Cl(BhAS6M`1GA`U*0UzoS7}j1qASy)N8u&}e<+79Aa5HRX^y_KtSsRE$P7)Cdom`J*w7gbZ+hhSC@#g#4<%qWrK@LM2Rtdv~R6PVNYJcWM;@uDRmF% zC`_p}+*O}!NwXvtL=>mS8}oIRq{NzpRE&oq!X#LCIe@&gJ4W;=Fi#f1bGg={vFLm# z{kp7nx?*}*b(r-DH`IO>JCxx6$3La$0suTd`+@cay20L*c3^x5Tvs7qbIJ*VN&q^Gb5Z z#)p@r7-B1PQySu9bevy|6r#_upko+`L$8}4PncuSc>Y>D*JO8&AcB`l@+DEY(A~Rb zMOEFjQT58g;AhJ`qxqW~egBowemjCF9*_dga8;)uRjb z@%JqE@lDSzo0FH-mRdBYroj|dXh^6_Xv~5PEyutnf(|l_=#%}1WG&TLH1e;<(0=O* z1^vX0%`VEsMh&9I*h?|?VC;6YpziGb1}8oV99ni*3rf0BunPpIn@mOTyraR%6H2r$ zE-fWVX>Kn0z4>{QEp9Fq8Ciu2_kpzRF)s4(l7#FD&hp4I=aL>*7h{vTpwrdG(BKi0 zT9=aE>=isFE3wj;(w1v3_Y7z&t?8f-<|IYuXmgSc*&6G<%v6X>Zdzg2v`NZ}fnn4&hgqgax^!e6#l$b~N`(O)#dTLI2kI~Jo zcydl%m&sY-)v$1*+&Q9*liNACIL%n)(NYwdFtEa9lp6>>H!tfh5PWlG=jJNm&`Trv_8#^*AoH0`)O=aVt#bLc;-rtDVUVjby~b^IL)gi@q;R2TVu z2-?Vy?!&I~dp|tSk$9PZuDU-Sd+ZU4r7o%Fo;GJlS*qiCgcU&8Ng zKjC=|jYj=a>A3>W?e}HUz1;Qyb~o_$%cc8x)E_U^uaMrqj`~f~^GfMC!+ouEUnSkQ zVTJ2u?75XAA5}p&OE}|uWZEl4psa|LILh#SGJQ^}S3F$Sig%xu%1VaI@clB~g>OaV zq;$Bf3-#<}$T2$FYr}dPQqHJXK3sw{`@`!z2bRxzO zh&uncVi@W1IJ6|TOC-azd>pP4o#QpCB)43T=B%hZja-%-v-TZV>q(ct1cdo$V?x3# zIohl!T}sIKbnKEt+up!$0@{Yf-TUAbgWpIqqL-|Q zxDm94SBgC#_YB#NE3h4MyY0~CP5V}No)&sf z+AOu9KyNBA#f|cf4ACX~dSQHl_?l-DXcIg{9eBkfZNxB1BtejTQ21s^=0I0hQ%z!8 zk!zl_E-<0Q%_mzMouzViZOY9m`cf8c(3_l`3^Dq|ZRwV##I#5+|H{Ik(3k>!a&c}} zL`rm6xG7S?F%R^a0URZJAjt--s{Em{4?lOKS{GWM!C44<;0#*yNbB&)TOy0(C(y_d_P|dX?yw9$8(*%M$x@9WTA6c0PE3Bd%qcEhtC!2iXtfEVlb_y4 zm*6BO7RCBUnVn=oasCl$PH<#0^qK@0MWV)#7G{YEi*Z(_=?xj*hXwelJi~%i5zlz2 z0)sUkp&{HwBBL-?SPAUIj>1SE;3FNj0VzWkz#(ePTe4c5q!#nzmJ+u-XMJFNiK|Zz zr>9(^r@yOQxhaWuM}~3AY9&2XhkZxSzz5~J zDU;?(Wlv(2yOo_lSrNY1x0hw1>@lfc@o-r=-hD_aD;X}!LA^)t-2~T7L+>`Dp1rJW zxU7z*(kG>N%ZJMr;N54XcPoa=a`EmHQoYLIvPzUaDwS2)%MfQ_UoGnK7zDmH_q?!A ztGe#in|18Ktx{->UfWe+6XGQg;sdLbh+m};_pVYIyoZdepQ--O!Rdo)1fh zWQ4z{(YrXO)WoIK=w0RJnmGE;=36f{eROCJNdqms#S?!WW#By)=? zwm8auq&2rnnY-GoF&mMqHd{SMKHSNR40vdPoXa^rPPrrS$s;vnJaC%we~Xj)4h(g7 zV1MV`{}aA+iG+A<90(v$$@=!46gVLBk5hAc`Nwg3{ht%$_lW}v-6j)k{W>UxWsv1a zhb%|pR~HsY6f9;TICKADo$Y5~;qTTd8LpEh)j5fF?DEgs`GeHX?K)4OuO+`}2iBg? zh2eI}r8>MF`<&tJJo}qE8`0Nq+gU8tDI0Ewk7o(yz{v4n#|iqnU8j7w&h5Ui3VKJK z9I4LxXvaQZ`FQMOzTM6`+YMpOZ}76y5WCFK9;acmREM`?pX0aNxm}0TdDZW>Q#srY zAM+{+uiIm`kLPxsW~t8a^>w?>oZ&ilUBbuHCE@O<15IJDJ;3S(19nXr?Y4q@;XGWk zM_K+pk&?;CK>P@M!K0x_xj#>62o@ee_<2JW>my;DjdGb+mUnr(dzDL0e4NLGI(PTg zZGumhPw6Qb644PI^HG0x? z_vWJ51m|Dm$xqf#pj$6z#3klhAIMFx6!^kvW$3`U7&>>Rbi4i_z zggjDS#4%98#~z{4;`l<3a`)VpWRDEb%xaa=y{IS8Bf}%FQC{hks9#l@HBRB|6~9SH zjEyfhj2^FSUQ}8*HP5NJ#kDnCm*{MrN*{FXsY+~t0p>oE7Y0%U-`kOSFsGPOw3cK? zYGgQI^z(=Bf&uUt-~4>1qTbt8Ilr}PvUiPpp)sw>Mdspk#r=$snMUtcSpD5{E*A*2 za6)!{TYX+cbdE(+7FAfVII$=*Me37()%75ByC1MFMNFI=3`NbIBZq@zNw#1NQ|j0J z+G72A!0iR)9>og*RRJ&H>IJs}1ldp1vDU}4>ay3b&#ud|<1q>`#5~}UfHnvA8_bbt z0qo=rNkNyyZf{hKTN-UVAzA@^W zfK8ugN4+PjD@>{KuMSz*P%`n^@~o(6Co!vjc9x+mHY%xoOqRkuJS?lFBQ~;Nbb*9t zEbz<(p7<_n@7(f@T|Z7S&U3tCd}Nj8RmH0w2NVZ%%_|x_ z%YC|^oYs6Tx-fc7OkvDduQwDnjHxfIKcVj!Gh+pe2E_~~*abb@zD8?Lj3BU?6aHF}hit+~(^!tEEqh0y}T3xcH)mPE@+_c!-I8$DpJ~KD2 zlK)bb)^vh|}; zS-ulnl&%$N1*vYzwNqR%iH;wLUK_JFIX^nP($)I7aZTLcippaj^JTBh?pI8zwiJ8# zcH}j*PM^-^eE)qzk?TbqN9CJxh02qwcqWAan_t8gE8BR)?sPq}Xsyrsg7^Su36zi4Mf^a;k%CFkH_^u3@ zEIP*+smaK$GR_T+KBtbc{>xeJbEIM})zo(0-{{mc$$8Y8=c1D2GOII-(?u6YcT7

Nv@3(+{e2ro9i`-z-AwpK1E+bRc(G^3jMM$qoOWoKC3urBeim$Nc0n_MY-1BqK!2cx)ga7xfB~q8@I=n z7>itsJc?b5jU|mw8aGDy2CNDm^XeGos%GEJU(;&Se#M{tL|>mbZ+&fTE&tLJnek|h zW3?oQ;kb6>KawTn_(*u>3H|%oXB|gg?t>AF_B`z^1c9%lVciLd^d^`!fpw8lU>BdscRV^wx)Sh!4MiFS>lk*K(gBc z8UZBwjiezyZqy2pbjUhrXs4r-)?cUfpXoHgwSgA8ra6@et0t5y)-)Hjq`DM_*1L{x zt59y}{mPhhttxEVK%ZE-r7Co~^;Qa{bIfw;1d9In;}1Xl_#-Z?zfvdbugpAy6SuZ@ z{hBqaKw2In<9mn$LvcEvfk>tcaBUIA3R<3(L==G>HmB|K_oq%Al~@+xCQBsY*k8S zP=-dE6WC?!^g)4fr#{ahlZE8Q@v}TMhZV$(^Yrrb8JjpJtRgxuaa_z;4=-PzF}N*{ z&P$SoXX;~0V!|_Z*s1^}Fqz(mz4zbS<3=7A_-GL@($3PACEZ8s*!R|*G{|~^<3sVK zUI%u;XWG|&P*)O0`&6I-hZKjjMOuC=rg8c2y_b(KnfN<24m*|j7qwE{!Sl7<^i72| zhxX-jzeo>4C9v7>ix%77vwnneEON1+yqFwf8T2fkFCNv0(#4m!mm^>d-55g}#^7Pk zvLLUGSbk^$M~3M5Yc@`}+i{{m$g)FLhkn zac#oa>D8=ch%P=Bo!lLKhLTrWd+AsOaQvit&!37oj>)KNxLudZoh-4`!VGR!=*2%+ zpH=|ZPikI3WnT9tYrvOKQicc;0X=FL32r^5bsH%q5r^Y)=OP#P3R6T*bVPhWhP%AT zMNyFwksT8e@1IVS4e=#;D!n?m)DT~sAEr~|N749L+Q_^3R^l*ZTv~(B@!YzG(IT~V zHL!noe?IEek$x7-B8K<~sf0X26d{Bg%&T8F9$IT&eaQF*{Uzp^CrnT7ig{+cX$RW2 zy=W_<`)up%yi^&u_mW5dt+2Y!FF@anm~5}$D~z+mj96LR`z$o`?Red+kH_~>JRLP0 zPu=#0!mtT#*!&5YBk&vJ@u?Sl$m=zeTq~S&lVdD0XQQDiqeLOkP7L*v%af`T5^9p2 zO66Jch72c}QI{1{sBv-5kJSXoUEA(ziq3I%F&SzM=}xk!vbvmbgFZYaR^jJWlNX)m z8BnN?%!-J}Gir@7ny5H8FGXyATx_N?pddQ6nidAgqIB`G3V)Zl2z{KLu4T49wiY_W zvD)Pn>7x|@_S8wThW@*F-T1ppie2mGS`?lSFQA^Aso-?eO6lf|X_ezUB>G?#3*V8w z4Bm%Hqq&CO6x=m`>&^vBx$Cyl0_ruQVDREgcxh7C7Y0Yn>P6uC&m;->!c4- zDSprKQTipj0sAKIJ*7gDb7-bSV%8+ntd0JU)MRB(OrE~dZ}hVqRDvWy>- zm?73uY$%Phlo-ZErW(Tme6Ba)hikXucOxa;iuF^>$99^bu)a+TKyW<7cY);2=kC&c zz5QIc_M7LzH;S8qj^`#@8@-EtiFH?T?;QI&KX>otGWdfgU;o~c+WM|hQLUwkuk_D9 zJz@IUxnF%Yh3kpW@tzFtNvnJ+`?@1v)<-ay><4$zueY?OzT3wq&{*UeoZz1#QY zw8?n)ZM^#~-gV$4<*DY^S}U=u_2vrTG!f6MZa?>dy_#6|WSR7o;~b3VYw;XA4E844 z*Od7hcj{*!qFn8n!L(<_gp!deqy4 zdJ$;vVYKJ)E@d8EJ*kjOm-$KVrap&q??GBgJ*pgEkgkZ0GP#AgTC@=%Zh@}4_!xz; zvP>B)_mB5g_-W0qthP2MUz_=doD^rzqKx~J%=s;Kj~dg9W77GYlg8J3yWe3S7uy)P z3LWmvZ+HYU`8&#Sh3Gdr#WmQ?9H9$#4RVP{h<1yuD)k7K2gdoj`)X6%yl!_}oc^0` zfp0#(g3ZVG-wxg(t13-UDO)c4HX$N4Sms=rpflq{o%Goe@wqrSbYv>BfpPdsSF$~z zl{Af0pRU_Ap@a&5J^lgb&nVP!9Z?59pf_4QZ~Gf$U_~GZZqRY*0KE;pEn)mtwAD@El{lyjG)yAGDdh}kN zt?TzrYTq`^;O$X4IV%?7=(xN(m8+tpH$A=DGqlj!;o_d1l~9==IOU`oN>+x)TH3Ow zJ=QpW=ajy^J^r~0W!cK-JH|XbGbdyEqir>diVd2o(wUisle5xeTVF{mADezxYF<$S zPz3H{X)^hQ?&9)_oGxGAXSQ!}=zvXnLc+A zJKd4xefM29!8(YUg@q#Q2|oax=8rg3+`APzUBj0{WcIYbRs)xu=h5|AVd!EvWmOYn zT)q2q(_9qm*IF+eir!l{!NnzS+*h%O+uB^*>RQ+V)7bQGtzSo8?RYlpVoFkGk@fGl z(#(bB;Cm+W35SqRaP%kb$Z+Hkh8Q%oi3PhM-h60G#f%a~gJ(x=;jBt`4`Xjd?R5Xq zzD@J1D*LNKJiOSfrnJty;@aYc)vfo`*W~APq-IXJud!W{2aexi;OAz~hyXhW!!bV{ zVgWyY_sQxdjcO0?>CbfibggFf7^Qb&f6E%sGp&1FP1}8S#ksR9#;mKnbpmZtiDoOL zHnHc4(~EmQ_J6zSNriP`G+pd&T^&mY+^u?cp!j03wRq^gxp>b7bbjgfdxCa1{j0n6 zZX=!UZrx(8!5jZ6w!V+9&{sBS^&w~_WwCy6>^89Ht!|5RHgo`A;*mDB&}(8VN8=kOY-uz#nM`^4^J-f*H(|q) zws>;OvV!dC_P#moE{wo4SDGzq7_s$J8hNXfR#;!Q(}(Lj+Hgoa`k=`!2v&GkDRlk}YdtONwtkYv$MzA8vp#3tMhmS6 zrT+Q5&UjbSP15+lz1=l4IM>V=PdUscs(D4gIXW_GJtk>(@V6vbx~!P z_1QyRz{QHGg$0RRYbiZ=>!3qM&g0!7UMEvd20pxUn$cCgsN8zAyBd8RV)=M4@75cV z?)nj5WAnXA8c^mja2wIf$h}Gzc*i zsAFh;^AI-5vXt(oa(!d09XA&5%F*FyDdYRwHK6)c6KSwtm&{#XdGu~&P~rr zwPfXZ`S`ZACfCOG)MuEp5-gcYAAd5g z#kV#|b!#x@Pf^z!bzenY`;d8p!@j2Llg#c5d}7hWCGp;QH3fbx)9#wo1ulU7mtNZoQ;0~>JbIo1q1O@qV#BkD=M zqhlWNiuesL?X;Q#>ROsj70uSY6}i?Mm3ZrQ%!j|Q9L$U4TkpgF`4;`yb-1K&sURh_ z!9&b<)63(weBW*T$FXtGSg&>C#|28PKT;3tUh5xdg7r9;aeO_7_xGTGXW*RR=pQrD z=dPyvs7sr5e`0k4X7CfV-8#m4nR-d@P`tAk?>Oc!D5|kY+p1>KrxdqtD5%xi>CT9v zsHlJ?6xm~5FWXi6HTFR|*8NqWh{Zu)?kc9(BN%B%gr8jGz6I1aUmc;36)ROuCQEIE zv)lCUdzESV30dxy%1Mn~cX?aiGDVe|oMmfE3gKUr~^2%6Qb!1p-?cDBL`r-s_ zLS!7qjNcx$UBz!GoCI$r{lPt6WNPj6S=#3(*G9mS$K7^>c9bWon-YsU3?A+!y*ZgJ z{@&#@zenj7TAX62G`qTnmZClD1)ZVh?uOE-nOUZZ=C;P%&U9mKYEBoNQ%WL$^DW>k z<>95c;0|%;nG9~r(WO2@>0U9tpt0MzTArO!(4%w>EjA?c?);YRO$)lf@>*`l(bWgeqN?fHzfflrF+Sw?2J}_ zONDiBUblx^Xo)EgxP_D!=!`XM8_K6lzzTr({WqAep%G$NHCDU^5 zogb217oV3=Mi-ZqkLGks#(}R%O7l>~_wVow+Q@wMKza3ew+XK9qwB)m16sUX`rKyH zgZcIhb-qL!KbsDF$=r~kmUeYwtP-6a`X^^-Zrk^sSdz(YKbWt`(a(MQJFDk=*D1;p z3+p`;>iUGz&KP(1;@;e>(f(c))@>GJrP;+TxYz=+xdzeRS*1mq$g28GZDh^r+Ttmh znMv)|TtlrTqa$N{eMYA=Z^FSe3cMt1GExGilq%&^~BV*D!bF6=-5#?iAYOG&!Ul;9>pBcVV#BcAw|Hm&+J;{Aw+k^OC)LstlFjNk@ z^8P*0zvLH4{bP%Vj}~hbJIX7mbC30-OXE)&d2h9!Ub9~R`Dgn(9HGrPd<7v3gBo z>9nkDll4O?NNT7Zx+o2(0XIGRG)sLtxa6co!u5`Tj{GJNqzB65q5VobB9##Ooa`1q z2z^Tlq&~&P6_^^TQe0i%r(Swv{kqzcshK$`J=VEd#`@IE&P-!nYUYq^&`FF8KgsW(-{<{& z-WVk9o|!pw=FFLP&Y3e)(UF$6>DXfQ$@t>VWoy5Lyu}7SWlw-s_^cnl8r%`m1d}%y@p*> zH_+dhnOB&Z?Y2h6jy2T}%=2ZJr{Y`evKi`w=_xY6%iW~e(y9ieuWGap+8m`$DPq(9B5BAv7 zhf3;)GA-48)i#f>dP$V0uPv{rZnBA0D=&5~8bcT@>PMUn(U}h5<9iB{9I{`#8i$7e zJYRXUAFJ+scBcY!$RpYiS(0X+zmx~b-M+D-}SC?AYV$0#WR`%M`>XweK z(Hn+4TC1gRH8d#qwZE&p$o4<`ta3Ih0KN`Zf&1NWVn4zqjqeP#F@gPvs&7>HBPJM} zg#3Bf1x=Quj=aK_baTW+-zH`$FLzZ~+w5cQ^M)hu?6XCzEvYk^it|~MH!C~OUsO|P ztgTITmb5GzeY~QgEHg6~DoNBoANBtY^+$=dboTTr=9^HDsrfcb<9K!Jf*hNrX}r2+ z0p4wjuPLq`wuZ-hi>rs&YyP^06|Ecj?~6+YD+XJNhsyg=k4$G|Ig+27T+sU%k6#GQ z>YS4k)@rU=z&g&Ak2y4d2S=QzQfyJUdh3ZBSIpbDFe^G{%?*>{gA}>JKkuT2c1%R; z{Ui8bOH*E`Zl4#{X)MjF8njp%YkQrY(MwtzMsuu|dTHB&mMup{ms}zKoLkkucc|m6 zr5^sLyuU6Zf5_LlxU;^eaeZ6eICMDu%#H{NPx5)W2s;B<-61ht*#>&~tQop(tE{2J z6uGkSZ3)OHeIrY2pDeanS|%It*KTQ<*kH4`daFu$E5h;D)1F;a=yB)REAj@5OU(Q7 z5g|I>naS*JTia`Kb7f0=?IO>`ZQccy`JE*-3oCE24fgcBT$bxdj&^`XJ&#Mp?Sb?n zBlo1y&ZzwA3KkNT(Xp7r$bA6^O0#;7G_zW8HY#S#jo|EpY<#$3YD_S8Lz`f1_lCB= z+AYiGElT$F9v(frw%!z8QazX&MI2pv6dXM|y5#5rZ~ys&DoZ=hTH;}M`0ARDuv=ep zWfhg*%w?~O703q=2QSZ134Y!Qi3pudx#nFb0@|d@Pxxjr-hQwEo})Fc>=I*GdU4+N znDEht=3%qXT$YtlVl<}b=hoIlgwJo3<_)*CR^7R@ibf+VX!NG0B|DQdED^~o%4!=c zQxlV&NvRPLw#Mp;T6P)6X5pXTQC?#EaXMjm+vrt99Q>nZON!-6b#4Cl*8DuTr>Mf7 zm~50Y5;HTx%+j6t<-T-dwj(YT_9&DeM|qpB{G2;;kLPw5l~Z{ml{c9r8RawNY)5NX?z7IKZR{(WAhPv_)1B!I9 zuOSi^?Ztot5#?9HZYyw5N;ly$NRm77tb94*UquN!7i4A*g>!HDNmu4}7gpGlV5kMj z>0#zQ!SCsu1rhn>e&PUc!D9UvnsY9a%Gt;8s|nU;n8VS$Ip5j7oSwqUAd5|=b8>n@ zSSCxTBhBdIEO#%W3T5>R>bsA9j(Y>%+4`XMcJsx%x(ahpg3~eQJNrpZQ<|LWNXSC; z;srR!>%K@o%*iw-rDPj9diN8iB~CciJ7jnjLwz>`j#52ePi8dctVm3wYbXTH-YO8?F*L!tfhu)gq|55 z?a(u$uk)D)F~Pa+IS|Hm&wS5qcmivkZvVWecQJN6e4%E1uTJUeTIe)!+X!?HD^oAa zu_X^jPxg9_`BzA^W{p8VLAs1+v7Uy|)4c;zk)wl3y>H0( z%EoV1Px@Mi7R_VNV|_gXGK+JTbS3uodB(qUnSGQ&XQIMOgK#&Zl6ff|@g!dgrn`da zX0#9KqMY0jgu9J@Cw!3n_oYEmUPA0#HDCQn`GAM={ePIQZ1_{XfxDC9yyX_>Ewt|g z?fk_KqMbr-cnKC4%Ud4Nw`+-FVL42syDL(XHPXI9&zufFI`=g{^WR&F8P;dRmj zdz-fOE}gpY*Z!uV#n{HZ3TLNe;EM`srh+fke>Q0RN;ru&$*LduZJ~E zw~S79*4J0IyZo=s8&|yGse&_+M|w`D4|0a;gJ#cRYV&XDEzcS&7YDI*Ezq6J9>ho= zWKHj?lAdGAUvmpjI*UOi)Cvs>?Z={Ve_%e&H{?O6?Z?DLAoa{lniL$y#HK5cPnUe_ zrS3_cT(@Eh;~Gi6E4|Z3+OXF7v976x4J#vMWxsK#dkHC8`a4!Svp2OhjOK+~x30X5 zk?!n$RxJBkeM^+R;dS1|6_H^FI@{Q-(3jOzR@NLo%Q>WT-YkxBid`EsJzx_p~#A zO{aE@?wUxB>B1HPSz1|uH++9nDR9F(oEAr9VxqZ$M2u= zO_W$8+C0q-_NenGw%gYD7F%qCIPulik49L#e67o)#(LQ&JGNC%G<&+D6V`VwSk{JH zN%Pxks(ig8iC;hR7=X5XJTohfBa;VCdUr=Z+w5JTU)vxQbcf-=N zzP@Gf;36e(tgWo*v+m7JRjYcHRm2;K+tGf8dXj|}hcw^Vs+PhHJWJIbYGD9`lCMU( z&$RTqsi7@xNfAaV*;q2s)3Z8hQT+T}T}v)(Tdvf}?(wd*2iTS~?p)ElcCSC9cV}m7 zSf7EovbU%6>;WefmaeqgKIONa%NF-=9_E3OzvFW>*B_y`Z~!@#J(a5x zzO~S{ugElg-WyiqW_o3qZ_7`d@#u>6V0Co0$;n^ME7>xS^mmh#D(!L`cYU}cmIWq8R* zk=}=eDo1$t!j6Q!9CxawIZ>=qbf>~>%1LlycgYV}czY3NJ~uQ)9%=n`i>emobd0X4 z%#S8hpl4A|`^c)wg6No;pCx6nttozY_s+pcRtSrrEjdd$lG5hx97{ZEkv0X*%S8)#5(3HnvA#C4Wq}0SYAZIS@$!ISV)H74`0G(qA zxRY%I!cm)KBut!2K^SKod#T5oHCEO&RbaDqtoPL|a%S9UxhbkIdcZQEd|o=}36Dr= ztEw9D$YK4y+@is~eeD}sYO3yO93O8yR5s63yUbfN(J(j(5r7Plyc$4%akBTCcqjB;XHZsM@1%AH?S6BQ3js#e}{n)1$ z%$CQzbxRJ6ES5m$YQL zM7_?mRHIRPp~_v7Dy2JOlijG81r~#kIoQ`nf@ZIiwuYKk_|TDzhDMV)H#;*TySd$x z*O-u2o0gVS=Z8xL3oKI0L84HOY)q;$`S2APXIjFtva>81O&ykEe?nu8D8vGnpv-36 z5QwHS&>-omJ2N6IXQ(Y>pxqSaLQz-C4{MSty&2BJ#>|Y|I@EVD%5K1{d5W^TEymoT zwse#=<+#!!GO4V`n~_k|L}k%ekQKbPn1H@=;FODhfHel<$u9}ErrQ88BQC-aho=G# zJ3rv}sBmm|kbeW#GF!I>}{QDdphxnlcuK*lAD}et9MHxQQh!!ynW;1U&F50Q^3lKatLe zfaie6unhzLa}JL}+*5+b0uK8J;7_aYUvqc?^@(&3;LoV=8#tWXIq}{^9*;_DX7Ox{ z!?V#Y*!lo}oac`C{=ZAkuD zXgKtA#u8n?Qwg}9&R7FQ#0^cFu0 zBes?-8*Y@Z($cyXY#x-aI?C%YyexIIYf;V&9mhWa!g7Z9>$(m9;JC~J8)j73?ell` zHO=4B2du}$85{4`%7y^PtLDo^J~q0ekHszA+m9-aq7v}tLe%j-ucMxNiMQ`PR5T@D zg?0$}t7SLwyxc#K!^JqPWsh<=`E8xg;X*EJ@g@!7rpaduhm%}F$&Bdw1cxq3F5>XN z0PY4{^n0yzSSu%m^K>!pY7voN#fLX5h%PbiYMGGFChUVgdNL2cJuIvZ3vg`dk?;E#}VFDvDE zgf9re#d!C!b`B>W4zomXA^%?1z~PwXSSyEv!3Y0Jn?;uZNOxbYw_UP0n@MQ@bUDEAaH;i6evj$m^ zXA1p>IYO7mvT(AmbL}X4Z*me@fd2&g?-=f`E0l*C2b?fAg)u9J<1C*<=9 zFYXQGxv}*pL|kp=qyr*VV)}MLLP2u2Bg-T=hlQ8tCln;bC)%^Z@XaI}V`70ev!F6M zF4CGGn_0lyaH-S>{q$3is-1}Lj(-?O?}2vxI7xKSLrH97LI~*?7plE2QJ(YqJJ!sxhs|l>3JL9XBk= zw~X(}3j3Ryq~~83I3LD-yI7>ry`;?k3Zz{l(k_yA8II$vVk!sw$L-3SR+c60Y9(6E zmCFtH%XE*Hr~OFzgB5R0m$woPw@SO%Gbm@q>uBouF#Ha%Z&X+xp+~;Cd#t1EQ4S}W zdz6oj?;@ia@7dlMgzNWSM`ynf$7^QkX9qc4jJ<0@<^swu z#@IC>V~pTpj9sISFeA*&`e-_AWX12v;G!K@>)J7hQ>Qv+VIfCdtm0{vD z{g$pviib7bk&>zYMN_dw+l&41ee#7WPO&EGu!;NQ9l+^urt#lz=|0QN367L(rz1IA z{R@<6*LU$c+>@Zwj60ru++m#VmpToP;@+N(#y)9G;d?+ftv&70lMTKB)-Dy~^+|8^ z4~%pbd654+=^Ml2@)alLKfgVCs}LjLd9$^QYF;6ncI50QU@3;Dl4ME(gb(G?{oYQ;SG4=JLVI14TlT*9}1zL@Cf=J z3Zb9ig8qj>*2o6|7xH}~M7{|wTvPKeIoEc8&*2s_Ws*6VFgCTShT+sQTN~iDO8IX?eP;He zx|Rx=CAg5;7gd>MeC;CmLu!7$c0oTlK=_d*{>aIr?cpsrCuMhGrsiLx5Vl8+K<=gL4zxgME&APKP()Yw#SB0=G-GhHK<>*v^tP>EY&T z7sW@#fq^v0up})pg?E1HVCDp*tZSiFxKj_6tLec2+TlsrlWE=Qo(8HISLRP>!#&cr#TS-+xiZ zH`h6ExEOzbQRffJ|E2spHNP8_K^FrGYuPy7i(-L2*xTL|+}qOGQ5qfSsu~}{*>QRr zS&`=)sn2jf&QxfVI0Mr842E|Ej;BQnk=@W2bb8xendNXL#Kc9|qh%w4DtIyzvJ+zC zB5W}d9@Um^D2|DbvcyFvWtJ7k#)(ga|4PvCUqKpZ@4T_QX3@Nh#-z7atvW_DNP)|+ z&fog4*7;kpw$qv%mhMR0Pit}_%ol)2*I+%)a*B1C{)%>8f%W&{koDK?iKq3~AHA#& zcX9kVt}a@BhkJ^v$d*+YxJZ6eZosEZ_^ud_Uk(rK2q_MZe3)gbt2i6X9zNl~;SS76 zV+jcAMw03P-a@!XzN+LFI`hiPE5~b>Z?>ErR^-agPs%ULo`MN8qogE*t)MJQE}M#o zb0r{BPkB%NM3*xo-4T|MdC>K4kS@2R;a`IE>s7vNGfF4bzn1;X@B^)WK97nq z8Oo1`g6&7n_a>VsYxe4YY6w1>~jvu7V_4^Ic5Gx_{RXPOI7QGQY@kMuWr zdqVLqK|0#Q&r0X>_JpRdWhbaTqJG{Uu^#E`Jzu8waJaO1cAdidMS1{{f0qikVt&@! zZ(dn~W{UdR3XX@bYtmwMT|>L@A~Bue?jn9bA1(3x(96kA-d@^U5x<_Kbnxo~&aWoO z<*c5Z_{Gz~FP!F3pU~@({DhXD43>vKKj&9y`VyoQzj%4huh8_h>>bW8UO%_LQ96wW zeZ9ml4hO%8E{YlPDPEv1h6S6)SNoWvlyK)D)%^lawVniZJ~SsFE(Fbws-qwxLwp%C zdjT8n7wOH8=uE5ZsR=iChg;hls%GBMUC7>wo6PLG<=TbptHx}murMZt_f}~5lg_Yr^ZnM3= zvuW;KW9fmy@|?o{Hly<0kSoU|H=+*WMW)IN?A1AZfa(syb+iILo1ez>EFR(o$AdQL z;lvApf0qiM055QV7;P43m{%B1bf|X%!gDttHyjQ;;%qPAH2y^XT`HW`>UZUxfTKQ) zn{{HXmR4dPb^-9k1P0jb-rkKFAQ8Pf>m%SpGBVEP3-u z_?IG`#5Dze%_u?{7aEe?V<9iGPNf(eHFv2%sidypS1_; z>oxQCP&sN3=u+{Sxt^25q50Fh^*t5f;5l=CeId`scwcslsT)4?}xM;E%CQ!h?Eep%|fPg)%QgxTp_ z^-ntG<=58al9Z@ecd;4a_N=w#X$e+)-zH!GdfiRfluqp?tkq&-Mfv%qb?xEqjm>i} z#RmC1N&J&Aw}XB!(W$lzbOzxs8~%RE{F+^f_%uoVD)5N*s{ETIIZ^R{q2jMYzv=x2 zUKV~5!j8%1<5}L%i^Q3p!*7vxX#Q-cywfSp2PUYVPI0%hCchxu)NXd3Gxv^XQDIiW zJ~OqM&NH`gUJR+cXyR=qT2VLJAmA?>K2qVK`88V_$HRHS^Me-}9?DO=5O}^&;oQc? zeJ6xoFlw_QI{KMpGR@VJuI8aM@hyh!dt^Llfoj3RTbyv%BI>;%e=b!aC1{di?qdt<5Rx3 zx+FJ0G8TMcfk&hdQT`!bUMM=Em|H_M(oh?2ZY^uUF~e;%*N@5%RTt+L+GA^N5%DoN z17KZVDBZ?#<<(Rkp1Pu5t^A77`r2?)RYiGrd9K@Ns#_qht}4hZh>R+Vvc^Y?@{1)a z-YEXVDa%g}H-PSzYIjO;UV&_C7?J+qDakF0w3JybjyN~!mjdVF40f-)8|9(BIf(<> z1!tihwPY3~OQ!N=5o6Ug=CG=|oPB<&I5*p4@!OX*v~V1+%>?K4nC@0Pr1ohU7cgeNz9hy=!6MNK~=7+ z-s%sp>B(yum;FXomYbdzZS`2gW9-%#&!ZWsNx9{TbvPn|pMI&w0DU{Ry}%ah@2T=j zZ(WBZq-~UXm`h7DyZ$(|6?SzstsVN0U&+g~a_zkhK6%;2qTFu0^<1i@nO$!TZSB<3 z0{3D!UM_u3<;eEi-&^UIJ{LFx?<4I-kw*SLgF~CUf@zx}_rFB^^Ay$oN{81|7+^1Q zI||cc6CyIgrTVb&3Vch7ON@X$wjOs4hoa-{5k-+HMWxYkb}K%RIT|wgeYC|bk83n7 z>aFp~;}?iDdc*B1kw!Fif(OC0rSNk;Aul#0ahkYVK_h9_Bz$ImL1|oq#l;D#&Mz#C zPoyt(Mzi#OR-~oGl3Y-d8EGxGBoZc+ae}`c2inc`b5IRZ<#WzpJ2aH_Dxjc0G)8 zCPLuG$zC!3zO5Faj>7@v-G{BxTN$X zPB;HcI9Xt?v<5G!->T7lyYi}qRElR-Es$el+WKn*GgyA z?*q3PXU@ktoX+Nd=6HUf!hfT|+07jOLk>Txo_Q%Y*$W(ghsZBM%&ix06LuX3lpX>Xz<21fsM^*a+KUeWHFP*F0(k6bcBE8`Ah93go>C!j+i}cm{bom3& z8H#^1`=jCez)xqBZ}{HsXS^LpbbIL*>{U%h@DqEg3ar~k$%c7_-!h^I4cd(zy6)}J zCRBbp3mGr#*dv$BP$EtXo!JX0P*?HRDp(S_*N*8mryiRS~8GU2W!Y zrcjWhk*Zb^OR8X26@VGIZ{mO`4Id15Wlxc*2!t*`)W=T z-ygFh>b`KUf7^Dl5n~+_ZJ3Rr6Xd09KSj;ACoYJa>Qj05JMe<*raAs+Ih{vzd$2fu z9woS2+Q#>nW}J8D8!l4IIrx4*G<^%s1EdQVI&V79hNe&J%E!q=C#9c3<Sgl=~JA3d*q@}H5r|nT|T_c5r z@l^Czgj?CQg^m8=vs-*?n|jabZkWpQHx*A-*DR?m9Vo%(lkT*6B#Ql~##lpCCLw|? zjNv8-MFNyoL@wyTV+8Kfh;ZepRYQAvS^eq(&v0*Bae0N*G=peo6d4e54H>y@v~aGP*3> z+O|Xu3pz3`oiRL_Q+WPX1P86?igrx4^XQdW|bJr!FxU0XoeAjwC~!P z4aST)@yzzdY*>WnzUq9o=q!qBRv2l+!G^)-uw!tp!okJS*q5PS;nIb}i`x zKdFz;MISp1>aFA19&W&uK?&MU2&k&jL|8<&JWB15qdPj2;+RW!$$Tapv^q-6$_L_2@ot@aJ`ZS9$EOqV2o<)Gh+{s`WyoQ++NYn5Cn%tbIx^vpLyo1;b|Tgy9_ z*_m>_Y;IoPJ-DV2p{zFSmbyFr3v7|S^%>a{Lt*JXJ3EE2vFh$cOFM{8darW?GR7mg zLGv68N|P?0E(!97ACW>HYn8e!gO(9r{Zgwrv#o5l>z|HN)lJDsO&EZdYwXsozyqHM%^lcC@B(I4WUTTXmnCuN85S#rOGo zPiqC<0tbB`fsgbO9FIxLuvHIk|5^}>5s@4X!cYsNZ5bw0wl6**3O3uSrgn=dH`Vp) zu{&ASG9_L%rl&gbD7Y=g7N5jS?&7A*lG+aRN`{-=rCh?!oO#lbnw6XwUy_{!%e@32 zwOpL26s>N=a-Lf^MW_g^I|b&*Fte|;zRnVso|yO@pSW-h|wetk-5k&p!WKce!)%yaM;ynB0tCDL2sFQ;r{NERRz+ zXTO3OPP;R`H)h?E4r@z8tF4el@VIp^7xhYF*7}FTC5L5sJ8xi zU+j-BKDTRhT|spBP*>c*(z^8n_G0_UK&dUWu*@tyu(iqGw|`ujVwZ0{*Sog=?}H;9 zo+6NowV3wZpTnkNGA<47DN?zdT+iDxfjMRo=-}^85x?R>_+>I9CKm-MWjPy4E0s?Wg)TIostTDn*L7ME9#xFSlAl$cbi`3cdlsf zvV`wlwEN6FZZU0BXJZ#{Sy$Mdx=xK>{0qcsz3i{I^9aT?Zd`#?xDQQpr}6lF-RXdn zjhC#nd$1HC5xW({fGiw!cO{Qe97ya~D0X3Iei339{_VBvDGnsR?XI2&r?~IUExfN@ z)NHqC-^JWEb1R3x!U}lbRs^Q?aJei952IMJ=v(368pkr#`~xBIpp6xHboqft%U>D- zuTy2yM>c+}9FKhL8)b=c_F>v~MJ#=oBehvWn?Vf|b z%us;(_o9CF#y;Y4i7=@_T?xNr-T<8}A9>XNRhr{gqug{?=8i>(+NQjTXJ$-bM36MW9rFXHe$+fE?d~F-$Sf)mYzJ ztaR;I<`tpOOV}2KK5vd3?JKgS4aJBE=(mmxcT5uQxo7)Y^=v;Eew5&7^CBS;*6gl^MQ7AZx7W`vPQi=BhdAE_4K#;*pY#11l?H{B9Ux{XSNtJnvKPMHye<8$&=>#xom!@~)xf z5t#*9Hg{)-9cQ7BD{I=8_E0^fpCNhX^9$LTsSds$2?pLKetMWPxJ*}S&P-h~+So9g|y9+|mWTE6}4rp>*|u3-Cg^`IR?!FK53 zl`35LbdX&9Sf%$#p1%olgqZe0cp{^5Lh)EU^6i{nemeiS2=Q7~R{WJE@`T$4HTQF?yk zhRf$Kzj|SITGPyq_!#!FGo@qq(89yKF9t&HPrst-Luk#=-K$2M>o~0$;4R?rK-Z!+ zi3yzE+T(HLP3k>&17>mlUfi9b;pD4h=8-73S*2u(DF0pT0Yak6|8AXsNsrWpu=2hp z^yLERFJ3}@Jf0fgd%7;~_W2;VShGO?2FQT^KKCo)J~80adN}BZ%?0`AVQ$vt$J|Vw zv~*7UR4zk34sCV7E|*?-m>p4kC}Bf`V-L33PYin4G0%`^m2H&;ej8Kgmc`0b*6EKX zcEpD(tBnbW_^!OcvM{{r$aiCHj8D1s-0j8AvoTc?9Ydf)=TibY`jEg3?Vy8Ve{qc> zMG2+(FcN3#*`u~l-Ox!D6C@r5d8!|8Y|6HV70niV#n!>j?SnRJqQA=ajqN+7O-^g% z>T8w^?tzpg)xXDL?)8q4Sg5 zhgk9Uy@$`a?ADH7-E-4jcV5dbARbJC@vo4-jat_(hW#_(n*swABOh?#KmQ7RzX;Ai z8{*Id{*X$WS%pvQ;pl6?!Al1G6t_MGG(P~@!Bj}+Ua@P$67T)uX4^A=60z%FzrpsK zchzwAt-jAX+P?68-O&a#sF(D?Z*yN^p$~pR)d!RBFJi)j9y-Us2G62+{@g$q#c2op zVGfUF78O1a0)JVBXG2zXc;wO`o@sr4z%@LnA@B-4bb5T<8SAt4X7r}_ zTKmfSSd{;+yZp+h$W3zZkrwOq6Noe>M(~`N=_kdO|6@c}ezGmC!*7oZ#+ZkmVm#5? ztNb)1#(Zvhb=%_p^;)F)Mh~kCi8K$|1Fr{i*+-yV)%(!iP=tbmkk6`r_|EVmcpV#p@ z>1L(>;r((%$adg!oUJXhb?1tGA-P+NsB9wkkk}Qjk7%0MVzqVmR1d_AChZyP#^%)8 zcV_pGt?UMy^5FBe1HS%6C3sn{X=7*8c*R?OQprPxas#P)KNq<|^{<($WA}#W*g>nR zdp|;O^zi^XEmnG4%u?w^+J257|F~LAx1Ia1h_u@B@04213HS9B zl*F3~dp{}dtV>BL&8=#0EFYY%8m}qoE^roA=6p!?Uh~msJ)06+!U(fK&L)T#$uZ&4%qZkRZvMpq0HE;vfO1I zOMAZiGI;#MScN*?3KBQV@JCWhmO)XX{JnGY7>bW%V?7 z056s2^~>QQ^;0{dxGjjzA2@rVUnvd&&6WE2B%0q1&3!sJ+V?T1`}V-J{{Dnq-VJo9 z_q;%A2)shTan>H7cL~5h?6vq@ypQuk*n#NY##4e$ChS1S557`<0q6cxkb5mo-K?&G zbSxNDy0}$zUKj5SEbd)M?+l38bgEs!$UfBq)1loi-haV*&_3a(<-&ZRs~_`$fD8K+ z(MviB0o%!i2Qp10!7U8vc&xbWsjjm^;HG%ej?7xGaUN*$pOYh5IVHtfH_^4dR zL;6Q3Ut5L4S6A!Z_}6+q;`&!$${@P)(DynYH1s`}dFT}fDBeH%7_mr5hIy||_phNhD1!ll+#PJ7Q+<|)8@Egw88Sq;N zhl~S`HGKxZDDJ;H$Iyfwy8rtAHtX$IiYEZB*<`)t2Js5OTfS@CTCewg2k!td!xls% z`GK4w-Zl6Hx>TsSwRa7+_zRBK=U?6atNOw#>g5#WzgYu*nv^s7b4S?!F@yO}EaV+f zyZKq$R~4)c3}Ec{GfO3h@h{?RfhhCN7&rTUTbuDO_OH&fqi!!-VtyRDH7%Aiwu~L- zy^&w-Wj9gzz=A-I;Yi@;xLbw0cl82 z*90ErWvcaMe%t|<$-FugS=H>@-&s^%TGCHP0+boWTzG!qHqc>kVqdgVc?}BW8(VqW zAkq%ZrtMLFkNfXx+A^f=o=w}Rd}?LUYMK}G=vjdik$xV^e;~?Z&B=1Qwtrz|u>(WWHBEb{{23_!z9>(3{++Hf0tOpP z8yJ%3Ap?UC&Mw1UxW^BkQ2MRPdsdc2F##agY8-g@C%i`T$*S0E?6=tCnvKbPL=nsd z7{yC8L$c_UfjXg=G_X;?)Om`_E6J>gUq~lG*LhRcsd#8_in@#VC+l=k_m?f3TE67J zAX90$#dbG*1i8)_c6QRXakF}Y)11fB!o&M&ENQq}mzZktNc9yZ^{hrpOG?T&R-+{- zACB_JP+on<1NKj5#)Bk?Z|O~7$HL8hwU*`xqcaVqFRUo7W7Q^4lG|8=!VGcAzcg%- zrlEHVxqli?_es^eol>H>3`$K6ye{2lSb#gIbMoW9YIcHlU-gM(XR1rRulg7GMvcfO z&U0|X)|u5Mo9KuGT<=N$Fbv9EM#a4MtU;D&kA`{=y>E&G>LZb3{@{uHSDjYRwPNsVM9YFKU(3Ytl28 zb$$A0kFUP`6X2)!qo0#r#l6jggde|g76jZ4+W&|($qnz<=aFK|-76W($$Thuuln|~*!B!twxgod6rR;l+AR`8qwu1EQ&Fj z`z)Lh$@raigHO}3?!XBIYRx71XRlvhu*RCQ!JTset_Pv#~krspOnrGeI^h{&`A zoG}|NGW-yJv<8gMK|m{|7RZ1Tz9D6}Cuux>fL`3l6EXX7xleX`=}~9#z_SjH_fCR& z%(~C4H9j>hE(=p0Q*526p8c%%AtX$E=J(HKi% zSzR?w5A>t1ghzhx#NDHBI1)Ru2ff8bQvdP0?>oD;cB|J*{n~;4vl-T-zm3?^;*}e= ziTX8gz4CLs^?m@d3K+elEMlgcv;6D}t75bB-)H%{eV{Qv@TB2m=)n0{{-lU*PVxsA z=$+_%yS2Wey}I7&tZm6S(%!tgzI9LY#e1+MGu)Yd(m?uqtsKb+h0Ck~`A6-x`ii#d zdW+NBobff4^X+Lm@GU9_NvmbU@CV|0XD;h}&Z2qjziJ%YBhGNtIL?UJ#^#?{dFGjw zf{!JJyV$Vd4YbF2e8ne4WcjFJ$q~w*-=GYLcZ-+4m%vV@w)c}F+Wv$gTWthodyam8 z;PIU0@gO_+<_xSdyw0W#CVA-No3K}W2;a}=--qct{5zgz&4{3HlIMMN6@ACc%FTuy z_&&nFM;pTMs>5-7AN?DpZ($2rjXZ`CJ!I%&@bCgG7Iz?C=CI))Y&+!PYUy?4n`h`` z-{$$YOJ2a%0X7nZt!4jU-;r(yY!q(-VJrh5_6hXfH`a?I{JZ*oIKE%Tzi+`-;so{) z_?}GpZ$jkZcNzJ%Ue;5J?;-io5Bs4ti^STd&Sv6eb8 z<_u|dj?0~yl#s$Qd}WouN9BKs@_F(!m}0ouhacn>B`z^vwU|Tj)zk|J%3M z;9nVDJycpflxH#5457~!%-p-+HIuU}qo_4JETJr;xRp88OjYV%_AUNlxD)_U>byBQ zW@1AzJYaH`W)ebYDIt6)G<99gcvVSfzWB|C@Q$ZFvnH#iII||RMv2xZp6>H^DQgi5 zdGYI{Pb3%5;(R3qK8HLSkH3hsyx7l8jSP0ztQoXFdgu`PsaJUy@y=dF-)AAhEPn^+ zL=h~Vh`%XZj=W@TtFGdwUC9woYLz^SR$e_7pTJi4 zFLrx|i_7~w$+f#zR1Ksti#xR}Ev}({IxK6WsnsNnwfoyBOJVs)rDuE# zLPw{Y!r1ZjveZOZY}x3{%A2e|BpHyBgf6wo1KHsZPsnD-hR=wyJhcQdeba9@IScO^cehTRQ|_#vuIi;s}2u{ ztZfK9<}l2|m@jl5#55$B*Hva=*Ty7-MP#=ZH7rUR$T_gMdCFrkIwHF*Gyg4BSd^6( zyKEV0^G_xYxGtS)Sy^S3?GZifEN>)s1C3c_wTp%-7u6-^MSI)4_Vn1c?%Mh#2rl(_hWpOY!ue!Kx^~U;bomKkI5?mC7Gj ziSkT~@*rzbegRxDz|{-87T4gNz-wOmcgCDqW>*5<=rP9&V=t|vyoFv*6<&vR=T)O> z#J*szCBy4uha=m{ntH?G6wbmdUoc6M*xQ!3Y*hv9fL%$jmbFK%w5*b3nGHv@zzbh` za4+@%{rCHXTzGk~;xpL`>&9=z#r7LRX&Dtx5m91AhW*;2xM+@nY~dG{&#uJ zEfS*Hvq2v6|L@sHte3}JX1P3XD3d;2ZoQlyQ~pIuWAjvVL(5C|v^2I%;S0w*1ia(} z#VwkA4AV)9ez?~Q?Z3IVC|t5=2;qnQW+?4C+jWu0Y{#M_+k4uVMVk6e_O4AGT`LOh zG0;Lr_S^bzZy#)5GPrcSKQp&uO?|`qt`z`KJ4#>+CqD=N{xsSV)G?8ptg6P>CJ+}5 zI6k_Ztf0kG+!=3mO!*NqPBr2}6EJjN$ML#Ck|>)()J2_$LP3Gx0O zcKI^RsC-_9oYPoVCNNm?uq~Ct@Jbv{76tGSDc9}GT*$Zr3L)ibzPRt^ z?F%pNGl#XV-sAHO7Nq$yOTCg*o0nRX9v}UKBkdJerAB4=3o55aYmBDK$(A3oPe7SCv(#sgZyEHOdRr&An3hf2 zH-2|=`PEB|tJc`JEo$tuGIP(_!{>Yl6C&{fnjpTG$YQ6Cxdz5AiQevTdDq&cL|e4& zo%w_F-?d3+wzVy4Q{3$3Hj?p3ERYh$D)+;LjBBZA=li>U{&I{GE9k->=>p)#s}Jvp`z@n4X>ynrYF-w~bs%5_=sngaOiJ!(_@B3D4)U|sLMzUs`@>mi({^Xvr3zlxQ zT!zufM$cNeU}CWSGHILgMPohMQGj}W4*v6#F`sj2FmfkfC|dd6kD65DsZ?K0aA7@w z+Z_ginJsB4Y#6ae*6v$zuYKm%_IpOVV{IL)njOt?QBfIbam~)bu9yL8I5{Ia zntki9sk;|UUfSQ$QFQyZ{&VKX?OM;mHt)^8am&0jGT!>y1idua3VYL)I2#wC#w(!JdS`y&Chcr^mxMc0ak!=pp5ovccJG)vU<97P$Y__#4;+x}4 zqjAk~XZ)<-rg;+VehkY=_xR{!1_Q-{e1Z2(~ATT7y(3 z?LklN3H)05J@#hj8_B0N3-ECj{v?O*wGcejuv?mv9LT>f@Ihb?^6!gU04})$TvQ6YXiidcyUQw1&or_)0POAgFUlEp2PouV2&Fc+RrrXE(A-wjDgU4f^lP zCoUNnxCDAy!%gT@x__YJV8I^J^=<5Mf3mZ*RPEr3p|QrAvYFe|ZiCL_L(ox-c_GXY z4lb2rzQk6IdE#Uy729ThV`G!Uo^{Gg9UUFg_L<8h=gbF!ewt6P117mWUV-tHg&7V% zm$G-x8QQEov>C5d;XJf$<}%O;j}7T8)@y)Jx`j^gYVRt-e-N(MCoRB{CWA3GU zP^sz~%}03-t;pKRhu&lpL99sOLjCWEr!`j#c}0gI_I3M)-i8&mE$dtT6Y)%5G+5G= zFfVhi}PP50eEf#_Wz@X1A2w{@YTP%F;@omU^ElWogRWrOyEOYluYpSM-;fKiAE& zsAf2VMXZcUZA{F`t;0wQs2|1 zY$=dG-Trhb$8V56lp?kKafmk$HdJT5R>p4fDVtaxd!tO5WG-cME64vE_)QsS>A~FP zbw)58Z=V4D$~%M@(stsRQsr&5oZE zRUj98gy%IJVS)=jDmg($6^_)p`e6fC|_R zw&Ftu^)~zE!IGC=Dmf^A|5F)eKamz5EPLstvV+Q{;_o}yw;0LKOWp@uGBldeV)p_K z6L^s!kY+i|o)}lI5D2IF_O`!nDSi0i(k+t5j~k%xZJBvm@(@IN9!}SL-VllYvy}@= zxA^G`NTpa;^4aKrs>67E2Q=6abtjn3i0ks%Go|am@VD6YmCD)<=>fn0M}B2>B_2Y5 zo7&5uBWsnHOS?6`<#O@#y19%p$zOVK26vOPvuEy>q7I@9T1yYMWoNe?M2~ZR85W@Y zw|RNhdKA)m40by=%*JPw9%MhI^60{Y${-a#SE>ctr?REpr3dLdrBIG+{6o7AfOm_q zCYv#p2xHnI}eG^pJAvLu}o1&po&5B{r?x$fntT<-xb!Qf^@rXjkApWvev7 zjNl9OyXi%8R!mlsw0q`aW}Mj=Oqbu+(izgFiJ6O~-OARm-Bg|#rHR?}V0rodnN+nr zbIqnxc?`<`W{sjeKBNB7bUab2rPl^tR>r00X=u_5TD*C6)JdF&G0Sh_pJgnkW zR;wR2tJO+9>W7m!qT$er2FcL?EPD!OI4K8b)Zi5Ru9|n|$eh9&l5_6+-zdRrR|870IJG-nb8$W+h|0;v(hwrI>nMIuqgOhS_Mh#A} zuc>)w_RlG-A=$6u2$nd-Vx<<<2w|J&B4xk-_bJh^%>Ano5^RIEaW~v@Xz5nO)z)xV;y|J zBwbk^O#@zyb4urkhf##f9L%O6*z(-1ZtedN)Z2n!x0`hDpwff;y_dpf$GvAz54S~; zS02CeU0sy}Q@B0Fd?XdC=ZzFHVEU!<4*W`)e%}5Bv`_(D% zvQ!PP9MTU(&}$$0*FE8`f;RyjNO4FxZLd67o}FDzKd0@UH6p#7bpFPWe)1SvK*j80^6!`K6)+K5pc*- zK!Nx2Q_?W(3920=7Zw529C2k&DK+fZM?IMtRq5$f8JV8+{p{CQWMp`>|B1gc_i>bJ4s-H^LB?!8SX_>(N(??` zs4=X;o!W((JtPk1u9~mD`if297mbxZq&M(%)&HNHu-s;+)PNj#3jP?+N}s3=I(({6 z2DlBJUX&nv8sVlw!}u30NZ+Wg#=zgDOz9WEqSlon$<-*?_}OQl-FoY-F#oMmu2-(# z_0dNkp)S0yYPdyyN6OG>C(g_7e3K}@e+xU8+P1}T3;SDWI{TZlkDbfYC0)8&AIDJb za)?}QUm>6JIPrnug~V6cJ?ve5T?ildKg{wf1tXAj={j6y!o_p7U5I>7UU(tV4L_h; zVXLV}DeimD;<8xchf1MTudL-5z-Qu+eJ1$awjwtQ~P0(LFij4CKy($%lsCW)i)o}7pecYi{3u=wXh-dS2S z%e$SmFgGWt?YZZOrtQ0Ti@dXRsd+(}+2!QKZU1C9=xYO3Nk^ZWuS5!SR!PFY9>`%2kLsmyR5Uf+zGEsO3uk6jMCA7Q3( z{5yQ!iJUU-38?F?Tn(cyw}$89x2w8b`rgNWR-s(t|CkN>{rhKrARY7jXI4w&@OA)? z2nRa=9AUyk1eQD4L95|Sg2GwsfFCgv@86HY6{zcV|2J=nf|B78weFB|4!API5bt8l z&+b<)*^in(R<_ES{+Vo4E9!%Pg`ioLUm3P!`VW-OET$iJxxbzAqJ5MXHcqNTZ6C{m zan=)-RgK?dE0=aE&-y=BE>WIEGtVRhY*0Ej^8>$M8vo`^t&N7K*Z~!f@%Z_O@raT! zh(p7%98h}Lng0ER;VHlL`pjw+<-CD)bhdt8PI!#qx5tt72;0UEybR=W0FrGyelM3wwTvhM@zM$q&zUNZbMtF8fHF1zYnn``@~ira63?r% z!XSs#B59`(Md^FWGk)b6jGX;`v}Rnig|%b6%+iH&^dD=doR#?50o3C{hH?E;tw)z% z$UE{QCojQ=2$%U;Ud>7laKqbzuoF4d3_Xw9!6S8c6C+p9K{Q;C*v@MGA$d49svCL0 z4%Er47?T6qnj^*)A4^tnOHGFiXf$!ZDYO`lQk595l@Oa%L7JGAwR3qf9zR=f6>{IM z9AcGzX7x+g6HQc~J`c$gxLT+K-35PGplF>>-nS7Lh}Rt2S*vt zBP8B4i)my*xH+3RKl!-SWP;B_RDxkH`WPiRqo@GKZ+JxImzH0}j{IQB$4U>V;kdy# z{KK5C&RfBq(f`ZdcL2swTz~KEt=j2SPN!nYvWnHL-h1!TsY|wO$-Vb(i~)ygz!-wT zfa%>h6o+64y>|#DbPR+V5}Gk!N(jMs-|x-t-tL`F&6a`uzwF)YPJJ`;X6DVCH%-P4 z)o)#?u?yMFP31|{ z+z^9wDRmq06%N*cC*HC-MUe4DDELZ4nT$EA&T4?sZf8~Ixwv=26^syt>G=6>c8BA)uvDN?M9@Yhy^~nI;W*p zhgC-~s(TcGFss!)HH?Z^-Ub{|%BVebBh^Bz7!_8D?}=9QlhNu;R0%Glk0ht%mQk!g z9=37WBm25~WmVP6Y8>xWwW21oJtLz%lfUOhg@i;wt#UZRW3B2+ejbrML{Z&i!d4_^ z1;!R?ok&V34D;~9swy`7O=g)rn_1p${`h0&aZ}(=KLy@o`_BazP&g6iDI7Fx#yUwK zwRW(igz-!cnG}FUUxnc7*|ZN>)Pe;p>Vwt;2iT;Nl0R%o-?lA%3lncMGs*U|`8M0v zmtM*)zUU(3;;W`jyJ|7PHh{+x3|n!gff*~7)6k#b&Q-?us$iE$o_w~t6dINb<}?=q zfgqP*kYVfd*eKM!*!TJBHRpA7oVTXh_E>{)O822v-BWz)ySr&@g~1kvT+!^Zw4|i8 z))n(qNXJz&SJ<- zjEzyQf%Pze8>4ZVr&p-9qLv0_o@e!4RlV`588fchSbbIB)fXq(9=AQ7baCMHdGn^* zHnxd*ICQzazP{b`NNh}DY;0jnY(Xq`zisV1u`@BT^Ta;eCic|JFDFf!w9s}Jvr-m* z#!krC{Fs>h*cdwV6=@ZPafV&&-#EW79AmB;VDAR-PjUIUAu6AK*xtxjcY%g^p?;#Y zy+XqB`Mc07zc4>m9)x2#XGmCrDk(sfGwk|gY3|jNJJX+yNKA|nPxvHr>()c}&kSo! zste2L4XgR^&^bmwKO^%En-#}w=CClce{gVrhH+6$LU3Z>x#0+>q!F1MVe6>58-5@i5VgUX8eu- z5@n&C!<+$;mOn&WQ6j%@S!wGe@KVGz`G$!}`4B}EhIq2CTTWY0RJ7o<7WG@cybBB0 zmzOKwRS_0T1gW1@P1pR!?rGDy8~ya5D0HnZ7!S_Q;33t35f&UURF$7!mBZ`Tg=m|t zpcfWSw+fM?^;jkCk|Sh;%S>pQK2q<{dnScY2J%Vd!@h1haZW+OoD-Xr@4;}3B^>JS z%J=h04Rhx5|@vSG)aYtIGZN7fKGKNC`57SSGR| zA|NZPJw3fWE2|9~LhgaL&P;dSC|qV|f{$$}{ARbMr?+KiwF@GPv41Z9Lwp9(r)1#q z0;(B_N%<89stowjx_d6`v|#-RuOcidim~;{%zyRbV8Zt#)SUFf~M`ph%g`EZF-eDU}b5&y0F_)%UI9nU$c zi7eDA%tt|q`q&}$WzW`Zb4h_d0de9zZqi^|$1ohw>oSI3YPPacTDNH6Sv!J*Qf|UHd*ZDTgH{e$T+||*nDDzjWFQw z0~|0qb=;6B-itIydtW8q7*`s|2}n!|iz{>pNDJ9}{!iprr?qA#Rfh)j28328Wwxf( zOW=^o5y1`*ZRe zBP#qqo4LKVXi8Srq)Ay>Q;KT0&-{!9-E~*htmf#V`bMj@vA!s}c^1znwaqf*6K9F+ z-HDo}W(>Oy)pYT0(n~FT%07E0q9Q7PYEF7mq=D& zE>$@6CG0)Z3x#zV9ofmXp#l8?p|#1`9T|0nY){d(*B0hgrKNR8nkICmrB#Vfe_Xzz zBCjoFLij}&g-=Ln%d1#XzO>@*yDNH9v&(X`vvbR`Q-KPU`7OXjKk7Zd8v(db5#UrX zajd4LHLE)>bK3ZbX_4!=smzw$9y4% zI72=z6?*Se)2E-B*gTW%X4Y$Bz3_&9>7ar3qbCYefR7VVFC2AjsG>0NISRQE0yjE> z>FJfEQU#8@Fs}^8?l(nN#uWDFXLe+H8w>gi@HcvAb!6uE6-8G?njTNAOo%T-V@NZZ zOlhTY38nD~m5KgQCJ(SR{pEdg!B;Z^W&a>|~#EVIg z@hwBXpNdMQ+j|J{1*m_#Y4CH)Q2M+9@}mgy^RQJ}BYcTTpvBk{h1TL6Kn@b`-VwE0 zd~WCT={x(THX6Jne_wyk8c+X#K(ooutH#Su%&utO)Y!PGIcXv{2bmo{ygeGG*uHeN zHJbwB;yS7-+bv!J-cflG(b)k3nGw->F%xsbqH+QQv%-CS{W23%vO_|$Q=(G*C*}me zE;2Vx?>8{O(q2(@4EOHh1Ta*mX5f~|0@Nu*mX{z;4ax)rBQmidsWLGxHKlr@r$^0w zD7^Ry6L2NOOH{vcGH{l7Gs5ya4tR?rHby~RCNn$1Vo%jfZHbGnk4_4YPS${m_e^f? znbaN?F(p1CGdkHePmKfZ=W6>0*mw1=e4anrj}ttzhckS0g1BD>TNUT&Vf(8V1-tV} z6)2nAs>Q(GTt)3v)WxIBLE_#Y6$1&*vE=huEg=c{Un*E(cRI(qh-JsfNP2VCp--PJI5_27|j;Qd|&`848gukPORA}HA&e;4XsUEYVspv z#)nMAl+Fb~&44g)Pa>yqBJ8AW}^Yr>n<1n6;gev@kyG`B`0C)uNK(zMSUbn53?X zw5HUg=;9`J8TMMlUj0GDid3Y#YLGH;*7HpenyN!P3asqx~1)f zri!LODDfTJbv6@j_7;WXQQ1Goo~{Ua4?SkJ)EQPU&=I~1II*00dZEw?EfYN_;1mWF z-s{bk)vdGF=VVv3*Y+gmNMUKgXHQJ5X-gF2dS=h;Y~4A%zA&q@rz0#XwlOd!kX=}m z^KIFN(%edh z=#4k|mQBmA!q-^dmv1Yki(5{xveJd^Cy0UGA%&@Voxb>_ zY|3m^6wV2Zoj8F7hY293#D4%o!MI|soUy9S&7G%a7w6wNw`WH|K}AJD!H%BEI|>Uc z4{h}DVF!J(PiSg9G0Vq;9hAz8YYJ+bi?Xv%vs$-gXBQV|XK%3@r6+uS*8O`gxxp|jPlZo@;!iYsH93r zF!KrtNo+nOBnND>UVa&SQKPnRXFgwl{q(K7z{!C&EymsHcD~P#?=_=y0QdAvtrlPZ z5zdGU?(9a23+FK62rImC=#OkKb~)^mWP5UF-5l;#zFx^GUrhs#ADSy2S`{bcO`leGElEhFQB<%O5)K zHnO{ncXhIrY@*S2Q9oN^w0#)K!hBILOdV{T(RNiod%U#NR$f}lY^7J1u~29Mp3=ha zaGi&<>pl4$n7BI>NIny^$>g}Dg5m|8zD&{DR7)u zu!s4!;g-Un<0=Y$=#s*ZA4!d%GJBp^8!YQrec$eIimkl@BXoXW=V{--2`9IJKeh zI1PY9SRc_c$+m-FT>LHvBHG_l?qc$nZ@~I0H37AYimDK@Fl@aSJgLA8;xWsTTP7U3 zcS1{YVTLup%HFnx7bml`=h+@EZ}{Sd8@>oWu9t1M#jTAO(`pXU;BM+w-w&3hr+5T>_hujof-6fpkpr(rMfTIJGa&cl3F2vwxdVt^;KR5Z# z^9ni&PGvFkZTG!2?ZGpOJBm-Vy}Fn!LJ_`1=hZ^pY`cn8+a4yx@k93*(U~@!($W0| z@`*X9A^a=~2u_rv(CJhHY=|V@om0I(b(*bfo2_dKO~#g*nlfLXrqvD0wgq^-DQC`A?@Aw7522Fg2?Bb;`gq|?G<5fk=*Df}(Z)^6JTH$D!V$`22fhZO)c%2? zM%)JqI9^_DhIABVHQ-ocsij7?ad%n1Oz{Gy}8{y|E$V@kY}zl zcLdG1Su&Tmx14-wfY_I{yxmHgh%+azKizZgJnzY>#`eU*3Qu;Uw6HgLVTG4%svN1eA3#`2^|xHI%ek0PblhO zlhSsae#5Q>%Ol4z;u%mTpc_vEZ+wn|*DK{#m%`XR9jn;p)g7xj#dr$!_goQW^98kU zUY1x(;~I{_@^A^O5IAvl`w2heQWQBL0#nB-$7%SxR#p^7QJiM&=~&GYDLesLmSUao z^G!M!JOOgL-xv@-zwD0EeGa1jWE zxv^tSo`1}LY(JcxdHd~Q{=L0uRR1Vk+PatZ+P>My&#I>NK!W%+U^)6IoP85J02)hu zz{E^?h%MaF=sQ;?UBSd{If!X?(dY_e?+)WNb+Ya7uRes?;j z3wI!1oOOae;UV-1d{l<}fiOOjPt1Z8oe{Sp^Se0lj4u@}EO>Nhf6MAdQ%~T$&YG2N z0c?ez?P5dD{JKuMkaxhYh0~Xv8`6_-W$o%#oa4XK562Ru&9b(w8Q&Y$cYI^d z#>Oyzb5+N>Mdwwl>oACowd)$%n(OBm)T}+PW4=mjalZu7Kbc2Fy~bQL^}jHqU%p#+ zM|~UjM~6)_oEt!AdMNTGy1$ut1UgDecTFZrCjBv!DEJ+i)VuK1bzxDRQ9-Tq^5?}B zwTo*T8~>j1`!jC(!{UV#!flVsWevU__jU>6WLc@)JIV7HH0Ho0Kp{H%cwnP|&t~w$ ztaA5srfi4!3ibP2yohHEKuU04Ky} z1(6ji8>*&f)g@G?G){|&uu;E?xH6HCs}L7I=LFc~oG;yuN2g7G$R5P?hiM<1J_f(E z(hqaA4L>!|>^1T1GF>D-LA^s!VYP^`0s!6Xrtk_k^n(%1cH$TQx&H>@7#wiWm#h7$ zjz04ZF4sA0U18(=)D}KVpjPLFoNI$ zkuv#cD_r=>eCF7mzt(hnN_+AjO_u}W)oiZq*xLR3YbiX=rkYNDpqeI-58QNsu`JIU zxXApwX6*QKk?qy%C~JkgZQm`zLdZtjLFRAUYkLm~0~<*=nhH21M^Wf2=al0t1NzU@ zrIfh=eP|;9xpbq1cjWarz~pNpcnjboS^3a8xl#aUDRZ;WTs7{+DCaQ5mdw|GT$fY7X63yS~}Fp3WGP`6Wsd=a+E02=Pn2j5xo~$BSqudVb$g2`yN0 z=EC#u?oY8sW(~&o{{uKzIDZ)*dfI1y2V;x~UeEr;@h9I$hU44l40`nW)Fgd zWz|4XCF2rZy6)JE%8B1=&IwgP7zrCln9auuhc>TF@@()f$Xhjk!?@_Bl?C0oK}J*k zVn}H+Jpv-$uAS-U6Iq{AJKN6~RWI%``WO-m^E`|`t2Y&wXSB~Rz#*Is$Lwf1ebMyR zqL~G=T8sPhktFo)6Ok|j2ERnBCW2aB1_=@G;*laEDLBA|2Bdtvf+Ikck;~>ca=O#K zJ#&+j(|nA}C;xQG_-huLJSOCfzk9+B1=XIO(fN_g7E_&h~CWrmK4*R>v9(9w>@XAAalslp=Nx5PC2ty33!{U-S8S;vI^NPu*Pf4^UTHl;w zzjo?*O_Q1@HN3HidH(no9=rYTcnFUnxbyB=(N~=v#L@FgopB5_c~_vElBNzxA$ImR8toZreOI>i)ZoITr1*@&s`%)% z{IsN|wv^_qq+H*?rmTt<$`7wYxCmI4K!HXp(*7jKL33t}6*=(sisKn33<}D&*zO?SadFM;!5pLpe1H#FkKI;pp0IO>BE}j_vy1 z?VXcQ?r*(y$Oil%m;x`J!&^h~p#C%xJp1Pur{x2lGIxM6C*8=Z49>AK`Wa)>B_KH<&|ar1*b4%9R;0CA`iVh-I9_cN~Kd&c)6G>`jnWS7#$_}$@j zt{vSSj=1Ez#8Ec#UE(O46r|dKGwpLBr+=OE?Fpb27h{!??jTm8^Z`udWGl=P#OYR& z;o!v9V@mu@Q_qXfu&6bcQy z%9;?{IH{~B&N$9G&bO!{wK;N<^(Av)PG8Bk(<>W8gFlqZ2DBndUp9P9$mzvqYdiC4 zx3>2rf==|JXzSViw)(ers%2{Ma0+wTgwpKaVeP+Vd{g9JC9HVKB}g**BciQu z^KjZNY6|U_sXiYRZMH@qTRwG?uW8-f7mtfOrL4i*SX#ksx2H`mt7~P(XEIassOtqh zs&i$4=cmpZ6Q79AL)VXRh_761`p_NB`)gf+%Kg)2JdFxBP5qMwlaEPNfhkhDu6!D$ zbl$UK1{#s=hPdCAHhTG%l{?aFLu1sXndkrnB{o$WODp;GbU=#<^g$javbX=PCkz{+ z*DBa~=F1Inn|X8^t#HW@$GM>Z@mVkvCG9Ao_TyyXK8Kyx8MHz?wLs&?`s?{=@syvli&znk7??RDAp zfo+y`z0FIW=xJVAF|$53cUntr%bcu?rhXb{W6ulX_z&Vxc3jeZNHlk?bVvLIX?X&L z_e7!WKY^BQ3v+slysf5&?8X`X>>s}Gi!Ife*5Hou$F;OBWqG|TPENLFrsec?7;2mQ zbJDES=pIVcS>a#&j@M72!@YYXrXZ2yC7PxmS+{vo+i{Uf~zNHooXsHK3K zN~M570O!M;?h!nz!_;nWF<{Ne0=CfO`apu1I^|LY&Gt zR35JFVzW%P{S)pq{Ta;CEWa7x9j~u_{qPKLl*Fcp zbO-P7@V=d#d62`WA-sb&^M0BK*Y(xy-HDA6Q?&lNZr{$&sIHL=D3|UDRL@2YL%e*b zcreZyzp|tApOd9$UjR*>%=W-L{D&$2xg4C*OT$X4lKjZ!G74e){$$g6(bnkSn|>#U zv?bT>-Alt#ROk1RU-XNP^n$O{^6M4%*VB6EUqAbAtJ{#?v<-L7W~PG=J@n%f`*yyH zZlB^Wv@c^nBfPQ;SRRiS$_?46FTJ@N7@O>-Kdsrf)3r;L+a~VNd>#D7V+ybG4SH|z zG7Mdy;$8?Ty21;xot)=0aGoa;?I!>&NLiJ^=Xt8~SqM>y8Q!; zdHZ9hB{qg3AnPvI$^)lUxSw4vR*MoLJ{EHl=L4LuE$O7n0wxqX#E5S}qyjJA!t@bvdA?(nJ> zlQYt*ed_`jv{((3Yf}sI(wjPn|N8`f9dop(KO~pf%E#T;ar}@O^PM=SndUJ?zCU>- zWFpGB*pLR~TF{hzSS&lW^K#Rn%S~*j?GXl%&Y|DqZ0x8*A0wCoS3l>-0F3T6_oREx z6?exx6Zm6go(Xc0SoAO8kH3;vaA}ed4EMPVRw4B{lnim%rF|WnyQBO`7$R#f>+lQ6 zTGQ3IQT{f~^Rs>B*D$4g<$2vC4582T<65R}pVGLg6>rwq%TV!+3Nl1rqB`-`^ZEBTiYv84>mRu?wsc26kK9X4rNxL#!vI)h!68_ zXoq;HOx5`zr*p@f-ZHVTOt#f;W4`e$6J72$kD2QmY@gdcZ>R@sr#U*975N`vLmmjT zGPj1fvAzlQ3)I_bj;k9PHX&|Bnu{eeL=DxsH<>bqVKvM3D~}0IY)cHkVMlD+#IE&u z=J>Yw;HmT5+L(vO!ZmNbcm6i%{)db$r_6Zfg{A9^>~_l6IK=fOWNduRDdzO_^@BQ$ zT%8<^fj@~YED%o)49Hm3UB98#WE@|U;cN5Tm0cd{8@TY|zV_q0qWuCEbX{b!*-SH; zXK&Y*wu+UFm8UF=u5FyO=ERC=XSS6rJfo%O_gRg#8I)JGEhX~x4qnn1I6a%}cOhL= z_vLwXPw9()pac|qs`H67w<TnQS7L$-+Ms zGvH%1OZY%vaKGL^)n&Ky3cM`&d;`gLG2g(p;45;O3{KS0WNj+f3jrkNTmqOU7C(V7 zCQ@hRa)G}wq9&_i z?z6oO%gQQ>BMQS4`uY-LX4Tiu$|>l$xne=doZhPWMRSlG&>%WHJ`DH4hskT5&rh`| zd?v}+XylAk(48RiL=@e3DKq7K!NBI=cJEJ1c_E&Lrb$(e(f9HW&>+AB{ z=VLD!ALoZ@{XJ$Uxcr0Wkioy@`Wr}Ue1d6wxR1#gpElk%t6+LnK(l|oby`*e9vvd6}HdnFTc35__VF36#kXQ8=^1){ZSC|8jBeOY47jPryV7jli=MEA9;AmLjmfO zct1`v7StVa{x^q7t;@6gCOlA*j@zKR((`6T`$kX8Os(~h;xcl&n*4q0mSpF6g`Vcy zHQ(eD)sUOwU%j^J%IdVX)JeG!k>(n6L}W}vZftzXww~hYc}uHPHqG`PKeHyU8~u%7 z!8qkp)Qcpf?4qbtiF4tIpLdnIQIS$Tlv(Utw&UKa87V&AO+^{0-oC3kdYAdN`BmoS z*Lr#t7cDX^KHcBDsGfjthQW#k6I5euf*i6sV!#no=g9Qg?+}G zSzCW?O>jWi{Pw5`lkz6|1w<^HIpf9UwfURnc#2Wq#vz}|$sxFHT;<21;l-@3_$cbha`swKjWFRzV@^jzP#r87HBPX;dBy zTwablba`3I2o*7y3}CSor^qKDE795BIx{lN(zT|pW#;&>;LbI*XO{XgR_R~fka;^) zSD8t)Ju3cv@qNErdP(=<`!+ATqk=%CElG?r&_{Vrr^o4)N9#LB9$Xh;|7{rID;+w6>_h&by2AKTA zi{egtt^ZHuG2rUHxGp)Sc3jj-+ozxB-NkWg|H}R|dlO}+tckZ^NuYvBi%d)slYCj+ z4}qphF~PpUrH$rUe(3?Gz|11Q;upKvfrk8)n#N9BeO+xu8BUg`1-y*Y1>7m2mJ{U% z_<~s4kujMh-wa-EfjlQMc;Yj$E(fN5^ygp5#6&)lTd zxQxV9*S*+iY%IrhVN;eGjV0w-ooRkl*FEA>vU;xWuIl#jO0S4bC^y&EmKNkS)K-+1 z)&>WJFR(^UNX(5l1w??>#GxKq_!vPMA;@%IsfsRyJubk%c|~Q@lFUHA=H*pQOW9&$ zcx^$|LVus|y8NmIPfc!IS>As76nfuMzMyRGlrp^J_EZFU@giM4lI5uil}Ie4HDs61 zH;EH{=VTjW5&}(uO_l!H&v(dj*3XMFYJ0m`m@*&nFSN~xNOPbnkpcRY5slmnC|rOt z0n`+}bz#H&)BxYw1(nrPg9@$eu>jNf+MJ3x0oa+CQ}OdTr?sC!NpC8!JF0XD|3h7T-{-ov{AK!wutoBTQ zPY^nf_~h&bPtUDdT-M(-sW7cA$2vDWAbepnFAcwd7{n)+$1LEL)JTuv3YFu91v6wCMPfLyJULW8yuD+;Y##1d#%S&n}om5}BprmW3 z^@Is?dh+L%&E$E@M|-3>Bz1i5QYL@7$iyx--IHaEP0lstH-H%>nfHOBoZ3FDnnhhM zM%|(DvLXYLWmZ_bR3Nu%tqaC|zM_|3$nRz^nj0J%Q0!y!FU>cF1r%0Q_Xhg;m|KEb zLGAHpp0Vzf&f1pRGp5X*KdW|2RR!X}-9;$t2XK!;81SL=IS!dQ8jaldV~Z1pWwdh3 z1Dyb^-1<+vbz_;I@3eFJA}o`FqO8Y|;p}Pg&-YR zRkx_bu!DJd-QZilzN>CwiQxtIURiIhH`^rjuPvLB>uo#BgU_SbAG2q2S&EY9LI(|t z#sszj`$~A@rMh5b8fS}&GDLsJnu%HmA)r|guWyQR9R-uKI76jOFrrT^KJHHzT}3f_1pzOX9#>d$LdBWerH393xbgHCUtM^rAEr<-Sfuisgz}^E zS9XsHN*vUNh^v#y5;`KV6<|9GIHL{$8?jj8vC++O*gdPZ!w>-5Y ztIEey3{H*Dst6p{G|t;MGcB{;&!64h*u1-a{)(FX!m6C^#d(z{aHA8`xVxwBcKRHhes=rn`pHp}6df8GxKXMMqg0;%mF!{x>&i(!Bv=`jKiEIC_mUmPHlFaL zxB#~=iCydmq|ZzGk~J&gmGK|Op7+B>6jJTCU{;j&L@FafzH$r$1V(eiO0_PCcSwtA zK}A909g5_@zqMd>S5M;PbXktTZq#H5$B--m%g0E1(0)qVMQcMsxgg;>S%%O^@&uHd zdgmfvw}v}0LA|Nn=+>?&0wm7h;T}eo5~RCi(X*$qbaAtPKx9vC!@>y1MT{&+yJoSW zqv7P<8E3V$G$>avBHv7yX8#WJ!B5C?<)PyhNg$fRruq9}{X;-$K=ov=R?oWJjIKf- zpNRUryp}+#U)8kSfYQLSc9zvtx1!pkcbc)UB0Vp=7h91lv$Ha-sZ(eA_fov6xKFU# z;JCw>@A;JBYcuHzS6+&dqXQ8nHitw_oZxrrJWtP{@*wM6zc$l?#>OrmV_AF6tS&#l zEprT(^5Etf=GLI8jdfk#-lfgfdvEBTHFrti?7HIes;r{Akf4Z_Ep1bmuJY@xFUcy& z%&iU%$>sRKo4pJ^i?6E%pG$Ifg>mIKu^Qseo9R2t=r^@1$}f1D+2;$>7m!6hP*d8{ zRWl!F&(tAr>Rq0E_x9j8C2IEG?k|ozPvC-&gK(!zVK-S9f~P zY|k%iIC<*KGp%`ZswZl9dm=B?2VN*H1dc<%@9T8DWrr+KG0vqQ$>2rjR_wq_9A4KZzy_$PzPVK ze+fLa>c>NfV8M#LCwA-pS?~QTLg^J!sr}q`|nCjo8)B6i$4*NiirqsC1 z(EqH!v2goZto~ag94D+3mI{k;3dsg=b(4hoSo;x!G-g3YQiQ&cbUDyxz_wjj2w#h^ zduXY!2|RlY&Ujdb5ac!&x&` zh!Jn3jIu3P%V&4YSTwslGpyr%>kUiN@e&k#&2CVfw|71rP+5p$N&@B{J za-Yk2idF+#F~}*U-8B|U?*@()Dy0o5wGzleGI3WH{W9R9g(8@gvJ8~VYQ&I{asio zY+(1Wd!WCU-3R^s>~-khU~fXdkL`p0E%r9_?=a+reUAG;4QxL<2>rM0U(o+3P8AsL z74666yVJ!P(9aZSK|foZ2mO3;3G_=rg_t4DP>99HMTT%jd3|D{Ndbcf%@S3wQ)PC@B#i2AU+cwbaE35k1{CF+z;ah5X1q zv7}JZ$zg^vO%wcUt0%_@^BS7lV}vEmJ#8_8+1Z_Pxll>(ez>dU5%}b*lvbm2=lE#|j>` z28+Ot$ZK~HR+ajomW%jeg(CNX(JXz#BDpUiqD=lq=izI@o>zZdad-yw`Bb-2r!6>* zBNX>WjzgU;K|NiMI(a_o-$j_`y9@OVwTx;P)hg7c@7VY32i!~7hZ-~=yEGP}#-tlE zaCd2zA={8+$Tj2}3Q=<^4ONDPs2yKO`%x>Ha4BTy*I`fNF6=$qjr;I!#2WaUagxFw z>`S^8^6%S(y~6FdCGXF|ox)wh-NHSPecy+*p$`ZT3J+oJ;3LALm?3=X-4eJ{|6aFRqDEvqGnW6GCk$FRO8jq8L6Il++#k^oB zIPxf*NLwP-i;ZHN*dd;Z`=ZYjw~0H&%f&0it8tV3wc>T+P2wK$QSmwKz52WOiTEAX zD*R{&HAEPq4HKnUX_AyK;HqiR ze^tHSzX@=C6u9K>ZN6Rb)5UXX;75UTQC0olG+{Q_wt3Pr9HbnmK5drq{a#ZGgYy zWw)S{mG2pv-=K3eJ?VmWXy%|xG(C6Ir8DR%%`Ipb!riPh-=@>w1-A!u=Eq=uMm7h% z2tCR#=rzUsw&ss?K_6)5pnqz5?zUeu2OZS(-0err9Awk<+)WqGB57_GW8g=YK%F^M z)055;2{lo+vrKY!)0+mmSuzkZPwz+5TS_#&v(8e5Pz|!3rPal)8*bASw^`0^a9QAL zT%P|DZnO)&4)%mZjakG zh`&I+q_3>7jT_=B6=}kyw z6jZ!y7o6hamZh143p71+M?znQ$njwO;IMq_AT(qs-g`*iZ6|2a6;3&J!nU~v?gMC~ z?0+yGj9lHhsj&VOxk z-gR*O0D9J`_v_4?4#A@wX4@QmjDUKJ*6(gS<`^5Iz6{zPTuMftr@`7g`EeJY8g?+6 zJXD+JFkew2XkQv14fZpi=<4EHMtb+5`YQc+MIVOr!3yHIyBzq{FofPXgEFX!(+&>QE} zQVM{nuJ3b(1_ zVSnQGP24B#CSmMUu78*N`H}0f{tNcuTwhFj5j)-hGl4@}z+HE6eKoho{tLMNg~NZ5 zhca`UE4ab8$05AV^$FZQh1)#GVbbaV z#$vgRmGqd!q4%|9BVs*0%I*|;i}n0n$KO~xfl#k-KeFqG+=f;FGB$_no47uezmMlO z*YmeGf78BX##WOaQd4>#;PypiFZ_estmp4_{QW%l@4@{{=K2br_XXVbZ(JY4?PW|v`si9az?n-n146}q3*IKS`+u4dejcik zzmxg-qaOZezmP`b_wmRp4#GIc}v`5x;>Q%XZ*=guB^&SjE^c z&Jq`ii^U~|JVSw@8Z#VBm_CgB+MV?J0Z6VNQKZ%+m9~Rac+1jgoh15l$egbgZxkQI zc@nb3`8|?3L&_Y_<;+<+NplHg%~nX8JETr7ah}0t&X+)@yi3~cPNs~todN^a$4E^_ zj-G(*{Uyu>{|&RTpQ6TmC;Wtbc`_5tk73PnyzmA~VOgvIGh#KYiM3+}te?%nDRJ#= z1zQI{n{bueLx6AqK7-g6_}+&3uO~3y5X7zkgzMOi&_%IZG57lr=DXftC$dM7gHKry z;(miY1spyv96;UQhZx?2?E&^NVrW-I5?u)zr_fIufY zF?I!iZ{hFb=`B2lQ&-6qZx+XG9w)uwh6Pje->V0 zRqR%AiTH-0&agugq#De0&~KTvK{}rQPL@uV&XCTLF2j7(zw8HCiyiBMP+uZ3D-$W6 z4RwhSY4C#bhVp?uRvz2G5&p&r2;*L(PW2=xW@`=P#sIso+*)YnkoKplko7V0}8 z*wAMG%CHP-CBjf#5r``SaYZ1m2*eeExFQf&1XewnFbnJlbpci!UIg_xOrJu1FZi)M zsBWkps8vvB3UTcBPq1r?1r z^sxmaw&0^0TZSV~4M66z_aPocht`U&%~X;A4=x+n!1p<*SD`+E889%nYy=LBz=2WR3U!tcBc6xvWl+1J4nTba^_^feVD8tzpbStF zln0a%$`=adU_dz-{E;TYlL=*DQgRS6MtCxzR7@xp6H3K|QZb=aOir;;sf<+}a37mR zmnTOlIALDKdN{Bi4vdEb4LP zyX>DxciZ2Qp1?UmzQQB+kAyFv_CtLMbpYxssIQ^EfjS8FE!1~V3@!f;!gI`zrHC0~ z7HDlg*3uP;Rbq`;E0sv4(oFQ6H2eCf^n~;m=@sc6>1*jtx*-Gil58P{ z<9`ofzp(Qt2Zcq%v&)0ze5#Ub5$t?%%50M_f&neu0 z`4e_n=|8+shZWUuf$aO7ebD(M10VZ7J8kuKc|d6$RFCSH9+u|f5(q}BVfNeg_Oc1o z`w8rEH~{L5uMmv7;;6NH-(#!Sr+RE5k0msfY1Mc zyB#1q=g0mwcX=0gTYP4}75w=F(2>0VxAmmzljFYc)%!rsiSKfRlDRPMgXTo&QNOZj zuy?~?4kNKw=nkh=3g$_2E(m@oUCtNkp^y#6NW2CpsS75buzg$ht$mpn z@P-V){zsm+Zz*4>{XC5hPR1WL*MZyKhg$hZ`%Uoih7bWB{1w>0h8X~Xb5ei>*L~oF z6#C@-WKJrT0j#a457(b?Z~u^5HX{%6&*{SKa*A1L)hJ@z}~ z{snyGpg!RTT&7w>T%iqS@9Ix)mTQjQX6*DdHnKmX!n6MgrhTYi50i~j4|r`*O|nn& zM-Rqi+^1qvd@80tksFndN(p8Ak-Y8C+W$GwpZ!+N4r%<3Oq52*NStEUd>yXN{xqPH z#N@4^A&@_MWZEt3UElVnRoBBwrMrH{H3s+Qfjx|Sf10iRc~#F#%!sk?6&Ty>f;{RL z*@YxDpn;#Ej^8%+SXW~o>DT^{k}6QO9o*KPRq+l4CDN;EEZ5&H1 z>C^SA=*_=Mtb+a3UnyQ%1LN}8@3r5Exd10B^-HzY-&S36;XYmPbhM22Jru7KPxQV0 zP4KxNKs_Osg#CLuS^j7|_P--;j5G#3E>aqK3_8GT} zWPiqfE>cFE8jYb|0cW*?=B-rUWBYXr7xU$a#T5@X%69hqhEBmy6m;m2B?ALJxEl3hKPc$qj#1U_BaX-o0Q(=v z68)s^!F=KQLX18=@Ue^g<6{dhKYY~ws4j>g!{L62vOVsQm*_o>K>ypSpTACO&?D@# z-=x$vqSZ!703c-rUf;ziT;X8tPmKVbqxK?Ge`ikKewBSY?wa5-TNR*O`Z9i~k94M@ zlv33veW|BAya%IgIlsKe!l;w;ml%sHSODy!!g0zNaOO=Q2fqxfrJNp(KR`;T=DW=5 zILk1#R679~#z3X+nomd@KgS6CAlzih&(|~>JsJ;{`@r4_tGHaAq4_Ay^Wn=MAe(&{ zlv3swF<&H@V7eNTmxCyox73nA86AZOG@7^;r&)B!gN_r%yU@u!6uG=!g-d_mcV4?< zzh9p?wDVy`ta{(79=WBo$g_NkCD}MX18tn$$JoTB{$mW&EjU+7Gtd)nEmUhiI~?o| zgm+{%hfn*4j@Oy8xbv%08?+B6+U?HoSlYSo`_&#nP8CK%M`^_4J}19yZ0^$_+d8L2 zEu*1A{MWk;=qrw9j)tduoIarT;qI8jpuS_Bc4r>bxpo{;T#oTQMpiB(ZE%s_KuptOV~c`EXz>+5Indj^hJZN*J<*0?pv1PKVm;gh(Xwwu@aHK z8c(ecs@GSO!F6?^{V--4APdv=j@W58U@bREt>Ei6y+6bae~{}reA7Ig9+u{!C0lQ2 zr<*aMSblOYFz<{J)O5lNi{##e-8@%E2D1FE|LNmn&wR9NikIM2V}z-okI4 zrRZ+GA5}}phzD@9&JjAxE}h+whXFE0S08%w$os)xvCq;c*hxR~)H?+m#sHr27nO1i zog8_P%tA+ z%LER=9qd6sV>P17+9%9de2kq?ztY3|)imeGb+i0~W5_wkVVU@z5M_P2yVdT81@uS) z`3@D>&3x!k-iLN`)EJC6aS{PMr{e}9?1Q2Enq`Wteuw4>hmK|>uFmwIn`StNlUE&Z zmlnJeon65*IJiKEu7vvu5PV^Sw`+)jW~>R+{Q&cO#SdDBLq9Os7}*Srf!jnlgLn1? z)1a-^*(ad*QSO|fxi?|oz3zv+)4nOnV#mr0!2JM8#z;}+W}M`hLn0XJdSCaWE;$wlZ5U~!qw=Spf97j!KjNdZ1K7R8 z*Lu?U1AXs-vF1I|d-HiV!YwGa%iIF&_G7Keh{pqmUqbGXMLaLo3%!MOVYjW^!*V+s zuO4_V__=QRy6V z^d?>dv_8?~fM8Nf%Y_EGn-g>d`ah&G|8jY;-!ckGOR)VVN}0Nv67}Lctgm|xYb2gU zy+Y|6Zkt6N-_Li-eeTG4FlH8mRFmr%NVQkLoOT}uh0;&Yf8bAy0M9c~m2R;9er}4$Hh?Jlz z`^Nd2a+vOP(1iW&B$D8lUFhcFq~q}LNK_YXf!9BL-N=X!CoRrlR7yD9NK;L+aHAiJXk80nz5(rD?C#7SZ0_4Xr$qVSUn zjX-F$Q9yVeBaDYRv=NQ<2gZgxflDTS-N}vfRJ_fg1crNPmT|O?{n?R*K>0g*1oRO< z*thYsfF1w`jJk-lgKF~rl=mJZg*v=H{T>UXfnyml@jA%k{TwyI6>o!r)sGej^)zA! z&aJ$!^p_uxPQEeXNEDAQHDf?<;Au?9Spy;%)B@OdQ#(f{^%@lDpbk8)qp;4wIYR9` z5NG>=CiDajZjM|*$M;eF#9pSSRNtTuN7$DS&Cj5B1_wuv)<&T}!ZKF=a;nUqzTZk@LS&D9~#^^lQ)B|BC(5aWH>4G_1i~aniFZk4z^_J0APpvR370 z9S&orp?ygs1BU%dx63VqQ!5i>;(r7VZo%(O$YzLt*z3UA$Ur)Lp|yVn?&NVOy1@ev z;`$T#hhVI{co!#YkqwP!f;CDBn-A2MN;{L(daH7I6h?hiH`P4wi@oe4#lvpw|uYrY^P_wR;)5ztb91qB$jEBF$#T5^VyiRMQ19M0= z!{fnyxY61EGO_UdIL4r(OW|O?-EnB{{i3Xg+_OkMGwp(wLmydR#(5D3_0z9bQ`4)) zqr~>B^__#@>M$fA?ja3ktI~nN+<#qrUVcx2hAWhP=$bRKv?{YA@T^fpbu=|H2&3_( zjSGis+c@X`;~^o3%Y8qvQ8uK%kvhgWJO(&)QR;~q`nJMdb$zSD_b4<&^DTNnBiWzj zZcURO#*yot>4FCD$mkL3+);1QT3W|Kr{cji-A9TCEp7G(H2r9F;9)+H{SVi&Mg!HI z-vMpGou}W39qm{-nn_X1-F05-=*Bvl9HCzP!263Mq*!X4nhefS-ww#LCaRC4^TlZT zh)NRDIts7R_9INDwhu+?g;nZsgrJ8E?r#6ZkO2lbj^!B8U6~9?AC9mVYUgsPEpI^f z6br~3o*v9iwFi$e#(cjD`DlMfJ}w-Yv|~7I9%+75JP%nnjx>_rZg?)QKB~!!83pvz z5&9kbR9E7@Fz2Vx**K_4U3aU|4?6qimwCp1RmRw3)&jGc78u`Cj+EteY)=IG8|UURz}>*;PsNj?YMd`qpG&y79; z`Sw@XWeD@=9#y73u(pg2&M)g5^G}dr5pOjzF6>BBH&|U6Eli9r{xsS!L;1IFSJuqD z4J_=!?8x&Nk?x~&`blDeTCyAKTZW*`_LB!fr#niN$Io^C4S2{WH=5&+B_Zm1aOd+q zxu16t_deW(d5=6JqQLyW^2r&CjL7HgjRhnX2HG$th&!2I?s8AJZZktxi58-ch@aZaAMI$G`HLQRi6Qtt(c2s58K1cD_MtbY(E| z^|WftLx0sX!TC)FSM$AGA7PIX_NSmlV9y5D$-Xg^{8?R3tB+-{^|Xo~xn10j82HS* zx}J6*kAPL5|HO)w+i*hpJ$m@0ANhJ1*O2bbkZ00rG7}$n5FUuL-bd?yvCi)esM}!Y zSXng?hPoa`@1m>&k=LL&Rt`FRbdrwWp3M1ml}5Y41KouD0@gM?r}F6z7-O#CYYU&F z-Ft`kxLaWbJSrm+%rZFEN67HiT$4#_Bg2osz}q;Daz?kFb|iUI*4(~AI|HzOjCM)v zqtv5yyo_-cPE{T$ZP#KOvcKSR4+llk0=d=6t3KV0x1FiWfWWR2!T}zfE%Z|U!{R;M zj6JlK5WuDjhl=$$j`ar4wZOIJIHz;8CXAX#y>1o!0#Swk(m4(H7&;t0Xpr>ln!#6n zNhNkT@}&`$#gIL+ma1P~r?f_1qi|9bzv1-g)e}9|N1xYW+fl&iVGazz+w};_Yvelt zoZ}$)&a`8Ye2pLt&X8r3au=;TrxDoc%O36Fz`)dw00_J|_i=-Tai)`l`5MfA5WFeg zN7ypm0tap3a#Vt$VgMY#;ZWYAPIJLsL4r2K6D5QV;X0%f=D~5pFUJ61TgZ9L4}~;0 zV-5|DIwPmq2tuvH;OHYAHiEE#^t?@I^BhI6O4RvLwPa@&5=U0Q)IekV1x5}YF2r9m z-G`gzUoYH|(TAbajgisIL;D!YRi=}iKG5E-kI;7xpzA}0|K;4k?|(gXY(vx0p~7-m z?Y9&jIQUfd$!9i3FmpTI44Q|@{$mY;+) z5?uVIa`0P9Oz>~w^uHq+X9eoeN%g@OW>SMbqnA|;3d+5MzJ}_23`2o+K1Z(wbuiMp zWlkw+6*hj(6k{OFAti7Fy*q4p@9Z=?Gq$t@rzDa5`7j^nk%Yokfu9)sanBPv!x=2} zktFP81!ZpwZ!6u3qd>}oM)W_Rl!Mi!eWtP&)_8!0j3zAz6+EE1th7E;is zr3;xj_q0#w$9Zzwg&jE4>q6loAyl|rxLOz|Tr2!Rm>}FN>=ELHKM9WrNy1~oUxZ@e zdErH&MtDhhO{f?CCVVJZg-->W(8&bmFDzyeELzyeVp)Q)nI*9l;S`p}@`Te_Ija=T zWi_luIG@$AX~GWH&t?mMU<=tI;g4(yTP^&Ft!3+kyV-HhwNkF5c?hew~A-6wc^?0cD7!;M7)x160Z@jVJC{$i@VrK;%@OKc8a)1 z{1ZD}+$-MB&JzDDKETcv9}ypC7m81cPq9nH7sOZCW#U`n+w2!y>5s#B(Z&lWJ5B0-%w;IVjmcq3{C7qLz|(EePozr zn8W^Vm}gkP{%KfdSjIjxtT3!(pGy%^G}|x5N@?t%lqF>|o0Ko*i-J@rm5WTOlqyAU zsamQQeWY5cPBcmll2tTGZBmD5mb#>VF<6=*%@U)eInqKgR$45r5R;@e(poWHI!4+o zW=bbYCyIsA$M zWa(|`1Mw{BW9d`zJn38MTk#@+36k_R-Xd29)K3Yu(-S{7%2wmW{d)^O{A)yAmu*G~ zW*F}{<6sycW9Mwb`FbXPBC)^It}mSWNGCt?v-d1G(=P;VH4H6P9%F`Mhk`to3>6~b zGC_#KxHd+J#R6d(g;q6ta(37`jALN>lRLava9H(!sa z%UFR>i1Lx&I%^IoTu8+?nJZ0~E)*e+VMuKfXif^~M+O3hBIYE#LxmEgaXh}In43&Q z+{rv0df2Kygy$zj$)%h-3zrECPQLYzvp)-WqEHl~oI|7#iO^G7 zxO?Q%rc}#$PY^VilY|PusX$oDyMT0RaP_dX5R_UnQQdKujuhC->C(az-a_!BRY&9T zO97tbxac%%`LQ5R7Q{YKHg18vyC2mK{ZEENbwm4A;zU#R7lRn8bia7ygfQ>^M|DpB zOGBNIYde*r{HqqUAH`my`{kfioJ&$p-$>q6*VML2SdK!<)gQG!8Gi(c%AL-Qms=v0 zp!1W++fOLc8_FHz8WPV_mVvnoa-k;)n8-o>5D*tZNd(4&@zXF#z69W#s0O7?O(D0& zD+cY0epJ5thagbRqaU4IPCv>^DCi5RiKrFyBittQR!ncL{ZW0=9<8j%M>_vyfHF|~ zmFbb{gGz|_6#5CE5A>ra2lhmz^7#*|i@5Dzufo9`6npA_h*3C;Xxg^m1J!IjES3qGwvo6s(F;MXa1!Q7;% zZlN1GdiTHw`4oi7!W6EX0?57aIaQd(zp`x$VD!RQGv}J83p4N&gqgxD@X)h`Il^=T zfoBeW^3xBOxxzeQK7Rdt9&sidNG-%qelLQLeqph&L|7^;gHFJEhOivI<_jzMSGMhk zS%6tUd@J!5z}v4D*5JKXSSPF(Hb5N%(|Y_ia?`P}-y|F-950+8Y!-gUeUj@2*!>RQ zB1SpnZj1QPZrJ+wh3oLog@5SI2Unk7tRyT$0*?p;R5{R zXB*5XBJ38$?EDEuYalMK9e5$tht#cu7Yr3(9^E4f2H>O`lvx7L=%Jgh9UYvZeN$=u zQjc7hH=^vO@%o^~>xI%KwHg-Q!m;3MkL5r4Tc?<0d$Oy?c+Qvei?cDpTLq4B6SzeB z5x2MrW!-@HCe*GaFw3eDJfzG)&Oz@mSB+Zv5?9#{s!nhHLy(RI)PcVZu*cCDerT6N@Tbi1b3+*sA`_|=^%)%;bn~Z zAZ8hZ;(Rc$B-iX}-<@|WV-rY|t4ZV&^!u^lNTlyc}c21ag|C7_Gy{S|Q z!-M@?`B37KMkY^j&Mld!uF0iFZIkM+mToF{3lyE$PxY3{y#XahX={X8OUDv~rQS=$ zu+~2Ok+Nf9-v!u#zyYOGehY}J5pGM620d2gzCqx2lr{lMt5;;NQ4xJOO1*DuhEPD&2vr|COmT#VB*WsvCvuccF`_+yA~t zjsfBg)E|v9wI@>1&I9+QJ;cG$xI})F#Dm5q^D!dP9-2X*&{p`9!?s|2B!K&g;@p(L zB_whff;0gz=HWXYqp1Y&Wb#iPH7)=@soE_C=Scb_u1K~*@{5HS*=ar|L7xcK2A5WF zVv`{!nF4b!-f7%lGDbKQ<^%;!GHe&X6c6r`o(#wPROo0Nq}rXPm`>&LkN?-)wFg&O zT=6~oedLJ=Mu?Om-Xvz(vdb5aHFmD6!wtHe{7YjLx@4zengRpTaDhIAS}h1TG6 zb{+16r{X?(A$X=T@MWlbP1I;S&;3OJS$aY+Nu|x4HD|sWzu+fJ7pbZXm!5x#x@KYX z;)~SEOPUuqtBs4ATNkTsxWnPEw-@&~^S?~YY9FEW+}Sf@D%RL=dW@fJlgi0+{SU{^ z!e?MOJ)ML%11ssz?oegtsGRT4m>*LMXPsFeQ&*kYI5(z#J)3yl9O5l==go{)hE3$!eH4Hzs2nXE(-VN+aY_*Le2#VuJV_U>wK9d+o=;(;{(X zIVqB?6iJXG2~#AJBGD<5VJVV~6iKROo=J&)PD_!b$0Z{dEVy)ux@ys73l^zc3|?jM zT7%acywTvz20v=>QwDE0xZU7a4Bl<<9)sUD_`s6ImtU$5wi5qy`T0v*RoC*RtxMHc zh?_LTR5{{52a&@k9|1s2M2UHhC1y_$oOP7P3@$QwlEHj}A=oL1xtoZ2!XjpM3hv63 z@E0J0)lPh>!BxN-_qgGP^8Q$jv+p{5#=2eIq1LNSYKwYG?NBe_jmleUKi;K$qK;q# z%#aZ>R*GbbR7jniA!o@s(uDUUOXNzqPHvXlW|_T$aQCvrq9 zouNnQ(K@Cl=n`G2>+~7=EVJfB_S+F_yb@sl!hA=^J6hrBEUOou?f8cs-Hws5-s_I; zceKONE=Rwx^|8Q>e)?OMW|TU*#L&TtU+nl+$N$3dH#>f{o z-FUM;aQq=x?);MN{F0qx%dGg-*7Pig8Qyz|=$eXE(Kev72l z{YZg&07=wFq)0ovzt z1dbDn#7OL0r#{Ir^o%!pF+CJ$Q;&T!^JC~RlrpN>X9T#(2dI^Ym0yWgoAC7IM!ZY4 zHR+oGedhhyde}We=%;3|gQTd&;+H)=SJ&%D%EJ z+Gmc#O1utdC=ZZfN)DqA<61DAQF#aCZAQ)+kLx4OoId6{Dxcx?5eXW1`f-?@O4IUb z<|a)qcc{aNQ2LlN)PW@GFp`$(kaLW7Y_{Bv$o4WSNl$1O{V`EbOG*84a*iYiE2KaQ zr9`GeE0k|y-#u9-q9hA&K)na4R_|hOwvzYNKOz$JA(#kWf|F5B?vyx}r%soz^bP2} z!u#X3kmpOT*)alZ?l?IPIA8LCi)1|Hl~M_xR7*AR zOqmHui?krWP!=NJDy_(0Ay)ujEmuRbQdT0rM%Dmtl#P&Vm95BclWo9n%A3IZWFPEz zN~h8~TjL41&ezz3aQL5ezDwT)e2=~d_+EW4@O}C|;0<~M@B0J0LPtYo6d=xMvS!3p_fSaWm zc$q8%UM|alSI7$B)v_A+NqG|ZX?YrWkL&^7D|=y~LlCiul1UiJlwb<*)BsV7h?xd? zc~A~q6Vw3LnfURmhHIF603jCQ& z*a_eT?wD8=$d3dLvAebh!vdZ;M;l9DeXK24U!)9qM3E}zNgPB)vqlCo$G(?>=G+psrXzSU&3Kd)W}MV!a`kXZhR zdcu_FLl?WM953v@#=5}XgdBT1#!-dXWYq04czr^73^IkM$SmLN%Gv&AXq(+vec+^) z!%l!i8}x`hMftg&(q3CbuW?+W&Iv=%IdKR&-x`8W;ShAbJp`SThM;ru5OmB5b`<|$ zKSUbbhsD$halY_e4DWMXqMv!@rs`@YV;j9PSL;VGA=E?gZt$GwRt7 zdyla>M#!`j3D-ytAOm(bV|Qh%J0r5KPS$Q(Wc#B{%qToHfvznztugw!n!@m7M6hWe zrG8x$xmw&?({UPX3fscX;iKVJb1tyI)*GG1D!DOO5q!4fznNpdl~Zg+I2O;Jc)y{} zSUi_X{zebI!0&wU4@bUqeLY;d(s$#xUUlgYQ)WsW3MPj=k{R{{pGV+U$U4EVv{a^o z)&dSWC;$Zn&`YQnwJ}IuN>INPKuVx(g3o(7=@9hvuCPb{5kN|K-GR?Lxq2h)QGWrD z681Upc_-H`sJ{R}O1Rj8&pSDNAcX)@!bAr?@8ujDeMq3buaiuDJ<4gzmpi-aHOQs` zqJDC|Q-8u({){r(@cxX_TOiK@JcF^^1bPGL@u1rQTP)z)dp-CbUyr~?;bovKr;XDA zV*%w35HqS5d9Y|KtFHGSe(*|$xQQ@<8KyQI`(f1|uFkIGq( zyw~?-o^p=;zQX!E1ALjYXVx9_g8=;$E%$z-En}ZHQQzw&+w}6N4Woa#v+L@|W#l8W zwbB~~Z`UCopaz9_B^tqp}k&fq{NjY@Q9FtyRd9w#a>vO$e?@4Ko_1PEF zC`4tP9}k?5nGs+cQE+Q3DpPOcF7$Z%ZWRj-3hwA(6@Ixg-=rDgbSYF98K{#&DKM!B zPc~Pn_tn4Dd@E6J;Q4JWp5;D)H)93r3w&}}qDzrB1f%ue^eVk7$O*0wX7Rm-d_!)N z2K-is=|=rHtQLkPcq&ndXCH@yTlL+z$A_?20E^{-5&$t0eh2lM;cA)Fmm>dr$${oA z@>8S(dN{OL4+(E#N~Bw_(G`%;#x?R0@JFD`+mY1DwMaSo)A)Cs=HcCYf$T!M*kIBx zyu*0YSEh-@C56p-G+|AufoWe8&6Kf8%>V*Yd3B?`fj5^U$v-B%!4iig(cObTF5&M zZ<{(za-y%7M9myQgEsuUF&9lG>L$$NO-^kP0ixdJ>BYf9)}a=-1!?FddZntc-HO7K07Ww1077DpP3 vHt$=vJ@LKo#v5e|nNp)J5RWsFW2y~4hs{h=!l(NeU@j2#G7V6`m{;mQJAzii literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/netflix_sans_medium.ttf b/app/src/main/res/font/netflix_sans_medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8a8776344a1c591550782fb48a826ab21e5b6863 GIT binary patch literal 146636 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/netflix_sans_medium_italic.ttf b/app/src/main/res/font/netflix_sans_medium_italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..536aebd6584c4b3bc1d8161396264e065807e79e GIT binary patch literal 147660 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/netflix_sans_thin.ttf b/app/src/main/res/font/netflix_sans_thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4fa549ef3b92b472cc382f2c12cd66181a82d7e0 GIT binary patch literal 146064 zcmd44d0iHcUyTC^^R2s7{J+?h!Nt>5MS{qg>S&%I|~p68rr zJLiss5<5RS7ZfgX+9KMQtY}c+L14qiNUPoMM!H74@5^&u!_<{{5bfghT`pGHTiMC0(-lvh~q~ zXk!Rrq@#1j+?_A3jVDC8i4dQ>87+%CsUK0Hzk1XUnlWc-$DGTj{Rpv#5gN2&W_wH9 znD5pkp#5^>+h!tR_^ZR;LVPXa@iXUkb$_y#oT+!(Rp>v3=XWk_@4Wi?&(jIfpiNS` z1pSRz5miE%2JMmfCFpN#Wx+#;=U}*%z#Jh0aeTZodB?O-89$H_!X>1TcY~k)9e-bV z`~F~8^o>@(GvY-=hXDodKhzW6clHzFcg7X%()gW`TJmJ_m-rJ;5wEdbB!x5+AJhyY zIpj;s#D@fcc9e*03TZ1HyXs{MV~2vK1CPuxYAi;xt-+kAd*MJ^w> z>jpdQQqtMN`!ueZ&__Rf2d)3a4kJrO{OI?@B-Qu|PXJ}4)&m;6H7u3bXaH$oV@NK4 zlNU%Kts;fMK(|!>d(08oKs)Yt1=_g=coHK9h0q2euL80G_(Pfjun~|0NC7AT;{lm~ zVt^H3W}AqEP9NFDtn?y=k7b#?6WE@*aG7wG!9uff409Jqj5D(z#MnDQc z=Yjtx<;W&ivCvB#KIz0E>?6}WVD-EWL*hIf?}?`k$>(8?Cyt-V0FE~~AQZsU3IGz` zK$l>I!vG;-59wfkCc{Bz6XJS66QJ%tKq=BD0&IY4z${Kn77ILYl4xb1$z%a5;B6*KE$a}7f^jj%B zQD{P0q%(_Dej=|!^T|Yd5o;=w1Tj6CKtDnK4@d;PL;`u3{+(!rO_=WyqNnFcvY;nv zj1hyNBWc3js7EkoFg^)(A-oG=fN+WZ3i~Jr_}l}SM;4Qj*t^jf6UODb$c|%9#puh= z4MGG7=J21==t1J(coDm$19l26wCp0WM2bkf5SfFb5sxu`yOit zAOwQW9FH>*<~UA8od$rN`0{y^ABZo<3C8z42h_(7cY&8m0KV;7?SY+m??nEy5^cTo z;dJ%TlGBk5r5#@jpg;N9bzPz(Xf=Ru(8)uaOg?7NB6A>*(-!muuD;~JX@kB$;efL5 z;SG-DdJ^F+(t6`-P3niV0JoE{u9^qdly^K>N75SdtRId~jt>bZe2wuw`C5|tB~`$C z6?&@TW8?Vot`$BOX^nW-2k0QJDZZ}UFW~yafw_C<<;LZuTl?U1me!+pU%1CVwBH}m@9zO! zfcfbEUI~WoZS3KHqdVW91M_zM(>uuw z;ynj21L1W;`uNe?#{h0U{d2wpoc!IJKcxL1yzP7G>CNLj1bF$xn~(m^abU?9tvBpm zZ#B|h;Cq128T^g&Gzl;VX})}&^Lb&en6rac;!Rxlb3oc&X$~G9C-u+Q0H+IaEe9<2 zyw~Hsgv(s;mC;fiKEMAdV5|}!{)IPqat%Pnc`?V+{}!aZ&FAn-!1tUN_?rLU0LSBh z0UW1*Q4)XWd+>h?9N!%5{~07X!1p=;G*}AQ?SW_Uej32{%M8FnfGdD40KRW{{Br=` zyF88auNHu}{9J@N@HXcIIITJGwcaJsmhZCxx#{{0d|y7Ww>kg)Px$7z?EoGEF(2?V za@F;|BoigMh;{s)>k>5*t+eldL+pHAa-8u!NUkF6#mCThF=Q|2?a8i-0@pvd+&uwZ zWEjywKI+*L^mivIk>t)i=#)1Qe+=?03UG#Wa6T_+Aj7%*L0^I$>tQGIxSYbekmT+@ z=p-eOMVJ>Ki{OX;O3@Er<6NKM!1altz-8PjlInq6X^pqIzL#*u@pc>0N#P$6=6ll( zHvXnsSX2o}yUnAEa2e*6W7xx9y+oS#Kaj`g@6l7F{jmdWa@pbmu1iaDmdjcI<{QT8 z1NoaS&EYKe2j(L2C(bLp!0E;D$#obv41}?+eWf%?qL~{cx#*=Qy+&e~FZ60oTZz7C zCzwd4=xKO z+X4Cm*C8bRDGOu8nvrN3H$;~L@8kSW;(-+;1a=d682u3M1gvw3m-F=w;Cq0teM$b{ zZ3Jil-n^w;&%)R)gAY?$0KF!ee9Qr5eg<3wd;z#beuB<%9b;eVPY*-Tu220-WDF6q)-H-t{e$AmF*U7#P~ z6xvGKVMj@JneQdYgGQ2oI@}hB1B?bJ04hL^&mOEV=>LFZfJQt`Wc=-c1R<5=N#Qe+ zEdaZL`XFuraNFP!#DhFx9_O|Jw+ndtu>j;t?=rkoKsRoy*;yXw-$vH3siX??+6wp# za22q_^S(76>n=lM@NNU#1K0#;G=l zZF?e~HUx6fHn%?)dEjZhITQgn&VK0~fjkbgZwBa0Z5+sH_j_o3XxcA@DQq=wouOf# z#^XcNhK7f>_si+nFTR-X8o)vT$8jDY3}6Cm1Z;N$4MEu?H;{`+dj`-8*ag@Fc-Yg{ z7KHaA3>s06%g5Y6A3)qBJOy41I}!L;CcX|{c!^9Gz9reh??^V%TF5!9+t~mYWbZkW zg>V+aSw0UyCUAbe5i~doC>{jxL(NbfL=1&32i`4=K>Hrxc8&yB+`0(o(~`{imnWUe zL(X6Ch5P^r4Ty7_a39HMH&FIR*hx_+`xRgv;vwL}c}Sa0WMVtgxXk2jKz2dj;daT7 z;IDG*Bds_J`M)M=AqMTkR^oC-P$12#xAdc*zoMUgNQ0dvjhV|cj6-ZBQM^5FQ%Jgv z1vb7G{NT@n0K7%rq{sY7hSQ%(BAX1J;Q{(J$^%#vaCj8&QUK?_9y=F#V>y47cIL*CWTJclKmfFnv1~umx*>CJL%Wcv7#r6MbdU+$HvWOj18xTjBT+tv z#4|Ny#XnJREaFY50r@N)eJIIrv?-990?iOi4-1+ zA>v5L9Qp(ze&h+H7)dI;kd367EF&xFDtagFp&RKIx|KdeAE(dLSLg-$5Bd%Lfx6fj zCSz)5Wbh6!2g_$Atb$EowXA_nX46;;o6cslg={gqgWbjMVO!W%_8fa2KFpWc>+FIc z3L}LOAzTK2+SV)=UBhk(Ad$j%2;izJvKYGEOv72wAksf z3u14NT^G9{c3bSu*q>x0WNKNKEML|sTPj;6+aT+eJt6zO?4axqvLmvWIY+r#xmLMexk>pe$RKBb{p?qEW zSLJ)k&y-)Qd{x6#qf{}fSXHLVp=wdhRn6D68PcBI^yK|Ni_Su)%XPyAKQI1dB#ER! z1DOo&x}5BisI-Chf=b)yBlP$57=4p|N-xvz=~WiRVws8=m zZgIbONIW7@>8$v!cu~A0UiMJwWULq)5E~UMi%p0%#iqqNVjE+p#boeGJ1%pbX z<6=EjYL07@s3iLZDpd_oX|r;daN98e4>7??s@?GUcP{{{W3Y4gnrpi$@ ztEPLXbkCEUekT3}^%p8b!mU_bC4Sg}>#_gUERl0FEgA6_MW(6(~*c zw2()n^My7tlPn;s-38oFCv&cHWjYV>nCk|3{cl~L(ZzHj`n3Q;X$YQSQrSp%jlrku zQSQ6*{@wWe#`JGZ-q_9WPsb0&FHR=pqaYstDD1;iA42c?ux~Jzkc-DY3HxN^C!;F0J{&>F$FOxjKJ)QkK7R4z-j91eh6evJw0b;SK5qUvM+!xLbQdB07yCc$ zKi7Yz|F8XT^uO7EqW_ux-}mFprC;4I?~m<|=^x$CPi*klA1FO>PJ;883xH!6zP<3z z3;($A;f4MSuUvTf!V?!Bzp(r5WoN%8WU|8N8y{cx6L>hrMPZxMqkvZdXXx9!1@N6M zcy1SVVpSg%P6(e0-wIcRtD>(MAO?xRBTj8FZ#Re`km8;{cb=3ljYtX&N3B3HN{ka> zo4EhPB+=##^S7u2NGbd&=7`|g;xh3Y@rrm=ye3}v@%8cN<@noYxKDtOr*$bD;v@5k z^U?X3d@_6red>L%)_fX$+I%{Ex_y=*yqXZ-B;TpLK0;l96~1eIH~K#8yVG}3#GKvWf=MPTD{>fP+sa*XUm$bq_%5o}uy7M6+lW>tbsmgFmG^=u`C9^tV)`!y&&9 z(i8Nr^nLmXRZ=s3nC^w#?V*37Z_$4GcRH48*%G#%`p^-O75CEL&}ZmL`Zubg7Wx6X zgj2^KSU0x~yJ;WW41M7d_9!LnF`Q*AVsAiq_zl~`o)kjZ zQ;=;%?0q2;ddPV8clJ5k%YMhc64dNP=onW82lR<_A&)I$ce2~r2DXg#vK8!pwwgTv zon#AJ$$rIFK`Yq@N#|m(u%Fp!VK_T4L_qfaDEP7C>>69j?xkPTZ)q8wLd)q?8b_0; zf+n+91@n!M2_>uUEctZS4{2UUtAKK7|khPzRAB%q%6Ge?UK~#z* zVzF2zmWvf)p;#nVipio;Ocx!Z99&5OiLDkBM6IY3^`Zg1DMQQ@v&Ag%pZxiQ;bH#aLr?^mDBzB35#Ye;?;_YI$xKvy&t`Jv>cZjRR z)#6>^T5*lILF^Up6K9FDp&PV|>&3gpjp9AxCUKqEBi<`65a)y2-6^WX4zXIiUz{rL z6l=sSVlDK&I&rHwQG7t01f8ND+FygXU2GP2iH+hz;$&zUP0$CYhz|=^!ORY^%g`_X z1r75Xc7%P)4zsV>3+y{~jQzy^#GLGuAkdHK=j=5cn*UiK>@UIyc18%GAJa?hufj<7 zmJrB-ST_5cFp8ZOg4j7Bn7t!}LI;9%CqzMG9nC%xV%W#RSawl}WuFQ%c1cjM&jdN0 zO&8E&+DKofr`Z=mJo{2mvVRCFL1ZGdV;^RLwrpUOCE>I_nF-Lv2s6@aERkMkT6%-& zsFUgG&rHKcurwCH(%DFs!F-vO`9o_P#!}dDmdgB?jg4YiERbcg2v)!%Ss@E%IV=>~ zcNoiK;n40vpyQ2(jyHytvKUsz#zNbRhGrfM-FzIYWOC?!3RcbHp#dtPtHT>eZ=(yL zA=W{cpU4u}Bw?D+EVPi#V>fo8UUFZ}R zkuIT&EFrg(Zec!|CoF{a-%S<^ON9l(GGRICB=f~^a-O^|%HTO@B2$E43l9hn3J(d7 zKt4Y#Y=fNM0XtzA&e>N8tA+c74Z;>-tFTU3Bit?YKtsJt_^WVE=oj7>&IlKT^TJ<* zuZ6FKv%+QJAHv^+A0XS`6}}h#CA=eiAiO7>7XB%GDZC|oCwwEk4e9@q@Pcp*I>5`q zpM>MWN#PaY&%$%UVc~hwxo!bS86Q zg=~f`a<6z+xChex9r0Vp+CcajMnKNrM-~a|aq{p8qdK!UQLq1qdD zTKO~Vi_iH>*!?_SvOffUArf{_E9)b*^?mgDRW6t7Afe~9Wqq>NXTBipoOX;iSsX|f zk=)0Whf!7}=d=w9d09IzA4CLXOwj88U-nE}WT|@HK}tz*NVuX8(Y{YK@;JS&KvB@# zxB8yGEQLd_Q^*SXL{*=llAg*wL7813+gPAzk+teYWh`%TEc8O0J_bLvUetnT(Xg|oW%%kpY-b7d2g49O-#lHP1ePEIwox62Z$7tiaO zg$rsjQ<5oJpKQ`6rO1+PX_n+POKPI*w^Fh!zhzF>{CTp9mX;Z^@~$@WI}DVB_Yp-( z9|`FrVSO}Q*0;K*zHfC)pP!{c)|Z2DPD`IJ56cjiwelEqx*n`y=e%(kJk+ zh_Kkw$9R|_%v$;=4^xC`OCPZ`$lb^U#!C6>LltGIOx9=3t5NjX@+LIaQ)Ka60t!?k zKP9iazAr`L=(8&vW90INF>SKGbe@)uv@~~G!x(u3)}xG1C$EoH)yt*7J~F1yf>LIH zN#Q8y%gSr%v$+G!eO84dC(uq(vQy#jNY93zP8{yP1C)jW7Z|TX*7sXYABm954+Prt z`YveuQWcH^Bh#c{2AWOH>&wWKVZ59AWYy?)%!K*_nf5UUGSeFJ91VS@yuQr5Wodop zyx=}dUI^l;QFs`UibcZfO{i}fqmTnd|4k56`$7w*8sXm7d_VVX^X$2@x4jGPzIWUU z?Y{rJh1Tbzlx=kM(PsB^<3`W`$Q)D9+~DXFnmOrc1jYe^(Uy`nzC2_ySui3Y09jv& zM&7{3$fri?>iHI8O2y#(WtcrFkX1=wZ6~fGG6#v4Xx~1^4tFtEoRgsAJ3l z!W<0;WO7GYpR2m8Z&=mjKDxdS3;@rZ35|We>oLQ|$@N$(y$$Q`y%$n;79@b4rjZ8M z36ko%7q+FAZN$lQ8ruzd^gbc17uMquSbldxi>#xmY!=I*SD;hte3)oze zLw@IajAXmMb{&U3^%Hvze*J?kn=1`BMaQ~kxpv_mSRE{0A6F{-p1ewTvsTvz=t4ic ze$T$6+gzdaPizhSGc??A=x)XAB3mi!$Dr9GA197-~-?VL7F^?qqG$1^@OX*1>XJHL%#q$tsr>ei#+B6dmp~Cb;6@ueyWy z)5ly3X%{3m2L22_^yBP9*Rwcx!Nq0IKLa>QJ!ykZ^eSv_18nP3Xz;)MuNRi=ThOyj zY$oetCxlp`O_&2OXb<#`ZO}&dKp#8=o$(~J$hV>?;Ev#J!AFDN z4gMx1A;b|<9nuxDIb?Upv5@njfuW|*g`qn`KMlPW78_;>s}Ac7TOYP7?0k4gxFdX7 z`2O(I5n&N&5rq-e5pyC|L_8aDJ~A+}Fmh$&j>z+o*P>)mmZ<8eIZ! z3QvEPdDxkIHq_*@vh=i#W#xU@&55)@v?YBd|G^Ad~JMd{G9mJ@mu3}#_x?k z6n{MabbNpOm+@C{P7tV!R;rXHWtOs3IZ@fFoTpr->``t}?u768sPdHZZTOonE3e^P zAOvRu8kG&F0+p&p)lAhA)oRs7)i%{`)qd3x)k)P`s(#g%sw--y4pc|0RcezuUp-OX ztlp@8R((``O8vI_qWZG>T7qvvNJ4CaCc&2ANT^I`Oz22hn6NToW5TwC-3j{>jwGB+ zcq^el;hRL37?2p9n2=~o%ulRLtWRu9>`YvdxIS@f;;zJfiH8$UB%Vq9An{V-_c*2v z&_rqCHA$KbO|hm{)28XvtkA62Y}M@2?9;rZxvFK_7_Cj4qb=9gYumJ)+7;UM+O67M z+I`x?+7sF{+7GmswBPH9EqRt9wGXUw1-xUiX>qTb)xs zTpyv2(;M_@`a*rReyV=9eu;jKzE}UO{=EKE{WtpSh5$p9A>Lpy6dD#9)*AL2P8d!Y z1B@|7oiW2$Y^*oVGObW)3vRm=nwvbH2IO+-B}FuQhKrKWzTM ze98R1g;)YCQI>d1lBLNq)6!*GZP{emZh6AeXZfS$wB<{yuQkq^Wu0f;Y29Z%Vtv*6 zw)Io%x3(CY#+G7hwXLvivpr=yV*AK;*-q>s_7r=ez1lw2KHI*;zQ*2b-(lZlKWIN{ zKW*=~U$#3_hNpz2l&93EEKBK0=}p;@vM1$e%BhsMQ!b`lPPvvEk{X+8NX<&EOr4s# zHT7`nXK4v(Gt;)Gy_$ACJtMt7eN+1B^tUsDGKw?0GPY-&%s8F#c7`)EK2w*Ol9`{m zFmqex?#%s}Z)9G{Vp)M%(OIf2Q&v`1X;yXCnylWeqgki2zRbFk&9c+73$uH&w`A|k z-kW_W`*ikaIYBuwISDzIoSdBUocf%$oX(tWIlFWA=N!p7nagqmbE9*sa+`DK25J@;dW&=IzZply^Mu zYQ8Q%C4Xc7w*1}s`}2?FpUi(N|9t*u1-=C#1+fL10$YKjpt7K`prc@6!S;eD3i=BE zSnz5gD-0}*F3c#*FRUtTDx6ukqi|2*!NLy;FBN`YM2Z56bVa2_jYYGImKAL++F$g? zqEkih7F{a3TI^pORh&?qQe0SESzKS-TD+$C;o?KZZxmlFzFd5*WO#|D#8y&Y(pJ)0 za;W53X-ui1w7j&wbWZ8|(kDufl%6X6sPt0lH>Fq0NSS|GNLfsosw}B2t;|tYT2@`w zSk_v$rmVMYN7)l)`^)}VcB<^HvJc81F5h3lDhew)D)v{Lt&~-6t2|oy&G@A8N2^#> zMpbRq>Z-n~?hm@6HM*Lt znyQ+Pn$ z_QAAk&4%WV<}J-fTUg7~mQ5`OTfS1oqTr#DWYIlX&&&-8864^KZk{hK!5HceY$TWec)+n%;p+rDfU+hf`-?N#kF z+gG=5Yk#W!Nc*YwkJ`WO5Ie#;3?2C$jU5X+Hg@dpINI@c$K@G8Gc+^uXUv?jX2!!a zKALfTre@~CnXk^$&FYO?F%JZM$zfJttyL^_++2oS1Wd?!>v5=LO8m znAbUP@4UC>i}R<>@0!1V{*})B&Z^F)&Ly2aom)D0cOL9K+IhOuxxjaUZb9>cg$q_L z*s|crg5wKLFF3!@e_`>$)`jyHE?c;L;S&pwFFdvIt%c_oezWlEBC^PTk!F!?QTL+N zi*_wKvgquhZ@Y$f8M7cXV&+-rBvZdtdjNr2$J- zOHE6&mewzATe@=Tj->~e9$9*P>8YjtOFvus?K0D{v}N_n7B1VgZ11w;%RXK1zudIE zd3o>hZOdO>er<*CijWntD@s@Ntk}Qe;)?5cMBib%qx_DUcl6xx)E#|y9KPek9nO`* zSISlvudG^Gzw+UghgP0h`R>Y(R$f}gRt;YjvMPF2?W(3#ZL5~8TC-~7s;5@*{%{m#ywuy=C>z)q7VTT77)=={v*jY`Sy(oo}rPT9dJ6 z=9+D5_N_Uy=GtAVyQ=QmeAkJ)F0PGQTe^1T+U;wPtt0D9>sr@sU-!hizIA_G_v*T{ z>poid)w-)aVoy*{OpmT7t*5wWV$YhMO+DLscK7V-dA8^B-SKxf-M#1Tlk3IxS<|PRl=BdCA-}vDgT#_@PzvU$DVmm4~BUYn1Ri14rGcr z30;_7CN+nL1yW2YJS>8$BpO6V>&+F@{mlNEt$ET!w5s6-Off+_Dv|kmySp)^JdICd zfl~JxTq`8eFH6TXCrR>06G`a zFUewd4!95xJQ=GEHTx+FjY1+j_Ybs>^?`gk9Qi7oVDj}5Kpv3NyIogslc@^nVmu*g zBGN@tzQ~T_&4|JEEqwkbF{*KB+1}4rVit8%HxR^D#k+|f2x`Y7-DYqsuBce-ke>8G zTXS}HvrT$_J$K&j#>UrJM3C6;#I9-vZD#u=zWSN|%DXuVoq*b}ck%IZgq7NPVXzkaAc-Jvs z&C-0;WFwG!~?&>OwGaO za7DPnDz_?wDczme)n#z5r`^t1*kPwZ`@#z`CUl z+p@A|<=L7`HIAhmtsn%kd$Zv~Ke$x!+v4`Pd5Dz5uYt0HOx5~r6w@`(%ot|O$YO%s` zv4U7JZX?D$245AVqijRJ*2lBU`Cj*;%h%6MbU|9!gM+^{PEC%kqPl|gqO6(O*)y|> z(hKT}7hSc~8jLj-XyeH>rl^dm7R%HO_cKsC$>C@U37wQ_uQ3{H?3t57Lz*0pNm>?T za3~ZGLt=J(e719yHBTnXvnJ=q$L9mrW{g>lF~{Od8}UFhAF;|!N-RLNmrV@}9o%bh zz?@yhl`W68M7CLH&E;4(<;}^;oSj$NUA+FFjpKQe)n0Eh2NsuDlJ{(t@SQOuH*Z!} z!LAKYzC=wxvbD~XTxYY?ftNTimQswxm&D-e3B|b&zPcUUyJl(cUNh`{m))Ifn1D>w z(StUAQk~d;@;K)!VN!hD6&_b0KU1Qe%*o@W$d^lT`OiFFh6a0NO7y{VOlTc7fNvh z$~Q{!A}Jn^{J9i79jlDjPecAxDPAJQjfl5Nj6iy&E{;t%&F5lJEu+UysNCdv9Y|Y5$kFiUssgh zuz^fCgZ<29!ZrQ8U(=~dO)@f3#)?Nh`invt-!odf#F*VW2pA7nm_|Wt%dH|uqaa#IupTod7B2zp%HB+qRsX*TEg|1Tlhbp ziI@HJDM95yceFw&SL+fTs+`J^#^EUms_ayKa#DghMX9t5Gmb1zO`(lexqkSFX34pP}> zY%J?gBt_-e9duN#R-L0%I}!?~QonSqHZxPJO@D3L+=k@3DnI{miE(MFgv_`$d4koZ zR@p&VP75Ff`{-UbPv>hO6jCt!>7=LriK{|woMHmedB!#xk~0~jn{q}QIfS2k6lV_! zLx!xuh#}!u6N_}o32})v>FG6zaS6$~qLh@x#FR9(y2zo^=H+R1{-ez*g>}U6WO-6X zMv^>v_z0^)Wgblf zD6~xXt^kEzPOb<}3b4kcW{h%p5LPs$Hcq3>$k1vs{q@?>k#*z!hsl61WkRN+bzaLH zy4aSWw%Sx`bPc|dKvU>UcFw~~ay+~w2R!<$hnKk1tT;)om&$Qo;!ZQ5ZljdOd5JqM z9_8k;H$A+>otB1jQ>8S{OWbKjq_s(PIWKXirJ(LasT}7e?lj#%InGN4(^{muoR=Vt zP#5Q1`QY)APRq3*c<%#k&NLfz`C@Pd$RVK|Jbn}jh5Rs+JYc#zRvY5QtY^>idbjEx z_^pOPK@VzpRY{L(@@Uv4m#>Gfqs`i$K<&OY8QliB5R8~=~?4#}1e&FCJeWhmFv zoEydTNtrD!&L(p|on=GwjYG4zPLBDAKixE+8r;Ga577U+ImvFBWBgE!AeNeCx5WP! zlcuYNV8Gcm6a|0blaOb&EnizQEv6(-TQR-1sj1vr2@FI=RQFVRcZAmHocuaRF)HGQ3TM?8@;TiAiaR!zGq}IzA_vM8#@M^OFVE} zzg7tkiW%Mo48?6W4H+kw4UxgVv^u?TSaLvXg*LOvY;MZbmbC?#h83p0HmRs+sgPmF zsTJ!R{foOwO1g^u>nDn}xrR((!{o`!N6_OVAWL)5aXk2oB#|UbUahd2y+&GuB+NMj zjEIEQ$iGWR#OAtyKtJ=e+>#m5R^2R1scKX(9qDxXD9rRX&X|mIsbfZKRTf>8 zwJy0ZbCN1sr7SgTM~_MGOwp>+`C_85M+xo+9>&S1cpwgTsd)@F3*4x68@3pS6(CPN z|E~SRM-Lx9Yy><4>-GV9oAXzi7R|P8c%Yq7==Ft|gXFR4*AnP>ZtD_*^^*fpk??$g zxk=6&NLdx1BK$daylixMYt@`sOH^%I<-!o^H_Um1vG0ccJ!D~JYHgHd?3}9BO<|L& z8LOTI)105t%*TZJ;QOTmMg3YkG*c@FbGSX+ zPXca$nc*|g((RM+XLufejrZ5-ram#t=+qf-x@6FO`+lRLaSq_if}k5i9mcD0K9(pp0idgqksWiR^q=`0|yg2n{v|N2r4_ z6(1f_Nx|ZC+Ovh}mF2fD(VjCrdFD42_0A1XeldOW)Mj>Q@sbg{Qmey)8*(R27w64m z8=T+Lzwpr2prR1kDY)yR?2 zx)_@-L7SxOu|1+s+ZK0^eKI6CYnb^^Fh(eVm%&fXbiOWIOx@qy#c>PFmD_XYz_rTt{>ik zLQn*Us?nKR5sCaztD>nGe@VSZjh^?EDNmZa-s^hu$thIOOw~}qd4mIj?g0J6Z-2Xf z>eQ*u(|hrO2f>#lY=X3gW$X*QN+Ya*heBQ+T% zSJ=JNw3bIBrbR|iZcI)DIv}$<&x-4{8j;f563uPMEW=hOB#4UlD)hg z4qq$q0We3J?Go_8a==G@3L(_b>}R$EUmRz4g`Xl)sfc{c)>0o@kZIDJ9!WQ3?o85~ zG7Cc+T5X#AryLKc$N$_CdC$%*QPZDl3AyL}@$vNb@j5yZ!@qj<%9X2E5paG7?)M#= z^2mm1{BOg-gM7S_f1mYWoFm<3`r^3-4GKu_MtQa5Ze5H68vkCWtD!>|BRlPddm+r!2-e z!#H}w-FI*3-y}p`zeFEfG_4hz8)qzd*w`PyHKa3^CM`IX)eW3`0o)u7(of-MM@GA% zu|>VAN1;``1{e%$UQ@qzBJusi6Zmt#{`o$}wFYtEuIKz@lPZXMrxp~IX+N3z=kNFy_>7&dpju#ugKI>(;Yw4yZ&eXiiowME74L+L! zH&0R4)+(oL4&224wbiUkToS!vMf8$H9e$pU;Wr9j!hOwi@K1QO21&aaa&~4P;wa0G z&r++i;-%-yR<+v7fyJxU{4_uf$C+|QqRj@66g*-WTRpy`+eB>xG{v5(rxxcM)aKr+ z1o__&<_CNBoI4x!k&j=_7^u(lg-h3?2J(ws&A2c5q5A~Neg4eN6K)(RR7Gid#=O*2 zJo-92|316(!_Ll+I%iAcibuUOgY_b~V-#LCepC@2_F%p-KP3f^RNHLSK)u<2@9dQ7 zjdC^9X{hJn5t6URnB@E@iG6tePK@`K{80FGMkP_1I~)14P(Kd!aq@j&1oSg(8NM24 zJ-=zw^Xx;X0P)>>Q^$#hBwaV441Dl?IeiM6Ed;LyNIR)A)M!9&jHybD1UUzRM z9n|`uBgf*y#;^!U4OCh0OVme1=o2gL7DJ*{DVHm)i3W?ka(jkNXU%w~Btxstd^$ZN zARsgS=}fgYgB#U+JhXJMpHNR@VCi0clJ^J(h?m>r91-5{?x!MUXg?_x^vMjX&Nk3d zWuL75waW$e(w{1mTkFx_*HrwrsT+M(M~Mujh0xGwi=vP+WJ zsEe;VdHTar`olaOd#2W96`sNGXXN7--oyiGv09~X1kSQNC%s(VkCjg5!?}(lnXde* z0h0LP)u_Pp8mm*p;gbV|+YdVOG^E-=vCdjHgE`+6#jQ#Ll0FDzGCGNjoclM$xc1{QCE|TO=B$1)^Jn&|@bOz#94HdX>!ZlEE9l;yu zpuWqx@>0u7R;+OT!}v~PTT|;hpS87hw6ep^H7Vo6LK<@FXRuAqZ_2W=N}MO0A5;|P z;HSi*0gT(PX6U|B^|cc>-izRt<_ z{p+^d9&fsBMn!#PZ%a#WWqonWoF)kmd@he+E&=XyB+SH1W-YaI(;A8`lI?%edpIsVbrc=+C1)pH>s(>v#Xr_LU&5H(I5_~E zNIb)h6KNevr{7+jWML=sV`=wog7oBu+Y8&b#oI=&t{T6pqmd5gx6M>uIspv4$vqrcXFhIxz z3mnn)&ZqboQHMN$Iuf7dSmk9=3J!ItO}Xn^-0e9`f-&cfeDww2a_xb>X0*2#wB0qz zJYv>(Yn2`yL}j<7cTO#-H%#D)svFm{(S~OQqYr8x217s^)*VI8>2!aIvx^Vy1kG^n zckZC&PJUO3&nFS}B%ULUjZ?=6(%@wHMi;CMsSLi59;720=2IT#ZakjGVb&L%7kJ}I z^vsQ7*6uXY(>Hc{$F&u8z4SCwjxKl2Ge(0`!oxN;8?|n`@c_pMlh*k;(A0DPWniUq zaVD8rx88qApSZ21W!psQ$-+uk*HqtGT6$-7&Fb%F&3=5!l*eb!dSc3y15Hi$Raf69 zJ>C8AxlfaD=GCgW?Z{;S3`c3t^40zYS09fUzgRGT{(>Xy@Pd|>1;ltLB4EtC0r!`eypr!rp& z4L@f5x^wQF1;^OoS*_)>LY(KJ+dDs@CH3VM)y@~uE+xs}H#wM>ty5NMO|ISoE4KNugJl=oxAkj zdzW(SZ_38<@{N3}qaCkYknXlL;eHg~QfAt2aDHUQk1B}pMc(LP_`MC3RX`X#jy~}l z?gt7v1>kGt?pAJ%D@?CEt3UEm@_Wh8J!3|lyV>nG*0W_;@>6a+j=E7E`g!=IT(aN= zsXDtO^T!|4XxO#P5Bk%7rp3;~`0BjU`3_BV{z2-OU&^aNzy9cVxTjw%-{8>HFFvr*txz+$yi==S+qJC_ft$fim;N&Ma)0>D*bA znoyJ0TQv!{jjD=DtH)Ip85QY{sco0jROM;rG$`7POa%R8(BVVidlC=K;hH79p+j_d zb-3czgSCnJnC8sn?R ze0qFkcYaNtJ}b4&;b>3EFgj}UBpmYgZw#G}2Z!D*gmVTnk#IN29_S4|p}ZMsX*2Tp zgq&MyZ1$R@q#C=e#?XP;EUiFq*;bl&<4(_1__PN4`x^6!bl*(yT6%62j-_(0p7DE@ zl)|wHV$s;**{LZriVLPCI~NLV6H_Wjn1koQeL0QQPN-@Q%F>R&5wIduh2{ zX>P7>wr1sbmsi}LpJnZEwxp)DWoNggrMBhdU@j9emya-)aICoAp(_r1i7#@f62Xdz z`sjLddbL(xT&O8E=&`Q;rZd^5raNDwnwqKVid5%Y+%E=Yo@{5oWh(r}3Vzy#zC9N& ze8_zf-_W_!zZgvS^nD3^OR`()TWR*83S~Gtq<*>1i!?pY+0VNxdH=HWhpSiJb;w<) zyGNwiM=rU%J4}EX)VC6l^p1nwFk!|EOnDL#G!t!Dycl7CG5gIaObj zrY_KUp7f^*O;U>4lsb0`^ap#zq=XVPCXGO1nc4Zq0RDUj@h9~MiTz*TkNz~_-_Yk? z7vJJnGkm@Be_%)cqWtH3GJj^e^rY>a>e9}%*X*yv!o|kt&vm(uwp46QZ2eTAU*a=n z@EPBse8xL!j{+9_zv46Wrx}B6#`)%#H}e_3-th~6L;Hib>c3s%{}ms2lbQ!P!3^h= zn;3z6O()>@BL{pbn4M%cdlvKPYU79z>O#Gq{?wdg(&_5+oj-AYr~|+H1I8(xjR5;@ zxzBAFw{OtBk|Yh=?E>@?CJnB>+U%_HmW&AnrN$at!SpcG$RcZYxg~8vQE76mtvZ)` z`;t{=zv2E_>6*L*N4iRvZd4}t4~OGWm&f%PN`hQBXbi)5?^wI{VC{0QVzYBcb#Rz8 zers1-TX@v+H zcL#C>j@v?E*uxV?&!_L(Z-4dK^zA$B&wTzl_51d(fBhGqBR?DC%aSR@B$b%`j`y{0$>&(@JWcmu%W`*}A!zxis6MR^_B+ zIHEFaiZRBN@(D+@F{wJWPV#p${64sa{ucdu{D4jFQ_dke#Lx?K$w_r~+)J09G`cD) zd;Iw9tSb6?R(7-9-khD)VzWJ3-rQVXK8;^Zk?y&E3%xiLr>F&3qst`Uj&upz;}kJa zZlb_D;&q;6QKd(W-DH?bQkY*$OS4W}RZ_Zgn$g|-&>KX9r@&wA`E{0b>%vf|%wJq@^lV0=Mwu*00;x8fyB2_dt|YWdT& zl$2>18PYSUT&F8fGFIqx6-K>XZ_*~zskD>SY9ozEY0b)NO>sXH_2bRv@p||3*HxvZ zZz;zastOcw{EU>}BlhCgX#VU@_LBtNh zr-avt8HNWY)YQbMWwy6xrUecn(2Yp#AO_u-r2Y@n-G!Oy#ru*DCh-GQ=*mTUpa`R6r$5}KqllEo^+=$KJHQBjd$kuZvPi$+@mrz|T23l}V|N=;4sVMv_A7i#^AvvjC&>NvMXWad zmJxDq_|Orv&xa0q)zFA;=#ox|d2oq*r|_4Y+>!KUh#IHlg^IeG{Bl5qXT zP+U(K8j7fz#+~&Dn#jikI80^m;%iO2jlD}G|4!C^cQj$GKWl*hlLP5$ z{8AJwZ{mmF_=8X0U5_3Z$oC^BdA_H;a|7vW@kibsR{kZH>8{WA4wO6gZ{^SqFZciB zaXFy3EMWZCZMkivl58V2`GUOX!A>FGoK{}nT!34P~H! zbH*bc|6C7u(DfeS?m6Dha0u+zLGw{=ZIXMA2EEYyTGufAeC5aV?}OfG=YO0_l*tx2oo9+X?OcuyCUTa8?3dVJ^XZ`|9hy|4BE zu=n2KaTV9!_|Cm5mYXE2tkug(T1mUoO4_Dom6a`9maJ;amSr_tmV3jMW=gQJvB3pn ziW7(-fFTLQA;B0@Nhl!%FrftaVh9NdC0|+yU01))nVGwLSF#}az0dPL?;nx9?cA9& zXU?2yXXczEbd^gF*mNzp_ETMBId|@*cKh+R-k$G~>aw@(IH|^2z0_NB?Iq}djtiB$ z67`a&*D)9rGUCE@4qkK7SpvygYI9rI&7Av7&p`s&3ya|5)5tz@FH) z!~Xe(#fvxEI~p6??VtB7*Wis7eCr&cz0ud{VE(hcQLqkX^yWw<+a%m8592pTYrxt0 zn5!^yb3F{b)1hBLaY0%FeK3?-sL(=EWkk{&4oo@A&*xevUvGTI%1W-=V9OT@-Nx*u zwMhwEYn%Gk3>|`cO6dxj-_p_-QFN$v{_L`X4mbM&lv1ARY7dki$;-Eq(qp#IHyawO zmdw`Kr`uNyb>w?Xo#lCL6&RhM-%$^Mb_-;p9JDVMH!6aKVr(T8T%QEdp#}+|ExGcG z<_!r+Yvzaz?VPnqiL2)YYi5r1PhfrO{MO++p?;ETj`@zMO?9=+Gk45u*)g-Zrgok| zUGw(p1q)WcUQ4^*S|4`5%OO9|55U&zd7-hk+em(R?~)c9emfifg{yppAa$NU(6zS0 z+0X{{*|LF_Wh)lUEH0R9@2G1UC}3}Fzi3%|d238`-<;ZRhrP9BUaKvws>nBc=}IjF zVkOFDZ(+>#>pJ4jsq0{$|Mjn*Xv6QnPkDwA(eeoToILlEx(w1S=;J*d)Jk>6ki&9$ zYuC1lnKLb!4%^0cwHxX%cJG?m(t2fw_C-Uht-|%i`3Gif>Co1YoRbaP4Zxisbd4Av z_|~7!elz$2o(U;Tg_;oE*b5m{ZfL%`Z_(v*6uUZe>)f)g%1BS@!R6iiTeOQC8n@u~ zT`2qjE;;Yk&aRu5Wen_@on4VRZ)xYPTHCBkyV~en9eoSBCcnP%9QAP(!M{iphn&`v<`7*ZnbW6fR_u>O>ucsw8_83vE~>b${#{=>EJd(EY7r zigujq{**8%|441>SN>@)kn)d@5wT$4X^cTp{!s|~0xZJC(Qwe2A?@9Fu3kO%~wJ89~T}UVw~k5UkdXpx{8e0tE#d2 zmNa#MCOPl=&mjUjbh^DOgdj|e1^#iBbm;??wWJ`dtsF=`aiXBJvuD1`*->2F;dIR} z8FW?ow#;qZ;;V8!mcMLS{sX17>F@WJ_RoM8gF*LWVt+%~@_ax_9Hb*o4C+BT&Ly3l z1t(7U^~8CN&=b!gtoI3B=l?LX=&f&Z8PauKiIC=SuBR2(^M#@dXDO%@y*%FI7vhgJ zUW53Ho6bJZ|4#l6=)88|6B;lG`$R=qVLyeqNp<*PKAhYJXnDN=05lI8HvrGRVfJK1Rq}QJ3yasw~dW%*b}toB|D6|Y^AohqJk&27x2Omk z%?A}DNpuGW5_=l};VcRs4hdciIL@j8zfZzva<~m}oEroF6A5pWc%~@0Hv;&b93BJT zz=UULWD4TfQ2OsnI95s$j(ZY-|3Jd|zLlrP{RY7Ak?@QC>2W3x_>U!gki(0C-$-W+ z)9IWNL}v^Wbecgw?j`}xagHZmSu61;(cK!rZG&x9uu zexV62&howl{MQ^F%aS;}b7Tsb43`%bY+(_p%Kb=$P3<2o{ozoR9@y8*~lb}Bdz6}UY z_lm-qnJpyv$UER?;4Dw{?j)KJqV^*a4og{QYjz3w0$fmbgn6j|X_P~}Bz3HnnI*SH=r zmI)V>A%qR1p5;>4Vn><4dOgRBbijK*$BT3-(qLG(Om?+9UU4mPpoD|6jKe777G6fV z>0K%U<(g5@F7-OpL&#n!dLzO4Jv0s%eXvyaLE5vIa=4I5J^cXCz0cu7CQEk;{0p(p z_%(;W13(Td-9nr%Ra)_j%IOEbJpP>eTo@}w*CIHTbA-c1pDSge&kn*@BsiNO(+m1X*Q-L-Yc!n|l1}WUA>*&2 z-Gq#L8QLEB#XSdti@xraeVyR%^YlXYy@-ZF>8Tx_=5Qh3UfD-mv41%MSX>MQ#1QrUk;%jME> z*nOZg9I0HZX7J+b1r9Od~J1jt z=Fen#Ta=xq{mKcR4_c!A+FNY8vNNC4vH`JG52-8kXNO=|nyJpfGw|?gWhc8G`N9$D zN$(%$g3*|InD>F(fHNF1IJfisBy-0(9P3pMU+IS%*Ib7UeayIDIl`hx{sKCL?9x~+ zWcRR?U4k3eNQcK+BOR7&B+QMS|)Dlpg{;%uE@b_XlywKvxA49y)+~p{S+g91hkcR{(gRsJ z{-=;LoL$FefUb~}O}_`H@f`2WxE48tNHu}Jc>?8&zIilg3?aB!(;by#$lrkVW5iIC zeOA@ibYhMqxagZlgZd`HMc+Ki`=)|;wDS@B`2=)LXj!9WlRTg1r^bcTr%juV*W>b2 zSr;9hkYIR$80=-vhd+WV)r-C!ms8?+R(`6r20VllsKBS|mHF%#?`QY>`}$hU&qokH zUfy0@jv!B^%~pv# zU6nJ`w%S6kx3IQ}%HDvH7jl0p zNbU(P z@H;u42iYaWe~MMi%p5N0e=vxCN-yYtFo=GF3;G`nnj1;(gk0Z0j$Gd_<@#Ts;?00u zvrmHN#(-SkKaO1AA2c^oda*~iKWJ_w_#b(F?+=q!-u*P{{L-^qFiUKc)O(=O}BB zp8Y>ci+)6NQK@<{>Lz6FMJbCIp-p#jxR9+E4YGBZ!^NEWqMS2@j1nFpqb~{>6*dq& zz1Ww(DCel5kv_8RLi!UZ33o4LdV(A0(wD`Z6;7uC4nMw$Y%b^*a|qr47H$86zwOD+ zfZ$^P{epikqI>WJ7c%jJoI}KzPjIpSd_j)+og;m8R~_(&Ih`-b^ggu54gNVQjJ+gf zh~}sYtY^eNoGc%&Mh%paLrR%xgBl~oU8A(n-2wd^eM5CoQ8iv$tSKp0yp#*<_S#y! z+LGyaaxO^VxE=nwp>qTDr*L=DQbE5;tGMF3Z2F<6cDPd$6qA=2JiFD!T8E0J)N(_b# zuWl6=7Ve~jCvLem71urD4PuypAh)k*+4<%iM^VY5(%$tGF9@|+GIJAh9hpn*_Vl!Z zf;2>wMG`HWEscn=#3$Dj7B%Gd%{Hf{#zm)0Pp+~@KT?pEURa2OBJU_2b@N>OBynJ3CdQM}$v0SPThX>WCl<)mT`t7ow9T;&{Z|^s=0r(t-6& zSl7$-oU&NLL6c>ZBuT|5$@x zKX1@?5c!STT0b8UJT3IYgXb9)MB){M4gU1c^mG-@~K z2CbkEv07hsz1jg==S5M`V0KJo+}3Rhm9)gDfS#M zGdC|c1_IK-`v!*tkJyU{IPl2y+a+Ag!L-MxJ%LqOCFWp6mJ2s+L-~bR7yX>Cq4{&{ z!o(c?5`FULI2TdJ=3nN|Z+srE^aSM_98>OcJf}WEbiJ!C_CMc9`OpXY*;doVe!56U zAn4+HKFD7%U1Rh2A(pJj|B(`|uGRA^I{qc>I@1Y%y=ZMD_@X~2`tJ3YA2{<2F4rXU zg(LR3;1jK1|HbP=>sKqvi^)=co@pQ669ao+E!B`;N^2krK`8 z6O4ZeyM@p5g3@%fKF;XNEH%;u(Jq zo{2Lq)cKtG``8_Ro+Wag1>;}BPUt*Ce&Sj1^X0%Vd4}?dXY`z8&sZ+ilf%I?qD$NX zi&2&b?pt9o##d)%-2LH44*uIgW5SWpr698{J~p!5zBDJFUj-X$l^HQ1tj}!T5@}ge zJH|Rw$Pgl&gU`xrPH*5Wtq<_!ez>t7#`O9b5AijI+EL&U_4DJ0&ROFBSn{I{vTZ!O ze~HZIkzVWzh)+-RcI^`TZGyv};|AfA)Oah+Xlp1KW*KF2nA>5$V2srvc(O;92fS4O z7FmCs4RSjAs60R1KwnUL{Va>q$Lr7Wp#C}@qMPb3@P91f(ykEo{39>-CDy>p<#65~ zI9%zH`VVn-O1gWIejCBDCpn)zV@d~KBCyxt7qLiY(s$;e((IW$)DmfK#r|`JYNEat zLooxad@I0HuPAldCgo-2)pd4mQ0~sPM@_LznUp-eD3@^Gs{8}_?i(-PWVo24d}|6d zal2cT7sDr+&AGYxIoY|<;S;m-^0G^-s?bMAR=`i%jUi#<;1?=R-5QPXgDIXe;J>1X zRF?}wi$cru^UKw3z#pHNn{Um@i4G5Qc)f^`kU@OD5&OJ^z&Z2^cO&g)(Y$!}37%~e z&*=6^t-Cew>@aiY}Nm4TKIEecUSIByeYYEk06 zZ7Y0c*==6&7!PVjnQ0)Eny*r)lLFa%mo%R#PGruiC`b$cGC+G~v zUhfi~p7yl3O91!@BYe#H`C+-&gnubz2Ty;uO#iwckMSPW(x7u}!hbJMe^~BgM&Fm( zGR}Re!^ZthNI3A&{>G}Tsw_G)62-V`fFbA%~C%s{jI<{1aO?yJdaNQI=`Pq zdgL&+McmICbI;&kwD!fml=RUd2761~OBWs=-OkH8U+TnvIAh4yy7Q@f0zdgX5%(3| z=J@sd3SKaMKI(`wQ)ouRxV10!gVATH1Ae;69)arfJK&*q6l>Sf_rEsNSt{rR9?nDE zE;PVHi{xE_&bG&>T)wvB_@9>e{c9xlqPh;{65Oh6;%mNe>^Eag7wP%bRb!rSz;jx2 zi#6le{L6y!*Nw@49_2?pEa-oZsE42n&?W95X6 z71_qZ=f)CSXuz_H4AseNn{tGkAlbAcxUGD~;o_!LE_iOkb>m@pa081lUZc&&v26k*mCbCS6!gDtp_nS+em!vMY8+ zck}tP>b5oaZcT8dAKuurk3B|SAlDAe(Y-*<8-f=|;rr!<7Y2mbJx0Ja`A-5 zYGH?1)i|MK%8HhmE7{!UmK}BTeU8e~EK z7JQ1q3zxzF)?M@Fe!t@R%P^J|A5=eBQQArgI36H zW7rT)u@BF&Q@(T9?*(EGA{YhVf(sE-rSGoL`gPIHNiE*aK~+1ixNp{+6{TgX{=xbt z^tZMynCdID6l`9aS-+0lpSA6%W%&zMuc7`BvAI_(%AX%0NY=V2Px75KFINI3|e#ptpT!~N~?fH7gh z^FcL>cOV)pnZt1t=V&jBU=beeY_d6})a@!ujZdB(9~&DVpP8DP3B$Y9?5H*~Khv9O zD=)XNO6M))8LE{=QEA?Iw+f1qSIwu9wyqyv9Z zQ>=e3dE$4)+H(ohrzgavB*)t86YL4O6&1Pon9ZKl`q=T|?dj=>>A7$$jU5#qXHn=6 z!FL`zPuL7{wF)1^=+HHiNw1wxmUNZ+W;=@Pw)&z+*}c+x=OeFsob4^SnZB}I_Y2yM z+>0mSrMZ)Qi0M9)bm2~6e*0Q6`WPLGiQ(om=~Rr?V}LVbWy@a9v(eP@h?1mY$QEYin!klH&So*Q84pI_>M3lf#8RBydmUDA$K5qygYQhGT@L2>#}|p{YFX6plI* z_^OiJ8o+qf;HmRh+)*b>v9k?heRZOq*o(n5^h@E@g4+=K{d{4e%7fTqaj*pbu7tm@ z+i!;fq~GUPQvSmai~UJ4^z=sCq?e^_k{voRqm9i=WMhMJheo=UH_=~t#9V4G^6iT9 z+^?|J+{CKFav{pN||1xZR^R z5&Nr)(I&@1UpVx5Vt++veDKi>_^lkSpIHv!MBV^b9r7%bY{1|L8R^Ac%NVwjw=Ll@ z!e92ow;JKVqo*$kf|p1*R+nTqEp3{asSN$13Vf9I?c5z_D3=U6`MHnSYaFKY z7qnk3%B?q)OI{nt4*M0%O1@Uv9Rq_K9o;pZ8)Hfmu3Xi+sbbH0SsHs5bJd(n{D9RlgDKXlpsU>u_iZ$ZL=$F|jSNc2l`*`-R3;n*H#fnopeBQ?g z65yqAE6khfcS!iBn5l zdEVsWuFA?DZ>Te(Zevs1x>AG*F3g?a*lVBd%S^I+^AM22>dSL2bh#Hg9ka?(Q_Cu{ zc2m6t9W*uw`mUFB5S(HS$~l)b>SKLXdwA3=Sn89%Y7qnoGBDO>b@PtYH$Rz3ZmT2O zdee^#j`dvKSZ&MBgYW9RY}@>0J-j^%jrW_>f^qZ-oNq|)fz4pxK2#Wsx)|&vJmBXI z{Qi{CKk$21;4|c!nbEFt%|mw5VnB$t!1mvP|Ew*KRcR0BY4=jQz^?;~Hl!!-eA2LV z$Wc(7nqEH(TZ`9RmHDk$P9dHd_y-^9V$8sriVrr@CA99qX9ihJ!(aC5)j=M!w`{>Y z@nqhA+-kHBMPCoV4R^r+pDWXs81Igu z59shd@~I6O`5`a&WqJNjygkBsdk=@dsNc`V*kiOaeVO2#hfb`S40GnWY|`Nq!!S?# z-C+!0uk`v|VaU1}%0L}Z28Wjz%SIgpoaFg=PAi&|kGFexda}`_d0y8aO=o|^bGi%Q zR9&DoR_}@Me;FQajH3;A0(`CIjV3!6TIQ)P$IbEei`_kkIyw&Z)UC~WbNG@An=5*< zvU)0-FP!rSwXBp3Aotoo;-ajqhlA}! zO(+WOSI<_NwxTTV1C)9Q5N(M%r_?@)LU%gt~Dpfl(2LT!x6q5eAoZfSSE|706W~ANw7kSB zd3q;otl_^J@LzEFG?p#leL?V-B)k){X~3fv`tfWvrUzWdlNL31K}S`*DO`VbtIkrvs%io={wi#Uoa8Ta(m4Oi~)JFxCsKT!3)b{jkL z{Q8y4wl&q-Jw0t5-MOE`BP+ET$4fd&B>ATQc2kw_7I^{3WNZDm@`5Kg}{DyTOU*#YL?}Ii)4Z$!<^D>%@;t zy!{dTiVTc3>&P4ARE2LJ&s`+Dc_v{+fqD6n!3DmNWwzN9J!KhX?!@@)yu{S9EKy*i ztr+bgbm_FugDxHIag=n`Tn2NLT9j`!c|pM}@@ej0#ECVET;ItHTwJ4bZs+FV^SaE- z8l;2sRe3*nXu{X{IL}VcEt^4Z&Kq;na>jRb&fB$fB+qz%T(t{5`c3V#NH z`{rRRay_aF9{9lP*VQY*gNFeKV=wCV-6LBK>BG^_!5gIK{h98$D3$dS_TYa9{CJe`s734WfpT9RNj8b`Q^1P_ z9DCqC^3jg;k0Bx5$q{otR+Qi$jn9vBxNZ+1?)_7G0q6JrA@jOFG3mP=t17=YH0-9O zR+_yoz1B;e`ybe+(rcLRO^mk@)y?34!{2tDBgsMV;-Iq`qLp-bWE~>TX5d#O=)RGe z_TqAk?nnQK(MJQZ1{4#n>d6H2Myab`c|JFC$#Em*gk;O_zEjt^?!y5-dn{ z59FPGZ3=fn?suU6OY%YvS(ocNwC-!1uTh}E=#S(j>5n7|G|Ue8DS^jV$RP4Zf!_-Z z@M7*iMKOipzELUq0Qqa;Bb;Ba&38WN57HAFt>I{8HG{D?q^EOureb!eezZHe%X4w} zs4Mp;_{*Koy`InKWw)Vh!yPD$`&iY0rfAdwKF874&j@)n{S%+U-}*m5lV6v^*HoD5 zC|neLFpu=i%+8de|rhBtuPtFwMuP^E7r)sZf)IzxQXb zjx@x3nU=p{wPnqQyj?k~R_CZm+MR46UftSOc-}gNys#t@3_}*?g*XCijhLojUx|@%@b_zxLY@OL?uSHV7%KuatqycFk?Z(?>g** zDv_RTz_UYvXZ+rV{%j|nT^@LLx%L$c(VuO^Sb1>dKAJ1SgM2B>yB@9-;74Jk#B{^R z!$HrvAB8G%qrgTSxF@t9d(2Sik~0v()3SqwyzePh9?i#Wh3ENd0`8OS3Ov7DOV+ifgF`wAc0SCs$H>`T^){9I3qb7ZpBixX~A<1+tTRSb{f zsl`e~K|xI(8F^usFeVSq0mCeA?o<{ZtaU~SWhzmw3uIy=U$W0kZAvrrKS8#Kp}Os1 zwBH~Q=fuiArcyOzblOxs?PG?tZ!7htc;!ptDeZ8~h;#DgPtlfd0cM8HnS@VwIMi<+ z2kk@P+O?>!-apWPh61rs=)T7%rtkCT-0LH0pH)k|!Ekw~NX=Ig6JuiVLP(WAof2PT zFtyQ`0{m`|Q{i~Oo3~RwJIIFAY%y=pn84>=aeRTJ9kOY%- zGYizD%FL{;S)aaJF*C($eIK|HHE z2k_4zubJ2xhMeq|n)GumufxPr*jUex9aG>p1S{W0SJScy&WOrozL^6~_so3j#KLgB z{xJ{7rc6%E%TKaYW;Irnv=tS%I!ape6Z7*E-3G>hMlhUxtlDssW*5HG{x{UrY+9|v zD8I*Y{nJntlK(;WFh=<2QG<|^R?usK8a|#Kyu9?h?%nL+tM={%@6GHc zc2*fi8og(cLrjau?1XHz+gU zVJXKHl11$t!Z!2vG-=PuxXpl3RMj|)?(~l#dNaND+2+|mU+u_ark}wkjF!oY%VnE1M>y(nJMYWiu>=2E$N9PEpP!2|Doj6S3rsJI{`ZWHbQBdV zQTyA=xj+TJRhV`FS1g|MIwA#jt)Y1FR4nlyxw%_%DJ}R^J2HrRE>*(R7Sne~#_;<> z{=1d`t{8b5Wxb8(ZQSmz!ZZiJH{y4@{5=)F@5b*A{<{`_-F~JnfIS7yA{ZQm5Kh1@ z#tXlt_`rOc)ALu60kwQ+6^!Tu$uwv@WW)BUySpIcs?077{3pk^Y;y` zoIS~wV@^9z;{ZKD>A_FLOECQo@e=eH4m5rXwE}$dTXNM{Vr@%~+WvXQq(qM;{y6zzby7UODRVfjyq9|50H(%&3lXAzr$ z{I62925^JtFCE!vdR#4`{1hAbmOSmoJhkMjD&RIPM=WGJ)`SYA_yk5JbNKSd9%G+x zCmY217=Q0EwSpfZoFDT|A0sAfGsX@4HfeZ8n0zB2h24R1EfTYy1+g{i5SwE4VC5B+D?7$09U-jA;yGv;~Plb(*;z~RkcPZ@vg@E29sRKRKwT=dR# zPi7`1p?9s(_DR}?ixWRLr&)6>|CtHjux&jTF~j3`b)SM3^8@% zsQoSY1W?gJMJ5+gUVPSuTl{7Q?nC!P{Bv_nD&N@2row?HZZV5Cs=&E5hW~I@?eTiF zH!bsRuAbPb6W2C$WE9NFt?I~guj_C%B#qcoTxl_t)oVi2SA>;>DV1K2clehfVUaOY zTsex8UT(F`E$rCc($bs|7Q*h#aHS^Z#yOV^Z$A`q6R67^xk&LrcIhqNIdsl6ZZC29Q4%@WijJig5 zUFDqgg85~sC9$^kQ90)N`3;pFz3Ud*vOp2T+!b6Ou_i@Vr0I(80Ni%*#SRu(slCZE z5yURHN_zuG>^4@dJ#mMN2zP-R|ic$l-VwqZr0+rG**H)k@-(B2IR4V}QWVKvLa zZr75%kGgD`mI+hBZ3+2i_k!a384Gi&a|(-WQzD}>V{Gmrmo10R$U|@z^ik;fXtPOJ zh3A^uL0uX6E)VAsX9zJtN5v+KwuI6;nTJXao){W%drmKd+~*y_46-vC=>mm z%?7pa4J|KNJo6Iu>Td|EwtlMez{SgJhRQ!-p9i9$vFdw z`SL}{Y4sH2v3?@}LT>VLo>Pu8_t?;7JhdBnvUaJw;i9qW_e>* z!!;}S-x7(HU;2nM z3bL*sOs@&Y1pM5KV!u_P8g$)>r=aDT33u=K0l8yoc(rKodWPSs59wWyft?ejZ_w0TKbekVXJ~rsB|dE=6V-8cmA+uU7c;#hT7T< zQarKUdi%o{9NTo>vH9UEmWOn7cgI+Ars%oc)iP7=72^XM=(e|Aq)iFr%64Z*>Eo9EgVZ#9=h_tbx}skb{VbivH- z)uDZTVV&RJxbZs+!-j@JSNGR13jO}I*M2ubxnLsdj^(RTiN2(Y6*C{JQxu$B;o4WO z68lfbN6Iz%`OET$M-)bLLmkq8g7o~{i24fnAUg=>kN5#mVn1;V?(jP54s3L6x~9(I zIe$=5!nQne$;^iRFI*gU)m32^y|}+&=B2;g0u2ha0p8{UuRQlXYZD!i57m4Pi7||N zxd~Fn53{#6;_s1bF2DSmqflGj#kQ^8a`fnyTk;3;+2E?az7+%cx3JqA8{0I}K{L|= z;6H#i=671q|1dy@qmugcPTEN7eIBX=`UIl5>TM0B*4+uo=_xkf?6yOFzZ!n|^pX|J z7T8iV5)*6-dqayueLkCVFf<$*6jR#OgM)`#Y&Pp1JGS4Me(}bzkoD)Mes5#Xo)nFi zi{SDc$eI^)q$6sjoI_&x>3}Q}`%G@I8b*yCMgzCo62s5#F&3%%mpPklbK_!cElmhn z+^~4DZSs^Z+h)h<#e++ho_>6Aa0s_gHg8t8UJ$vsuC#?Ni8n5tvS}S#yH?q+eb87{ z)u8=W`=5FBwb(~MmxCCttPgi3XkB}f74WsL3;ZfVdxV=7nf^hfgZ2fcl}e=I#kX*S zwD27f?TQv4RAjw)DU)rsXZe5P{dcfNo3FgB=YgKke-6T;>)INVrWY0}Z-4b(%Yq7z z!^BwBNE&+#>mYtV=d6v+3FO`NIMyN4|5=?TlJmB$23`F^?}WC+3Iaw}7^U&bfG24P z5o`LCMa_55-+IlePqh#D7k0vS_0_hM^oF%Q$w#8H5|&yQVQK#;){~c+@&KO;cw`X# zQV#c+_F#>qLXUenvK4O{) z4-B0i?}_ZntgOmxXU+8KH6@1cyIs~Cmn$dRZJy<_X1m?l`1Wjeb&j0j@{`&D`Z?UAdrapO`mS)Fgnpnee3kmT6{}aTsH?oNr}u(Nwr|tk zy_=v9KG?T!-n@N%vMnD++wik3@Y>%p#x@mADjT+#x6|;gvboUL1sxAyR^rjXG2Wv4 z#W-UoF{OU^Ps4>zyzvHZ11S54w)EgU9MPSiO(=1lH zBJwyby2Q?ceNww}Zp9LJ^_p5ovuBbLvTTv7J;CKWg4^-hMU6!zoi5v4hijqp#O68# zq@2B>##)=3;F)^q)g{hDP5b)WKPvEamlih{;$7Uwpmlr8-17cYi~d?$e`VMT#04B7HLxXdOyz6%6_X|HjDk*NB2OO=?H{-Cfc3v zv(WBl&Q9f6hBisLco zSM8(JmxuRr{GXYA$>Zh7JlR%S)uD}Q$S8^p)Z7D9c_+WlQLL{wt}V=`*zJ2iWIJ{4wwy&co+?md5TStX}qVn%0W2 zx|h>}4RydhBn2|0*8$N<%n=M*#VJGI|a8KdgJ%(Ou;hck8}^%Z5=@ONGTa%DBI0ZwkI4#7MM$JT|KQ~4lGYa^_` zI}09ZzX!88J*nW)6X225`o&NIRxf82g)Nzy`ieRcT%u-p<*QGKUj(C42?DlpKIDK8 zew}=%-~u9oQj;nZbzbNVyGLh;I!SL}kYZ98?p7#Iay;@R3)!JcwSukql18n~p50sU zr#}_!74Ki5HGZTl-0S$$pB#I&tHtMEvHS=z4MM)I04^104^jTpJn$k-Af*6v2m&kw z!W@2Q-9I)I{^A#f8x*Inu1@>=hT%UbPJ$rT+ZF7h*Th5L1=3A<7I3)PpI_M2+dCCUz+ z_t{)Ly;d%xOsOf{JN%*2o|!p(j}p1JkR=09mzi0&w@|+$v>5rX;rZn)f}qA@<_zG1 z59`9c?8lT}`)lD|t(mg#R7!#N@l0h$;a>VpPbftu{-|EyT@U8sFr6|cB2mRy!82#H z+Vh}x>w|37nKNgW|A{TrzQdNW-P-9l-q4P-KGbXE6K$iio`rxfAt&!lwW+WanyBm; z-orwMaquLct8eMg8J;WahxaHuw2h%V=s62f)(4*Z^Q&(SLqvFfM0F0z&q@%>y;9`I zca$IeTv_jbUOMuYwn%wh75-j@9)C78a($*XrtQ$l9WWikUFZAYcjfn{7fpYG9p;y4 z&lr}517I(!V=b(kt$@#yOV~bkJ#3!tWe>5(a8K$r_~rW)vwb9NrL2lmsa6`5g~~Gc zx400|4X;&hF@- RCrd#GwDjWh%c0)ObeVG{9jsb5ce}_4;@4 z8`F%J*GSKJX^eQ3pGC&SMdJ0M{L}{Im(%i-rA(YUbs}EFH_I<B>J9n0uXoe+N=ej#ycL-NadS*+3^zl`CC zjziDtCr1ac%u#F@!xtTepKiS(ItopZDEw^DYvKoJk9;Fj)qdW#)V|}q7V)N~<{js? zalD*5{J$6kcmFT&!DK2O`5&!G`AB^bdl~&)`oHrTHdFGl@;A<%|LOQ>Om7@Fjo*#q ztTDY2KHhiZd|<>m-dr+X`tkA_(~pPK2shGig#SDEjddI^y%E1L&3Ji@>BmcB#H0Ky zK0Q6&oR;>Q{M5SSmy`07#mUjoKPdZ0RR0kBrc670XjEn$$%PV!KgT&HE16X%gq=Sc zDSP$br$omx`co&wUk820_0L+HWwA=F{4$0kIu1RnpD{XsrO2#$^~dlG)_&ZyYy`-;L!O(;MOAeK*o?#5vwvGG6-e@*2~Rhtmi*(r<+SJNS)t z951~QzcI~td5!7EOJl^N{4CSq$i(X%`KdL_FL%gKHc^g-{z2J4qWXu}S7qAa-J>$= zNOnsc{v79+tYlW55Vn3aQuge>Pl=9Y^rudUzYhA8W|@zj$#ZybQHt?k|_La&}-rcXg~f&rm9Zfw$#4kyc)h~y1Hb%oI3o!7zB6! zFYtl%yaT2K&hx}wOuBxqBU0uDprgl}eGAZzYCs6T2j|LP+m71z z>+S2e&Qgz6zan z-0@WKsdV!&@uc*P*TI;cpKs&57C61gLFZ|Nn=-ZH&!3TgQCYRPmzj-wnesk+ zsx3)T-N;${(MKQMdh4w?{#&jc(~fO_@4fd>7Q^ef`j(QW(@vaM-}-90`qpu_lb&xj z9cLc~KW87}EOjS;t{9%nx^WC{OPokm_ZiY@j}RXiyo_k>7H%6RY_6Gk&EByW-%G8A%F-_^xpT|sL zL+l1^rPB58yXVTEtx|sW!maEEJO-_!^JhmRKU9M~!i;`A@*2MHLP{0)@8!I!;)(~| z=gNr7#yc(mDR;c@W5wD&-}|iD=i5DeLb=K38y->?;l=}aL^xmze-LA?>_v1A>dsJk zW_Hx4?PI5QBXcpzI_vxDb&*jqJtfNy$`_XrLq%Iyln>&#Z#PPQU)!jr`-U@7tSAq6 zECkI`eqr^;|AErs#q?qaeKRR7>PKmD#z|$!`mqeMq6@_>HTXCMoysolY2W+WKJ95# zb1NZ$UBXSnCwx9-(O0kQbu>NBj!HZsCp%!{kDSp?RbV-)HL$I|-Gt$BpK^A12w6FA zOivlg=lR5~5xnC*)l(=>YXEDsr*USR=~L5Ds?0|`~)z_avUXGeRMWR3l}V5Jlw<4Gek;J!iKMwOJ(U z!aC4j0(2oC{bL=JvIMU!K)p!FFk{iO97B2`??{i7JO}S0T;>D33P=ucd#<2^XgKD>9YRrlP#TVn%0?Qn17#vo?k?bwcNs*#;(cinxFw%M26USE z-6_;K+Q&-JUvW)g7(QcpI}k3`!R1BR;emR{eTRlCV?H*?hlmcKiOMskA$bB$q|t!>Nq!Lv-Vd-$aE`=TpRahk=q=ClMLRJRT&6M)*@#QMEBm;1 zT}{opTHo55nzcShb75h#gTF6Ljf+dgYrX#=64vQYDb24$0^E`V?R3vl9m3v1mkDdO z*Bly-TUD&gM%#f^``gUW4Yz!cV1C&)l~&oFy%+lWI-SOBkhA5Z)DeBciqGl zH!oUr^9sVP%6oFBV8@j#?!D7|LVSP~7I*2Gs)WRw;gT!H35CG4QQIK(iYF(oY;RvN z>B-uS2U}VWZmiWFuAjJ|eRzHQf=RR5+i7ow%@(#?Y3wGu)oO2AJ1aeX*4hHAUAu`5 z*{#}dOVZLxQd9Xm?QiQ)*V|FosjzU%Hn}mw)xlyfMyDmt5~DhEGSX#u%}^DqF+pv- z@m4KbZ%^0FwcBo9vgGD%wKsR&a=lgijrJSs^-IL0lgzyqrbU}!XA&E` zxNzc7dUkA1)HMm&$w}Gy1={KJe2Km}5iWOPaZ6M|Sbpl;lP5$>PONJS&y9K;@NRtH};YIJwx0~&xkX}S(MSN;9e9`>Piwz@#;3nzg-DgQ?MG_IZAGQgU{} zHBmXS+37^4qETVFghw#fcN*afKx|DhjjwRU`jY zd-=U}x>H{bpXDJZ52o5eGr3(3dFA4&EcA0>rIgA2^(l(so&YJl^g9D;rqvBqgf}jX zecbr1!P3&fZ#C+lGuE^f7PhUKAwSpaR`*}&U)q+4ZvDVOJ>pr}4T*SS_A`-^QuEhy3Dw2L)d@)$mvs94 z{=u;Nh1W}SI0lS3OISE=?Q#>994oHx=8*NHhrAMsaIlOLoM@ zjS<-`g)JpBd^1Y$X#hkP6*U(WG#3@kgCWQRNY?5&S2D`yXw8n$Rv~Tiyn=#x#YN4y zTRd py#kacH0NAQ{U^G7xy8N(B`PKFQ>Q3kZUDg>w;M)>Oup$L7bmo1F;>&SrO9 ze(b#6LuF+{yEj!;)~%_kT2oh96(1TpdFrfz^xlhKm-*l^jQVDhw(-ct?DI+wO*(9K=`w^Fd)ZCa(UIB^fvedmhk|3Yv5-g|UXK z3g_C~U5L-*hJEl=SNZxa6_%`Wi={lvQgLPNnhD$qwO(MW#A; z`DiaHj@KMwB9vNQHyTPYmJS0bw9r+fC@>O$NEYXyLEGprzdQ@_@&@EN47#8My$xW6 z!ws28MsL{8bm$S87<*71Lrx{qde&F@5I&P5Z9S092D@9J^wAWxC)2-eaS8 zR%yfme;V+B;a;yvbt|Vq1NYUBWPGjF z(Pa0zl+&-2udFPYo0mNOz=7$>@UvIBlC((IVBT7c@h2WpAwo_LK)zt`JKCZL4)Ikp z_h&-N&TuhvQA@M;e$(7i(&;JgPEPJF_H>rCH2>y1*I)0MkvQ93(q*0;zqrIbJF#l& z+dT*BN*5Fr<>nR@Ehw!!(DOE%cK`h~y$xxlvu4koJ9}1XT0<|d=XX)hIjARma-3WV zNz*F^YeAhZGB$xB8Hv5DK6@^)D%HKP1UB~*B3zv={EwIb8+}lgT9r7dbAM92bAhY4 zxhQ06W~{5pW@~cAW=;($YA$vyaKx{Kku6g+>3D;eh zkdi;oRke1;s;X0`syb}NmCoW~XJxSstU#N88@y;lzl#SiGQkU-0r4=t5giW=1LNn$ zrlNM2V^LD#B8RKJsHyRxNv|#5TLZUjg*iEeh3%f2y^CL?H7KkRv7RKmH}2Jh#igrmLUcm##xP)p zgc6(+;4lK>m3eD!iG}xZyRAh#z5z*dkYehyz1=h3C5%7aV^}c)LA8HjWC#2ybc!02wR~J`*0~?zk@Duz z?(U;q3umk0AyX$!4fBOfjf{$(JSE&0K1C_6YS=M*_KpT?CjUJx+Mi-VX#E21e@8>3 zCr4T=Ej886G2xLDQeBB@#gUPY#57kr;wz+7gwC0nm;wOBoeS> zr^T2yDl(?Is%9%sKI#$BkW4K$6)7&rl+@*Ai#{m^5bVelm$f>_V#}}13=8!=h{nrG zN%2|`my+_Sdt+93OdIc@G+0#w$Nbw(b>v-0&R^YEL3|sPz3wwBRWzx z&=e?vy+}SdAv1w;^h&dxU{}$&J{j)a(Yr+ zCXRHjoi?M$HP4*?+|HPZ*5o8KBk*A@t=tcMNrGEv$%^0LbpTVl^gICLVK1B!xRuPB z`S`0@B0rWaQ69K7ZU_H&$%g3%jo)`n$9^`%lsfXMdKJ!Ss0_asD!&&QI87?VcrMS< zxYGdgC^O3L5a+9U61VGS3~}wA>Na@F@;GM9n>WMZ4J(Xjt6{(KXyqQ|G#I z;S0=kFMNUd&y`5eN_vUQ3+PXIMY|O~p=j<>Ocy|?-+}E`qA3ew$7-Bj=_je-Sl|k; z`ZHKqI2y+rLlKwwIq~xi)wNB1n@ftTn(I39NwdrX0EN@YD zM~gW%V|G+}6gyH{^3TdG70zmu8G`c=Q^@(SM~uYGl`YmMXKf~Zrl9c#lVk+Ux}Zsc zI?80=UQAquTiuOWd4bo<-?h6CD1=;&vlUgU%4;2(y< zhlpTfFiZV*e^XOmUsIE|lE<3^50;I5sO~}=XJR)Ka`GaKpR}@NR}>eQl@%9XF?V9fZzoM+PfRZIlov%rJ{%VIDEU;X z#+>#mw2u{MJB8q0nCTjB4-QVM8vxIt-@P!f%?D0Yq4~&gTf-Gq z>pgP@cBZKZ4zP$1K6vb&?|lz>n90Z9FSy@SNGHEchEl zS$MzB5C63vZWVBP4$lOHw+eWJ{OOr3Ro>J9UOZ>HJbwzun zv4=K=E<+u|v0IaCO9(ir@uRazGbZ&^I7-Hk4}Zz-{sok}1==0(OYj1-Xs@!T@qRCR zE<0PZXTvx*`=J~bhOa=$>xI#UV(!4t6yHmbyu!Nd0azaLaSPtxsRS`_*;tJ31Yn3g zqxC(p{j$rpf9`mqZ`Ga82$+HulI}}hMxf*S;S4`I z6?n-KeTYrldXcZ<&}G}TKRf=?wQ~8AzkKlc%6{d6B@4Z6v6*|i2g!-R-0IqTaw2eT zeIw^R(VT*ExK3VB3~z=wuGj(NY&$Ho(XKoV#jqe(a`3@h%4P=gs?B1}MR_rPLf<&V z_BeRvBRNea6^CPj?OTJg`+W|m0MOhhA(xL6jibpwM8~7 z`@MFh{mkvRpP6=fH~WqjcTu*|uN{!|lybh`&f9_LE9EgZ@Ei0kC11dLI`twY$0XpV z2o8Ch4hDY-dSoj_U?|3PjzI+Bz{D95-dVZ!W;qMH)^RNh!&jDBp}lvt<7(}_3f9jS zX}^Y-A$pxaKbfc9M=y?B#M*gO=rj?FfE?e6JtO&L4>%3L8cHs6{kyFW9%`qu+qtf% zu5RJ-p^o`j|POR`~ryH;6;0dCze=`tmsZdY$W`@)9=`p<1_ zw*gM=&f(xI;IfRJD1%$r@-hOf8*r>pR8fyN)KrXjaTpP-n1K-MA_hw_;lG6@L*a17i_I&!xe0|b#~NLw zR4h1#;5f$?$d96iE4#a|Y*4->KG~9vi|Uo7OO^VII_MjmT;W^&%5sH0r|uZwh~0PqZ7~xKcFsSehKMOs;oiuc^9Zx8tR%HJJ`pk({>o z@*n>2tR)SR-_aI`@%0Mw^RojkOE|xw(++nAB=#N$hJ*(pv0KPX#2MUd_##z7-JOyZf3Z4Rhhh*!oz!R8HFk)h~rwPD->7S2( z8iZ=UeGD!!pS%iODn?;`YR}^v!HJrVE*eh9u-C^ZrQG9i4FCOCG(o-Z_!Ui1ze4rq zpeu6{sj6a@0(tOH^;HP@A@aw4VL|=YkKQCB*|?O&+Ai6VUwnwlc!xFgJ@ioDDnqXh zqMzEF^EAYZa;Ay9Y5_@++@KCjm7yxoSobT3K(5s|lvH4+SyuZK%k`uFc3`%pKF4EE z^L{PHaT+tw&&7^`qKMKm4m}JrI_>=Bs!v~wDu-S1JGz>-&I*NdfpzMvZOw}=jCCa+ zTHkX`TiZ1~e`X)Ibk4bap?&e5#s!UotCoCkHG-pZS=QsN@_pPA+caJ2NV+dMb&x*< z`0yg-)Y?Dy9B6Gl(6jDPiYxZQMa|o0soFYrICR$5rbXw+Blgjf+t#f9-jY>=C}z)M z`@+lTbixg(91~4g+oIpzMZZTrMMnzdb0dBuCc!D(45M_2fyF-K>F(zAkqrKew=eWG z;JR2o@l`H&A4OzETmJbt`(y;Y{L)>bmy^df?Zf1>J>TLz9bUUnzy&}ceLx1eTUk6Zd<+jww@)o zuGCJXwH@f`InXBFAv?Ixr?^pvsY2VtXBSJRRkg%W`(-%kSTz&77{%>46&v$-*&jNW zuql80+o@Bpo`-cjoJHWGSljQlL#*zWmIW9OY?!Ow1HN>O!4$nIz!R}2;UhlZ{}{NF zzpk|Dyz1%=4G9Sqi%T~2&RCn{;B0z7Io-Rgu6D4@UeM-h?axZr9z+?jpm_$$;66Z5 z1}#xBDq^O>7_ghxXP1{6Wqz zH-jGe8@vxWc{kAmKlbEgTT}$WHvjJZ?8GU@>AQgH8g`zx!%p7Xg-@U9m>Yf;po8&P zsVWY^K*%Jl(ClB#_@A@oUw+*0nD^b6$v26{4BN*WrglriwUb=q#(g*Q0dfSOno3ZsQ+<9(pVjs1{W>_<0Rig_R-LM+9Zq^1I4dX1F|G)OW13s(j{QsPL zFA0R1!BjB}83}LpB4H#X1Og!m1R=6yg$*LI#EqlYI;kR#s;EV4t*ch7b)ZG9dn;=3 zXQ_3ff+CLAy#Md>oO|AT-~EyVYWwfKpR=BG&U2pgoagLw@3AG$9y)_J;Dx)5LjNhu z=UA4|TESo4D-{RXqCeLg_$uuyBf)qNJwiG-G!JL43r5O<{M?kz_$Pk69$<4>=hI+_ z*rk^;i`JjNxPRK1C8HZM+a5D-SjD0eTs6J&Gf?b+!=GT(nPH!C9# z{HHYTKpOhADYXav8swy=cRkpkZOSJ8a2UG7$(j%T#N*E>BxH76V}Tm)(to1VJgq?< z=i%tfuSR_Evxj$yQO)_#XSS(id7f54(=?i;b(KF%@-z+bWY*y?Gfx)E(M?~a?by6| zN8g{z{!MeU<*$E0lAwj|UZ%U9;_%NF#|_%Z4cg}5G>!wOdyW+R0fK{Ol)dpeXt7rS z3kT+Q=ilw&2XWo|4dk&`e7}FZTfB3oXXei0ci%03VX|lP3&leBl4CRM!Qr<@SEhipXH%s)ss$2o5a%DJp2e@6X{{WBdKOLbS z=K9#v;Ff)Gb3fd+qg`(=A(>fky_My8=N%;8^r#$# zT;AQZ9oLgg)O3I4j)%oMdtnCLDfM`ob|Bun?>5CBIAF_&y2qLB00KYgUumf=?QKEy}xf@m&Yx)=$2O`~j2N+EE$c5lW>l=3FkxMV z@zP7$_Sh$KhfzGQtZZJfe6*?5wi2D4ke_#$&R02qSl4pjErT0+;vm*M7abX zQ#x(>Vo&Y3S;d|`S#yrR;x<$~_)smMvdu%HFXV$=T)X zz9)|tOK9el*r&ns1KuCc*HI8HUd8b)F=?TU#}F+4zF5?zsCbh+KhAc=R0%?^yh|k2 zeV<%0ja=tJV~OW}&;53+_|2QqZc)3p8{zHw7Nv%EHlXQk@LV%}{`~1rd9EH^T|HWU z|5*LF@snhi4~G>Nl>i51#uxz{_FM}%FwP3b^2h6+@k8@cnjaqS3k=+S4O;AZmLE0L z21s8Y;JK10dR)E&iJ!4)%s7Xv?@?iF(m)&Pra1{XQ+&HgeVn@Kqp?4xZj2gtCV$11 zb%Bk)u6p#QO{1%I)n$s`C_&B@?@Vw*&#RbP>K`=!cwXgD4DPG^H$CZ*SJ|w5{`u!? z$BeC&X48+-e0ykKUJ*n9aytZVFWu`MCthHgpgv&_Ty`Ic7MRCR_TC8n_|rAD8jw45 zVa2#5-E$Lbi>l|w@7r%?*5wVI+&;JSNfRm-{;sxmeR<8;+@j?TX(MLlm1LFIquGV5 zQkw4~4PG-Pl+nw%NZge)jmG6BBd0FRiX< zEXo^ISD9OM@~pHImKElWttnqeU*k}|aIjNs;rr7*hAmFo_sn^}f$JXz^<@fvkeX}z zUN{y06DK@qjRV0SqFvE2uEJgezL+41-pLuzuHzNCJ<0pp@~zyo`pr6kaQo<%)q;5@6r^Yggj z^G2$nQ2D|V<7XcEDt$6oSTH#g10poZqZgH^YtTlmss84ep^asu<`1P?&>DwQx>y~S zJ0VM*^>+binfmp`9Z7t=0l$JZ;iThGa;N9~`1s!x-Z^%{@+J3q&bwo3{WH(J*kQu3 zan(&f{B>C&uu)mYd1uQ5;E%oYNHx-rKla~f?U|N~O-D`5J#pc}89jSslnmT zITvsI{a?~1Z)sey#%34Nq5k4!(9Lr#bypWgJy^M?V?SrknM0$jr=RwGlJ;TR!)a86 zh=VrDXoEQRof*3Cr2!KUPgMWAy~5+sJ@D&0Hd7yL$E{#K@wcalOZ^{5rO_7mfw+!d z_kw5^(GMb?-CyZ{TdKJf5=_jw=kBa$sjD3>|J)S+!V9jjJnte2u;*Pw9}=S&Ph57j zXY-|YtfSJlZ>Rr6cS76SLfygv4Zhnkj_!PfI}i1FAGIOUV7D40 zp^n*Oq0KxNGHJX?V+3cuM5oF0@=U;dN&U^0=1aYlu(Lk0E;qL>+mb7-rl%+71;h>GngX=qw7vyFhFR1bM-@ym_EQ(%VZjit5G`&Q{ z6i>aYlA{FllN}M(-kyl9UjdmefYP_rXhp)rl04JVeeqqjJR&yKt zPb5fp*ka^`I@MdBEY@7ob+juQNYv3|amNNNDGyK+mQ4Kr6;Ro&d_gfTM;ul@Yx-4J zSCtmz=geGCykLEPUE{Rsq}q8IorcaYE1El@d+!k$`D4>EQ~M1$x>xp@Q_35Q(i`h5 zPa4p!_vy9y6LHQQtIXn8#@ncK!cYrc_LAC`HXdt4M#c!sV;x^nVO&*_S6rM|L6_-x zXPIrp&E}TiIKOJ+X;YS&ISO&6j;S;!>p0+t-yR*@m}45J@SO5y<`22C|AqhVqV)oQ z(x6?6d9MABV_GGZsGbtv5{||_qd#6Rv%UBT5o@6M)A7U~?6qks6pem!YoeL%pN|v2v2CPf-P21Axo-zj3UU5dF5AC||(GrkPC}jCjK)tuo2JTR@oQ?C#ea={ z6?kb~lkPX?eH*y#bt<^ko|3)Sf^=~v1^zu0f8hTh^9xYpex&QHrPEcI*!1AjwHeBe zOCaWCtUD`n%Ox3?Y{@)p9oD!C+nllXl~;aoRovr`$6fV{S6*3rMjOUg0(`GwZIn;K z&PW%k->01%wAi_R2c{*Bl=5kgVNCP8yFBZJ4OLYePRP;Q zmmRNV)0$&EzDLf5$!(o67A1C!KL06j2&1>UzGEP!OuBoZGlzw_aTxzQ=bw9{-hbAq zf1iYe6&(uB9yPbHaPFv**+*Lm@k82guN^X^HZPBLIeM69Y;EtflTR;SUt71XymVNP zlA{ZorWZAiDyJjr<)a!g8a8;&Dse}3cbpEg?mck`@7(Fde%}@_ezu*=H6O)KCq_Vp zy(yDFC1-jGPC)i4>^p8|c0tT{~o8`N!GX1%kIe|3>;}OX)kec zmmWP+EGspwPtux<>dZD7ZTc0C9^jGlGdx|=x)$X&{l4ngHR;%Dq5A1xcYVsq)x+=zPIocgaS!i;+LBR=rVp+>I`@RC@v4()JT(s|ou*YM z*PxdvUQj;uXMK(>&Kx?SLzncU^N#B~82!wK>7y48P0yM-bnMar?T*<{Usz8xN$&Gr zfgEt%>aFgwbIQIS&k4}E@gXMn5v{(tLkqItnwydo#9z>|@kUX+tnn4VKflLSt?QQn9C*6!bv?5>sfP6nu3 zdkScSr|X>R>imk*A!)e^Z%#WKz28a^^E8rA^H! zD42x?h}v!%AAF1QYmXTr(>#Pu<@^5ENNdhGtwAFwkS)6|UAJy2RORf66K9)pQPZ`v zCrz4dG;Qhh^u6~!P0Hwd%PpH8z3sLu!3+ByZhhhiYho$P^Zbb(b;)tl)pWZt=a0sL zUqC*2t)0fNi8xutp*u_2Zy=;|is!-1k^R~wWDY$KcR=kMNdv2I9?a~YHuSuvFTNRp zMuhl(jQ2V8B4^CG6?lEqg#>VqxXc+dK8?(IzfEC*{1~ z1@-z#lko}AM(hH%>d>Q_6FOdEaT0U^eUh{}<=kO#GSiifX4Cf-l& z+-+iU?zpbB@P|8z2T#bCSxp-!r%g#O$Qa!8De!gy^8Z7gHLbyQvY)R;_Dx)qJ}w(I zvVTFvaYIy%>{giDbbsZoTgwX0{rN^*h(NRs@;2gk>~H85a-MUmx=0sx*@HcwbQmq!!3+gj#a^n-~3p2(frl*e?QBs+H!kAH` zJGg+2`2>cXFw*MJC*0 zl%70p+{#kph3|~~nZt+AEWj6PWab-GXT4eY&;0j#Wv)=SFTL?^dR+U9lb~F=GGwMS z`@OcLdusRg?T;DICuQbM*$Iy zbL#4xvkME)%DiY(++&Z${ql_c`NXd(Qxk4Ed|_Q~EPjD^rmp=9)uGKAk( zLiSx_MdPz3RE;VrA5va2s%k>k_!FlV*LCbzS3LDZdGYeoE}XY^$;=Bbn7L%_ybDiT ze&wl`j2=34^d+Za99`r6QG9QFgs~kB7~-tGi(z2Q_YuQsUe9p(fE#X}qv6))3XZ)h z$Pwdt8wld9z4rQmf)gHw%Lm+W#-qZ2Zo}1lnCN-U#?y`HQ9NP%yKVUS45xL5u?&CG zhWF8M`3l3IvEe4eRXX z@0rGAvC%vSe)PvDjvME6?fr6G_WaDcw8=esBqc%JV4LPSTQBaJnAp>;IFIibm2xW5 zL@5Yb110`VmUoD|3`^a)D(`1lnzw;r12D@(nc?iQkoP0()y8Cb9q5X;K9@c1_!y4R zdhIqTkKxHFQ*Jowy2owB3k`Q)8c>(|tF zzrCn-VPfE=ZtAYT-c!d0U+Kp8{9TXyNiiMwHFq?gQ0oEj(H-i+=y^8aqd-p2oiRLp z{QM3%3FAu(r(|VKDJ-jQm)&9Rg!JJvWJ*=l>alGbrdX59vWFKhEXSaFcvk6Tt6^H( zv6S9$+-Eir_nFx@eWKs@eWE~3m+C7xr_x@YL7Ss*nWQbIG2>UKgplF?l{c0RtUa8f32yTSS<2)hDf75%>jTJP2OR2_r4HG1(i^XHEn+pTME?w8z+ zmsZ!M4J#<+4Lzs4c$|;&3dcj5sROs~#H38qHvo2>KVsy9;lme>ES_1YuLu0hx%{uB zeDo#DmRwpn?36m!?SLphbw6A__?e8F#qB87X{6!XQ08?3eI=ahMz}Z9U#~2wsb04> z{f&%!XDoT@-W>}UFPm>Xux4%hn}$wGNj#x!>O5=33ghQZU)78}r|IdYcPCVq(*#{$ zyhwK|ORF91H$W-76n)`Rb_2UmJ8Zj&@8Msx?UpM{8_`SBy)c9)4)eATY*-@txQ;*KrZS#}X7{?wBQdy?z{drw&ad!a0XeW)A?d$BBm zeV8nTy-b$j%dk;$672P|9`?y{HtcicT!fh?SHXUYTn+mgxd!&NavkjJ@h2rIuaKKy z|E2sT>{rUGVc#PE0Q&=SJM0h22VwuCdoeTRsV-f6&(aK13h*>C1v&b9{ z`v|iP_EF}1*c;78*cX@!VP9k-yt&xgin@KXb&D{qTP@5ptbX5S_})CZ~$Il~pyVVo_E7gjBI&Jn5^eN#8J` zwmMauT~m!TFQ}=VlqxQ+A^4`66A*sOq*~Z-s2^XSDsHVO_#O3AMyJ|m_k-zj)7TQb zv9xj33ejo#oK;Kd*v-*)}_{E)|J*())wnJ>qhG))R)_>-&l88cUr%*?zZl+?z0}S zwp)L)p0u8^p0i%CUbEh|cH@sYA`ztrWhCN0cqj|14OARdJXG7nn?kB~fG33X+vdYX zaoZ=}88Ut-yqX5LsqOCY9TM*o?QFc{PktR;>0HxgPnVCOzJNLarQaXA>`4^1i~K^o ziE#+iA(W4TB_>7i>w&nv{rwaB+wL&wk#Ay}e;n;f%z|Hmb~&cTnK%soqZ~Nde12p7 z;ci!AjoamKPn_(y6Q?uvj)wi!UbJEo53hG~1^ zdD`Z0CSHUzF87CNSK?;)U9VmKX5!88yIsK&@3PJNU11`X-$Sl&;FJ4x^V1C{d*Y5j z2w+b^JrDJ2upgkipx%S}K=~$q>K`g`pTAqX5)UHIzqG5%o+QJulP#&O+nvKh$d*5*qE zr#cW4${kp?$&;K=$qkNO*>szgJS)g2eEs_vVZVSn;EMADBvUANVh`ARyZpdqUpqex z_ECX;(b|cm6cr;>od&*0=`FKqT7_`yZTp$FY5OUcqep97b-U}t(6&f)Myj{kkMQU^ z&Gu;Z2z5-qNl$27u$esHAC|ntaR8tKue~?%-g*jgC9{LdT96mm*hl z-4Jb0z7oFIY8OJe+7|^)zRB_Rw}qGMP!8e7gHgw=VcL*LWABJ#w~J7Im%H-oIC>P@kG4DEKCf$f==mp~beI#%54VD%dp zTmU*RcKHRhJ&s@CxHZ->LrR>3BhVIDAKdB7Dt`v-_!o4ZI9UrV@3GZIg)oL7ew_!KvWIX=&NxJ-w z^yECOt1hFEvW7w$%Sgxn$!X=SmO{#P?7NQPi&&@K7m0snLigUrVa_GHID`v7RXZ3Ok=9m1i*ILe^;yT4J9Q>3W}L%<~yu!I&%9KA!F4*}jVH ztJuCnK@6<$%D=N6>r%k8oblvvNPIa4-&@%puk5VH6MrPmv_kL2zWBh8>_$D? zu}25^RMr!js~5BVC~q#{M=@jtW55nGAe~6ZjsWRzGlqK^PXA~IpUG}WjcCw||&yXc-Uru(MjRV}gk96Zo))}Yq2eQliI1J~}_>Li;5JWCu zdy>)_XA|4|ux|?4>EFH3uTpk~_vbME**<}(`cml(&t|=pb6v`I`lA&4e$Wq2#T$#}$g%w89Mk5-1U7Tlj#gR@-g!Nph+9)~sITC0xN zg=g@}@D{B6-fC?Nwem}+#ARHosp-hkqhcr4$jN*e@0;SU_#^Mf;tT9={UClo4&!78 z+Rws3D@^b7Wq{%cu-vnmhM@R~t#dPgaG$DK%%8%Pnw36IDvQGa+ zkn$|j1^pXJ;wv7~Co+V-EI@n5y0Tx$cKVaHltWn`&pQ5S0Z0w&e`kFh>$$AsFVn!k z9Oon?zMP|-lYIm+o@IR#hoQguLv~p&Wu5;0p<9Zp7*fGH{nJOv6Iib#-Dt~p{Ou8N z{4oMGqNf-pDzOu~1bX26Aa*|<1lRw9-3@wQ$NLUS&k)~w_sj9#{YdqD??Ie&*$F(K zh@QqTptgvfW;|3|sCIzkv;%5z98@Q$9oRp559Z&XK7)NP)aOuNKz#|d4{ATuS5RL= z9S|udS_X3o)N;g;))McF!m!@+?zHxKcfq~SyN7k$Qey4H-nwZ$;N4?w_wKfKdUxZT z?+)+Z#Ai@@p+1ND0_sbseNg+MzJmH1>Hw4ky|-b<=Q-I?R^emtv*Z)_(qo`82;WEl z&MLM_teL1ev`6x|wbOdmdez!zeJhM>4iGP|-?VML_@$tB=6{cm1Kx-59`x???(#k) z61;o7*L$z_J_db1&cwY2{SVNu_uhjrdr4u3T!p&c`y$}`$Z; z4z7Kg9B!8d>RSS}*@y3;+8v3)!R#WwPe)Jd4KEu2>RoiPIuc!Qi{%ALl4=CM@u`XO1(@)H# z^}d)Mdo*E(ca1G{FRRTJI6E7yAE}Hf$(RCxk^~CqqR7v#kSJf2nd*6aGn6UT<0Rd> zMZ)6?Ls$@7xly{@HloGo2N=mmKaAU`wcLzT->)hPf^|EkI?Q$svSRrP4i_tgCEV9; zVkEhfOY@-jF`}Gk*Ys;sC8^p@_&!DyDw-`LgQHomT3y7B zxb`CxG2M8mt;4RiJH(;dTNe9JQwtS7R9g`$+~I{m257a4#=z8OCfM^rjCQp&c ztA!K~E#R49+D_GCVx`QkEf^8IQ$p?bYfBMsw|@wC=xBD(Mzzu29bg+#NBpo$E>t~^ zhP4^~VdUZvF*OTIq`re|4b_~Fn$P1y%tP4Vs)n{y`op4Uk7&H0K#+|PZF^+A&GC&$ z3$G+=UZ8gi=t-KB-hXNw%>_ljzw19WPZ8;ERxOW+@sKc6(s%w&S1wa&;sOdSbIEmlAPFZSaPrtJ~8 zGyOB0p2O3lyK@mZj#lY%+J0@B1J^b;76oe#wZT|4e#dMMUMF_z*nF*L>!6E>#aX9N z!I(!sfw~)!=)>fRadu$8)R|kh&Zs5)1o^i*C0_alGURh4r6ME&N1hkH)z(0I4u3hg zQw-d@4o*E7;*NiW?T5AY>h&v~r{MPTC&dd*=3z7GCLjIy_pk(25UkJIr^sCEfdOaE z0`S=FjmE4@y`H5wOfIzEJH6-9nNQds;W4t7IqU>0)3$rW~{I|d(t@L=#NLI?t~!-*7FGeF7G4I57PP}I6-TC zbpCY@^1!B!8*KVXX%49Uh*XGhz7^bHzeo_uxxEG)z~4x*TF(zW2pa8%!X7=8_tQ}E z{ySlQQgIlv7&zKG3`s;m8J+_Fyt`{|6qNt5-v9Oy)lZ0X{-ytKAdGLVh?;K9--SfQ zod!`B znrprgz!B-uwKWvybv(#`g9WRgkWFeW=-q|)Ag?umPSARYy_hcr`YVz932ZMCZ2kvI zf*M)6WBxbVnHz_z7DQ&4`eq_>7~40+KJREtEa;^#nRnygQ+rrK0?RH6QfeJ2&#tyN z1^r0CJSmdgsstddRF4iZbvJPVvg!5;X$RfZb5g$Xpq5}I>QHfnM9kQf4{^sojN+Dm zPC*0J%NFF_KaE3od7q%xN>iYEHQmA;IyQ>IrO9oi?^~98G)B#n(5rf?=WSl9tq}{k zr}2edZowtkk~DNJiC!Nl6{H$rYnOAQQRqVL;gw->s<+k^jX;eT7`Q?UY% zk!ZlF(-27fK&WR2c?9e0jp4$%aK@$EBwpL+QgBAVz=AY=sj1%FS4uv(lbDuf2NK%m z;0*1rp-eSN1=>pmL-}A&0Qw$oNqqk%(=no%RL_u)7x7|zKo81A?Fl^N%Ng?mUMbFB z2sk8qbu#HeY2fs}j-g=wdL%#pkda)j6ghR!P3_iRq)+=HAx4Ffu!ZyUwGu)6bkcX? zg7+j*0j;d#-+Y_e7l|#tqKqh^@I(#9;xzgS^Dm6ZSQQ@R%KwzS8ejNw8Y{nJtp}lK ziiLiXUnef4;UHQFl7|;jUr8=$bl?i%gl(QpbJ>ZF@eA|WO)b~TL+Vj=+w9JB^qkaoLUOg# zdf#m>M`DQw3e?|8TCpYuGgX&ztrt zT*$D0eGWiMI2=5Y!v;k4IVqM4hwD6$3S!G*03HASe$|%&m!)|ea3T0Fv7!ZWzF+jU zWrv&OT}2NX!1>KPbPn z8rW)25cXzbZY@0Fl2@%}aNF6M>9!`WaQSErxXp`zwiC4}2-5P@!kxL`-;v@4`Z@Jf^FW6PbZhwGF)^QLK`YdfxRLS`1Kt+OqC53iq!07PsOtt8 zgB+3hw43(Uw#UGE*F2%MQ;!{=;&d$3#*XtpCS)the$06e*v}3VFV=Ivt(OC5Jnkl` zIp==e@mr5RH`dVh)=R7PFomWx1k_nG2;pgi)TylS{>n%1VI7`29eA288Z|BPkKR*6 zz>Yuv+r3DvbtwX}kY1r{Ovs@B9l+pT`S8eCIBo@}A1-8gIIV6tcn$~s;c!A8TQEWl z%#p4ek=l(|&t~{=tCPXlJK)_``lsJcv^n&zuAp^HUmUVTXfIIG8? zUieyU{?1jMpSDl0IBTd1H%lr`u7e(;iSD-aod*h+`hOHWw((-jSyKPwi#d3dfM`ik z_j&(0;yp}J(8TK9@*X!rOBE{-v4lI^?FDG4jD?PWPZ;W7^=a5cp&-)EL--alaPOo! zymv+fL@)ZyR$PCudqDgr7A+LrtGf%W72Q=8_HQ52covCI&KF?OPkd?$(BIR^L+<0F z$BIT>bbmV~drT3&WTRt>5~AhpMU(`Z?@fFEt`VWrE;+W4$n_cMU{Adk9PERXe5VeS zufGI^t1*GG+?Wq}Z0U|1U`LNlb%DS6z=>cwu|qG7FCw0L3>*tMXw~?F$NGNfe(exG zxu9^W{m?tIfjEf=7<=xa_6+o>@rCbu9EC5|b2$!u;ca|!hfb-`JY_3V+(l>lTf__I z07*wkZq{j9m>0bFt0gIj7j(`H|EXo!r8AlIMjQcZ+|4tY2hsMUL!hQHVd#h2UGfPv zqw%j32xIT9gvJyaO6Q~;pF=tK>%-*9w zdS9q}U@kA41Ho-a#MJzfj^+qozL8__S~-0i5Twi5Xij1ANe6AtvIp`Ppd1pAp=G8r%0h{ zmJeA=_Kk`EF_nSSD)`bpL<{B9Kd-^La``vo{&C&*9wK3OaFz@8)#VXsJzRMWNpLiO ziWJPjkf;t3s9Or)-*<>n4^!~yyr9+Du!;9}W`beEgz*3`sHLD0DEhuAbcFu`T(3}s z?mg%GCGPAqQfS}$pDzs6SN=Z;)5Lb4-(j~Lq5k8r=i@&wfhe-q!VF>b9KQZU#m#Le zjmCr8_^46;O#veJ70qu~otWHiw1B6vFotC!O8>1!IVc;~Y%CCWc-g{NhxTqmV4PUo zkSh9L28}*hHe!hr97c^jT0UoT(-4Up!4$L{qG|?o8rKB3>HZ0X_t*W1e|YWkkLO$2 z;q;tdg+tpVjvW=)aE={7Ux-+Eqs9n2;X^ekJ)43kQ6tf!RwZJ?6D65{r#F2`hdLZP zPg+Q9l=h;9*y&b*=09DS7erwcDv!+E&6SZb^t8NHa;4$2LhhAZB(Tlp6Kris&#kag zE%UEwf&A5NXUpn+Fzqoy!f-#v*gvsg{+vEk;Mr1O^g*EizZPch3q66WiNTGSS|a=My|%0==d@jH34TqXV>Pn8?Qc6quyTl`U;D=!p}%8TS> zVyC=T{z^P6ZU`M!8lLOGA=ZJE*BfWFfNlzjV;En%cTw{mvXL+&lr16xz+<3uwRc8|tkaEw z)|u9sMy7SPb+(aZZM4ocvaR#2OO0IXa_bsnxOJU%y)nkR!MeeyvTm}r8sn^6tv8Hn z>n-bJ<4o&I>r3Nu>uc+4;|d{#W$lA*ureW~7GyLIuRO%s5pRblNf)9Wl!h>%l{B7T zwGysqXxgKOwnzOGsGXf$f$6R_Qi*^iiDcB&uBfNoVbaqB|90%fwyvnRUB%Jj7_3IE zr8`i;n0f$fIb|hQQqw?@-X#3udt>0To$@OLT;KQaC>; zb}m)!l7Lwtj{|MGRc=%+jzencL5PyTEs7(USBgu=f_xFC#xTZ6F^+?s$|BUGN}Ebp<-I>-fO0}u z28!XpGaPX#?*eo>c-^?17(^|(NOnTeF&J<~mlK~-!+%-8DdJu)q&N^ZO3OF?1n3se7*MWXs(p%o6rajnpiZcoNG0h19LM#iE9mXY9!f&`F=ZZ6 z2B?Az04K5`9|CC+mgB(jWV{Y16<*b95N(=51*f{ylV2)d_d^&+^5_+)v-DD4x}tp{ z)dx~RFY$H|*J9G0`bYBQJWg3rh#~yan1t%DYL9dXDxo2eYI+6Q2YTtr2Ao={Jbne= zMado^wStgP2T1*odZPC}s&%Fm^YJ9B0ivE+u&bv5J|~LVVh-L0F&Dt;wwfpA!AAOgglG_r zVu4sF7QrT&2;JgE);tlJ6?OPJ_8loG#7~XNt4L+2S0rQJgEz6X%Nypr0vzCN2~|hZ5o< z@e6UWxI|ovS;FP;y8`dU{9Xw6>4>|LsnMGK&s4MvYLrLZB6U2dTZurJD!_ec&rpmM zr(u*}Vw^A(V?}B&TmPAY(mqm88;p0`pqx&EL{XXRCk|3XYB>P$Vhv_+Yxq^>aNDKe z1RH@d??|*f?IAB6P{Q+3;&hicjU79}z6!nBTD*;TJKCzu9!DlYk`f`g{tt~T$Dr3B z-Te@zHNYDDt^#~4Q@R%EcZ5tf0@9XKTMAk23L2Nf-3@--k#-N5p`W&#ADw5FJL19` z3v3jV;;v%e5NFDOC5ic={2ZK$ z0*XcBa+PLBJG47t|8S0rVpKsX{PSU_vT*P_IGmiTyf0%Jpgd6i=~X33d7sIYD7+2^ zB{eDrQA<=1R-I=z9fU71zT#M0NRpB}mFi69`dT}gcu*6M)Vo0WKk9*P&XgjJ zpPh$dP6=oi9pC@iTFL<$8|YVHCgMDLJ~9hqKj#_G)}w5lX2!JYaUy0W&OHhYcthxnxl3R#)R<|sUo$0(x_C>73fdyI7I7U zw+N_{Hl)1|5(;muum;hoylir+SX5b6lPWe;)lWzj7mp|XhHBF9nNVAuDxRpR292-R z)J{qjAJ!23yP6YfQf0!V+6k%h==$;XsWPJ;a9LVEWpt_p_XF~&siuBGkT(Kg?=%};IffNlPu;-kCf@bSBp**3>ESK8)X9L{XJx80C-;}opjGKLxL zu5I^_4SCsyywmOn1xe^(o4r*!3H@z%j_oe8-F7|_s%`f)+kdw0USYde+wP6F`$F5j z*>+!VyYH~w_t@@7ZTC*w{i^M@^PBLY?cQtq+k9zn^QC8Alt83 zd~biL9qtB&r~MAwe9AWW!0gMC)(tjGmO;MnkcDiohkYm84am|YgzAO7{02LIe@1u}GEUr#*ATbjHN~&-O0f;EB_75bCmzGw2L2T9H}JayZyUt_ zGhjRM#sN>MFy$nqYr+2()EXL(Qp&0Z!orV|NM&!wJppY)T2 znoUn<#OefI)SeTShJv&KWo^+0NTyICG=(GDxO~`b z17DJAs@V{5wws8RgqmN3RA*y+djWP*RZQZI$+L5!oj9x@G&}Fi5K5WCI1m*^x5(7y zYYO9#qUtv^lqjU|9WgVdP(&&1p(!qrT~SDUpgQl+O!-g{9fnFM35t4jOMHW{B-aw9 z>4tv__#F(V68D9d^*TJt+6^z$;UNhI))ac9bc#6TVHg1sb*A_fJGF)ICHlxk?a4Pp zfh+`^@~HBr{8f$=m2OD9U%H@&rJgVa&|||XC!J7R`pSMXOJ*aKC67Y8J6NW{Cjn}Y z_&cD6_&>DG3i5&YhYJE4%rv;AIT-%rO82>v#3=bM<2+C=uzP$h;N4_rnIyX+PsMVM zJVmaRSIEus0r^MyrhHp|Y7Q|o%xp8)%r{4vrNCE(J$PyZsjgY#QM{Jef!D%L{1ech z6i-1vD85G+FGNyGQ-TKc9bHi8l4UaVZn7Kn{&E1|MY0GyDUl`6D`W*AbLCvv7s*Ai zua>J}Unkc=KV6;<$R@c7_N(R9(6`BLfNYoBVSh+I1pO`f7W8-IJHY>${7e`|d!xOO zMmGa(5c(gq(ME5hH}q5^75X*CHPEj!u7mz7<5$pcG;V~x)z}LC7ULG^zc$dq7`GX> zL%+j-lo@v$cSHZZ@q6fx7>_`I+;|-NbH;Pf-!a~S{+{t3^uHS@E8`>MBk125-=Qq< zK#wy~AIu~Zl4^QPk1)+lGZT7_nFBq~%oB!LU=~0hX^wKdW|&=`V4CZ^jX$y=(k#bLRlDG(w)JtB#Bf};#dh~Nj6aO#o*c; zIS2YF@)YQ6;ft42DvJMB&~+*Y=X3GhkU#Rx%dp+&IFh9z}=DH>7mPif6+N4?$2B}JBO@I zpSo(%PX`NH0&sy^Ow<$?=r>R=&^EzFl=iYsI>$g#_Y~_l`T3^;MzeDAzti@o z^zTGyZhggvHdMLL65u5q#1Ykt((Im~UKPW*+IIPeIVOfM$How*PYhxD#SrGW7{UyU zAxDWph)XUDYc?ol>(ACs z;6h1Se}+lrPEt>FP%WYoRBSA+x>2e#dY?c@|G{T_@_@Oc-h zd^t6eeyUBD^*JP%(}%CF7ya#6)V9W>H@Mxp!@ASD&)UxY0@16u@frM;s~2j7b1Fxj z$f1=S%Hh%2O{eh-a@FMB`smd4+1QvyRhrpNkGwzGaT zzjdkG0Q(wl+mzXGkA&K9tG+Oy$louX8%G2?AKSh2P3$q4e1hYA1wrTmxT^T zdDlGag*bEJhDl1>$!ra!@w!dX;H2kJjHPV5%F~Hdf zJDGrmRR}}j0+q%W(gaSz8>AqIVgukXtx-ymJ5c%61Cm|0qWs!Qpbm-YP-I>LHQ4&m z`?2jNSPsGxPpE9jB%XjS>Z8TOP!<&B4W<`$1*4rG1==6xJxqdmzd#%+n~ErYXSl-6 z4HGuo#`6qJ3ZqTJ?T-JL?XS&1e}rW@I!2i+8)zTJuRsIxNv8M(6nH{yg`oUK@MHdf z#}rP-*JdCdNqr^p(l$8`@fYn2+b^{JZ8ON9!orW>%+qV(&V?N^PW)!vln1~`alcA^ zE8=OGxEk&fxNm~G2I?%RGoT&;d^l8Zx72Y&F6<(uOF z%MK5?PLJyy`9)B~C%3=m8>P+oDLus3;R11LxElwD-(cgl&n=;^$9o z1o0=7BK{Fvmlv7)p>%n5wcXS{5FBB-jLBT7+}sAa<5PX1el0LPichqV=~iwSm5)|b z9yET?iqdc^$i1H%e&Vkf3q2nHvKVB3EdLMfXfamW8u+ciS1j3L6{}j=*Y*BJe2pEx z=?WrV#ro|?gnC{+Df@~aFmu^x3H!K)bs2bNj75x>^o#9guGf_hWCJx zh!tguhZkQnWy>bxYNG%U!f}agg5Cs^>?$uK>}{p_=e! z!8KB5vc5}>fSVM-val;jW-q*C7NBG(|HOs9i0Ljv;s?Jt!SEZh!8{EnxP`MEbIEOo z&}BejP6Q4+UVzDEgb;*7ms?=SY`kmFzx(+m zTW`(<*#ch`rUn>DcJ{-c^6P#HTFZ){gxwlEW?=7VzO`0ZclQd0}G-=a)rTYeymZh}Nma><9wIZABA|fIp zBJPai6h)>gDuWCvG7dVZh*||5WEgc6(Q$#~|2;Qpfq9+Z`@a9r|MU45pL@w2*K5#CM(6K)e z@@Ck`QDcgVd#;TjOdUW-=&HI=X_kEl3aU}}4ZPnrWmbDn{vRISNJvx&A;HV0ELxyi z^z~wq5JMaxjCA!(ofX&8@F^kM&4l#yL*+z={+8hVkf}45cFp+qyG?{RA_$G! z*4^3O5&PsF-=lr>LG0Zq@HYfC;(iV86T4?ESn}SuzkPtVlL-mGICIXF_ERD2ng~fn zf4)D?YG2YL&lkp^{*$P$n%zFDGv~uuuM;}zQM~hP&z$)S+?z=~`fujXd**fa{B-I5 z-xH!gjr+<)yuX#hDhRUxA0%-R`WrE(^bOn}!f5 zAIT(L#0PJNkOJ}zX5vEvKs!oA)wF7$+T`lx`z+G)dvZ{6&E==F7x=gt-aH z6b9QbDXb}^Xx)9C^}Drny6_QAXe0EYPk~4JGwVk^6_L}cL?*rQ6|Mj(5~By)15|Vm zuq-l-1`sn#BmOjysL4xYKCLJ7F_Su}{#DEo*g!j;v%mF+HXaJx-84Kw2=64_+1(*bg1Iz;W1EK&S0Hrry zi!=Zb184_41}Fy90V)8zO`cx|CfJs^#ixQ`)?;`ic0;$*LrK+su+dm~^mpv?=s zeziBx+j4qg2GVAF)qRL5Nj6(YYH%M(t)z(FPbvX2w3kU=CyOz+h4c$D9&{H$f00I$ zEE?_ph|Y7rPPYQDr9@3j!9(*&3hi`%LCZ-w%_P}?XnGduSrRSA0CytkVmsacWKUxZ z$4DM~59u<%c)&kMDf@vq$u;DwNHklDw3;}DsYt&=svsq7gOrxCJt*r2pKK=c*r&kH zcG4xhfcyscS7>W8RuNz3LzCA-q=sEWV=>Jt>!n|d|G7>4gg1Jm2T6zU_E(2%pkph;EIVJ!n zUz0Qrf{YZiEx=tn87BCW9LNA8Ys7oN5#}9E&beK{i5lbE!2$0-g7h&jK;F5#0j+?0 z0C(g09*MSt^x<^%(vs8h7TR%M0R71|@F1rnXywH>r$L;DHtsJuU1+iU;t(x3Z9z}a z@An)yZP54EfN#XKgTWjYKW%>c?tbXJmlpcj!%vciSPK> zc%Ph?q<(=L;GOf$AkPf)3LlHaBZK?_I&l0+eB~MEVT|oLjO{xKhH&8J@temnG*9%y z`SPZ=(Z(Xc0<>`y=~BR4_hWHU()&-Tu+1Z^2|x|pMCH-gD2w#=jg@r z1L$iT`hvc6UkAJl*b2B4WgO>cZdu!2T;D=>zCM3r-Z!t+LEH}Fm9Ig*-XuDBX)rim z^v!wi9`p?z?S@`=uSfo=-|FLLTyUP?a);w&5Lds|{vbUEpK;ylzjg#iZWzt8`XAMXR_?oyM#vD#abKrF1_~QJ) zd4SV}xHkY+0rq&|eyCiomkm-~UjP3TI3E570DdAR96=UyJpEVj!?WK2X>Id4{5Rlh zZV>n!|8Icf@qYl&N#wMWj3ircLR9+y8sIT_W{;Rv>oKFoAu`4 z+UD~2xA4tzI|;lS0DZ#c?Qr)&NhjW<7ipT?&2=Mt1U7_MCvw>fThAcjVh|^TX$5S% zE8roMdxy|QY9+n)f8;!0m~ff$O;CpectHuroQ2 zk9R*Rtw+8lyU7Hv-67c?()#?teSxRcfPVBC`)sc*!gbi79)nC9v`ZwNb_wwdegZdK-r#Ct40ZxxW zfJ}Mww20vt!ANWP&8Kvmn#5+#j)g*r%zm!MF{T zLh_}Q+ftC}68s&qy@t$}pXN3u{S>Jad_DmHKh8ZKFc#1WC<5dF(f}#|Ung?`+qkU- z8N2~tf=#MIss(^mz|X{u9^C&1Tm>IH$#wAlqo6al-9LiOdj`D8{huGe6CD`)RoH4* zLHqk*<6m_@1p392BkohEZ}LJS%KT9F67FNY5Q?(NfN1e2%ryuF-mj6eB2-2$n!GyhjcU%=t=h(Qr?>F8fH|3=^aes5!E%}@J`|stzBab#T0m=Xz z$I+PoC8RPyD`2JP zP<{i12+;Q?8$@u!ms?8AT+R;yTZQ@@&bzNl@)vTN%Ubp&%HSJHb|;sytH>x0LLK}e z?i21O((ryfL0}Vd;Jzqi^*qrR`8(ae zLS8RNJR+UFgZGD_Y|w6b4gDNLKaZg7Gg2jund>u*Lv*;m;%#xCf^m7Shi=rsr#X59 z*f{sQx7acNaDNIJ9LMS)XT3l#qCS8%xPM>=k>&#i{Z+2ZFF~GqZ4q_~@)&+9bbKM# zlfrO7D(XAYh6`yc@U{4`NJ0Dqx{-blefR@>OUOR@E6V!t z+<( z-v;*Jj_2Zt7&sal_fg!)$)hMqBUz+?j3aGi8R@0B(>v%ox{+?DJLvuN5&8msjebJ^ zNx!8(P&bQZDwf33n3XwL5i4O8tcEqP(X54yXOmbvo5H5Ed2AuOjjd*z*mkyq{gJ(Z z80M?&E%u2Z3V}kH5Glk88X-k63F$(nP$D!73x!@`qp(@nCOj-WCG-hz3U7y>3{Hyq$_@Vf@_z&@4K6lCdgYrkbu9Pdol`%@K(x7xG^OaS~ z@ybccDayIZ#mcqH4a%L$y~>|e{;DKZo~lIEqgtxEU9~~ARrRRq52|NX&#PWky{Z<} zK5AcexH?*$pw_8P>e1>}_5JE6)i0>uRi9CRnZOcc3Bd_r36TlI6I2N)35JC7g!Y6^ z4by~Z)L1Vp zlyxG?F`l%O=kx`ofU{68={g5O<*ydYi@zwuJ(gi=%nD2FLk%4DTQ=~Nae$0=Ktla(`-iy_I|2OR6VSETy;=&NY$@8GDM{)wMwGWDD@ciHc+Ym7Al2; zN-+saFO}L7IwUHo{tK1rhp4noyH9&S`>ggbsB}bo6jVB)eOr50djVAP0hNLzDmirp zx;EVuFO@buvF%slD)mwc3kp)6U>y;xCj8N16ka;gV1E5Cf6HCZ;>kmRDc%|*$y3-p zDa4(Q*vNy@{z3=oCUeP3Zw)DNG1u+xTo+5ucv?^dupl_T6|&$c3YyM|>Xm zdGP1J1Rq>Q{v0!WY67;YZ;oQ6>h6AtLaIT^r2X17euy!74A6o=MN85lJ~eycHx4 z6B9&_BG0|DS>m${`IUso%(7NbU$OCvy(oJ{b`)qJ;n+~U4G@mY`FVm^CfG(%6P=T(Z-L!=sL4Klcb{7A0TnY50srJL!S^jq>PO{TMG19a+1I-54qJLy~07jk@b2cUP?(Ld97=>YvZ>;(f`#MVJMG9g+4}~qbKO!sE*oT zm0cp2$q#G^+eDSrNL;jtj;8T66`JIC^aA|{mD504Ovg|a{OG;(Y1mh8O6lYD5PhAV zp*3_8`H5VkrF1M+(=_@J{XH$C&1^Zli^kFvx{p3XLgQ z&`{seTG~dFsGau0&bXa!qQ__@oj^aO7wLZ5$F{*}A*(KM6&!C$fcNHlN+W7PAd(8QaQMu)Enx zb}wv_?W~vG!)}LF@-#G^o4v+ZErI1vHEX!|UpV z?lp?vh~J4v#D9q|i?4{UihmN1iJytDiO0o@;+N2{1F(iZg|7WVJTLxTOcC{BgQyiN z#B#AptQKp;GI69>E2fKSVzyW$sv(s$(AY_0vS<*EVyb9@Y|0UH#e6Xja;Q)&5lh8l zu~MuP>&13)vN%QT5T}aW;tX-7I7^%@_K5Su`Qid`q4=P8^o>RUE(xxI&6bZalLq_xKZ3BZWh;y>%=YMTyYMh-5sJ%>=GNryTw*< zuQ*EFE{=x1H%8ncHi`F&V_{P?!}@CxcZqG{K5?9Qzc?ON#st`d6U9A3hG1of**CB+ zzk`MOEqjq&Vg2l1>?QU+JIa1$e`YRrQV{58^h@@pz}V{oVSg3;*=Zqwo~M`CJ3=6P zR|sMuET8>N2xe!55cZxB%03XnVFN|5kA-2dSYy~{LL56Uj9?c8CHq29u}gx6T@=)G zI-N_)={Wi)`ZoKAkjO3zTJ}#tCx}dhb?n1zu$E1XGBaZ=oe8kT2uq{CuoQZY8R&Ip zq%M|9e`R{+&zvlPWwSt*!(=RjDPXnvvP|a3vY4FNSuo3EK`fU=u~HVz%2+5XVBxU7 zBUmwugmo7N8!rYnUM#C*ajc4sfVDRqmbntPc|5CSYS?}n*2ofJ0cv5ZC$I)OgU*A6 zI0m+S6H8`eg-Jr2&`!3IyM zC(ML}9xKL)BSfVbFU%qjkUc`BFj6QNDunUE1Yx4kB8(GCg(hJvEd0?puQ5+pEc6KT z5ouUJ7Lmndi7*Edqj|9YmmqSmRG2F)6PA-6GDnOg=g3E*3Q>d!WTNnZaIbKmaKG>% z^z$BJC-n4g_zC-jtz?C;Qn*XlAZ!uO;a%Z-;alN-X#ZD*mxQCR0sbWX zSvV$~5MC2r7yc;p3oi)I3NH%J2+s+>hlg@dcuP1X{6#n^VBb=RMMTVxYcOtL+@#5F z&9Yl>|I6L~PW#1?5k!gIVik5|6NrW+!kfNTBR)*_V~6%p@)&s>=Q5szHF*HLVo#Ib zlV`{uNFO;!o+Zy=@9ueWnEa9SlNS)Fy;+OCgy`kVfMu`)UWWGfX#J^NXPU`@6rN7J5)rUY$6 zb9<~t-SWT9rW*TbyDAIG9K*9u+iMiGB4I@a zbT+!VkG^ob+wFdq(20hsKGo#srVuvK5Ia~b4i$?i?qk}1)D_7@LyJaT)yeB;5djZA zHF$bXFc0?J`xj4Ny*Tgm8-3l#Rl8Y9JhtiD+-{q>R9KAWbf&kC?;ic0(Pirf3_ zo=jU`hNh?>2q!-Bv*0;r=fmP7MV`Ndl!iwDFMUL#>ib&XN21i~gF%kszNZa+S(>7Q zfleu#gJ!de`*MmomN)mQ8qsZRL-WC0N9@5|XG?KWOP{5rw6{-HRWIluhcOr*t!iUYA8qqoH*N%-fymg>ww9tkp^cB0M)mm^ zR4RsppDIam4+6znzwJ@k3Iaw8LRIh zFY&8G2Z?|3q=)#39Q?l{ek4ei*+;_q2RX95wX(fWZo^8zfcpyC`(!+?LSEJ0 z=fm?M$cMD|i9D}BUeVqs@Vtn;*xtu@o*~cL`zX&-NYhv}Y+Lp<$5{fAy`=s}nqfp@;uJLVu~Iw5@OD zroKE)5$6{0grKeD!UDL|R)TLbibrYs?8Ob^nkgRfrv&(G#PiHzFiIxYlS5MkM76|r zsQR*bSvJa?o-$yt1>!@MN)Gzae!A_TC~WPkF711wbW`8aQOyUzGO-6Cpjr;9)J0W& z?#8M<-}>==bbTKd7_J2k^=7e z_Zx_=+{NyrYuw{V5O(6r$XRHl-O#1^?y+tsn@1Ad4di<_EE~F>q(FZb!7F=-Epk5( zFDsILPV2BIb~kp171&K}z~1H?^brz4F5>(olmrb&jDCOxx*uc3*gcGQ7b3nqn#MpE zhtqlVMRz++FV(uO?s;8+o9;XA3;H2C0?pN3m?7)3WuhD*3qs{a=@*Lvl zRqhS+0zCD7^g;FnSqD35qkA&zr;XUHeHcFG@7#B>!|*x}ksWR&I|Og-OSaejq5Dhv zfct*(5-i$3u>08S&Ibj96@n2yt3L3`Mn z%*TC@>fHO$5@Js{x8V6_hS%SUxWXawKD@dT_`GefI{y2=K6s`-!76Wshj)?v0y}m; zB1A`E9iDR;}^&;L|FM8M>L*#XA`ehRD& z>qBRUt_w(e~(d(S6ZJqTh)A zBKqnu+pyMQyN8{LiHNbpEQr|^b1dd;%#XuEhU_CqS&3WU&R%~wZ*NB z+ZeYqZhu@~+@%q!5zQmEkJvNf@ezkdyglM4M3f^CPqrybl=aFsM3Pr3w<#Z0o>Kl8 zUlhM0zF#$5wL*1Nbxs|mu2%OVO8kxb$ArX$vV^XLevLv?ty!sgMsqT8c%n0LTw+J! zn#4yFk0lNy{(>mJU0a~7);4Q9v_0Ath~e)*Z1`zyKVrhCwI6FQX|L*tEa#Pad7zmOL|gN%ETHZOMC*pG-cK zd@T8N^10+MlD|#9mZC_BNKvI2Q!-OZQtDGCq;#h&NLiV(Ic0B3U&@Oq7xfW(rCzVM z>x=ZY`f>U${XBiIexrV;e!u=1{fqh&`gipM`pfzs4a^W^7;exREQUNoy56xX1XU@sRPj z@wD+{<0a!&9PS86wWqeFPETEvx-a!W>fzL5sc)yAOTCzSCDmo}GewyaOeT}lRAy>4 zwVI}z7MWI=wwiXE9ydK}dc}0o^uFnW>6f&yw74`|T6J1;T1Q$>+KROGX*<&Pr9GY2 zpLRU$blS&hm(s49iP>mwGPjv$nwOZ@n|GM^nfuJg%-^Q7bYprM{%uX)oBmAtE9ocG z&!t~Z|Ha~G8E(;A?3N-+qh+#Xo@JF~yJfHCfaS2|nB{HDIm>0sFIGS6aI4;Gw-#Bq zTlZQISPxr|S>LvvvtG0fx9MyaTb`}b)?}M(n{8WW+i2TuJ77C(J7Ig@cG31@hAbm0 zBO$|-k(W`M(V8(kqc>w?#?FlW8P8_Cl5sNQT*l>$U+jMN;dY(fV(+zYwC}XvEpX8OSx} z_T)aA`+iKZ#p00SxMF*8 zV{vcs#^RmD`-`6`ezEvO@w>$XC1OcPNnA;CiLGQo$;y(=B_~SWFS$_iP025%meRb^ z-qMYwhf9x@zFm5*^kSJ`SyWj^it>`2*_GS^7+$efYoBS(+iI`a6) z)8+bddwEfLZTYzJuJU>1%gWc4KUsdL{Al^9^0Vb%lz&@(twK=|QIS_sSS{Hs_N$JlR#q2PPp)27y}SC^>Z8?fR}a*Xnt&QxO+ig{&90jL zHLlu_T2rmFw!XHzc4h7E+5@%6YERa_TYIkdLha?+tF^8=MO|23TwP+FvCdYPQ&&ba@cx`%b!x+}Y9cW>-|y!%M^3gOhntp2fh3T#tQ8P?4>Ss)!v1Z1TGmg#p zc&2J*&dljEpPi+cHFMV6vomL}pFJ=~HK%RPzBw0qf_j>Iw)cEISDZV1?#_9pdDZh) z%{~UyX@&@7nYOdiOb8D z*DjyEeEaev%RgIwY5CO^wiN{{=B-$^V%3U`E8e}$?>6Oa`r8U_n||B8+j?)?cw66X z-}d_TM)fB2=Ji(gPVZgcyTA89@3Xxx_MYxN+xx}siMJbXuf2Wx?dxuT^7iAmUtSru zvS8(+m5;A{X61!DhTV~Phxv}2J7(YU%pGS}g{?BJs$bQ!YW=DwSG}?7%Bo*hv(-VX z%U6$HJ$?1g)q7VzzWUoWiZuyq^lL0@oNJoaw65t|GkZrSq_dS}s{tM2T- z^Q-l`^#$v@)^A&XZ2eaoY#X{aY~65t!%rJ+8%J+kv+=~nUp84b&EB+Z)4EODH|^bY zVAJ7E$2Psa>D;D^o33nTn?p7$Hybxk*xa?bXY-29>o#xQe0q!DmdY)=wj9|?wqidG z`=&A&;yc+lIQbyQmt^Wtb$F;cR7SVCdMwn)zPTR8j$B_PeP>1CBm9Wnrg}t=#DlE{ zgWZuwt(GWx5Y;3Y1tB&(VwiLdj|ht%0f1lOmfrag@m?MFrTA$Ik<`GBrS1WoSX_@& zP$6h*hrtjf*W@vWiF%uq6Zu4EYp|=TV2E&A3ruU&tbyv3)Qx?VI!PcYf*e@QD(hi zSbBxURGAo^lpN#SCm~^kv=PdP8MynJ^#jkr7}GrlL%3DY$OgLw3Xh~uGQ@fiC|z0q z^&kGo(da=Xrm2 zQ~~W6m)c!I2BdKf&TR;vK~%s*58TxGo=Jul`t(rqyO{s=9}p3qPcM@<~CygJ6 zc^$|4dV~ELKC^hxa^9fUgv;|OtuoDfOL2|eS#D}ZomdRC2xC;DPVspIXI6<*F)k0Q zaz2&&sO4%0o$M%0^zln+uo~*@ij1K0$@YToi~w13Rj%u6x?bTj$!W5&*r{~XBqtP_ z@<-`y?Pa>6%Jc!f4)~xX1NbOLUk3Cw`@A7hLWC~Ks>D*ZJM20Gbx4`Pm4ad=M~^&; z#ryjfcILOO47G-KR#Z;+3uFPt&6WC+%$o6;Z8^@0z~P`v0_L2H_27qnZ1Lb>%-Sk|=5mcg?a+o(dL^ZC zfnos;8oBP0(|IMX^{n4zGQ9MX&WEmXA;iq*!f9(k8yd8*=)55u2oktR;sXYGy|Wrz z_Z*vV(CG@-%hd^^%;nRqLH<=Uvg?;cWJFBLGL6ae_1BMeDogVsl32gN_0M>1?wsnv zR%^{jTZX>ImN(8Eoic*=%Mkszg1w>>*kMRR&(qHjO2TLj$C|`Va%mh`eHcd~@albl zm1wo($H(rSH*$7_EvV6v*F7?jicB@XD5ohSLgAa;S+S0WndQE!#^UrQr@~i1#*z1O z|K8^+$}P6?wG;g!f*eJLkCZe5Tv`I zDbcu^xtV@`lghKQ0)khLFYU7W`PJkX6#EBl*k_Z;^-WnBm3kk!s;(gIJw@uo{0Uut zzR43RY-1fknUnJhn*xWmW>=5TY|YAQ2p-m1&^VFZ>C~o{>$TZxT}7J9Xs$Na*iCim znp_X=tr$CB&m%lCkdOQZ_c|a)CHaT7%=HqBx-o8$MMiyUdLnJXP1%!sBLpFSeld_9 zF89suDqpu$6?gBs#$wAD;M_3Ukvlp~p$Mw2usC<$9Tvk;-jZr>&pp}y_|vqF&A zk^Xi+ZO~ku4pPf7Kvhw4p2+0#;4 zji(-{9OwRtPNDZg>&IiIVWTU`tD(IXh)38$N=VD>Uxnrzl}r}gR+yF>kMuQ-xy)Z= z;rc|=NS-Q^3Tu*Dx|0iQbj<~$%VTph5~M3%8x-fr`FT2i`UkqkiS^HQ%{!^N3lxvg zR;?b)N?|(?Iv(R0jymB^!!1_PpsWvu<)C()GU`R27-MvNj(=KMQ@XAoK_R!NZF^7d zx+r^2t&dUa!qmE4oK#HFWNK!mXgy+RD zrrJawy~Qj~(>Z-GfSCBqfV8kNCS5_I+()n0D1Bs#bX&aA7(2q6G(r`t4O7Nvn3ay? z_!LD@mMYmJCvX-G^V~A1Uz|7eizc7StyF19&6nqgrG}lA{qowwkdUsVj*i6Fj&d9+ zF^3kstHKoeoC z!?kov(uUAbeW51ap-D}RiLu2e&^)%WTY9(j5}-KoiS1SO&h7(-=AJ@X%Kv0l>{%%mwMHyUCQeS#4kfQ3q10^m3U~uTslo#{2|bkyhqo)}|!Nt_5$|1bIiMxR$-44Mia1n)7I1*Z9>DlHYkltmQkOxeSH zd=d*&>#M_pJAIu#Vwh25a`*_rDTxYma@6oqBjvtv35KG?c)Mm%_t@^2EUFlDe0oAm zT0CeC`PEJ{>65TwxlAji1yb2#*g3CfXC#@H=`AZp**#LdGMuCL)T_h0+oZCQ_+0@{ zSq19tV1JQh+F;pO)Y~qVRSeau!`XoQrFScb%GTlC$EA8zLw%Iu-3O$4)kF0fP_|bp ztMSx>?-mDM*$!PJ+3MVK!`ZJ1_UlLO@L9?sedgk8rlrs|!LYj3BpttWAs%cq7{a)T z;bjn};ctaY7KN9c?YBN3bk;n4^I27*LXlBpuvBV&)rpG`O-W70|Blxw_@PMTW|l*5%Sx}kuWD+z}q<8Dl7&c zCy`Q#Pm=Jf86HopjDcSyUf=5eNtpkebw&==DVFNIhju)?&)b2f2NiDAdF(g(^3*9G zY6oYsF&^HIXT9)to{`$QQD+zWTKFI1;jV?8R1CGl$Fops=Re1DqfXsWog00v`;T@i zq&k1Wc*>=!Ixy(A2{hf;x47Nkzi{lxX@234i8h5V?U)uc zz9_xepPJ+`lS?YcO`qf6P;Acjb6t`T>#A&=j00C;B~E9t>(1(e`FiSlA3CHH@gyTV z23aOqHRlal3s-DXw1s=DJmMmSTfAduQPJUW*kw38EoL?boBeY~n`J&B`jGspn1H~M zow>n=kis!IoStj?dzPgxts&FTcZt(xD$zAh3>v$%pkhve(A4bPkZW?tT{|lSas0|L zF~y#|LE@yp_zrR7QNE(}1*;&;KiD-A81Pb?xS(0aWesz(|ab) zT7^%yDQQIe8_~Yx9q{-NkH4T*c`>Vhr3=tz6;uOAB*&{RkLRK#eH-w*g z*!;Lz><`V!F;`FYb1kqwZhOCBoNaPf0PCMSU*Q;S&MXUwnv&hzGJ7`L;`)Y$EhsR^ zUEldQGaqq%M76EsMAt{Gun4ju4YHy~NQSIPB^khZEh2W_)fE*D@c_=oP`v98WanZ; zCta}U@FYoEc~R!zu<@%K3`RqSKFe654YtVx{~?oQOpdoD_15_Nrx=Zv zEL2{*dTh}`*h0tLR=ON=RPv_`hUg$FMbI4Kd<7bFOn}ZHP+~t(qB>vs_Rp790e($7u*A^g?_{am57z;5UCLl_8_Js zwTrf~;P6ihnW1fbET|E}lh99+CK@X*+@Ud>%H`#u0F!yrtERLYcdbWFzb}j*_Ef^6 z6N^HhnioHb3i?((6cye58a*9zm` z=Y~>a*yx3#>sTl)69w1PAawv>%ExhWcLK!CoW^hdU3l0M@}Ra;ts5q&w>ZX`p_R{->XQ{PCxska2xa16|*dXmYR-+}PV$$aMMjB>p*fgm-WVEp< zZ1t>HSpS?(*8m;fJofrR`lHJ+wwZqHitC)i82iGFyb5=$!@ znwQ6Uqvc7GI8Bl#d7^=iBnQZPa|lrOacrJiTR42`h^bNGa+x-^LDLYMkCK>aBf3!{ zM@hm6g+f!6Zm7zT`zBUe^wrEaEIcM^f@(s1wO(5g(;C}4bf1DZTjSm_nj6!TN(||^ z^088!n%aRA`IkLDDfctNHU4ll=B2GR&-uW?F1rp;JruN5w~chO1m^JUO`$GdepZh% zqPi1`6Izp2x>Bd%-FFS|dfuTvd@`E^S+v4^(#7L@ z%Vo(ZuO_Eh2K^g&YDM^Ll&<({JK7US8(D%eBw-A}gH$BgZSd@n=^SzhbMdBoniCTe z8Yu`-p`lU2IR)?e!c8SCJR&AE1n{Q8u-=>X%(V%>2vNZb2JsT^(8>d;yh%a->F27U zJqpb6Bl{~an>h}hqz$%9{$O2beFuKxjTz;xNMCx$b(ey6>OabO<>fS9mkO+g9!6|c zJU9xR0ny5t1Nw(kU)oRN@l^^dLz6IDo~rOKu|+2tRjHQYMZviBE6Iq~rK*j%rS>F4 zR>P)3Ccw$trq+||YXLCump>UWj%KnNbdMRNWw?WL zC-@Q^d5?DUYa@^hn5+AcyORC^zy9OhL*k}U3H@H- z3fyD>;uy)sNx19yJsBG*#tc_OTHh24(52x!E*=i^`e-+qQ1SQzj4ut3Mal!oQSs6y zjV|fCvQB@CUtPMTP_9U~m**Aw%Zt-g!{l;HO={UBx!Jco-CQnLm<;75BmI5LEXl*= zeiQA6YK1%{qfV7$kojg>ibiG|jOi)HjKHvv$`V6iNOZAIm#wbKGnvgPsdoRc(1zUf zoS^99Bu5p45=+-x1H*+#1byt%IZ9H4GmkSNJG{GZK1tyk=s`{?NdwUj(zbMON-G~W zVUEE+^!|;X-7gC2YnMd&V8QhKDPuWLG5%XwY%Y5qd@ETZyA6{4#!WB2jJ!(;(ve#S zaujN^N~g)^*36MFE;W?C3Ap)g^qVzr9#8ow;ub+|Lk$K+< z2Sx9@Y`=V`=Pq^<^G}AB5D#`6;G^7N2x%EW zxp5ODT9YJW(cqLp|DEyW;YgCnDaoH`^z~~WrBliMJIgcbbiR>C9$x6&IoVk;cUsbf zW`!&+E6G-_kgZggr%&qa+Zh_<#4bqt2+NqH!g^DkdA{|`ioJKV&8Fw9!wS)u#BngCD5W=grV8SQoOEeYYcB zZ&)0){I(GbQ}h|!Z0!ynhsLf64{m;tk({HH%k;U4$+>Yd z-*wtdx!jxrIi^Ts@8MuwVwO6kG}#fCo@B^QPtxZ|;~S4}Det59AsS;1PN6o1>lC$P z5#rZd@!WOm^8k89;R?F(T)6Td&xMPMYs0HAMV{8~+$FcMTl&x8O^4 zL`6m!(+tBRqgZcy_rTIcAI_aQuxL4#4C2?KhsoDARPN?N5`Q^7*Rqd?B9W zFLjdw-!-UtRs=5@TvM#`gvKop7Vy zXzq?ko(j%ZaG8nTJmLK)jKE{{$`2__QonIVm0azgmJmPO&)aWyagev)bpIrChNs(X zhi`C!lXqKHFtd#OTx{*+mK+1k;zMqYU?l`1^qrDOKouS3s@2F zX5&|=;_>ycv>#$HIIwUX);UwmQx95yIrZ1SN_C=P75;joPB`pAx#o4L4kf%2^gVCq zRn*bp3n*?^K0x+Ku^7CIZ;g4m$KKq-%dy6a-5J6;b{{D~R9!r{&tTBIZIQv+)-BDN z?39F&DU%-P)`vuGet%v2?ylsJpz`j#RBOU$qrE1<*T1|wCx5iRD#bOeEt-Qux#PtW%CH~RS%FL|Wxj+eW#XWY|Lzr0kh(Hfi`dFAtq z3(}_?%BgG5?kcDmgZ*aU1z&xU6Zjs0pXzbaNI34!`!#;maI1T02+!9 zLRXvio2{JAYj}S)^BgIXO#v= zb>uWRbMMocXDHR<=chj^DlWy(HBgcV+5ZQ`9VEMFx8!eflNcgJ+RgXwTzH}kdEsI0 z+nT1Y54Q!*8(Xn#RB%Z8qNc{>(KglH(>ga(pSfN4jhfY2KelRZbH@W?aVm1!6p1JJ z{GP}B{NaI%2U9(><21)I@~{SS!t=-LZfjJAgwJ}k>-?us|GRvfjqhrQ@yF_hQ_<&1F)XIFE%EA!`LMJWv0ua7|C6n-#7FHu`|Vh1k5ob;aQN zz`Hj1yR-1FFGh3sP48-+r~L}oVhdd%cdgD!<8OUtaPeOsprjOdIto1Dtj)psz!Pj) zPsGcMCNOm#1OeYTl8)El7|WaYj47H?9b$`^JF2|5IoLlkudA$hPIaK~bpIeOp=ntSz-2yl+0GZ^V^rpt-_?bw7LvMOWKRhakie;@^L8@Y53&CJGKYVhL_{f2etab zQQ=)V=)*OxblO(zdMTTa?M>=%J?+{-9cJgDMy1e zJlZ@vZMn(y#B7d{yV)GPH~0EokYOdj%a@>asONNzm)ag7g?Q)SxhYAKNz&=fdzu@T z*2V^fbq;3eweGU2+sB3kL>2H%b8tWuPE6&^+*04M6Uo-PjyoNdYnnN|rDSNGY?g2t z28!(D7y*;XWokq?s0C}DuaXbOEVKuzTvz(dX*Bk;;UP0;Kb6h;b7qV@?D~r4Z)@)2 zKN!Qnug^+zi1eIxx-kcMxv=4&Log8$5(R>HP%1m10PJt?k=hV2`xOr}R-mEQk9T&>Ib1GTUmYK3;UJ}N_ z`F8{66@2sh$Gi|G5oD5we`9hBZ2mFN-fP_+8TGOyjcVT;9yERSlQ`Vr=qR~=gzFP{ zBd*VB`Mr&;u9qYp;%7(rc_b4sHeIy)!(nIxuWBT#?1PZ3JfF*Ql*_82Nu0Q3aH)JY&w~T4_^{X+Z8mi6ZB|#0%#-yy0 zxm~WWO|@wmmJB=k*Mq)V_#9!_K@krwk0Kcj{2%Oxn(&E;^w!O&djq-%qyNuXI*kmbI*&n>yZkRoy(VoA4_27H$PLJtm7JV4~<-ra;v=dr7+?Fc9kw6U#Qa zdNXS{aO!}XE#Cl=2tuuaS_#pxdOnTmP+jCsWj;u2me z-+tm!GMK7w*eVQRS8l*id2X{U)Gx5CJHKLlqQBr;8{lDkxX^q0G~cuEJ+tAqFa9SJUbERNpy|o5g%k8+3Ic4Q zW6hQ0LPHZK+OrzWA-+InerZc&xXD$TSZKD6ar(&vbj9ZMQJH>nS?3(3(_k{RjA=4j z%#B_4@>#j1_Svrc%JlZu9K-066x;Y*EELM)ec+k*5DO6x-Y;>fWZ_6rSdL2Q2tjke zWGkFhP^a{_nXJAcIhoqZOn(c$mvw#EoY$G{I!og%O&P{91+KqKHZs~I7uZuc|9%NM zIetxRH!pYJ>$ZB!|8b+-JBIHu2FaF?#-O$OPz{#I0aFc4D096Ezvg=*@38XRx2}I* zyN1_2?~${3&+Dgh1%JS_64kDA86eFKzX&87@*AykX}KeeA?a5s(CnQdLeAdY8dG3= zeTu!>UpdKP8L9HmG}(hPb5qLFd^1gsAV+Rud1gSmiT+%rH|5xHbbNfC>(A6t*JP+P zb3>ahlmV=xy+Ooe;ZuHk_fc3-zD!Yz>#Aw$gS(P{f+nqSnYv z6YUxlXIcLK3AGOTbG@;bUrhVI_ZQ69n%H{VtGxv%sY}$UG-}8K*?;o!dq^omh&YU@OW;^H1nVMMhE6m}k z^7XC>Vxa4)JzF`_&U;3G2i2Lx2l7Mh*N3>Cs@{pWpQ?F46YlpU;w_q(eGZ&_y+YEIMT?L|@AB~q<^6K@h0?&DQE)tm(zZbuK1N!;W>M23 zxr{xrqphOuge!Q`KjEo-4Q&8B#^XvSJFb}7ead32S3UrOKOnEF{~}!n)R{IKTzH9sf}rfzYdWkFsn zeK+?)S(;K`SvwGKNtqd_9boUytQ+t*t)E#p=xaJy-d{B^t9)UIl=d?8>7OD(m)N5< zp>GZS`w_5E-^XZrRlO&;ru&s!mi1iFUyx|saPzXB3+X{h+0@p3#fpTu8FMzZb{`x{ zjGv*r;&1FT`i&uX^`f}wjC!}LF4h-6SXbXy5F1k~ZR@Y!cy;fp>-+0BT+_Si@~W== z3p@9%sG7IGf7U*CM|Dy8Ku2X?g{!2xy>4A=-5}~=MC|x9MDikk!CcrUxrKJBXPxdy z3@vd;l}DxsTCkd&V&`N&wbC6M+qkl}d8jNdrg5mw(^X}O&YN59o*%SW^5#}myWGyQ z_>#go8z++TzOOXoH-}T^4v)| ziRtY`C-FN$<#)O8es#Nz+VYFfJDg5+wE`s{Y~T=LK_n@xU>0Y!7c44>Pa3;*2)uSA zB#qrNwBYiU#No~DJy#4R#%0gh+EF-?<(+d`cTark%`+R!(ZTAKxe4_fuI^iX-9qrS zZ`Jh+T{uG6xo@ay-X;CB_pJ!B=$qLc3);51a6Z23LCD}d=8`X!o`SAMU27%l{II7P zZ)sfSS_QM{dk-v)_C~iA7FI_^P-sz<*Ie%?s*Ez``pUN^MK7q9)>pQ8 ztA4z;%G>T)$6P_`8Z)vBsp$$DHRP}OUMX4t~9j7%ilGD?TQPTY-zQ$ZBGs&8gm*GJEmAH%G z3tfJ$)92F6+5Yg<{4;d<3$PCR6wynrFmz%3d3WN-_#VDyyA%!w6`-1!WZ*-ZhdQ_+5$(EpRY5a5z;Q^u|xP43g9}@iroH(Y~MC)!gp9DHPJl zM`3j2+b@%J`xKH#ru^1dV7dMWANvkTP3%QEZ?ZfCw~$P7w0O#IwsQ*pspUzZ>k4r! z)Bk{+{ango56Klc$Eoh^5IaUV5Cki+8cVaMPWjCqDy%3@VRma$My}nlpepj0^h8E> zVpMv1y3L-MU;L{62daniMsb^3J7S)xdMV5@P(kvgY-);$G&;`IsenL`+TA2p7T1^3C^@B7=&4X&uKm|ePd1^b0^knQGm z!CnqgKOw`$&p?ye@Dz&c>{Ynn?wQHkOI5pVkK{ky8-^Oe6H%kcKxzxn)lXfN-O9>0{dmsdUB*|``exnM_m z4-<|N_!6Nt;d)R;`ZM(pD=SAd%a@$&-6>jrr}CTQRnnmHBs4{u!vEcOrGv@^pb_(z z@qWx-+2ZURhl*TCd}v0V6&j@?Z0qQS=(i6 zS6;UWW4H$uz7KVEpr*T^heLPHhbtTDeZyu(BEn6^1?Z(@$Hgg!x3dKES21%}mBz%( z+u5{wF*>@Sr>@!j#V=Ouv@Z0p_jjx=`*P;!%(|WucXoq6(CsPvvS+2j`<99C zr(V+Or0g;cBibSjGvE}R)V<~Yf~7&|q!OW%YHVJv+r(A$*Trz1)JsO?gGr@Scps-~ zrD)9tU7WlCd!aJ;UuowegzC-Cl?@R(9NJ5YE+48=^>p@pp{)xE9WE_iua6~-+>xv~ z=gwK;h>?_CW@k@z1u1y8GfQlZ`x+}2 z)UY2E*R<{01?^8_!$>V@e|p+Gy4`atF1Pj!9$&nAnS_mA4wLSxT@Z*b+%Q{g$!D!^>{(@XZ0;25 zs@+lf^Hp_KLGA1@uCW@w%r|dKk_9Rr-`p)$?A;Sw?WLVP>b8BbanAhav*x8%4hu!r zM`O@rDLc+uQP$t?+BSr}JY=p2G6y#X;a7P~c){^WQh52OmUGpnt@Z&fdgrBFw4`RK zCnmb1vZWy|>8jCD%j)@O#m5gWY_n99cZcBi)>bxa%(}?K{;~P2@`2`>If-ebox=lV zt=;ohxb4-xKui9Tl?rx#wEiKT(?2HX1dJM*zGr`V2`g6?e>Jmx|D@r4?i>dFv`h-$FT9(PVA#-|KfqGyHG8L zBPkfbAg+3p?zXMGYSDsCjkf40sodh;ynOba{9yW`t@Vp`wcC^rcQ3Rm#%SkgKWp7^ zL-)*)z4iGEF7I!zukCU+Y`9Xny6yZWT^G$KzPK^&lD>%hUF$V0a(LFHVTm`%q}Hfj zn@4M-t##G4YixcnPSqWKhdbHMhIVevC@G~Rt|R_C$_VRHSy+37k!G@vELqgf&$1d8 zS3=v^v9_sm8FZ=aa$}y*rP?>Wl%m|lb*Yl+bY-fdDpMtR&Kjgw__(h6S&=4!Y+R?= zf=M-T%}UG4sx#Y!y-!=7s$xrEU!P(cRz7ztuk$%oElV;n8L|ey1a#gUlx+*!ph%BP zXFXLfE3&4N%lU0R<^^`IoxOyQZgbBv)w0@PSE}q%w0l4PxyL^v3oF}y$t7HlVN2om z9=?v}JMMr@*nFrS4p#f9<dEuOz;Xny1f*~_L zr<7fv)8l(9r>BDKnejO#Y%gq?x!KFkuDW#zJ!LK3TXict4>MfX!3y?ydiMAu=~?va zGI(QB8tUhMY~_MdnC(zm@=3Cz@@b5AyTU&J!cHgJt#PgEW;HAh zTBBnd$7Y9?WkeU|UTz*k2oQJvQOk}RWm8OUPouxTJ|-rwhqRomVMXi4MsN2j|ICeT zfwIa~o?`b5pQpI2)Sb6f`Ko$-n{TiVpVdoyA#!WN|iM&NcWW-8cPCg1S2;xnotyuEy{ai-qRfx6;8z60OS0M639PdL5m}GR(y& zl8gnh=vY{qK^G&IxUr=rrDYa#N3h)N&hr?fvh#~=3!^+G#g3B1G<$Y9fAlo@<4cRD z=8v8;nIC(FI}xpO3CcIa;+06v2%O)Zz+LPUFJT{Q;M0=uUFSLgbCEYS!)i%MH_G<3 zG`rFGoHrvs$0i|`P+G2?$MpFBpZ`6n(y&VU3hQ;lR2@dNyF9NT!44fpdU8f8bQmuL ziYuVQuqLGzre%Tlb>OEB|0RI-ke#a8DO-3BEFblTWf4 zcLXg6!{NUH_=76k$>9#**VAcZ*v)V{<0I&_A+9ap6CZIXnfidS)@+`Tu)~*V&@Z{D!}!0 zCgTIrJ~InZ3*n!HyVyGnZ*pAF7ZEm$u8v!)xB4CU1J)ZjUZexw zS2$iZ9hNta_`sO=)5@J5lyFEbpldnTPg#1;rTR1)32a>}@ zKdfeuE6|CBm2@_T3%RUjw`p)T%;6-LW>!t@i1g4|$u1862yiFh&xrTZ(Qv-6`Fqjt zs!1Nza!zu%=y%n)=YZhc4~@frAn?%5RBHNz94`7Jq9brP_l4qcF>X}jz95w@++{-b z7X77~J;mV|B@m;S;E$W#Jo{D*3}dpKMId^?99SK${DTljFog=+90uPSCrqs)osN z;;-n#8+jjI#p*x-)$I(h)B5$W+OMbf-{HyrYxo3OvG4^0LN}95?h$D8z&SFQwGP%TPD$lnU zr>A5m6j)^3Fk6wI=FD_t6i6$RGmFcuY02?^@sP{u*(S#to{-PR-fddEq%s*H#GZn5 zXGZFb#6phPS70y6O36+vlyeDTa;i0fV@^&@N)+`tBF$qbK(m?cqLmEQ?KGL&F^$aa zn3OrTMJIDRrjfZF)5zS8X=HB4G%~kC$Q-A0Kt3BXB<8%`lXS96Ib8IC-6}oQXHIgs zn2UBt%teGp%tgB+=p?upvv*H3W^bQH*0-y&?tpOU$L7CHBkS9zk@fA<$olqaWPLly zI%JgkceedroJz}h<^kS^v6*1NmT#SjkBIqQz7!!99o#+QQ zp^rT+`WVJNTEvV}54#~Ky}NTKU+b`mL+E2a5`9cfv(?|dtLnO-eBI{FnDUAKhtbDQ z@jiA~$R==)QvQrRfV7XM&529pnTGGnYqfWGEAN|Go;=eXKi;6D zU%*A*yCGsMp!A}T-4M~o2rl~A4Qe05`Ukhh(flCRKZn)z5BVI6{Rq~J;O9~498Uk# zdFJrsJi|uA`xm6A^&09+vi>>8FJ%3&D(eKlm&3*S<*;u3@*IbY^~+&({X+g{dpQ1A zk<-ksV{1T{@z}!$feGc{AA6VQ6YG-0EW9pZZ{SWxt#4i*F@_LatnaSZ)uV~0=Y1A8 zx8JDM8}lWl7k%^kh`vd1(KoN>eN&paR$6Gd39?J)m7vk!a}@c9=py?%3L1@aW>SjX zC}&u0&*`3}EjDXvmMtkQOZ}$m0Vm>m+$o>?uW`;Y%2`Rt(A8vGlV8?7%dMy51mC_Z zEo2YzKK3X}r()1g4`E#Sp>zs*WP=n6h~upM^ww5$d6dUp+89@!G}tgx`ew7QuGZh) zO?fWEIP)a#xWy_DW-2(4X!%V2oka}=QRPOzyP^@kU~*JJliL#td73$2@59)00%e(T zKV{^+crJJ<`od!oeSzSjFFdB}3tJHRN$l~>?6HWxKyc9)9*gJ;1Q&hbv50w*;E#b1 zz!Nbq5?t(M9@EW>gYpr;1)awu=p?wH^SDYUKmWzk-^1}AXU}mSU~FNRak%Kq$0O(` zJc9n?5%d#W(0^Q|U#tx$W&P1lxF7H#A?J zjR4pB{Ep=WMGD~nFvu~*~E9NnRA4h%6>@6{mK`%Ij zQJC~<_j33tRqq9Ogud_;Q}uq8&_Ui-}^VOT` zd?jRr;6g^;RLdo1zr)WDh7X zdDMM7ycu7_engtMU#h{4rZ$X~7`?>EM-s$M;iS)3-3(gF@_qIwbDKNABHd(OP#R?_ z%*!mnXRcI}7s_+hBd0&G3i8sGQ7a4%I76oJ=n@Mmch`OXWp=S9N|>ere5`Jhvk=x6FY$JTKL-?|@b_V!=m_wPWCe z7{fkhH%z>Wv_g;aD&S&_`Ixp;~K5vg|HWwB?HgOPh5$Ojf_lWVq`DIP(0=j+VMc5C1 zAA2jT#<{gJ4W<+=+6vzaB7l@5R5dF<&0)*7C!t^SouU4h^h9!MZkAna-oyjQK>0VL z{D&ge*dyNNt)VMwrFZx4_!9YjxeiZx@Q*Pb+XTB;v}A)LAx4;B zr7-sg_y7lIKgiS!mR1$4BpFRjNej5)eEyD7-b>ONM6Ib8}LyGt#pg%B(*v&&~6AkXDlw`EQ!6NMkfV za#?xIa6P@pSTQ+&IvJloA)TjK0q=_+qdoi1kbezkc%Gl$8%amaWmj4u=3l;F9hCoh zTDj|V`Ea{h`29weF4D>DR_U75@j4mbmrtQlyuVN7%W`A% zY2~kDPl)&6i{8c&$&ZJ^^<5(B%g6dxIX$%Apz?0xd>&@cP<{BAJh=y=`tbMQa}n=@ zVy+;5MCMyZf$pVOoz>YRpp$=!Spc0b`~)e__BNk{i5&M#V55x@8z zM;d1DbAGjf-v8nG_P_W}xsCY9=QiMZ3Uu*v)bd8TTKG)C2PZs@nBg=vS}Pyr5I^!%e*BjTAENTZa2>r7>4%w?{!_q- z9~=+rp@$Pc1peJBd>H(|d1=&J>|;);d+T8c(e=Fla5(Vn;q(YN^+S< zj(1Ay#hfjTV4b}S{6#;7KIxS99y>cRr=KLBU;ZBFsku%gGXLsuetN$~&2N^LQ+_qy zKjje(Z@Ao1*1-D>=#u8hOTzEBs&uVnt%mbdy58VA0$wiT`b5a1o~{qhkRKsI)%>4g zKH7L1{$+T7ZONn}E^M5Lh@%lvKUxxQoA1Xgyrusy_A7%Z# zKB9c8551@Q=*y-0a5(A%x&%JZOL|O^ewPY2Vq8CyP5ERe&>|o0^RhXA_&!e>RQGv+ z<4i8;u71YL53y5PInoMVZ`%J6--7fWd|RyXO=tUXf^Ymi=*MATwce&P|5RGb z`4)+P8QyDri{X5Wd_T&1HNK&I;v2muzUj*)zHvDCMs(5HRfN&4Dw)VsOt|{qvL0-ojId2GdeN4*J>$?J`l@&fPMY7 zvF4cKHXLhZ&zg$2k_%JCg0)rnN{BNWnQ6J(tL(<<-FAMKm(I0vJ_)+Oe`yx4hi04f zV4MNHVfabI=Vwf>*_=2YYF~jz)En&}@Iziy{C_8Y0NxDzdS8N*hL2S^&X#gHd5yRC zAlJo-GpAQeTQpy|{P1Z;?aV111rb{YljHQGr!{bcEh%?daPw~ZPMkoUxkfvIx^{$F zeIC30G8D(@`kQn$$)nV*@&hXnm0pw|hM&aghcl(uY;Ckxx|;KY;{iW3JcOV4A@KiQ zg{x;wAvZ$La+0NR{2b2t!QsG@H3{eaP^8~QaO`zcT&Of}7U$!>3{Ea&Hg5TA6oq_`}>To@gbZMSnA(!@pBuiYuHflnCp8Zzev zXCVbI1yKRr4z4&g#qW@#+lVhAi>a#FwynZgSyB`%=)ENST}86|<@(t^0CjNE)QZh1jY9(WQ!eQv|P7fnp|+dyTt zSM$p;j6i2=*4~*Nujzbtknat{J!p?`z9x^nocafFZo!S;)K>hR+3{x8rV8!d#FJRH z7ovW6M>hPnV4yD^eH7l-2x+gia$djCr7$;N!)-)^5yiYuUx?~A@ ziKd>)P`EA=zsI|4ChIZ~+UXD1Wf-*HA+P+mH0@StD#@|g!!$WSQwnI3=W&|SlOk!l z3hi>g^d#5KM@ZEcqZjwT6xPOglNQv5q{}J`U1bH;4b;!L+{lZ$Z>4EcK%h`C?beAy zBg8|t2noo|r6n8k9zZ?6Bd;)!{ssP2urg)`S52{cW>z34(Q*MdzB>)ymW~rms@-#&r(;v_@+#^0wr$sn z{5!$Ro28RFy5p*r_-5_%Nhhy83kE#Qy%RS`>(CFL{MU5rX+5f=Te{t&QXUSf94^O{i(xxE2%kkW+ z!i9esW)ddsnD+_%>Ry@RxcxUz{~eKDD(Co-9=3eKFYKYi5w?7S|3KhTZ4*=d$97EP zKX!yY#qsmK81|21FXogs5gco^wd_5^cTjKf-g4Ue4H558&%Z|h9#N{M;@_;kr@hPv z;XQ$w9ah)gWdC&0D$V#)&?6zf?ccC=HABa!#(>LPHX9> ziT_v=V~r7pQef6y8ov`yf{hx1ul%nTd{ zzR0u9?%-i|-9ucFZ8~8O^!|anC*M#pI81YCo zoN41fqq3*^)g4yXWJ2G)31z&7vYpa4f{1`9PhhJulIGB7=R-eY>iazW_~#DG{ouXS}< zynWk+mPbsT4T!I^pt`2NGCG#s=P4)+T+rCCv1Rtzv(H^rS`Zqmp1G!>YOt~eW!Az6 z@)T@2>WN!Kr_xrD6qSvlCQ6GF7PMzr(km)sEeM)eajusFD0&xkx$9=SC~~5Rq6oP; z3K{qdr0(?dYvEh3!YA1-ii~*Ox3DciMEj@5mU9TG9sqxWX!iral(>Lu!Jwe zpjD0TIM$SNwg;^n`s>WKO+5)I;=FA_T*um}G2qnmw&^REyE?nJlusWAj^cXXGkI20 zoVHR!VQ~E+IHsOsOq1@hS%IUqGq zjq~QLVRd~7#imA_>)5D8oRfHauSa{^4eCkhDXo3l5khL;zACkSXT5TIY}u`yhc}{4 z7tG^rI^`fCV$6zmd>Ug)rWU9#^FJb3_xN?bmm2_w@2 z=KaInH!~&>?QNLRvo(n+n@q{)F7F#HN=Uivmo~Oa!j(I760$<2S&rnbt0bv-+j-p= z&f9(oJFqOUnA9Dhw+dy_U8`z5PP)H_JUkuXiqKuFJwXg!S_B?4-G_=ViHm7n9a^+E z$rnG`BB{G{9Y5RVaY0IuZiSIkO%A6?l>@sZ|niHVOAG8jT6o#Ek6E${e)?&Ol9N zK`^(N{YV*QH;w-!J-0A7BfSjQGg33+?&Kn=7UR0jkcYDOQdI8K%SH@yE=DLhfqj`7 zW5M+??h213DlIv1sc#R7{X0IuADBBxy{)H{q z!uLM^L1~$F@tl%^b5h-o6Ut5DIKH%gptybN8b&wTY3I2QTGuSWmE9e463SSih~xM7 zvT{ibVV}Q)SbpQ1i0e$Sf2g4zgD|vmz~tY zma@i1u*z|^uUh%N7QYla9h}E9;T|*Nv2S3lC*p}kqUFKGRva&#nGzGi(8Cv91t0{~J`DKi4I3Um>A0x}NKd zBpwHV?y|ID7R9|gxc5RkkPWHVg7X@?3DJx70Gbwe%6_HAjpJ>q#?RxoLt#OQ70u=PIC@ccS?+Xr3KrqAHZ7iBmg|{k z_!<-E<48yOJnH!n7(q5R53he3bn5!^QzyAjjn+0x>0I07*=mXbP0Ls{4)owA@Ad^n z-Wfe=Ea)4g_2WBU|2B^XP5s9`F-o!_`VT)RD+W$ZJT9CHAI85pyqGEPb(ClI@*tVD zYmlWo?_WX6_anY?~?PCX%{P8<``^I7uhelSKq$|tHzNlT>7F^_8 zy=vav{)YZl1wzJoIWpPXe#H53gl)n%bX+KZ3(8kVak^?uw<6|yIj&%@gHjQv=-GXy z-rCj8mT2FAw{mEf)n9zmit@#nNMh&jYD<67!d|o}+t_7!8I3hfy~PD>zIDrH)DG0O z_7r~UR`1#JOFBC^u3{`AIwtNypQc#yY7F&o9QyDWrH?7!oY+GvXm4e#8e9HQ{~UL1 z1GUpsT|l*}3z!N&LU7a{qn5M~l{~vX{7Px9^Mu=sI(A`e*rDpoX=0G>!y6Z-arqB8Jptc^V)t!}taQRu8?*8G%&DZn{bD8&GH%oni z>rdoz&5sQ{)9Y1^;Qk8me&Pe1Z=6hTVoBIs@V>*-lb@W>!@Nc5!FvZj5IjU%lZio- z1{B$G^#;pWwYPfAvcBHS7L>9E<(X22_H^?x3jPUs!tu7X(3|iKd!%Ajbb&v|l2;Z= zF=H3?US*xj)sC?a`AD~O9`dPkHxt+1k*+wrkmu+0r9^KP&gW-OiP)dD&Wu&F@^b0z zlY#qhE+rnJZZ}cCQqKk-VHkfn?}j4I44-0R%%yQhca|9AMHsI44FnuIbdJX#0r!XD zLlny&v80 zCm)6ax_}_9?9%QFwq}lb$12|I*x8fVfmXY?ZT=lc$M299v!wQO`jow#)}8t?PzRJD z>JS0k?V<>S9g zF3PA`cXj`|>wEJH+s2pV*`=twoYsq0EWa7Wt8IFa^ZY>oXufo+@SPNwANAmH)EjWn zOXnd0H>8VoDy@EK&l+A5s7K}@xriVVpa_(>l|Z*HsGF2#)^~5slN7{1B7*+|K`!9Z z)5;TwbTC^Pzu>S#%VnZa-cu^eu}&xQMav-OC20_5b*v9 ze+1mmy(T3?G5k}!C?^#jH^yTvyd=6!1HG5;0_Zj$3{|)p3ZoUqsjvxoCOtCo^Y9AWH#ZdY4?i(zhnj6MC;}WaR8SJ`fAU-a4UbXoz z=6fqzGUAigTsJuX>;*aTNz%%4cS-uPJ#7O=7Pp<#(|PGuM@d=n)&sMaT{XXYMO0=jaIA$J=QQ1nWia7beQv&7jB!9cWj&hX6A8 zm`Vq}0hd4@V*LYtT!q6Lz~MXfaI`nzpj!fdx*I^9hiT4-bRxVEHp*sdUG#3AK(74F`W4O=p6TI9vSw1fpKF1WtAM`xQ;U`(L3Xhi% z2Osbsb9gd~52x3|Pln+;^>}1Y7|%{U9>6s`*%5Fb!BO8heEvF8Hw&d8pM#e5n0xX( zdEMr2zlUYJ9(u^7e2CPdUxTJeeMXcP{6Y;suU)+aeIKd6X*A+`4fag5!jo?3z>oqDdclFG^81cL>>VZ~67ssFLHHh|!_a}JC-Rb-5Q-mwe%NVQ#0g{y@5Ii6RY*M}nX^^b3*zVO`%G3sp!rWqL}H8c4&3iC=b^A?wfFH#6@dQZJU0d6(8 zPl4LpHBqWRn;@5ovk9l!UXBRc%f#!5)iHUN!Nd}0qUwYP{Qs+bA$al(=gC_lJ@D_; zr8mQK^shWUjR)KhOZq!cuimu)zyEQ=>m-H7S128~&j?$Ou+M0=9@uU8cygK@3Jewl zOE^wz4I-rF^6^F7-P}P#jEY;(AX_46pt}5x+nvZB2fHYI=qPRio#W8QQ#mQ(sqi}L z;HcvVoc4Qgzo{j@LV%ZPh80GCD zTq$LPMoq+_w3hF>sH0$BamT1TF`;v9TpzqA~-KWE!e>!v2&2IJtJKBmpdW{`olxf}J=!Q~2qgQr=r5djW;K5m3lbtj2%e!^>k_&3qv=BY8RO1{l`F@IWxz8Ek z&jaHAgdbdFzViujrNWG0j_ zexcZZd$F8_$c>9}efEH|lixqWcO z$$o|C_kXZ_e!c}|*RnO{XV_uwJ?Vg2%okWbzknU4p=M&)M5*DDi64>Qk12e%mZq$` zBF^OQ4Z^nDCw8Zc&?0=)6Q%Ce> zvtxuAEq`vQk)C&fYBrH4XBcjhFPVIg_{NqmTB_ws#QOut|CY!PT}!^*vGWrU*Iq5% z?Al2Aw;}(_BENV)_JxHdv{%bZ#rs3jKEvHO;{~43cHma!Q@p0Q29Q-XhCW8hcS%Os zL9BYrSRM*oPUvb`p zEB5lda_K9$KT*5=9q>l^X6;>)H`^=Ud^>+f`YFTzfWNwU^2f_BC|B=vqWHD{G$@^DJCA?MqV%}7bM(;twd?NJ6h3>?KWbsjT5 zzk_ew^~nygN8}=5`=PN!o$tBrhi{7TbX8ewdP`xTAwAkvUSf(zMA5F8@^ZPTE<4vV z&-=#@3uhKphrZ(F(B0oZz}?@)hJ#cNK2Z+JubB8JgKi!=fM~uFG2xr$oAvOTEP=ci zXg=fc&=S11&K~uo!=bo>2@`(NvMqP7_N5CZ^(^~ZkXdKONxs2 zB_QDLG2AOtBSt|F zJ%)SD{E!E-%zL$!I>T+g{0;;hR+OC~T+gju)vbf)>z>WJXVzrP%(UU}N4ekmj`sg! zDvT{vA70V`Z}YTBuiURCMy>=6s(nAEJ%DVq@d$ecGsYXJf$^9hW4eRF&ay)nRiCx= z0DI>0g9p(Lc6K9sPZ~!W?FMEFP3x$#2e46fr2XXH=fB_gtJL&%Twzt_mgXJ%{v&5s zS8ol4sBOE^wu+$#ZEkc>n;R)6BeWW~DJR$uFxJWhqg%1fm@VkPcJ_C(5|_X~uW)y{ zL1Wg$&kXOfpF%2dPi?Imjz0k`*6fbrVEQWWs`QN7vWC|$AKQM(<>&6EI!(Nc^jGWB z!w`U7JZRn#PXB~1J*2CKb+bO9$L4a59x9A+&$?1~oA;!;QumnmvcA0~d-u||88mqe z4`O$AO7u(61j>Uf)2#gN6eW;gkJ!VOz=vI}U#*+8Xi-R3ZDE-f<%AiD|5#G8qlA)! zPcaiChL_o}!6bKoatqdrPvG}?{C5xi1`l3i_3*hj$qW93bE8<}bmI3`{O;wyt%znk z3;oF?_wnDuY%wd8`_Y@b4YSlZFE*U*c!R@w!m#b^S#}hD&}Ml7`upXamhDnKU?YI_ zhGApyFaMTwD`0(w4mBOt3v^FA=8VHUpL*{*eqY0X--VlLe~tA7e$S>l-pOj&uh=O4 z?)2h!M0)V_2<}`lNWakL0A0ip91U?CiI2)jO+kQy+%(Ca?UI(XWz0xVv8FoeDu9df zehYbv<<(%Q8K2+6H=Fp$gw+r?@jTiAbGF%mwP$(lBS?Q*>_yA~35dde5BdV#_Hrq5 zBfs+O#8$(E?5F&6)y6N}z#WZs#;2xtF}@{Y_RPn*$7aI3^KrH3Q$U;K)^ZR%hB`t{sGizV3Q_RlGbHV3=P*dOc0AFlhIDKl8; znPzHp|GK9ofL%juqP z4yWgtrhHJlPP~nM{`(W3LKcn30`Nf)>$!EFxn;Fm0|`Gtm`$T$85|b=fS4CDM)V4C zC)N9I*ir2;?UQc`9Y_z-qBK}Z(`^n*O`+9nv6EX~Qd4r^&FHTg|aJ%+gPpBPPvw)o0oNy@8r zx^Ywcf$r}4873pUC%-Z$vm`aJY<&0O=o?8gC(eaUO2q5-qCR8qhi`W!4H@|z@x27s zi<%+~#8t)IgX+!Gqjuk8;ZeISi>5~HmX#(md+J}t?jHX;7TOHFHE^39-_N`szMGu- zp%`6%$!@nOSNqwhS-CO&Zp+;n-i#fV9XNQZykB;}edrK&4K$WICO(r^OHV^bS%unl z15+qpQ@vs87n(o53J4^!e6H`}?V}zg;f|vDK?}RrmJt=>?XM{8thEMGx>k6~`8@0h(OpoHHD^V8gQv8!(pT>`n{6fO?$BDFyV#GIRq&_y zH0lzp*Vz*niu!8O}Sdp=7=I?IxHx1l;}U&oz%2c4c7x#Qir$c z&PT5A-x-X{Q$E>U?v>V^b~9u3YGwDf3*z8=v#vot=il59*`Qq{DLvk}ZSHelurGAC zIF$s07S~Hs8xNsfZb99-&ITg__EO+?C{e=yj*z)gLiBz%tX*{A zyxwxX?K;bKrQX7$mh0$=?kxOOtEY7%{(kqG`T)EOfp-yK?;4No=6zV*O@V9BPYI#U?GuPNo=y0&G-@hQEQ1=e0m?~hzVu1y1@qXXHw z>-N>G?bwRn)UTMq2VL3-%2US-zPb+kBSHI*J|?=5Hi|fC(NFyg(dK>b*o^kVWV1D6 zrY|NZ8vUv<(Qlo#uDx$VC?+mt*@bhqZi)BD3@>QdFyCtLSj;YJF32m(YzTgrmCUNB zT2|A#^$|oME$VK&Wca}M5thghHSy1hfbjv)lmn0WdPAq77n0TXzB^gmk0Yi! zjIbER)IE+J2Oni_Da4(tCOA45(mTZJw23j%8yTbMu>JrTng@X-Bs*5lGB(rOlwWA)nQw(n8sd zbtI31^1i!>>(d|*+zv_us5m$a0IiNZ%!|D|Z8<7JZC7eGH!0r6&jsu&FJYZJbRnBY-r$|rjhmW!{6F)*3sFcYhwo6E7~lw z+RCYL`FXAg^Sx}Uvugl>EIPH`+k0KcH4PR?~ECTo)5HT;RAkAD2vbL-+`dm84{#3rm;lH#(( z#SPlrwrzL#?q9Y&K00sF>{FG`4{wf-E?zQAdVKqKiQ*S3V(64TZ+%v}((b>l{O zv@3tAZE$tCm1mS+&g>|gU4~}D8s7>J-)-zXz7GE_E9Yx-3;4MYZD%$N82(7&jym}b z>m=A0@nqIX8{fgvF78|lJ8n_(CqJb)xBCSDpYk)qt?e+ z7tGDVfnMnkU;nwgzply){Z-OLF8dkQaNNKBeRq_@T8ZnvQZekOvOYEa`*N~K4*boA zMx5-* zZ)H{aNL@`yX|*fltgS9Bsd0rwn_z}Po2tH2G{0I!+$bv^)CIiM(7JYPY;9}(Im=g^ zUC*xAcJSaf=*JHYUD4Hb#Sr*@3)=Msv=i5Ng74v0(zUNuG_TsoKHg^Icc@JV-3Kg{ zQaUUo{A~-{>KWXtJX%&(CS5tcL+TzsCg{&WzBODFu5f{(~Ia_7=*+Y$%v-bLU|=%%f$ZCylzh2vS8zeX7Bu}Sjo70 zpt3ikvgK&!+G^$8xfT9JmF_v-%Ef_ux3^XgH_Y17T0Xl%0Pr{?c#XIaX(*h=LC(!C?i3sMVv zTGNBskRsKwk3FYc*v_7Ad5q&(2fn1E?fIS&ZEt5tgUw8{cuV!{eN>;ac@5t%%R=FNura)C6arH>{T-(bEoBo_ebJL9)VtKZ3C%jntl zP7cz)44R&UzCy)s3o$EL5zK!%m;HdyBKAnG(!+wvMYKRd`gr<;;y7;s*qZTh%y9^P>l5LT&0_ZfSP@QLR@U0@?U4D1GAgKQ@fe5b93I2 zR*&EE4vRM_MXNItzj?I7$V1N z4GdDySN?*JZ*e@Tg%1(|b+8j`#X~N&HoN;^`P*-o9~7ToqBS0n79aG!{kHd@a+Uc0 zu$A^-h(;q10GA9~Oj6h*AH2vFNb^(80jr}(2!!2yPU|1Hcz*n2&lV}r(%P#0&zA95 zqyRyrHMOou#-&B4>^<{>Yh9K^)$z_yDDV~Glf0cR*3dX-L zB_4!_R+HyoYe7NlK@WG+Lirx#ALjX0>rzDHG1&E-SWnII9Apnte&s{YL1jK=-zQZA z?F$9c9?wDgO>Zbg0sc|Fz&ju2W|Kx4g9xE(+MM87z$*KWD|Z}c>tBEU_0hN4YULKT znjKJn_`wIt?Q}5_n|Mjtim`%LKE`9m+-^DFnqMXD8NZAf$2W%G%fHv&GrX5t#xIlh zC|jfUP=02VS|;Cz^UJ>GRnY z>;@dA`vH4`{fzw_n%j@rXIKR%N?DRq3P_Fc$6E}&%ogb!=~C%h={AGuaq0F4ASF5* z2DV~h`-k4n5zxV-bkF)U`e!|!>C)?Q>Yw#C6+Iq3e*Lo^|Nm)vJs$nD9?x|6^=b6a zdiwP^^>x(4r%SI-qtB~PuZK^UUQhpjl3tHrU$6fJzdp@$dG+*6mqw3AI+U22nux!* z)Nf^#`tU>bo6U$xNQlAT_^s*#JD@(C&;TE)X~(}gC9{TP`_vp7ibtfT$%8 zDD$M}<>S~JYqs_OozJkDN`p_qe=^?vpN^0E^!jmA|Ew=ppI#52?pZ${=y6UrmrR#_ zy1e@I)8W*^_4MoE{|SD59j8mL$FEN_U0!|q>C))&NQct$^3v?NxxZ4sl|l94nEK69 z)zL6KD2GSX@DTf3HSPG}DVa4S=cqWsInFRysaZ8b*xso~*}MNaB^s8g-x?v|I%qR) zc-Gpk7ArNY52tZN!=YsjGe!fj8ERIo`lstg%8&jnQ|0-* zZS5+qGko`I)6#U`yZ#pgUkv=ezz3SoZdGcf_hr)c{_lK7LHu?5zyD;s`#&8Y_38EF zrv6!9u0FjUKHamPem&0V=91~sPnTDpemb0bxSoDJ{6E33uj6#-_4xH^rpv2OKV2F< z9_dhl*IR(UkJN8vzWQ*t`psh0(J(wHhey=#5c{s0cKpDU%o>sdDvofDGfY-$R*ew0 zc`8zN;y%E^X&ljTXj#LI(Euz}&8k)ZG`?skPUDA$ zV*DQ}iZC0rnuGz$ga4MPa)7rjweK{4v;W<6b^dfYHTeHx5ZwL0zz5Rv1`XFBwv-Qk zjK*Wv!f$bkJ6y14sHS#BP0fnhnjz*4KO!73bg`3px!wHq+=b%O7qS#_ub~KR#m*+D z_J0KGZDFvJpXHx8X$U}Pc@^w-JZ>7w;TAs$E_?h~d9bn&_HnHgvQJt1HZ?SC>hpTl zZ}!&u=BBa6#<8a6^^H>OW2N1ed#>fByyh8ht=JeWo*ll3ysdcB?e)E2z%T zhlzqdvkNmbii$Ea3zQmiPt*7qybm>v`W?EH%lWo zicZW$^LAYiL*$3J$v$2)HrXlB`564U*D^&~tl2|sFn1+<{`u!@2p`l}<{H*Yb^N`0 z7J!?uoMyXJhZHyBA5Th8R0kbC)i(pa*T5M<4zi~aZpzeyf8mVui^@u!_zafer-4N+ zD@&4t$eH@-r=Q+&#~m>LjVd=OH|_rOpZ|=q82EL&{G^nt(N3I~pZq#q{{8I~(196d z8E$7kjeO633R~(v{$A3(SIgrVf{s*?s`YcEQ+`T(V1^3~@4)8rTYXvV3gw?H<{cJ8 zv`@a*;W860o~m^}(*11zexe&vcn8i$*CH49O=mWkkUmyiQmq0jHs047F2y&FVUHI3qi9gzcOse^dBf4UqXNEP)i#nM*S!)Y@AevT0fQ#<7^-*KZsAVl}m%lYb~EB zS17NcnmY*rn=joses4>QH1PGWC(C4GDjwsp6>fJCa#{njz%r)HW;j<9WL>?#}Lld;L zkO}w8%U}k^T*uo`rOgUm06zh)uO)9rxQ)ZX!J)4$wF&3&WQD)H46TE<6};y1q1HPr zA5?=NPG-W=OH#6c%Kji%>Qi*^P{`^AvL2QiKl}LE%jG%ju6y9=AFuw%-_OdTcn$a9x6+phGYpG z^($Alu&x%ILgO@ZJgRR6^&ZrpcbiFh0uJuiM)4BJ`G?v~&?MSSki|txl|ha(g0CDe zwE=L0T_BLl66e`ueaXWM*aelZLd?5b#z(35>UlFs3%QS=hXez6kZ6xDpVq8S~sO5y~AS7DjkZ4O9#pOJK zAKaxjS68m04&V{*(R?HjyrSxVGRZE%H4RN<6qcvIwV!zFS zKW?-p9n4vj#Y9eLBAmTKSkJTKU$dN%5tj4od4K*hE2*u$XMg$SmzVEn(gRisAYgq! z`O7!I$!@srx|p@M4h-D7mT=3ef81k;rID@OBg=3|f_#k~l{{3GWK6RoESfCkA=HC9;_TFCFQ(=>ZtyV6(vCQc#o42t& zFRy)LxwA~Uk*z6nD$n_IbNx9v{FD6k5WiC1i@GKt>R1tW+s0$_aq7^f(UM9I@x}Rh zW}?QQ7A0d9CaC36FD5lAre%Y-Ht)K1*|J-AHQzdT+YL_TS>;*h4M|IfhnFh5x}?f3 zPu%?W_W5y7<>!U+^Fw+0Rrxp%cG=**p5o%3eS^v#_WbLwJ4;GNl}GSCWa>kUpO{#{J6xFblXE~s-vsxee&|7v)h8xy;EX7_P6-> zN7r+J6CtP3!P zqR=&lU?Mm_Tb^7%rx%FV&T=aA^DD7F8|8vUlBNXki!i~rtVmHi&JuD<5t1RtZ!pe* zl@1QQcX9+JAfJYM__Ot=E+{BiaB98w{d#Oz*jT)O)4rcfp4QttZE{yyn!%Is%pa9L z$+vi49b1u|T`_hX_>zuaGShH_)Qc^&QD!iVhXGoC7J5{MXfG+3gu^i9W|2=abl$(UwzIU6zBd!6GOtt2I5nH7lzH8$uq0xAx3| zyivH!_Cz1sGWgAINl$Od&T0j&|FD0e+^#%}{`M5)<8ee8Ny+);CL#uW!AvB9gkhdd zkO)tX;k+n3B_h8yckI~Q*8GT+@Rqati;DWsKEAxPW_fw}@|x1}NG~t{z^T2ftkzY% z(*pdxyowf|)!cm6;v&AfcUMkC&&dr9C-+3;>_T}eSP9FNttiiE?Eg{k#uE6%{M>v& zpA@~=6^#RyBI1!{M4YhH*Z+I-3Fl`trsm@!k=gm!<9z=4)eGWF;uDJFPw%;j4`!)5xR z#+HmZb(_t0y`BL`CZBZmmNBKVG7q4Jhn!k&qa1MfK^|~+4sh^M9s&-y+W>u&TwJLj z5lBjo(l3joTh#fJf&22S(wZ}qt0IGD21QmSXEvu*=P3`pUA(k3r*X>oG26C{89${l zr*tWe9xX(>l>z<}iBk*IyH|j|a5J53j`VFkU@bx16on0S`c4nga#2b1y-&5a<;*O| zo-=;@oa};`Ic=>^U32;61!d87`8n8X7ul1OUl&~#`0?CrHH9;>vL;W?%9>GFvu*Cj zJoNtiE9W&#ESy@`*jP8UaALzekeI?~cAmEV6@wxT?*C1t|c%Pt!`A*Cg+d_~!^@&_I$ z?@G-s&CSlvEzQ>Um|!HxG!gYK5*S&L7kUP`6ihrfH#KK<=4Ez|i^iGnomtIIkNEwy z=e){-PFyLOl#$U{P zUj8wG+s8-c%`V98$WephBMREn(%K6m;)B$jj$GXO6g7T3zd5cXDX}zRBJPgNn3zzS zm{by{ntVd6iOD6CCY3;*4e`NQPn7Oc?Vp)KaW7vxI~M7_F*TL88&k#?U}v$Y8saV& zl)k8dbOt>fll5_RG9)E~JcV_VYXBJ}VPHp4werx8IdgW*o>gZutAT!j-qqfLLBS#Z z0p@CRfRbI_u(7UgV?%PBFo%XXd|14u&9Hs$ZX4ns6d&JKS0cJd3#kqjKWKpFag(5yi9eSM(v1fE7q z9<{8?Kwos1)^uFHyzOom+ zXKm)u4L2`W_#uhPUJ14`-rLLek~0c^-_tr!-rMYqfxo^A$pFnb3@VGrwk{$1sC{3?&pKF1uRJa~G?SNn{p!WspLVW|^MnY&QN`ACA8h6g74=UXb0YQ_ z8;)zv&G~KH1Yh5xuAH*zYKEzFO7ToTb9iyilycvU;Mv7W=D|G$3ed}`Xp4cj>DiU^ z&G5H`7v&bs^f!kU=M?j}^8S+InOT*EiK#Q{GCFfpl8Vdu4(zjh@ZjpKO2|z!vnm#s z6kDdHbr#I1OYIbWCyMU9*xVJdcE-cP_0;U4MMP6&DsA`_SHYdBhwWe!IjvAE~ z{;Rm8>XsxWzH9#6_U0XPrdqQqy4s>*Ce;PU2J=e`bG|8ESCU(S^2GTaP;SHi#2~b2 z$#V4IgH+~nhL8^@eCduDPP?<^0jX%raK?(&w5?;JuV3-3+^UXPKi`sBxwZv#Y0M^Z z7v3gp#SbsdE1r#?&z!&~#ZBPhQ4H))JLRFs{)rO{X2=jX?t^Uo?M&hiP`>+Ssv?Jr%Aw(qYfuVl>qWC2@mcA?mJ zx*qNEW3W#mGs=B8?m48iG^2dKKtEK~M@TT^iV>1Y`4}Pj!ZzCVzRO6i(}>xTx!4 z_MRk88lVGI?&H6=*p{T-)^S{}e~iuc(JJeGe;yOmzXZn({Z!On`y1Y8d-m+k1xO3Q zI~VY%Ziu@l0Z~BH&oLPABE|c@5O*gwmDU&f2AzG1?V}sAUg}xxYmVw{|33HcOP2fl z_bgH##GQ#nQ-dP=O68r2#pNz{Cdxa?;7yLB%aq^-4}Pb`b_VV5PCEzzn984UTtxSl zs0ma~ypZk>0o{=ODJF5w*xgYS9;X6eEa^UeT6AWdgt23<0HWC6)b4`{lx+m=fCDv9 zky$YcWeuCeys6+!O8=6?%n1j#O~_2l$!!g8!sTkdrK$Xp<+h7!(*AVw&3_6xaW=oz z=C>|hSzz0&;YN1^d9#}xxSk~LSb-V1F67r4>`%t|xYGUsvd1{=XW&ipZVL78D{r!OUvlK(0YL}C9(fCUEsjS-oxsjr5l5~g`|isuyq4#c+3tGB`pQ)$ z$Z3&NH`#8YT+-rtM8G8OMzr-H!L)yC|B3F(z!)aZXuS&OCxAF`sD;R+ut`cQ4k0+F zY0C8>mcWwc;OYEToLJKJxAeKQaX_<!rmodiMyr+tQ&=S4eABmkK)bl75USJ z`}H0J>|mtjQc%8jPgOY<9*ENp__Xw%+R5fAaXIN@0-w#fpBERMG_^1-G&LzwiL{O5 ze@C1GhA0!%I#DJ#_YnM}+P>eE30_I}$q9)MxD#MHZ3cVJ=@gUj4qC-Vvwid4v`H2uGYQ z@rv-IP~<<|ZGa&Vka+*mBYtrf9pU{TqW)$==7evxko7kc+GxLtr#2&;ubz9r*x0%d zuRz_+yS3xE95gL9M(-q72Ysa0L`CA^aW4|I?AO33DPW3P1ph`fBC z<(s@)I*uoxwtuX$-oK21F2N;n?odUWCB_11b(C010H|OpAZ9}|D4Ck_CFC^Q>b&bT zbl=@n`x}YwO-MNeTM3ybOWoQ?rX86az@;mc&4)5l^5jg6tUUg z;p_W%S#Mi%f`5>cJvft7TixZza}}d+IJaM{r*o9JBROCMT2vzM`F(krS{yd}TNSpZ zicFw&2&|4n=Qqha%S9!2DZro(XOyrTd6zj9u${pteG= zkcV$-l4yBYXzxWck3BamFJR5AhLgGi`2`l+db4#+d*`O8yx5DEE#At#j_<#`Yvtnh zS&f@#ruCfL(%sm%Y!TH5E8@BdacOt=dE6JAkAvleU;7P8=inaj@!&kj1i zr+#B+Ah&I@@H4%vt6Fa_%bm2Nzwdl`Kk>4@*6EFB%~nlm-J9!w-M0$SNgl~Q6g=Tp z-ON{;sQrZ*eEtDCAq4*DuC;RE?>5+TM!fT%C<`&pB&e0dI)RcnJd0#*lt#fm3;)dW zLqcca)8hW5%L#uJ-eU{XKj-T(nxV>W4*4DlqT}6Fo)Z1>E*s!I$>coXb^f{@w2y@H-0WzzRHb<>N zs}>)J!@zI{nYCb2E-Qh|L0upIZD#IGVWIynvv;-y#&DkxY^N=|rRbhzC;JES35x9l zJ~?9j^m}X<^P0Na8AQXJ`gIE3btlFbT=GryR7J~42}D0!WMKJIX8xJ^$IPr_a~hXr z`3GOKKEN`za6yG7aN)eXIv)ANXwBuLbQbL^^f| z+IvXkp=Q=)4hVuk4IoJfwB|0%yOGD0*q*)F^1F=8jQcD%1L8J*ylrEe?I*6{EF=48 zzzOZ3juTi00npM0DYEQ=!W+1Mx$VV!=Ot#w)IDH(wVbcyO}5wh6x%(vy}+#A_8{_T z8Q@U+sqt1JDaVI-wxMJC6EY_(@oYySD7!VKqCFIMhlWB1*&%9xd!0g~B5Tt`j9vY; zxIeL+^6d37<1X5A=bazLMB8qp`r3s0`UL8dH%F5!>$!4(?!T3?B2Mujnb4%)^@}-i zx-DM4d)-y3j3g4V0G=2f$tIHHDCljI$TG=W;U~7o1cfZVw7Z2gp?#NjKR+v&D_LHp zC$u-+cS3vfX|sa(!I!<1XG)IU)`hC3eb#U3HqAV{<={&#jc3g=%>-UjULw4Nyac>R zUJ_*_mE761`}P#2ka`k;;W*$6s>FvEkuLidVwqV^^Ls zeqvvB_1u)euv2=vmhcV!acxEQ^FsV5wOH%>xX;O3xB2=gVUejxs*mrc&4nfPo0?Xg zvhY_`{lyDrS07tJ>vc^Sp~2lj=l}{$4;tG)R6=pK1)I~oOhkH(9V)%eR-w~G< zZOe>Ki_4fkCJ*OYZK>qtjkX`sQugfFu_x|4o?u&)!*Bjk;)Q#HINdqsjdN}YPidw* z#b5@Wy}Zz1Pj_xf`}@frH2GI#I&Ol|M^D3&bECwOhSprKsQ6?dGn(T+xV7Mld6Uy7 z&-}Q|_S5aw8#+?bQriDn!PyTV;P=x(EF%8`ykk6Yp`J%F>f9QPXi6DImfL_GPSGI5 zZ`e`TmS_p=&B^rfU0gq7nI+q+wW_Ge?9(u<(p*^uO-;GAIW$}Go>G_55f)sZSK8Q8 zla-&FnpcrBB`L2mkID$~qbzWW@elgwle53MC9Q>DnKC=A#kK^;a@kt=?`&oqHP$A} z6?etGiE>>@`K8{#(Wp7>sl0_f@{t-l83)c>(@%;5gGF{}S+ym7@-YWWZ3l15zvh?} z6#U=*_8;44=%DEEd<6X*NAZAu>Ik0wU+AAr20ZOP5VyW2e<{7g_H9n=c~O?o+|s~8 z-p60E{r!QIhBi*DMZ@P@gxA(niFSkf*f@cCOV(vPAg3q2+4l8SEkMWiIIl*!Z?!!J zUeTh3jWmX(V>L~JBDbU`^TOox+3BgaXHyaO?YC{_Z@-oFCGMZ{ru(O0HscJsBtd`A z2{YYM{*^F4Ch)5TlGur$*AF_sks39w?lJQ_%uk?>y@bo9P=J6X?ky*3rhB|~GnI|F z*PF^_9AKKnT~{|7`<+-}Bu4~HZ z`|Y`LrndDL2m56#e5`H5Mf82)N$<8(Ti2fU;!E%mqFmZb^ga2u^rX7(qSi^i5$TZuRa56xr>4I(Iro^-lTT`x zo$@u61$YD1Lq|E99DTkm=^Z?yBfX2wjR?H=-c1) z?lDttE2;PKtEu8YJTbqktceHzqadefs(=OhQnCR{>tg~I#3fH??_Ozs#sP2clv_1; zFQvEw6zz|4g1}9alwGhBT}?pOF+59R_?j{iRmHYr%8jmg{($P{#Fgs0sT8oaz9p^4 zHuNnWNTY8lqrjl~4>=Rh*X#cLI1$~F#@BdC)z&FDP+U%SfP|Dc%Gk6##y!|XlhNSD zVImdOHaYT{`js$=1~ff^=Ov^kY@0nR(05VYUo&jaQ`C8e|Y}+=jXJQ z)^U^VAUD0a&{~PQ8Hsv+uc+rQ2s+g2J!Z7fpG?IW?ymhwG;}Wg$ti&W4aZhA_50@d zG!|t~FY*7}@~KyHtF^E_C_8*bbHkFyI~!M(ch=?<<#d;%l+DdA%9+;Q2%3vUydQx> zXz`#gDHcW~%k>A=Jh}itb~I#Pl7AE&apwH28CI`Mzs};uV}tqU=1@lkv z8vA(hj5TLWu7y0XdLGVEp1nfGMEr+F{KXxM>;QCTuHUf;d)x<3ZK<^Xj$K9kitbv} z{D18951c2%|Ht50`rV^IkxBw**lMyB7`DI}z03=yTc(F)gxzSlapJ@o*~P7~{^=PR z!BurteZlD&N_oo4l_|eX%`90u)n@0)to~zX*$@@gDadheBVQshE2Gki5vo(7pGkKH z(bT;a6;t*zkzauL6RG!hRC&{t_pLZ=Fx1o4*RdI%Ga ziKgCVB;S@6f58)ld#ft^{T4Rt|MxXXcNI?c@vZ#BA71e9t}U&%{rFB^rnMR{k#F_| zxSi`S`c77=*{=EK7|fJ?OSkNsnN{94K6t{f<_DSsGN)GtN0v8NFEZw0(>XmiKAh5g z{@fGKUD_@3k@k}@{x6XZ_3r!iyXk0_h3;}VX})>>6d@bNJ!RQDCH>=+f22H`g2I;k zo8$uR#7kW+AOSDlN^+Dg99&opewxCs)+K~j_v}y)kdPo@PFgpwG5iE{RQ#y~Oe>E$FzE2Ogk`0oi50{a9)T9r!|aMEOJsgmDn}pSM_7 zKT`DeNi}il(dnnYRLsp^zw*kz-rBR{!w*Gx%pD&qXh!rkHEB5B>e6=NYQgId0J zmj*PwD>gM903&sjxm+Z^>-tjz1M8Ml)Gtm83aDQa+^{N?uP{w+ zdcQd@r>ofCc8OOYZbfcfUDkX?2UPy?iaEtSxwaGXbB`%60WLz`5qt#l4*1IsA>T+D z(~Nx8XVk%}XcUcgkINfCU3u}o8-R1Z{ z@7Wn)czRC}5Hl9ST9Yke&>{g++n^XNFgxYmM} zWqyHE*VQ#07sE3fC+Fs;OpFbWPo6NT>g1-%1qFP@VsqgI#q$e$TLnGY&rznay~>-A zp%`waNs?DDT63@4THR<)RddpoMyD&&I;*nE#d;NPKf~n3o-thko`t*c^!pa6Jvq&s z>Xn<5J2fC9cy8TP)6D9;lH&YnEyUwLgwGW@fFUYvga_~21ewpn${soOf;~ebvgP7K#{3kfZxkXT~xCh;v z?nl?$9dk;MiM2T;@Sk|u|GG-7otEoGXzYV_NEpW$O%TNS2^}Z4jtdN3d`Y*inb~w& zcaVQ%<69z+_h z7I||!62(Q{O+#LYbEQR2#G5cQb#lhe?5(!T^TnY2`h&mH)8{dV_NIV!Ct#T%*D%a) z(wV*5P9TgcQHr$p0t??~v2|Mb6bs$`{%5loQv5xJ6@P=Hzk#dE9GsS)FNt!)!WPZ! zm}dj8*4w>~CE7tZ2(3@jg@_Ek=8MPUWYIcJ;|HaR0D z^5*Sv8F6Qx79E=r7twn{d&U>XAAjK6)3!YMl;8E2-oJOl*}nW)Di278O!*te)3h-% zIR_xG86?&r`%Sv6fXmWx8&6GWaNi}fS~j*$2n=2_?;6WL&8tre4#_)lcEf420{zA{ zSk2#9?rh@mOXhBCFYG&~t>>z|w8{C`%$>WvtN!Hn+OtZ zoOoZ{Cm$tmz_}ZtIte5Rjod}m35g&&;OUz9hyII%jz9CAW?=c1Yt z+f&M-=F-}kOXvKEb!IKqMYFO=h~OB1%vIE>lsDKOt-tx&O6!^Do>A~3IwP`eG9kT_WuAKDlhlV}L?An}-*M-04D>t)0@o?m!EX{*Jjy0XvQ*EqxC z+t6CxHYRuC%IOOh_*?38(vvKH%bGj-e6#sGnaMdNsrgg#E7G&4Rd?5Cl=*~CO)sjc zs}%X2XkX0#iFR9CYZ^*zGBQEl4wICLeho#82|2U(>R{i9(#)Ks+;Cq@n#C_^N^WuU z_<-R1@34Jq;ce#oc-?GkMFJM)CROC5rWED`goI~w*3WwH%+$oQL|o-4TN;_v;=+yO zVwMj%Ejm21pd++C5WhiQqn_MQ=%1BQWf?QU$Ko5G8FNfdaeKV~3*tKUjGh|XKlz&M zw3O8J3~S+{8bpH@fX1++P~Oz{5M#!7r!sLTtSt8295ebmG~%BWHc2mI+t`Q+7xmZ8 zlAor6IhDbjFAA+qwOtcl+g{L99i*Ib&!$@+>iykGw?35GaL!!IMK{}aZNC*dk+vNB zK}KU3&hZ*=tYC!DlVm+Lqrx{XBgQhZtc32^zNR=or3099>QC~Y5XP}b6qgA}&VVWx z7LTof@mn%7UdpeF^RxIz7A9T#M&X{iNw`zq_S5e&<5DI}I?eXQcjXUK{Pr*H2l*2y zM{P}f7{o5U;%M9^8AE{o3HD7)3^oVn76#3;j2jzZ4r~dl{ChtCxVtzjrJ~uEkTfY} z5k>=PG8*^Tp}eRMqxVyDaWOj)BnEXHi4(MmX~QiUxxtkUl?#KjQC3k@R+CeeiL-MH z>H>4pfbj?2i;tVbLu?mjaPFK$Y0sI2Ua4vB24PEEmj z(U4eVBlr*XB^1zokWTq>##`t7s+597UGO$5N?^dW{_<&yQt@3E(C*37G2S#kV)%F9ZO9b-JuK~EyJ&Z43t3S)h?J4 z6n6Ujs`&v$>3m0_uU~9qLH*JYzllxxS)6Y=A4sn|v!m2@FP|_E0|mgkW@RPtPC8CV( zY1x^V6yQ(I?##RpUmjn!WU9Z}FS#Z=XL^xua1mJg@`99%&5f1)rCIHLc~57}uAI^} zbxO{R{Fz&@n3S^;)XhI2UcDdhRxW?O!pzSx@3s0SWT#ouE6W1J5cnTUttp+;L_Mqk z-YUZDCoF000`SX~Y%Qq*$1I6@bK{uc@}~OvVSZjAnQ>zyPv}|H9X`edzkFPqhY{RckKl_X|=jLTlo2!qX@5Cz7@%l<^ZTuW?t@`3EZTx&9%>|<5lkEu| z>k0zGesj*aap4m>)|2+!goX_l1pB2eoSJ^Z1;PI53#YyCqIc8A#$_ATE4c4Xn)U_d z8n1=3D;JlWIDPxeOyFO?w`Xz8T>BpS|;~?8zySIVpR2o^98Mxl>ZYaDM3?oL)-ir%sX3Wteqn_*tCOPA#srsCl(YqoDb1*e z4y?15)LeE}`N_FOn@U!m@#5)c&t5%u#*WLjUq$^Dv`1*gKZ5*9v;y6y?G1;0(HZ-5xG(W!Kz^$Yy7qn}RMEx4)=Fu|5$NcOaYN^D|c< z+%6gJON=d?r+fss!mT@dMY{5la5K>jhHR(l47ky$Xu{pwa5H_5LEndP?PF9^bZ~f- zY8o4Ir9p2xW7pPkVIdLY!h$39FQpaBZl>}35jT|vQZLefBEOBjsi-tY#9BDkdZJJD zp1qsw(6_;jw7Lsu<4v>@9{@kI`Z=dr6QUQb?}t7o^>-A|&pjCJc?NS6LNdjQumW@u z#AUOX2c|JO#abYb4iqbt#Arg&1KPkrP(A+=;*rLyeOLW2R96L>!RN=U30cYj~nR5 z#irR;UUn75x!C@llBVpHsmA@A7%-vQ34RbytsPa^7&H$gm17iL_P7`Oqs1-@N)4`> z;g#dnR+KZV)X(gjJT0#BLm=O2<9+PJ_!*MDh8-7;Su zml8U5VQpj2v8(+$YRbxLi;HWbLdWzsb}r<7%L3u1gH}ZN6b~^}Jk@$nmXhG;C7oZhF{(4^9m=u5WOC}{eHKM*_yv3)fq^{66_^eYD zWcdrbvhLRQKfIOudwu`I+VvJ|7j~QlM=mPqo-OWa{K9P$1lFO^eeGPOrURoNnw|r@ z_cgtOdCgZfJ=R;pm-MfKUUWZRESd*v_TCtExL4DgahhU^rnj&dafTYj zy+Z7$CS4Bn8L(|-i{Yyurw=S+8-Z^u&U#pd5aiYiT_3*7;A5>Y_rvEh;kOrdi(yOt zs$jDMUuUf3dyQ~kjPC~M`rtDcZ7IY#N%Q%yd|o@^*BGDS-O zYpqB-*-`GSgKIBzi`h!ybGg80HF7EzsU*zZV~NGytaTzssD##`#ELN%l!<$?=*fVK zGZeuj%ra0Ws}Vyc;#m)vy@EoS9Sy1d66^~e=*K}D7R>A0SrFP7UwGld8>#+U0EHDn z_c2^P!K1KG*|*UD3y;FSy17x5zKi+Kt3rMwdQ zDqe-1o7KD%`YzrD{dB$n`d+>eeirdH(68m|pkL3|L%)Ha0R2YP2+l9&+o8XNUjqH5 z{Br28;P*j)KUN8w_=DIH!udn|Vdx*>e}Vor{yOw;@He1;lfMQ1+Z<`ZuCY&{|BPcT z2iC9s8~X2+S&S>)N)IzBbChGCpR3G+e!j90`bD^N)TAr}H{zx=la-lFg{Cs-%S~0# zSDX5vUu;?oeZOf5^h-_fZ(64A0>8LHy^)#Jo77LB|5W`P`Y+TUL{3va`|?_(-n({M z^K_Q9=)^TESl*H~iyW*p{pg9t!g55T{EdCwX`+IveR3e z5$D;hZJn`fYb)7rZ=DVQS9G>Pe{EM&S1h}!i|lXfnmILA+xH@4H{vx~dS&_IHLFTHSV8{a^jlCjt*G$9SHTy%=7bNdMxbw0hVaFrd*pZAE%^GglW->TUCPRiLJCF(dsQ?xdRy3R;Al z%ol@V(oGqrOjDLA+mvIlJ3N~_+9L7b`SOg+{^A`_p=AsgBTNk80$hGWskAPu}*LgdlIv#PotIk zJ$n}Gy`IBOde5^L*o*8X_A>hudj;#VUd0-SkJ-Q3_v}CHCrp!Yu5b&MjVAC!p2Tx_ zE@o9Dl`%?;5~~y|Q>U?#9+N<`dOVyR?aq4>Y z1ocGq9Q8c)eDy+gn|i5wnRaFT+>K*ED)ZeOisrRV&s}HD;s86eZ zQ2(gDp#E8XQ+*dFR5>y7^X0oO-^OYYaOA&;vM`> zi2uNDWE&DXgxeUn$GEzOBxq*BWsoN%Wnh<(EWj)9bkkvml)}B*)!pG%?`m%}g|r$? zuKJMaPIE|)Q%|~(1+HeJDWu0wPPpA*v~lV~Zh_mK(j0QH_I=ptE@%ny4)lcV8OT=Jg*-Ea+w-1oA+G?B zH}Jma=?2>m@qU8$OAY#WuD(Kkay9FwP$l@CP>XJYKG3NrU1+$`92)Inij}6&B=}1+ zy1<;{YBrie3!SFWa)Z7W;ToN0cU@?^yN%NvI@75Klw({>3!SFWrA|HRLRUG>q3fJ_ z;dYWVhn^09XKVVc+IPGBk{Wu2Q!mWdI?bUsIrYNrHm5oCE`$DlzXS&)NX*cX#S2w`CP}+oD>h2O!@3aZK%BhELKi&g)zcAQ-3;mDM z95MzqKE~jNQg!h8Qs}GQ-G<{6p+nSZz#A&Q6&g-!;BTX*KUIFiu6M;lHx0*Ib-t_n zcYcS$GxQ|sZ=iY`w%Y(htJ7pNTs;cA!w}A`jyYWbXPwc6QoqM(Hu5^M9gGi>TiAnm zAD3an_Bmk)x96PZu$P^Bgm?`vLVG@8prkOOB<%?^Qk1X@Bf1*!3Dc;JT*H~sPq?=$ zRHK`#-Zh+?o4>1h=z0_$>gwBQ3I~0LyVVEXeJEKYJVE#6`3+C;wDYv_cgd zV$AJY^24#|4eEU1XOYm;d4HTI3iT^e?f()!v04`}TZPXyq3;&zOj0>v!7;BwYK%~O z`8&{W75WaLE*I*}q?*PF3?qd~W9FPE3pHK9&k*`Zp&u*si-o>N=r0uNa|*_JF&a%_ zV}*)2Xkh!Oz}8#%r}G9m3`*p=Jr2jihJa3-x26{!^$cgt|~fdW2%sq8YLj}_{z!p0vb?sN7f zFNYciiYvj1UhDV?d^=7{cz{2QHL^X*Jf&Y*qAWG#nF>r*nDgLl&M+g|1C3`tim~iH z+Bi0iQ`^BjEOLC>FgATPMxl3Moy=o6VM2~Uzel6c7TQMWngIx*zd<9>FnXgmXb9@UwA8=kn*w+x|BKY3HcOv8sFx|rM zWRLTEp^HIT?1BCn_ay;KY0$&-#{Giu`l?C{5{0*32eXMUm}Jzn(tPD*N+-b zDZC!#C-^nLM%3HWgnFM)w+r<;q3#swKB3Y{Seyw}Nf$P|gv|@2DqPqIJ%3#2TZBsI z!*D)FsFw+Kk5DfmRml>1>`Q^4jY7r#9mMiDR)KIOhg4o9Z1#|zJtEX6NHs-}z1Vlc z`Sl_!&i;b`dxc78ZgIXvsOyEgQm9vuO1l)`ij6){9}_C}mq3qQCr}?0>NcTnB30}f z;+)QV_XE72LjM`w&+&eN_n&yb z#QPQAukn6^_gfZjYQg!@$KqXyFkC&>{vuP<_w4)BFYRx@{3Z6#lZu;=)Gx7KfYcbw zW8QDyuRdUZSKS8}KeosI5B4eE&+vYZ_Y1uL#QP=Quke12_Zz(5;-%dzzsCI4A9(<8 z;LG`Gd@s%=NLD5*H!8nTi`5c!F6t)D_&llZQ=d~`QQua-RR7JC%TA_Ty&`CifGh*iZ^XB=X2;3(BOHj(iFK`|VHK{{j<1Fvx`(90v>?wuGs-rH&XVv}mird4=?bIv~Ef z5B&@GcgYWMd>4``OuEZ2>^pg+_-N9B=3g^WV7=|sze59@Hnj7hxB>?nv(idLhBV-% zfs!^TC)p0t*s6xJF7fDG%ox_FrvMH=1O+LTW|XVof#3}Bup^p?heS05SJdFPqkr<9 zcpZ`-#d7FBLC+4(2x)L1(H!LoVL>qvOan%~z;ZYIp#KHhydB|50(=oZ-@r3g{FOK_0}{P}WZFOsPWd4#STrg#-^dHi%F7 zfGlOmJMuk5*$$4Kkjw{n9wirw#hDgSg5TMnAe;$n9drFFBbD3{u1}Cp&CVr_fI~2d z^Eu>7-REFm>QQ*Q$)1WHhZ8HQ^YlL&Ho$~x+A!Q#wkb&Ap#A_yb92qVp>aGEzn+lX zd61`%!EAJT6D8)0`n^+d2GLg_sG@I4wcn+r2ZMHK_9LSbNo^xb;b>t={NDZz$qc{5 z-)NTuF^XkmyBJwq7FA8qe2HFF>c zo-lv$HbQ>;Z(?buIpCk^2Pw;=RP7I-1rBnhLs!$!f!y%*@@Ql6-==4@h14!OWgzOi z(F1v!(mwjU0&`0Q*Hfl)s}n<01Mrgl8Up5!@r2%u#&?j<5^8B8_9h#-nW;3gXai=&cB7f*)yKF$cUIV-*H&hw_j!T+}y}qkNcM zHbSQPVyBs40^=hp!y^mr#O3gHt~}xJ;T&#A8Yx2gkOWIy(O2uqL1NV@z)mOXjM`5x zF=NSt+K3ihq-Z(vL3Ub;?N-ic&s=B+{*JIG2-$$BRJ2yIZiul4tsXdQ%V=bUj=NDB zz?jW0tZs4nIo-7y3LUy**|3*UfSid?|xnOkfl1U#rW z4emE?9^9k_od(=|4Prc!93YUGS$G1oq$DTG8nFxMrgbQeex8vF4T8iWSYtjx(6&By zM?t~qPW9l9k|$!^N~;@?XN}RI7Q1ju_L)`LMtuT`Z^rE z!*=(eg+qjNSmB3i7epPQdh*fX=k6hzdibONb*+s@2`4T8 zAsx8XeY6Y#bW4J0H$_ri-A60+##9Np%x$jP5v!ZM9=|cXZinxGjj9Yd9EtXS9sk2g zyJwm`Z4L+SVTOU!I=OE+3>uc*HYO3%6YO3MZcWA#4!N4imql z(@q;x;VuhPv<|)?k?>HB;ico@;OPm)BclKE$kTB7;EC_y+c?J&v>g4;**Y9P)c>`R zkdn1=1PA6pqP+=1oe)0{cZbK((%A3wg!FS#f@kco`T29fG3x2W=ji9XGcIr;x7lFN z*gb8G;eR&$QEf@}F(o}Uo}BgR1jKYDi1WM>@t9 zG5$v5D~7&2wdUZV+c7TZ@Fl@v)XO=`ODh~*;OJ30{g4et*9iU__Q#-pN27Vj0~&D? zsYe@^l%mJPs(W}Se{5ng?X*UubrCA$=b<5e2gg$aVsFfZPt1Hyu+=2fmt|9*4 zG`Ud1QAnb@+;kM87zX4(7`Ud|SVM;a_CM?VUzd=65jp2t?msI^bl%DEzY*_y7#@_1 z4^J7$ZHCU_8#@LL;U`C?JfRN*0{q#fT|nD&$S{V9VpwOfzU53;Z;mxDgXGTW*CK*A z`cPd{^~*O24jhA%5yb8dC%!)`XYO&vOF9RI%Dq8@P=sq3n6UfgP<}c`qE`k}-ym|q!(9zXdKdYM{BAc+Cg9CDQ=hoc_GVL^WAr+tCHe5CEjRaqk( zB{1@U|IoSY>C6spgHuI4S`3Xb?Xwn^JUyX3P?9G~ayVk>|dPnE58OAa+AD>Ze)S(kj z@Z&)7|5@a zFW@X3vhbi7DIJp2|I0s-4<2bh$|;sR14a}{xV8#V8*u}swA=Pwte)R5s6uPAM+6I= zhzn3^4cs*xwBYF93`M0f4oOKzl4EX^I4tVGnA$^1F{MC^4jn3t!6MPq>%twi^^L|f zB6g~OhnU-g#dO}eeZ z90HhdLaq8vNTECJ*Gj)IVLh~fg|*paCvx5?zmkn%ZM#a-Y_ zlmLz>p`m0j5)XC1;24fmt_F|G)rD#m((xk61{%cMwUPKg5NyqNxN}E5*-$I3xsddZ zmFjvf=(fi1P;N$lL+cGOir7g`pm-6#>j?`(gBixv!3_*K>5SPK7&$=n<0@e|osU2# z=OIQ0Kw?IikblAkk~GOtgPg@MG*k%(sd8NxF69M+J%mhXggjEJH-^#mBf%UgG}JXv zz<1yTL<6uFv`DSJ;PN`uBm3(4OHe>{B5#d&irRNb?WE3kj|?wkXi@e$9f&@q(RDQR z!_hjG!)U~F_%P6ubZ{jd%4p*xVsQ2(^k|OacO;Yy3U#ZSXb*`yf?9P-wZDD1c`0*y62EZW4~vd)5j!-4A);1XiLkHPPNu-q*LELRG5 z9cz-ga1>eucTA8@?3MAf^|S#M$Q?Cu?}|SDM(4&#dEw#VYPCB|)N(pqmQFp=_5>n^ z-H@6fApoY3l^{oZ$_ozqguDXot;ai%%&oh*$L(&m|7C=^KszjjY%j`O=E7a}M~^TU z_JNZAXvl@T&^s0B9wHYUQndXZym>0fuo6jifv`4F3DAr2}sxaLr<2@7eEyb zSygYthJtqFC7{=5{X0->;9NteO*^L+*pv0!|qSkoW9C>U5t;8N4Dx?}wS3FB<_^>1KaWqmo zl6a5A&(XnILiNn&qZ8ZVr(cZF5U)Byex;ANf!ei^XGjG{wHM`{=HVnGcmGDSR!#%1 zcQ*PPPVcOh&XRZPh6_C!?&t>`0k=7lutQ-#B-|sUJY&2gp*ZyMhNVl$6hBMf*NFS* z>4(g1EgGkMKYHELUnEx|AN0Ht~kY>l+@Q{8-y7qPSaCb`660yTs`)W+m z=wW7rI(~!Sc*A=D~apZcjZbC!Ep6d@yLBz9W$G+|#1hgrWGu zNY^$vVC!d$ea(NY}m&1BUkhjbgqH7n^iB-4Kp%zX@^(YY9&+Z#zRnY*bI!~YKa{P58;c#8a^0}D z?!!5XDWM9w#9D1&LfFWg1Py0HddB@fV{?f6AcjkqJjZP~Un6w~uY}Y?9P=p88ytB% z`ZGyqM~^R{Os0*rTz-Ckf(Ep8p@$Otz~lXcb9VQ+io?loXA4i59>NOX5hP4&-Eej& zI1$}d`UbcX-6J%>ITOQy8#r&YP>vcg{0c}tZj2bydOXo)4W6zMBjiJt4~>D{ z^k|ET^2NGPtcs;oF7%MRFyvZ5eeJ6eoRk-YjPA1bRrBMZ1@b5Pgg(dVAum9hfc&_h zR3Jq7 zR7<-jj~Ftxfxr%GH&B@l>e=9+*Jy*4p&mH6t-~2ML#z}qma!hh6Nc_`biSZqsuxG0 z*LQUAJ=#!dJVf^GNq1CZ8?q%f#66_Hql2Ny1)R4I$~eliSB7*Bn3Un%DbPUm$N}s~ zW@0JlN0j?ROA`)U2GmdrT~q0q9)tT~+}f;j*8!s+){^?x^txj?Y`{bIU`XYFVTW_4 zK%RLwAgDvMw126m-~Qzx1|4!;Lb#h8XXFPxj6hLs)C2 zj8!YpVdb{b$4GKT_5vI+9HNHe(=Zq&pJ>Ze_BZ5UP5Qf8Gd9-_5dSV90?Ar6BUR4!@3SLh5bp4ua7kHPm{Ym|GQ z>fG_j>bED>BeWTY0!9eszwXU7j}H|c(L!n?s6&T6vUoyc%vo=nN9kX^N(z*rq=_<^C0j_ofQ zx6w)Ae;vv|BNBv^W1hkV!+~uXQ`jn|G7J3rvOwkq88->K6g++`9gjcD#1p_~vmUr_ zW7~1d%%$uy#Bw#ejzzLv?AL4@yN%t!;@EH59+t@VvgeqUJKZ7=GAN)ujSqBBHqL2vzz&1 z-p_95OZjT{8-6@r!|vlJ@{`yD{A7MQdx&r5=d#E7`FtDO$FJhou;=)7{3iAy-_7r2 zukyS2z3g57D1VIY=TGpb*@yi1{CV~%f06%*eZ~LGUuFN|Z}Yd=zxliTuk3sN0soNw zhyQ~gV0QjF|B@^GYdqfkMnr)PPtyWo^McYRBqxYD7%$g`H9LM%5V88%H7I6{0!w@gifIae-Bf5QIH>AKGFDxpu23eb$En9FiRyawWF=WWRXtV7R8Ln=SF+SI)y+z_ zdX{>jlB-^%Ua6F+SF1afI`vxhTBSj~UfrcMsyC^BQJU4))dR|@>Nn~)%0-MbRs9mG zB3={dQw4Q;Qq5&eQuf;Ai%Kh#Y>)J%qY8EEiKH$c%F z0=rNahB_L78XBc>gfTb`FIxDHK#h%Hq-MB!Yhc>mz@gdMCG9xl=ouBB3@E)?k%-t*jYtZ8 zC~}L!Fp*a>mnI^ID1RaaMiYTYvIv_d@+L{-3Z1b``7s6f88MU?L;weZA>$->@@@RN z`U@3K6pBKW3WyXU5qc_%fqt^Isf1u$5`vP<$^fSfVJYnlcsk)4VL3w(wq&Aq zV<0@Hz+U3z3{SD)bPVJaWGJEy=Tpv{ z&qTyfVR+(^63Y33AEG(qlM0%U)K29nA6GmX+BN5sjZ$$fNq66an~BzByF3P-B0#@{ zyL_nj$^4`6RPGFQLe@koLDyfRs6P?FH$pf_3W*n(rL!v7N}D9)L^kMyAufWFh#U{Y z9x7ksk021`(Zf(@>7kTFfWOc?2~xRpGwt)5G>1GRIIY)aJu|?34+6N!qY%%L+OW0EA7}OrN3_g3= zvEnOjyJ2Q9GsL$XDq}0yO33#s*lKnhJD#nh{N%$n! zHL%-+?+NT=b_zR{oyJaQXRtHbX10Z$#mNY%9BfU5Ng{MR2?$X24^MbtVg)&$H>YHJR)Eg8>@3F8mqogc&>5<U3-JVKZ4Kx?i)k}T`cYLIID5u`PM>iMjOeZ9bNE#eOV zbuNUBMZ~rg)EogEm%j257#G5xe?xnkYmA zUJ1V!dMXPid?yTN%4OP@2^yfhr1aAxOOn#wBQTM0oe(6|$Pk1rVL?#!G#l|Cc<$jP ztyz#$GWTSxJtEiFYSEa1jTx~A<8}R+VL#9#VY#aoU^Vb#l<-LJFCOBe13eN)r%I(t zIXu`C49tyB2uhuLRfqEXn5}_*BVY$07YI+OGQ?E{w}qmNoq4+yKIlg_gc}p8?oRejE~4AYuPh6xr@k~j zlo|{8i{vrOKo?FPBRS1#;MF6zh|F(_Tc(#%L$!4^QYvL?GRcSdk^abV(nR-~(nEO@ zBHmB|ow$@2fgbcHWo=hyJuM7vPp8Alm9IIh4I0UvDMpf?oj(fM<;EACuK%Yy(i|Y! zKz|r|BF>-QADIK$&-rT-+HsmnJ!2a6I0iiv=Z|_U6uJiEA~I~P=*6sq+Aq9O|3!u& zNaFy5q_nY+&Eg=P$%jS==$Q-2P`B%YTuJ%_@ydQ>0%X5rgO5b$|uc>T3B*@8- zL;E0!wxS0}p_4FPLSdK&Ckb|QVOk{QQyN{NAE{^HO(TP5ccx}K13f3|`Oio1Xrpj9 zDjDc0oru2DNsuu&L0;P+WWFVk`f?xvW@Dsh6;vACpphLb#%NB5j|F(Bhm#MN)A24w z$bo*uUqs(WnQTg9-nG-3r?aF*C$3q+ikGZeyqul7w0G4qwtac;s$RBxMen**>|w}n z_}T5EK2t5HUTQE?TAFHOS!`Elbu2J*cPFzqg86Ga0ku-I@pIS=1b3u%-7K)CYI-bN zI<28KmYvkl)e_6LHj#R5GpTpAv^B@Fy{*l_@zvJ0&RF)3Ro#(1kMcsd3H0#e>tSQ^uu(m1xQC76VdLdt zWAU)@gzV$t$J@ik?63)6v}omV?4%W|7p-9D3-wZ=UM1A)g}Pg)cL?1nQ!qs1dNR;UR=O%p1OnR4(0Qi-E*F%rSWxCDn(OE$!1IO;R0MM5oxs-X4s z7hgZLizTR`r=wqW5xat2&u(FNu?N{6_AKU7-eB)z7UfIKQ7~@d!F)VV;2FGtSMqw^ z&b#>n%#a+%H}cc@x%?u21;3u(g4vG;`5yi(f0@6*-{%J~pYgrI6pIq9j8S5h$x4<| zs8lNTO1r3YoWCzqtY89w@KYnTZ<6*c(7w~8KXs;NzEk@?j+9Zj7q##E+V>Of`>po< zQHDo@E&Q9`kY7uV_B~Gf-YNWB^glyC2^; zL1~fDsk{pGeH*t5eHZlmgkAwHb;4IPTAWU_2WG_iKA#Dik?TCDKHQ4gzNg?{`t)Wu z;8EC3cueeOJe=*uqq0Zwc(W()c)^{*-3qtc@OUBoQ?T2I#~W}enTdBIUKQ>)qSjE0 zK{3l32#xqjij;aE(K1k-)qf=QQh$l+k;Ii;s6OexP}FSt1tC-*@*)osa}Ro*a)wC( zL=)myv{+F|gpHhaqSBByUck%(9>4{L9KOjP;U>yQe7)dGR82J-1TOR@gbGE?FGQ>h z(88XLSyUO4^2Q|k=Y%`uu(YB6d62N7n8}aRM*7h?GVy#JLvO?=+YQ~8FeLv0$PMyE zn9?ko%q7xG3@IO|&O33YbVwWB50y|TUW;(~7yJ@kbCjmh{u9{WChV!i9sYTj?jL1s zw6D_rgAx>sD#W043PsGNkbww06Z-_Sv{twheqP@D#ZC;@!{w23v*w6}(y6e8~Q8umKLH z6qvbb3f#$*>M(_}YW|b51=usp9A5?dNFKyPc?8l_%zOD-zJXuNckui9L;N-VCjZ2g zX38*Sn{rM0rgBpy;5A_Wop>PCHI+S%M`e5PsF;P{3-xLC4Ak$~5Ab6LNpf!DzyWPX zFzQ?w4}%)XBcUenB-j`7LgYyaFM(RiYhkmHFNA(6Ukd#?z7F~m_z6%?<|o5uJKqld z<@|D}yZLU|Jis4-{$c(w)YtjzP~YNj0sg1_Q>G|>iXY=jqyip<_6Iy#iBV#p#wxK; zuT-vtdbM&j)N7P$pkAk32X&XS3+j!^jZkk^z+seME4M(sO#ziDzg2z<^>@nepgyKN z2K7nhNvMBR{s{FgY1)YDzbyGm|OP zlnFJ*lmj)-l*bfPfvEs$g{cB+l?fE9#;FNt=aSUPP*c@(s99qb zdW?Du)OqRxs5hyPpez(o(m}|tP>xtp;us4Rl&qlSi;-)+ycgp( z_ztM|^ZTJb#2ZJ)YY zS}kgMw9+Env$_gdu1U~UZkL8cu@Zro6<#D_cvBQ=DY6wL27$S41X0*UqT)wLW@Z;f zMBPMeO4hPlLb+%A{pVxPoU^-UPtZj>{NLweo_S{GnP=ub`@ZLpwPegP^qqMp*VXxI zu4`QW#9gC3tP@L^LB?Fm6S&3f!CJk}p{gC9$I0BPJQH5$gZDCv+74#fN|wzbuU3&s z&yhR3$(BRp$2a7~MWVfwC@&|vUBvX?=l>!zV?y5-6Q46C+)WpfUG)F)!bw0BbYikA z;BVquaJrtzr{%imOf{Ekd-hgnZ|!n;vdgWO_wCondmk{rWWzb90{7zN#9{sBa>t<7 zcG@93Z2!nt+CS~(V0)cLWtXQd+!&P+>cfB1H9XN(wjVL4q2&447;5W3Fdw`6IoNWl z+MBJ$pRq4Eo8UCkwm=QJsjNHYuKR21Z%3#9%T?bW>eYWg*6#E*KL@RsI0-27p@{gq zsQ$l~cGS1XdV^GJGp`Ao+nTUh(1gvxCT#9#!sf0fY?d@(6G)$8#@cPQvixE|HN-r3973fcNv*PF6myQwICC4*i6)W&_WD74bq+QQx7 z-J-APFFq^|x%+~~>!Weed6m@0j;MYw$A2eB&vG=wxx7!;=Ns8f^nD)L-09x*GuBpP zhb!0H;g)lw7e?2#^o5Z?{9k@=F=DSNM)J!E4xk%Ce$P`|hIWC$fD#ns*wwU4eKg>m zzYcuJZ}ctyk&k-OMs}etJ5`Kip8-+fY(V6L-kdK+%-29v_%0ywL7(-|ej^YSHU&gJ z=oJII5QqwQ2Sh&VeK*~d2ZG`5pozG8jwYtEqS1ZkY)8yMznzJ!;LpsS&~(BA-# zGMC-Z*P)A`=fENG3G#zrHT)07NUomW+vT!iRWGkYC#Wvg%O=H@AU_PPOak>A<4}f= zSP36|=Y8;Djq^S`%)NEkJem)fHTgjHtdEO}P3%KH`Hka> z`q(C=t=OggY2K2*V*mdHE_qtuUogbS=~Z_YkhhY@`jT(;oAxo8PWm`D`jzjou}Gh; zMYPl0W6auU(}N5ZA}{)1Ao@+ISzqlh)lbF8vB|E{*qZipKYq3{7STrY#YT218uo!d z`K&<_+p+cO#^F5&MeNZ1H{DdH8$^UKHJ= zz(#iUi2f#|_>|vqAP>z8=%K}056ShY*$2wq6q;{Rfz?vFu{|NbCJr-76K#KwTTf zm-EH(FNOL*$JNW0I_X_Z-$Y&p&m0tQ=Ua0?FSz@v?yaHjwXH8;|pF}QOU+3}^SZtOhMnJT+jCq9wQ%lO&}L_eZV_T-ZD zE2?(oIyu&-dbN`r?PFb}gZSr+aQUtFSAsqI*q_^Xs%rqnDXoujQ{T?N`lEfctM?i8 zsSjtbJ+FS-tNvYE`3pYl+cc8nUa7Is8KL-01jSGKxL%@zAg6#v+;`)oFbq6c>Gdh)^FX$om?ZI`ciBg^snHGYj$o`y=P*eLQJf z@5)2`f~9Ppap< z7L!T#eMZo3*MJJ89jVJMb=Pn0YGgsWcHT;gwoyb^urivzqEMn`TthF(g2H!#4hFgf zO0@V^Lyz>n-o-Dhgn0rVp1X`OK$wj6UC@E|vVes~T}-zTL3} zPqXnj+j;bS?A?z)<=ufRUHN5vwz`@jI|OxEZ#&8RlKGshH~#=} Ci(sw* literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index b06886a9709..6aaad114203 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -20,6 +20,12 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_height="match_parent" android:background="?attr/primaryBlackBackground"> + + Date: Mon, 24 Aug 2026 23:42:53 +0700 Subject: [PATCH 07/35] fix(tv): resolve overlapping UI and restore D-pad remote navigation on TV details --- .../lagradost/cloudstream3/MainActivity.kt | 9 - .../ui/result/ResultFragmentTv.kt | 818 +-------------- .../CloneflixMovieDetailsComposeScreen.kt | 12 +- app/src/main/res/layout/fragment_result.xml | 8 +- .../main/res/layout/fragment_result_tv.xml | 990 +----------------- app/src/main/res/values/attrs.xml | 1 + 6 files changed, 78 insertions(+), 1760 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 5d39f6554cd..6d7ef4a75b3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -1255,15 +1255,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa R.id.home_preview_info_btt, R.id.home_preview_hidden_next_focus, R.id.home_preview_hidden_prev_focus, - R.id.result_play_movie_button, - R.id.result_play_series_button, - R.id.result_resume_series_button, - R.id.result_play_trailer_button, - R.id.result_bookmark_Button, - R.id.result_favorite_Button, - R.id.result_subscribe_Button, - R.id.result_search_Button, - R.id.result_episodes_show_button, ) newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index f77ca1e1a21..2ffc6af7b6b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -1,26 +1,23 @@ package com.lagradost.cloudstream3.ui.result -import android.animation.Animator import android.annotation.SuppressLint import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.animation.DecelerateInterpolator import android.widget.Toast -import androidx.appcompat.app.AlertDialog +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible -import androidx.core.widget.NestedScrollView import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton +import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.CommonActivity -import com.lagradost.cloudstream3.DubStatus -import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse @@ -28,53 +25,29 @@ import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable -import com.lagradost.cloudstream3.services.SubscriptionWorkManager import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.WatchType -import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator import com.lagradost.cloudstream3.ui.player.GeneratorPlayer -import com.lagradost.cloudstream3.ui.player.NEXT_WATCH_EPISODE_PERCENTAGE import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment -import com.lagradost.cloudstream3.ui.result.ResultFragment.bindLogo import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED -import com.lagradost.cloudstream3.ui.search.SearchAdapter +import com.lagradost.cloudstream3.ui.revamp.compose.screens.CloneflixMovieDetailsComposeScreen +import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme +import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD +import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchHelper -import com.lagradost.cloudstream3.ui.setRecycledViewPool -import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR -import com.lagradost.cloudstream3.ui.settings.Globals.TV -import com.lagradost.cloudstream3.ui.settings.Globals.isLayout -import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull -import com.lagradost.cloudstream3.utils.AppContextUtils.html -import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.AppContextUtils.loadCache -import com.lagradost.cloudstream3.utils.AppContextUtils.updateHasTrailers -import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback -import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate -import com.lagradost.cloudstream3.utils.UIHelper.populateChips +import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.setNavigationBarColorCompat -import com.lagradost.cloudstream3.utils.getImageFromDrawable -import com.lagradost.cloudstream3.utils.setText -import com.lagradost.cloudstream3.utils.setTextHtml import com.lagradost.cloudstream3.utils.txt -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import com.lagradost.cloudstream3.ActorData -import com.lagradost.cloudstream3.ui.revamp.compose.screens.CloneflixMovieDetailsComposeScreen -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD -import com.lagradost.cloudstream3.ui.search.SearchClickCallback -import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage class ResultFragmentTv : BaseFragment( BindingCreator.Inflate(FragmentResultTvBinding::inflate) @@ -115,86 +88,8 @@ class ResultFragmentTv : BaseFragment( viewModel.reloadEpisodes() } - private var currentRecommendations: List = emptyList() - - private fun handleSelection(data: Any) { - when (data) { - is EpisodeRange -> { - viewModel.changeRange(data) - } - - is Int -> { - viewModel.changeSeason(data) - } - - is DubStatus -> { - viewModel.changeDubStatus(data) - } - - is String -> { - setRecommendations(currentRecommendations, data) - } - } - } - - private fun RecyclerView?.select(index: Int) { - (this?.adapter as? SelectAdaptor?)?.select(index, this) - } - - private fun RecyclerView?.update(data: List) { - (this?.adapter as? SelectAdaptor?)?.submitList(data) - this?.isVisible = data.size > 1 - } - - private fun RecyclerView?.setAdapter() { - this?.adapter = SelectAdaptor { data -> - handleSelection(data) - } - } - -// private fun hasNoFocus(): Boolean { -// val focus = activity?.currentFocus -// if (focus == null || !focus.isVisible) return true -// return focus == binding?.resultRoot -// } - - /** - * Force focus any play button. - * Note that this will steal any focus if the episode loading is too slow (unlikely). - */ - private fun focusPlayButton() { - binding?.resultPlayMovieButton?.requestFocus() - binding?.resultPlaySeriesButton?.requestFocus() - binding?.resultResumeSeriesButton?.requestFocus() - } - - private fun setRecommendations(rec: List?, validApiName: String?) { - currentRecommendations = rec ?: emptyList() - val isInvalid = rec.isNullOrEmpty() - binding?.apply { - resultRecommendationsList.isGone = isInvalid - resultRecommendationsHolder.isGone = isInvalid - val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName - (resultRecommendationsList.adapter as? SearchAdapter)?.submitList(rec?.filter { it.apiName == matchAgainst } - ?: emptyList()) - - rec?.map { it.apiName }?.distinct()?.let { apiNames -> - // very dirty selection - resultRecommendationsFilterSelection.isVisible = apiNames.size > 1 - resultRecommendationsFilterSelection.update(apiNames.map { - txt( - it - ) to it - }) - resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst)) - } ?: run { - resultRecommendationsFilterSelection.isVisible = false - } - } - } - - var loadingDialog: Dialog? = null - var popupDialog: Dialog? = null + private var loadingDialog: Dialog? = null + private var popupDialog: Dialog? = null private fun reloadViewModel(forceReload: Boolean) { if (!viewModel.hasLoaded() || forceReload) { @@ -221,54 +116,6 @@ class ResultFragmentTv : BaseFragment( super.onStop() } - private fun View.fade(turnVisible: Boolean) { - if (turnVisible) { - isVisible = true - } - - this.animate().alpha(if (turnVisible) 0.97f else 0.0f).apply { - duration = 200 - interpolator = DecelerateInterpolator() - setListener(object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) { - } - - override fun onAnimationEnd(animation: Animator) { - this@fade.isVisible = turnVisible - } - - override fun onAnimationCancel(animation: Animator) { - } - - override fun onAnimationRepeat(animation: Animator) { - } - }) - } - this.animate().translationX(if (turnVisible) 0f else if (isRtl()) -100.0f else 100f).apply { - duration = 200 - interpolator = DecelerateInterpolator() - } - } - - private fun toggleEpisodes(show: Boolean) { - binding?.apply { - if (show) { - activity?.attachBackPressedCallback(this@ResultFragmentTv.toString()) { - toggleEpisodes(false) - } - } else { - activity?.detachBackPressedCallback(this@ResultFragmentTv.toString()) - } - episodesShadow.fade(show) - episodeHolderTv.fade(show) - if (episodesShadow.isRtl()) { - episodesShadowBackground.scaleX = -1f - } else { - episodesShadowBackground.scaleX = 1f - } - } - } - override fun fixLayout(view: View) { fixSystemBarsPadding(view, padTop = false) } @@ -343,7 +190,11 @@ class ResultFragmentTv : BaseFragment( ) ) } else { - binding.resultPlayMovieButton.performClick() + (viewModel.movie.value as? Resource.Success)?.value?.let { (_, movieEp) -> + viewModel.handleAction( + EpisodeClickEvent(ACTION_CLICK_DEFAULT, movieEp) + ) + } } } }, @@ -367,13 +218,42 @@ class ResultFragmentTv : BaseFragment( viewModel.changeSeason(idx) }, onAddToListClick = { - binding.resultBookmarkButton.performClick() + val curStatus = composeWatchStatusState + activity?.showBottomDialog( + WatchType.entries.map { getString(it.stringRes) }.toList(), + curStatus.ordinal, + getString(R.string.action_add_to_bookmarks), + showApply = false, + {} + ) { + viewModel.updateWatchStatus(WatchType.entries[it], context) + } }, onLikeClick = { - binding.resultFavoriteButton.performClick() + viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? -> + if (newStatus == null) return@toggleFavoriteStatus + val message = if (newStatus) R.string.favorite_added else R.string.favorite_removed + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + } }, onTrailerClick = { - binding.resultPlayTrailerButton.performClick() + val trailersLinks = viewModel.trailers.value ?: emptyList() + val extractedTrailerLinks = trailersLinks.flatMap { it.mirros } + .map { (extractedTrailerLink, _) -> extractedTrailerLink } + if (extractedTrailerLinks.isNotEmpty()) { + activity.navigate( + R.id.global_to_navigation_player, + GeneratorPlayer.newInstance( + ExtractorLinkGenerator( + extractedTrailerLinks, + emptyList() + ), + 0 + ) + ) + } }, onActorClick = { actorName -> QuickSearchFragment.pushSearch(activity, actorName) @@ -396,434 +276,18 @@ class ResultFragmentTv : BaseFragment( } } - binding.apply { - //episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f - - // parallax on background - resultFinishLoading.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY -> - backgroundPosterHolder.translationY = -scrollY.toFloat() * 0.8f - }) - - redirectToPlay.setOnFocusChangeListener { _, hasFocus -> - if (!hasFocus) return@setOnFocusChangeListener - toggleEpisodes(false) - - binding.apply { - val views = listOf( - resultPlayMovieButton, - resultPlaySeriesButton, - resultResumeSeriesButton, - resultPlayTrailerButton, - resultBookmarkButton, - resultFavoriteButton, - resultSubscribeButton, - resultSearchButton - ) - for (requestView in views) { - if (!requestView.isVisible) continue - if (requestView.requestFocus()) break - } - } - } - - redirectToEpisodes.setOnFocusChangeListener { _, hasFocus -> - if (!hasFocus) return@setOnFocusChangeListener - toggleEpisodes(true) - binding.apply { - val views = listOf( - resultDubSelection, - resultSeasonSelection, - resultRangeSelection, - resultEpisodes, - resultPlayTrailerButton, - ) - for (requestView in views) { - if (!requestView.isShown) continue - if (requestView.requestFocus()) break // View.FOCUS_RIGHT - } - } - } - - mapOf( - resultPlayMovieButton to resultPlayMovieText, - resultPlaySeriesButton to resultPlaySeriesText, - resultResumeSeriesButton to resultResumeSeriesText, - resultPlayTrailerButton to resultPlayTrailerText, - resultBookmarkButton to resultBookmarkText, - resultFavoriteButton to resultFavoriteText, - resultSubscribeButton to resultSubscribeText, - resultSearchButton to resultSearchText, - resultEpisodesShowButton to resultEpisodesShowText - ).forEach { (button, text) -> - - button.setOnFocusChangeListener { view, hasFocus -> - if (!hasFocus) { - text.isSelected = false - if (view.id == R.id.result_episodes_show_button) toggleEpisodes(false) - return@setOnFocusChangeListener - } - - text.isSelected = true - if (button.tag == context?.getString(R.string.tv_no_focus_tag)) { - resultFinishLoading.scrollTo(0, 0) - } - when (button.id) { - R.id.result_episodes_show_button -> { - toggleEpisodes(true) - } - - else -> { - toggleEpisodes(false) - } - } - } - } - - resultEpisodesShowButton.setOnClickListener { - // toggle, to make it more touch accessible just in case someone thinks that a - // tv layout is better but is using a touch device - toggleEpisodes(!episodeHolderTv.isVisible) - } - - resultEpisodes.setLinearListLayout( - isHorizontal = false, - nextUp = FOCUS_SELF, - nextDown = FOCUS_SELF, - nextRight = FOCUS_SELF, - ) - resultDubSelection.setLinearListLayout( - isHorizontal = false, - nextUp = FOCUS_SELF, - nextDown = FOCUS_SELF, - ) - resultRangeSelection.setLinearListLayout( - isHorizontal = false, - nextUp = FOCUS_SELF, - nextDown = FOCUS_SELF, - ) - resultSeasonSelection.setLinearListLayout( - isHorizontal = false, - nextUp = FOCUS_SELF, - nextDown = FOCUS_SELF, - ) - - /*.layoutManager = - LinearListLayout(resultEpisodes.context, resultEpisodes.isRtl()).apply { - setVertical() - }*/ - - resultReloadConnectionerror.setOnClickListener { - viewModel.load( - activity, - storedData.url, - storedData.apiName, - storedData.showFillers, - storedData.dubStatus, - storedData.start - ) - - } - - resultMetaSite.isFocusable = false - - resultSeasonSelection.setAdapter() - resultRangeSelection.setAdapter() - resultDubSelection.setAdapter() - resultRecommendationsFilterSelection.setAdapter() - - resultCastItems.setOnFocusChangeListener { _, hasFocus -> - // Always escape focus - if (hasFocus) binding.resultBookmarkButton.requestFocus() - } - //resultBack.setOnClickListener { - // activity?.popCurrentPage() - //} - - resultRecommendationsList.spanCount = 8 - resultRecommendationsList.setRecycledViewPool(SearchAdapter.sharedPool) - resultRecommendationsList.adapter = - SearchAdapter( - resultRecommendationsList, - ) { callback -> - if (callback.action == SEARCH_ACTION_FOCUSED) { - toggleEpisodes(false) - } else SearchHelper.handleSearchClickCallback(callback) - } - - resultEpisodes.setRecycledViewPool(EpisodeAdapter.sharedPool) - resultEpisodes.adapter = - EpisodeAdapter( - false, - { episodeClick -> - viewModel.handleAction(episodeClick) - }, - { downloadClickEvent -> - DownloadButtonSetup.handleDownloadClick(downloadClickEvent) - } - ) - - resultCastItems.layoutManager = object : LinearListLayout(root.context) { - override fun onRequestChildFocus( - parent: RecyclerView, - state: RecyclerView.State, - child: View, - focused: View? - ): Boolean { - // Make the cast always focus the first visible item when focused - // from somewhere else. Otherwise it jumps to the last item. - return if (parent.focusedChild == null) { - scrollToPosition(this.findFirstCompletelyVisibleItemPosition()) - true - } else { - super.onRequestChildFocus(parent, state, child, focused) - } - } - }.apply { setHorizontal() } - - val aboveCast = listOf( - binding.resultEpisodesShow, - binding.resultBookmark, - binding.resultFavorite, - binding.resultSubscribe, - ).firstOrNull { it.isVisible } - - resultCastItems.setRecycledViewPool(ActorAdaptor.sharedPool) - resultCastItems.adapter = ActorAdaptor(aboveCast?.id) { - toggleEpisodes(false) - } - - if (isLayout(EMULATOR)) { - episodesShadow.setOnClickListener { - toggleEpisodes(false) - } - } - } - observeNullable(viewModel.resumeWatching) { resume -> composeResumeWatchingState = resume - binding.apply { - if (resume == null) { - return@observeNullable - } - - resultResumeSeries.isVisible = true - resultPlayMovie.isVisible = false - resultPlaySeries.isVisible = false - - // show progress no matter if series or movie - resume.progress?.let { progress -> - resultResumeSeriesTitle.apply { - isVisible = !resume.isMovie - text = - if (resume.isMovie) null else context?.getNameFull( - resume.result.name, - resume.result.episode, - resume.result.season - ) - } - resultResumeSeriesProgressText.setText(progress.progressLeft) - resultResumeSeriesProgress.apply { - isVisible = true - this.max = progress.maxProgress - this.progress = progress.progress - } - resultResumeProgressHolder.isVisible = true - } ?: run { - resultResumeProgressHolder.isVisible = false - } - - focusPlayButton() - // Stops last button right focus if it is a movie - if (resume.isMovie) - resultSearchButton.nextFocusRightId = R.id.result_search_Button - - resultResumeSeriesText.text = - when { - resume.isMovie -> context?.getString(R.string.resume) - resume.result.season != null -> - "${getString(R.string.season_short)}${resume.result.season}:${ - getString( - R.string.episode_short - ) - }${resume.result.episode}" - - else -> "${getString(R.string.episode)} ${resume.result.episode}" - } - - resultResumeSeriesButton.setOnClickListener { - viewModel.handleAction( - EpisodeClickEvent( - storedData.playerAction, //?: ACTION_PLAY_EPISODE_IN_PLAYER, - resume.result - ) - ) - } - - resultResumeSeriesButton.setOnLongClickListener { - viewModel.handleAction( - EpisodeClickEvent(ACTION_SHOW_OPTIONS, resume.result) - ) - return@setOnLongClickListener true - } - - } } observe(viewModel.trailers) { trailersLinks -> - context?.updateHasTrailers() val extractedTrailerLinks = trailersLinks.flatMap { it.mirros } .map { (extractedTrailerLink, _) -> extractedTrailerLink } composeHasTrailersState = extractedTrailerLinks.isNotEmpty() - if (!LoadResponse.isTrailersEnabled) return@observe - binding.apply { - resultPlayTrailer.isGone = extractedTrailerLinks.isEmpty() - resultPlayTrailerButton.setOnClickListener { - if (extractedTrailerLinks.isEmpty()) return@setOnClickListener - activity.navigate( - R.id.global_to_navigation_player, GeneratorPlayer.newInstance( - ExtractorLinkGenerator( - extractedTrailerLinks, - emptyList() - ), 0 - ) - ) - } - } } observe(viewModel.watchStatus) { watchType -> composeWatchStatusState = watchType - binding.apply { - resultBookmarkText.setText(watchType.stringRes) - - resultBookmarkButton.apply { - val drawable = if (watchType.stringRes == R.string.type_none) { - R.drawable.outline_bookmark_add_24 - } else R.drawable.ic_baseline_bookmark_24 - setIconResource(drawable) - - setOnClickListener { view -> - activity?.showBottomDialog( - WatchType.entries.map { view.context.getString(it.stringRes) }.toList(), - watchType.ordinal, - view.context.getString(R.string.action_add_to_bookmarks), - showApply = false, - {}) { - viewModel.updateWatchStatus(WatchType.entries[it], context) - } - } - } - } - } - - observeNullable(viewModel.favoriteStatus) { isFavorite -> - binding.resultFavorite.isVisible = isFavorite != null - binding.resultFavoriteButton.apply { - if (isFavorite == null) return@observeNullable - - val drawable = if (isFavorite) { - R.drawable.ic_baseline_favorite_24 - } else R.drawable.ic_baseline_favorite_border_24 - setIconResource(drawable) - - setOnClickListener { - viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? -> - if (newStatus == null) return@toggleFavoriteStatus - - val message = if (newStatus) { - R.string.favorite_added - } else R.string.favorite_removed - - val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data) - .asStringNull(context) ?: "" - CommonActivity.showToast( - txt( - message, - name - ), Toast.LENGTH_SHORT - ) - } - } - } - - binding.resultFavoriteText.apply { - val text = if (isFavorite == true) { - R.string.unfavorite - } else R.string.favorite - setText(text) - } - } - - observeNullable(viewModel.subscribeStatus) { isSubscribed -> - binding.resultSubscribe.isVisible = isSubscribed != null && isLayout(EMULATOR) - binding.resultSubscribeButton.apply { - if (isSubscribed == null) return@observeNullable - - val drawable = if (isSubscribed) { - R.drawable.ic_baseline_notifications_active_24 - } else R.drawable.baseline_notifications_none_24 - setIconResource(drawable) - - setOnClickListener { - viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? -> - if (newStatus == null) return@toggleSubscriptionStatus - - val message = if (newStatus) { - // Kinda icky to have this here, but it works. - SubscriptionWorkManager.enqueuePeriodicWork(context) - R.string.subscription_new - } else R.string.subscription_deleted - - val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data) - .asStringNull(context) ?: "" - CommonActivity.showToast( - txt( - message, - name - ), Toast.LENGTH_SHORT - ) - } - } - - binding.resultSubscribeText.apply { - val text = if (isSubscribed) { - R.string.action_unsubscribe - } else R.string.action_subscribe - setText(text) - } - } - } - - observeNullable(viewModel.movie) { data -> - if (data == null) { - return@observeNullable - } - - binding.apply { - (data as? Resource.Success)?.value?.let { (_, ep) -> - resultPlayMovieButton.setOnClickListener { - viewModel.handleAction( - EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep) - ) - } - resultPlayMovieButton.setOnLongClickListener { - viewModel.handleAction( - EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep) - ) - return@setOnLongClickListener true - } - - resultPlayMovie.isVisible = !comingSoon && resultResumeSeries.isGone - if (comingSoon) { - resultBookmarkButton.requestFocus() - } else resultPlayMovieButton.requestFocus() - - // Stops last button right focus - resultSearchButton.nextFocusRightId = R.id.result_search_Button - } - } } observeNullable(viewModel.selectPopup) { popup -> @@ -834,7 +298,6 @@ class ResultFragmentTv : BaseFragment( } popupDialog?.dismissSafe(activity) - popupDialog = activity?.let { act -> val options = popup.getOptions(act) val title = popup.getTitle(act) @@ -885,108 +348,23 @@ class ResultFragmentTv : BaseFragment( } } - - observeNullable(viewModel.episodesCountText) { count -> - binding.resultEpisodesText.setText(count) - } - - observe(viewModel.selectedRangeIndex) { selected -> - binding.resultRangeSelection.select(selected) - } observe(viewModel.selectedSeasonIndex) { selected -> composeSelectedSeasonIndexState = selected - binding.resultSeasonSelection.select(selected) - } - observe(viewModel.selectedDubStatusIndex) { selected -> - binding.resultDubSelection.select(selected) - } - observe(viewModel.rangeSelections) { - binding.resultRangeSelection.update(it) - } - observe(viewModel.dubSubSelections) { - binding.resultDubSelection.update(it) } + observe(viewModel.seasonSelections) { composeSeasonsState = it.map { s -> s.first?.asStringNull(context) ?: "" } - binding.resultSeasonSelection.update(it) } + observe(viewModel.recommendations) { recommendations -> composeRecommendationsState = recommendations - setRecommendations(recommendations, null) } - if (isLayout(TV)) { - observe(viewModel.episodeSynopsis) { description -> - context?.let { ctx -> - val builder: AlertDialog.Builder = - AlertDialog.Builder(ctx, R.style.AlertDialogCustom) - builder.setMessage(description.html()) - .setTitle(R.string.synopsis) - .setOnDismissListener { - viewModel.releaseEpisodeSynopsis() - } - .show() - } - } - } - - // Used to request focus the first time the episodes are loaded. - var hasLoadedEpisodesOnce = false observeNullable(viewModel.episodes) { episodes -> if (episodes == null) return@observeNullable if (episodes is Resource.Success) { composeEpisodesState = episodes.value } - binding.apply { - if (comingSoon) resultBookmarkButton.requestFocus() - - // resultEpisodeLoading.isVisible = episodes is Resource.Loading - if (episodes is Resource.Success) { - val lastWatchedIndex = episodes.value.indexOfLast { ep -> - ep.getWatchProgress() >= NEXT_WATCH_EPISODE_PERCENTAGE.toFloat() / 100.0f || ep.videoWatchState == VideoWatchState.Watched - } - - val firstUnwatched = - episodes.value.getOrElse(lastWatchedIndex + 1) { episodes.value.firstOrNull() } - - if (firstUnwatched != null) { - resultPlaySeriesText.text = - when { - firstUnwatched.season != null -> - "${getString(R.string.season_short)}${firstUnwatched.season}:${ - getString( - R.string.episode_short - ) - }${firstUnwatched.episode}" - - else -> "${getString(R.string.episode)} ${firstUnwatched.episode}" - } - resultPlaySeriesButton.setOnClickListener { - viewModel.handleAction( - EpisodeClickEvent( - ACTION_CLICK_DEFAULT, - firstUnwatched - ) - ) - } - resultPlaySeriesButton.setOnLongClickListener { - viewModel.handleAction( - EpisodeClickEvent(ACTION_SHOW_OPTIONS, firstUnwatched) - ) - return@setOnLongClickListener true - } - if (!hasLoadedEpisodesOnce) { - hasLoadedEpisodesOnce = true - resultPlaySeries.isVisible = resultResumeSeries.isGone && !comingSoon - resultEpisodesShow.isVisible = true && !comingSoon - resultPlaySeriesButton.requestFocus() - } - } - - - (resultEpisodes.adapter as? EpisodeAdapter)?.submitList(episodes.value) - } - } } observeNullable(viewModel.page) { data -> @@ -998,105 +376,23 @@ class ResultFragmentTv : BaseFragment( val d = data.value composeActorsState = d.actors ?: emptyList() resultComposeView.isVisible = true - resultVpn.setText(d.vpnText) - resultInfo.setText(d.metaText) - resultNoEpisodes.setText(d.noEpisodesFoundText) - resultTitle.setText(d.titleText) - resultMetaSite.setText(d.apiName) - resultMetaType.setText(d.typeText) - resultMetaYear.setText(d.yearText) - resultMetaDuration.setText(d.durationText) - resultMetaRating.setText(d.ratingText) - resultMetaStatus.setText(d.onGoingText) - resultMetaContentRating.setText(d.contentRatingText) - resultNextAiring.setText(d.nextAiringEpisode) - resultNextAiringTime.setText(d.nextAiringDate) - resultPoster.loadImage(d.posterImage, headers = d.posterHeaders) - - var isExpanded = false - resultDescription.apply { - setTextHtml(d.plotText) - setOnClickListener { - if (isLayout(EMULATOR)) { - isExpanded = !isExpanded - maxLines = if (isExpanded) { - Integer.MAX_VALUE - } else 10 - } else { - context?.let { ctx -> - val builder: AlertDialog.Builder = - AlertDialog.Builder(ctx, R.style.AlertDialogCustom) - builder.setMessage(d.plotText.asString(ctx).html()) - .setTitle(d.plotHeaderText.asString(ctx)) - .show() - } - } - } - } - - val error = listOf( - R.drawable.profile_bg_dark_blue, - R.drawable.profile_bg_blue, - R.drawable.profile_bg_orange, - R.drawable.profile_bg_pink, - R.drawable.profile_bg_purple, - R.drawable.profile_bg_red, - R.drawable.profile_bg_teal - ).random() - - backgroundPoster.loadImage(d.posterBackgroundImage, headers = d.posterHeaders) { - error { getImageFromDrawable(context ?: return@error null, error) } - } - - bindLogo( - url = d.logoUrl, - headers = d.posterHeaders, - titleView = resultTitle, - logoView = backgroundPosterWatermarkBadgeHolder - ) - - comingSoon = d.comingSoon - resultTvComingSoon.isVisible = d.comingSoon - - populateChips(resultTag, d.tags) - val prefs = - androidx.preference.PreferenceManager.getDefaultSharedPreferences(root.context) - val showCast = prefs.getBoolean( - root.context.getString(R.string.show_cast_in_details_key), - true - ) - - resultCastText.setText(if (showCast) d.actorsText else null) - resultCastItems.isGone = !showCast || d.actors.isNullOrEmpty() - (resultCastItems.adapter as? ActorAdaptor)?.submitList(if (showCast) d.actors else emptyList()) - - if (d.contentRatingText == null) { - // If there is no rating to display, we don't want an empty gap - resultMetaContentRating.width = 0 - } - - resultSearchButton.setOnClickListener { - QuickSearchFragment.pushSearch(activity, d.title) - } + resultLoading.isGone = true + resultLoadingError.isGone = true } is Resource.Loading -> { resultComposeView.isGone = true + resultLoading.isVisible = true + resultLoadingError.isGone = true } is Resource.Failure -> { resultComposeView.isGone = true - resultErrorText.text = - storedData.url.plus("\n") + data.errorString + resultLoading.isGone = true + resultLoadingError.isVisible = true + resultErrorText.text = storedData.url.plus("\n") + data.errorString } } - - resultFinishLoading.isGone = true - - resultLoading.isVisible = data is Resource.Loading - - resultLoadingError.isVisible = data is Resource.Failure - //resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index e66e3f39314..a608fd8aacd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -38,6 +39,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -142,6 +145,12 @@ fun CloneflixMovieDetailsComposeScreen( val dimens = CloneflixTheme.dimens val scrollState = rememberScrollState() + val playButtonFocusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + playButtonFocusRequester.requestFocus() + } + var inMyListState by remember(isInWatchList) { mutableStateOf(isInWatchList) } val seasonOptions = remember(dynamicSeasons) { @@ -360,7 +369,8 @@ fun CloneflixMovieDetailsComposeScreen( // White Primary Play Button CloneflixHeroPlayButton( onClick = onPlayClick, - text = "Play" + text = "Play", + modifier = Modifier.focusRequester(playButtonFocusRequester) ) // Add to My List (+) diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 9db120ad0f4..51473c0464c 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -427,7 +427,7 @@ android:foreground="@drawable/outline_drawable" android:maxLines="10" android:nextFocusUp="@id/result_back" - android:nextFocusDown="@id/result_bookmark_Button" + android:nextFocusDown="@id/result_bookmark_button" android:paddingTop="5dp" android:textColor="?attr/textColor" android:textSize="15sp" @@ -489,7 +489,7 @@ android:fadingEdge="horizontal" android:focusable="false" android:focusableInTouchMode="false" - android:nextFocusUp="@id/result_bookmark_Button" + android:nextFocusUp="@id/result_bookmark_button" android:nextFocusDown="@id/result_play_movie" android:orientation="horizontal" android:paddingTop="5dp" @@ -680,7 +680,7 @@ android:layout_marginEnd="0dp" android:ellipsize="end" android:maxLines="1" - android:nextFocusUp="@id/result_bookmark_Button" + android:nextFocusUp="@id/result_bookmark_button" android:nextFocusDown="@id/result_download_movie" android:text="S1E2 Episode 2" android:visibility="gone" @@ -708,7 +708,7 @@ android:layout_gravity="center_vertical" android:layout_marginStart="0dp" android:layout_marginEnd="0dp" - android:nextFocusUp="@id/result_bookmark_Button" + android:nextFocusUp="@id/result_bookmark_button" android:nextFocusDown="@id/result_download_movie" android:text="@string/play_movie_button" android:visibility="visible" diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 6aaad114203..85c9b52a1f2 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -24,46 +24,11 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:id="@+id/result_compose_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="visible" /> - - - - - - - - - - - + android:focusable="true" + android:focusableInTouchMode="true" + android:descendantFocusability="afterDescendants" + android:visibility="gone" + tools:visibility="visible" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 7c71b557193..642b1e038c4 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,5 +1,6 @@ + From 177a894d30bfbaa0fbd6f4c186345f9831b047ca Mon Sep 17 00:00:00 2001 From: Spring Date: Mon, 24 Aug 2026 23:59:49 +0700 Subject: [PATCH 08/35] fix(ui): conditionally hide detail UI elements when real data is unavailable --- .../ui/result/ResultFragmentTv.kt | 8 +- .../CloneflixMovieDetailsComposeScreen.kt | 511 ++++++++---------- 2 files changed, 241 insertions(+), 278 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 2ffc6af7b6b..8e9a09c8a17 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -146,10 +146,10 @@ class ResultFragmentTv : BaseFragment( val d = page.value val title = d.titleText.asStringNull(context) ?: d.title val plot = d.plotText.asStringNull(context) ?: "" - val year = d.yearText?.asStringNull(context) ?: "" - val duration = d.durationText?.asStringNull(context) ?: "" - val rating = d.ratingText?.asStringNull(context) ?: "98% Match" - val contentRating = d.contentRatingText?.asStringNull(context) ?: "TV-MA" + val year = d.yearText?.asStringNull(context) + val duration = d.durationText?.asStringNull(context) + val rating = d.ratingText?.asStringNull(context) + val contentRating = d.contentRatingText?.asStringNull(context) val genres = d.tags val actors = composeActorsState.ifEmpty { d.actors ?: emptyList() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index a608fd8aacd..6637d584607 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -31,7 +31,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -99,6 +98,7 @@ data class CloneflixTrailerData( /** * Movie Details Page / Details Only Screen based on Figma Node 121:4892. * Fully dynamic and optimized for Google TV / Android TV D-Pad traversal and Mobile touch. + * Only renders sections and elements where real data is available; hides dummy/unsupported UI. */ @Composable fun CloneflixMovieDetailsComposeScreen( @@ -107,23 +107,24 @@ fun CloneflixMovieDetailsComposeScreen( backdropUrl: String? = null, posterUrl: String? = null, logoUrl: String? = null, - matchScore: String = CloneflixSampleData.SAMPLE_MATCH_NEW, - releaseYear: String = "2024", - seasonsCount: String = CloneflixSampleData.SAMPLE_DURATION_SEASONS, - quality: String = CloneflixSampleData.SAMPLE_QUALITY_HD, - maturityRating: String = CloneflixSampleData.SAMPLE_MATURITY_RATING, - advisories: String = "smoking, violence", - top10RankText: String? = "#2 in TV Shows Today", - synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - cast: List = listOf("Kento Kaku", "Yosuke Eguchi", "Tae Kimura", "Kengo Kora", "Aju Makita", "Nobuko Miyamoto"), - genres: List = listOf("TV Dramas", "Japanese", "TV Thrillers", "Action & Adventure TV"), - moodTags: List = listOf("Dark", "Suspenseful", "Exciting"), - creator: String = "Dave Boyle", - writers: List = listOf("Dave Boyle", "Masahiro Yamaura", "Kota Oura", "Kanna Kimura"), + matchScore: String? = null, + releaseYear: String? = null, + seasonsCount: String? = null, + quality: String? = null, + maturityRating: String? = null, + advisories: String? = null, + top10RankText: String? = null, + synopsis: String = "", + cast: List = emptyList(), + genres: List = emptyList(), + moodTags: List = emptyList(), + creator: String? = null, + writers: List = emptyList(), dynamicEpisodes: List? = null, dynamicRecommendations: List? = null, dynamicActors: List? = null, dynamicSeasons: List? = null, + dynamicTrailers: List? = null, selectedSeasonIndex: Int = 0, isInWatchList: Boolean = false, hasTrailers: Boolean = false, @@ -133,7 +134,6 @@ fun CloneflixMovieDetailsComposeScreen( onSeasonSelect: ((Int) -> Unit)? = null, onAddToListClick: () -> Unit = {}, onLikeClick: () -> Unit = {}, - onMuteToggle: () -> Unit = {}, onTrailerClick: () -> Unit = {}, onActorClick: ((String) -> Unit)? = null, onRecommendationClick: ((SearchResponse) -> Unit)? = null, @@ -154,101 +154,36 @@ fun CloneflixMovieDetailsComposeScreen( var inMyListState by remember(isInWatchList) { mutableStateOf(isInWatchList) } val seasonOptions = remember(dynamicSeasons) { - if (!dynamicSeasons.isNullOrEmpty()) dynamicSeasons else listOf("Season 1", "Season 2", "Season 3") + dynamicSeasons ?: emptyList() } var selectedSeasonText by remember(selectedSeasonIndex, seasonOptions) { - mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "Season 1" }) + mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "" }) } fun showToast(message: String) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } - // Default fallback sample episodes if none passed - val sampleEpisodes = remember { - listOf( - ResultEpisode( - headerName = "Season 1", - name = "The Offer", - poster = null, - episode = 1, - seasonIndex = 1, - season = 1, - data = "", - apiName = "", - id = 1, - index = 0, - position = 2754000, - duration = 3240000, - score = null, - description = "Haru Tawara lives a quiet life as a vending machine restocker, until an unexpected encounter reopens the door to his past.", - isFiller = false, - tvType = com.lagradost.cloudstream3.TvType.TvSeries, - parentId = 0, - videoWatchState = com.lagradost.cloudstream3.ui.result.VideoWatchState.None, - runTime = 54 - ), - ResultEpisode( - headerName = "Season 1", - name = "The Trail", - poster = null, - episode = 2, - seasonIndex = 1, - season = 1, - data = "", - apiName = "", - id = 2, - index = 1, - position = 624000, - duration = 3120000, - score = null, - description = "As the BNM uncovers suspicious activity surrounding a religious group, Haru's grandmother makes an intriguing discovery of her own.", - isFiller = false, - tvType = com.lagradost.cloudstream3.TvType.TvSeries, - parentId = 0, - videoWatchState = com.lagradost.cloudstream3.ui.result.VideoWatchState.None, - runTime = 52 - ), - ResultEpisode( - headerName = "Season 1", - name = "The Flower", - poster = null, - episode = 3, - seasonIndex = 1, - season = 1, - data = "", - apiName = "", - id = 3, - index = 2, - position = 0, - duration = 3300000, - score = null, - description = "Karen tracks down a lead connected to the murder case. Meanwhile, Soichi grapples with family secrets and hidden identities.", - isFiller = false, - tvType = com.lagradost.cloudstream3.TvType.TvSeries, - parentId = 0, - videoWatchState = com.lagradost.cloudstream3.ui.result.VideoWatchState.None, - runTime = 55 - ) - ) - } - - val episodesToDisplay = dynamicEpisodes ?: sampleEpisodes - - val trailersList = remember { - listOf( - CloneflixTrailerData("Trailer: Season 1", "2m 18s"), - CloneflixTrailerData("Official Teaser", "1m 12s"), - CloneflixTrailerData("Sneak Peek: The Shinobi Legacy", "3m 05s"), - CloneflixTrailerData("Behind The Scenes Stunts", "4m 42s") - ) - } + val episodesToDisplay = dynamicEpisodes ?: emptyList() val castList = remember(dynamicActors, cast) { dynamicActors?.map { it.actor.name }?.filter { it.isNotBlank() }?.ifEmpty { cast } ?: cast } + val recommendationCards = remember(dynamicRecommendations) { + dynamicRecommendations?.map { rec -> + CloneflixMovieCardItem( + title = rec.name, + type = CloneflixMovieCardType.MORE_LIKE_THIS, + posterUrl = rec.posterUrl, + showLogo = true + ) + } ?: emptyList() + } + + val trailersList = dynamicTrailers ?: emptyList() + Column( modifier = modifier .fillMaxSize() @@ -361,7 +296,7 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Action Buttons Row (Play, Add/List, Thumbs Up, Volume/Audio, Trailer) + // Action Buttons Row (Play, Add/List, Thumbs Up, Trailer) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) @@ -392,7 +327,7 @@ fun CloneflixMovieDetailsComposeScreen( onClick = onLikeClick ) - // Trailer Toggle (if available) + // Trailer Toggle (shown only if trailer links available) if (hasTrailers) { CloneflixCircleActionButton( icon = painterResource(id = R.drawable.cloneflix_ic_play), @@ -400,13 +335,6 @@ fun CloneflixMovieDetailsComposeScreen( onClick = onTrailerClick ) } - - // Mute / Volume Toggle - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_mute), - contentDescription = "Audio Toggle", - onClick = onMuteToggle - ) } } } @@ -416,6 +344,8 @@ fun CloneflixMovieDetailsComposeScreen( // ========================================== // 2. MOVIE INFO & METADATA SECTION (Figma 121:4894) // ========================================== + val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) @@ -423,58 +353,70 @@ fun CloneflixMovieDetailsComposeScreen( // Left Column: Badges, Top 10, Synopsis Column( modifier = Modifier - .weight(0.65f) + .weight(if (hasRightColumn) 0.65f else 1f) .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - // Line 1: Match Score, Year, Duration, Quality, AD Icon - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = matchScore, - color = GreenAccent, - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) + // Line 1: Match Score, Year, Duration, Quality (rendered only if available) + val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() - Text( - text = releaseYear, - color = PrimaryWhite, - style = typography.regularBody - ) + if (hasLine1) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (!matchScore.isNullOrBlank()) { + Text( + text = matchScore, + color = GreenAccent, + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + } - Text( - text = seasonsCount, - color = PrimaryWhite, - style = typography.regularBody - ) + if (!releaseYear.isNullOrBlank()) { + Text( + text = releaseYear, + color = PrimaryWhite, + style = typography.regularBody + ) + } - CloneflixVideoQualityBadge(quality = quality) + if (!seasonsCount.isNullOrBlank()) { + Text( + text = seasonsCount, + color = PrimaryWhite, + style = typography.regularBody + ) + } - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_ad), - contentDescription = "Audio Description", - tint = PrimaryWhite, - modifier = Modifier.size(20.dp) - ) + if (!quality.isNullOrBlank()) { + CloneflixVideoQualityBadge(quality = quality) + } + } } - // Line 2: Rating & Content Advisories - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - CloneflixMaturityRating(rating = maturityRating) - Text( - text = advisories, - color = Grey100, - style = typography.regularCaption1 - ) + // Line 2: Rating & Content Advisories (rendered only if available) + if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (!maturityRating.isNullOrBlank()) { + CloneflixMaturityRating(rating = maturityRating) + } + if (!advisories.isNullOrBlank()) { + Text( + text = advisories, + color = Grey100, + style = typography.regularCaption1 + ) + } + } } - // Line 3: Top 10 Rank Badge (if available) + // Line 3: Top 10 Rank Badge (rendered only if available) if (!top10RankText.isNullOrBlank()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -504,68 +446,80 @@ fun CloneflixMovieDetailsComposeScreen( } // Line 4: Synopsis - Text( - text = synopsis.ifBlank { "No synopsis available." }, - style = typography.regularBody, - color = Grey50, - lineHeight = 22.sp - ) - } - - // Right Column: Cast, Genres, Mood Tags - Column( - modifier = Modifier - .weight(0.35f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - // Cast Row - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Cast", - style = typography.regularCaption2, - color = Grey200 - ) + if (synopsis.isNotBlank()) { Text( - text = castList.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite, - maxLines = 3, - overflow = TextOverflow.Ellipsis + text = synopsis, + style = typography.regularBody, + color = Grey50, + lineHeight = 22.sp ) } + } - // Genres Row - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Genres", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = genres.joinToString(", ").ifBlank { "TV Series, Movies" }, - style = typography.regularCaption1, - color = PrimaryWhite - ) - } + // Right Column: Cast, Genres, Mood Tags (rendered only if metadata exists) + if (hasRightColumn) { + Column( + modifier = Modifier + .weight(0.35f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + // Cast Row + if (castList.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Cast", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = castList.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + } - // This show is (Moods) - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "This show is", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = moodTags.joinToString(", ").ifBlank { "Entertaining, Exciting" }, - style = typography.regularCaption1, - color = PrimaryWhite - ) + // Genres Row + if (genres.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Genres", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = genres.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite + ) + } + } + + // This show is (Moods) + if (moodTags.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "This show is", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = moodTags.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite + ) + } + } } } } - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + if (episodesToDisplay.isNotEmpty() || recommendationCards.isNotEmpty() || trailersList.isNotEmpty()) { + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } // ========================================== // 3. EPISODES & SEASONS SECTION (Figma 121:4895) @@ -626,24 +580,6 @@ fun CloneflixMovieDetailsComposeScreen( // ========================================== // 4. MORE LIKE THIS SECTION (Figma 121:4909) // ========================================== - val recommendationCards = remember(dynamicRecommendations) { - dynamicRecommendations?.map { rec -> - CloneflixMovieCardItem( - title = rec.name, - type = CloneflixMovieCardType.MORE_LIKE_THIS, - posterUrl = rec.posterUrl, - showLogo = true - ) - } ?: listOf( - CloneflixMovieCardItem("Rurouni Kenshin", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "2h 14m", showLogo = true, badge = CloneflixBadgeType.RECENTLY_ADDED), - CloneflixMovieCardItem("Shogun", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "1h 10m", showLogo = true, badge = CloneflixBadgeType.TOP_10), - CloneflixMovieCardItem("Alice in Borderland", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "50m", showLogo = true), - CloneflixMovieCardItem("Kingdom", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "2h 14m", showLogo = true), - CloneflixMovieCardItem("One Piece", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "58m", showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), - CloneflixMovieCardItem("Cyberpunk: Edgerunners", type = CloneflixMovieCardType.MORE_LIKE_THIS, runtime = "25m", showLogo = true) - ) - } - if (recommendationCards.isNotEmpty()) { Text( text = "More Like This", @@ -702,67 +638,92 @@ fun CloneflixMovieDetailsComposeScreen( // ========================================== // 5. TRAILERS & MORE SECTION (Figma 121:4925) // ========================================== - Text( - text = "Trailers & More", - style = typography.boldTitle2, - fontSize = 22.sp, - color = PrimaryWhite - ) + if (trailersList.isNotEmpty()) { + Text( + text = "Trailers & More", + style = typography.boldTitle2, + fontSize = 22.sp, + color = PrimaryWhite + ) - Spacer(modifier = Modifier.height(dimens.spacingL)) + Spacer(modifier = Modifier.height(dimens.spacingL)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - trailersList.forEach { trailer -> - Box(modifier = Modifier.weight(1f)) { - CloneflixTrailerItemCard( - trailer = trailer, - onClick = { - if (hasTrailers) onTrailerClick() - else showToast("Playing ${trailer.title}") - }, - modifier = Modifier.fillMaxWidth() - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + trailersList.forEach { trailer -> + Box(modifier = Modifier.weight(1f)) { + CloneflixTrailerItemCard( + trailer = trailer, + onClick = { + if (hasTrailers) onTrailerClick() + else showToast("Playing ${trailer.title}") + }, + modifier = Modifier.fillMaxWidth() + ) + } } } - } - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } // ========================================== // 6. ABOUT SECTION (Figma 121:4932) // ========================================== - Column( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(Grey850) - .padding(dimens.spacing2Xl), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = "About $title", - style = typography.boldTitle2, - fontSize = 20.sp, - color = PrimaryWhite - ) + val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || + writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || + !maturityRating.isNullOrBlank() - Spacer(modifier = Modifier.height(dimens.spacingXs)) + if (hasAboutContent) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(Grey850) + .padding(dimens.spacing2Xl), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = "About $title", + style = typography.boldTitle2, + fontSize = 20.sp, + color = PrimaryWhite + ) - CloneflixAboutMetadataRow(label = "Creator", value = creator) - CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) - CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) - CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) - CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) - CloneflixAboutMetadataRow( - label = "Maturity Rating", - value = "$maturityRating Recommended for ages 16 and up. Contains $advisories." - ) - } + Spacer(modifier = Modifier.height(dimens.spacingXs)) - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + if (!creator.isNullOrBlank()) { + CloneflixAboutMetadataRow(label = "Creator", value = creator) + } + if (castList.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) + } + if (writers.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) + } + if (genres.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) + } + if (moodTags.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) + } + if (!maturityRating.isNullOrBlank()) { + val ratingDescription = if (!advisories.isNullOrBlank()) { + "$maturityRating Recommended for ages 16 and up. Contains $advisories." + } else { + maturityRating + } + CloneflixAboutMetadataRow( + label = "Maturity Rating", + value = ratingDescription + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } } } @@ -904,14 +865,16 @@ fun CloneflixDynamicEpisodeRow( ) } - Text( - text = episode.description ?: "", - style = typography.regularCaption1, - color = Grey100, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - lineHeight = 18.sp - ) + if (!episode.description.isNullOrBlank()) { + Text( + text = episode.description, + style = typography.regularCaption1, + color = Grey100, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + lineHeight = 18.sp + ) + } } // Download Action Button From 0a0c91a07a15bed18e5baeb055d11bfa63698d1a Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 00:07:35 +0700 Subject: [PATCH 09/35] feat(ui): add preview --- app/build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c6990b36d3e..f99f81976e5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -246,6 +246,7 @@ dependencies { implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) + debugImplementation(libs.androidx.compose.ui.tooling) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.foundation) implementation(libs.androidx.activity.compose) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6fbd8233cd3..af0884db010 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -142,6 +142,7 @@ androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = androidx-compose-ui = { module = "androidx.compose.ui:ui" } androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } From a5f9c24380c039cc5e71c29354ea65484928027c Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 00:24:43 +0700 Subject: [PATCH 10/35] fix(ui): fix season dropdown selection mapping, active focus state, and edge-to-edge hero banner --- .../ui/result/ResultFragmentTv.kt | 6 +- .../compose/components/CloneflixDropdown.kt | 164 ++-- .../CloneflixMovieDetailsComposeScreen.kt | 731 +++++++++--------- 3 files changed, 452 insertions(+), 449 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 8e9a09c8a17..2ba77ff28b3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -47,6 +47,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.setNavigationBarColorCompat +import com.lagradost.cloudstream3.utils.UiText import com.lagradost.cloudstream3.utils.txt class ResultFragmentTv : BaseFragment( @@ -60,6 +61,7 @@ class ResultFragmentTv : BaseFragment( private var composeRecommendationsState by mutableStateOf>(emptyList()) private var composeActorsState by mutableStateOf>(emptyList()) private var composeWatchStatusState by mutableStateOf(WatchType.NONE) + private var composeRawSeasonsState by mutableStateOf>>(emptyList()) private var composeSeasonsState by mutableStateOf>(emptyList()) private var composeSelectedSeasonIndexState by mutableStateOf(0) private var composeHasTrailersState by mutableStateOf(false) @@ -215,7 +217,8 @@ class ResultFragmentTv : BaseFragment( ) }, onSeasonSelect = { idx -> - viewModel.changeSeason(idx) + val seasonNumber = composeRawSeasonsState.getOrNull(idx)?.second ?: (idx + 1) + viewModel.changeSeason(seasonNumber) }, onAddToListClick = { val curStatus = composeWatchStatusState @@ -353,6 +356,7 @@ class ResultFragmentTv : BaseFragment( } observe(viewModel.seasonSelections) { + composeRawSeasonsState = it composeSeasonsState = it.map { s -> s.first?.asStringNull(context) ?: "" } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt index 90b7e71ed77..9385bbeb299 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt @@ -19,13 +19,10 @@ 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.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MenuDefaults -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -36,6 +33,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role @@ -77,6 +75,11 @@ fun CloneflixDropdown( val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + label = "DropdownScale" + ) + val rotationAngle by animateFloatAsState( targetValue = if (expanded) 180f else 0f, label = "DropdownChevronRotation" @@ -85,13 +88,12 @@ fun CloneflixDropdown( val borderStroke = when { isFocused || expanded -> BorderStroke(2.dp, PrimaryWhite) variant == CloneflixDropdownVariant.OUTLINE -> BorderStroke(1.dp, PrimaryWhite) - else -> BorderStroke(1.dp, colors.border) + else -> BorderStroke(1.dp, Color(0xFF666666)) } val backgroundColor = when { - expanded -> Color(0xFF2A2A2A) - isFocused -> Color(0xFF222222) - else -> PrimaryBlack + isFocused || expanded -> Color(0xFF333333) + else -> Color(0xFF1E1E1E) } val height = when (variant) { @@ -117,10 +119,13 @@ fun CloneflixDropdown( modifier = Modifier .fillMaxWidth() .height(height) + .scale(scale) .clip(shapes.extraSmall) .background(backgroundColor) .border(borderStroke, shapes.extraSmall) .clickable( + interactionSource = interactionSource, + indication = null, enabled = enabled, role = Role.DropdownList, onClick = { expanded = !expanded } @@ -136,7 +141,8 @@ fun CloneflixDropdown( text = selectedOption, style = typography.regularBody, fontSize = 14.sp, - color = colors.textPrimary, + fontWeight = if (isFocused || expanded) FontWeight.Bold else FontWeight.Normal, + color = PrimaryWhite, modifier = Modifier.weight(1f), maxLines = 1 ) @@ -146,7 +152,7 @@ fun CloneflixDropdown( Icon( painter = painterResource(id = R.drawable.cloneflix_ic_dropdown), contentDescription = null, - tint = colors.textPrimary, + tint = PrimaryWhite, modifier = Modifier .size(16.dp) .rotate(rotationAngle) @@ -158,20 +164,23 @@ fun CloneflixDropdown( onDismissRequest = { expanded = false }, modifier = Modifier .wrapContentHeight() - .heightIn(max = 280.dp) - .background(Color(0xE6000000)) + .heightIn(max = 300.dp) + .background(Color(0xF0181818)) .border(BorderStroke(1.dp, PrimaryWhite), shapes.extraSmall) ) { options.forEach { option -> val isSelected = option == selectedOption + val itemInteractionSource = remember { MutableInteractionSource() } + val isItemFocused by itemInteractionSource.collectIsFocusedAsState() + DropdownMenuItem( text = { Text( text = option, style = typography.regularBody, fontSize = 14.sp, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - color = if (isSelected) colors.primary else colors.textPrimary + fontWeight = if (isSelected || isItemFocused) FontWeight.Bold else FontWeight.Normal, + color = if (isSelected) colors.primary else if (isItemFocused) PrimaryWhite else colors.textPrimary ) }, trailingIcon = if (isSelected) { @@ -194,7 +203,9 @@ fun CloneflixDropdown( ), modifier = Modifier .fillMaxWidth() - .height(36.dp) + .height(40.dp) + .background(if (isItemFocused) Color(0xFF333333) else Color.Transparent) + .focusable(interactionSource = itemInteractionSource) ) } } @@ -202,113 +213,82 @@ fun CloneflixDropdown( } } +/** + * Static showcase container for displaying dropdown state variants. + */ @Composable fun CloneflixDropdownStaticShowcase( title: String, selectedOption: String, - options: List, modifier: Modifier = Modifier, + options: List = emptyList(), + variant: CloneflixDropdownVariant = CloneflixDropdownVariant.DEFAULT, isHoverState: Boolean = false, isExpandedState: Boolean = false, - onOptionSelected: (String) -> Unit = {} + width: Dp? = null ) { - val colors = CloneflixTheme.colors val typography = CloneflixTheme.typography val dimens = CloneflixTheme.dimens val shapes = CloneflixTheme.shapes val borderStroke = when { - isExpandedState || isHoverState -> BorderStroke(1.dp, PrimaryWhite) - else -> BorderStroke(1.dp, colors.border) + isHoverState || isExpandedState -> BorderStroke(2.dp, PrimaryWhite) + variant == CloneflixDropdownVariant.OUTLINE -> BorderStroke(1.dp, PrimaryWhite) + else -> BorderStroke(1.dp, Color(0xFF666666)) } val backgroundColor = when { - isExpandedState -> Color(0xFF2A2A2A) - isHoverState -> Color(0xFF222222) - else -> PrimaryBlack + isHoverState || isExpandedState -> Color(0xFF333333) + else -> Color(0xFF1E1E1E) } + val height = when (variant) { + CloneflixDropdownVariant.COMPACT -> 32.dp + else -> 40.dp + } + + val widthModifier = if (width != null) Modifier.width(width) else Modifier.fillMaxWidth() + Column(modifier = modifier) { Text( text = title, - style = typography.mediumCaption1, - color = colors.textSecondary, + style = typography.boldTitle2, + fontSize = 16.sp, + color = PrimaryWhite, modifier = Modifier.padding(bottom = dimens.spacingXs) ) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(36.dp) - .clip(shapes.extraSmall) - .background(backgroundColor) - .border(borderStroke, shapes.extraSmall) - .padding(horizontal = dimens.spacingM, vertical = dimens.spacingS) - ) { - Text( - text = selectedOption, - style = typography.regularBody, - fontSize = 14.sp, - color = colors.textPrimary, - modifier = Modifier.weight(1f) - ) - - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_dropdown), - contentDescription = null, - tint = colors.textPrimary, - modifier = Modifier - .size(14.dp) - .rotate(if (isExpandedState) 180f else 0f) - ) - } - - if (isExpandedState) { - Spacer(modifier = Modifier.height(2.dp)) - Surface( + Box(modifier = widthModifier) { + Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() + .height(height) .clip(shapes.extraSmall) - .border(BorderStroke(1.dp, colors.border), shapes.extraSmall), - color = Color(0xE6000000) + .background(backgroundColor) + .border(borderStroke, shapes.extraSmall) + .padding(horizontal = dimens.spacingM, vertical = dimens.spacingS) ) { - Column( + Text( + text = selectedOption, + style = typography.regularBody, + fontSize = 14.sp, + fontWeight = if (isHoverState || isExpandedState) FontWeight.Bold else FontWeight.Normal, + color = PrimaryWhite, + modifier = Modifier.weight(1f), + maxLines = 1 + ) + + Spacer(modifier = Modifier.width(dimens.spacingS)) + + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_dropdown), + contentDescription = null, + tint = PrimaryWhite, modifier = Modifier - .fillMaxWidth() - .heightIn(max = 200.dp) - .verticalScroll(rememberScrollState()) - .padding(dimens.spacingS) - ) { - options.forEach { option -> - val isSelected = option == selectedOption - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clip(shapes.extraSmall) - .clickable { onOptionSelected(option) } - .padding(horizontal = dimens.spacingS, vertical = 6.dp) - ) { - Text( - text = option, - style = typography.regularBody, - fontSize = 14.sp, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - color = if (isSelected) colors.primary else colors.textPrimary, - modifier = Modifier.weight(1f) - ) - if (isSelected) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_check), - contentDescription = "Selected", - tint = colors.primary, - modifier = Modifier.size(14.dp) - ) - } - } - } - } + .size(16.dp) + .rotate(if (isExpandedState) 180f else 0f) + ) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index 6637d584607..92c0c3159da 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -161,6 +161,13 @@ fun CloneflixMovieDetailsComposeScreen( mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "" }) } + // Keep selectedSeasonText in sync when selectedSeasonIndex prop updates from ViewModel + LaunchedEffect(selectedSeasonIndex, seasonOptions) { + if (seasonOptions.isNotEmpty()) { + selectedSeasonText = seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.first() } + } + } + fun showToast(message: String) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } @@ -189,31 +196,17 @@ fun CloneflixMovieDetailsComposeScreen( .fillMaxSize() .background(colors.background) .verticalScroll(scrollState) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) ) { // ========================================== - // 1. HERO BANNER SECTION (Figma 121:4893) + // 1. HERO BANNER SECTION (Figma 121:4893 - Edge to Edge Full Banner) // ========================================== Box( modifier = Modifier .fillMaxWidth() - .height(440.dp) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background( - Brush.verticalGradient( - colors = listOf( - Grey700, - Grey850, - PrimaryBlack - ) - ) - ) - .border( - BorderStroke(dimens.borderDefault, colors.border), - RoundedCornerShape(CloneflixTokens.RadiusCardMedium) - ) + .height(500.dp) + .background(PrimaryBlack) ) { - // Live Backdrop Image (if available) + // Live Backdrop Image filling full banner width & height if (!backdropUrl.isNullOrBlank()) { AndroidView( factory = { ctx -> @@ -228,46 +221,65 @@ fun CloneflixMovieDetailsComposeScreen( ) } - // Top-Right Close Button - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_close), - contentDescription = "Close", - onClick = { - onCloseClick() - }, + // Top Header Gradient Overlay (darkens top area for close button & status bar) + Box( modifier = Modifier - .align(Alignment.TopEnd) - .padding(dimens.spacingL) + .fillMaxWidth() + .height(140.dp) + .align(Alignment.TopCenter) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Black.copy(alpha = 0.85f), + TransparentBlack60, + Color.Transparent + ) + ) + ) ) - // Hero Bottom Gradient Overlay & Title/Action Controls + // Bottom Hero Gradient Overlay (fades smoothly down into the page background) Box( modifier = Modifier .fillMaxWidth() - .fillMaxHeight(0.70f) - .align(Alignment.BottomStart) + .fillMaxHeight(0.85f) + .align(Alignment.BottomCenter) .background( Brush.verticalGradient( colors = listOf( Color.Transparent, TransparentBlack60, + PrimaryBlack.copy(alpha = 0.92f), PrimaryBlack ) ) ) ) + // Top-Right Close Button + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_close), + contentDescription = "Close", + onClick = { + onCloseClick() + }, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(dimens.spacing2Xl) + ) + + // Title, Wordmark, and Action Buttons aligned at the bottom Column( modifier = Modifier .align(Alignment.BottomStart) - .padding(dimens.spacing2Xl) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) ) { // Wordmark / Logo / Title if (!logoUrl.isNullOrBlank()) { Box( modifier = Modifier - .height(64.dp) - .width(220.dp) + .height(72.dp) + .width(260.dp) ) { AndroidView( factory = { ctx -> @@ -287,7 +299,7 @@ fun CloneflixMovieDetailsComposeScreen( Text( text = title, style = typography.boldTitle1, - fontSize = 32.sp, + fontSize = 34.sp, color = PrimaryWhite, maxLines = 2, overflow = TextOverflow.Ellipsis @@ -339,390 +351,397 @@ fun CloneflixMovieDetailsComposeScreen( } } - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // ========================================== - // 2. MOVIE INFO & METADATA SECTION (Figma 121:4894) + // CONTENT SECTIONS WRAPPER WITH SAFE MARGINS // ========================================== - val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) ) { - // Left Column: Badges, Top 10, Synopsis - Column( - modifier = Modifier - .weight(if (hasRightColumn) 0.65f else 1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + // ========================================== + // 2. MOVIE INFO & METADATA SECTION (Figma 121:4894) + // ========================================== + val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) ) { - // Line 1: Match Score, Year, Duration, Quality (rendered only if available) - val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || - !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() - - if (hasLine1) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (!matchScore.isNullOrBlank()) { - Text( - text = matchScore, - color = GreenAccent, - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - } + // Left Column: Badges, Top 10, Synopsis + Column( + modifier = Modifier + .weight(if (hasRightColumn) 0.65f else 1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + // Line 1: Match Score, Year, Duration, Quality (rendered only if available) + val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() + + if (hasLine1) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (!matchScore.isNullOrBlank()) { + Text( + text = matchScore, + color = GreenAccent, + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + } - if (!releaseYear.isNullOrBlank()) { - Text( - text = releaseYear, - color = PrimaryWhite, - style = typography.regularBody - ) - } + if (!releaseYear.isNullOrBlank()) { + Text( + text = releaseYear, + color = PrimaryWhite, + style = typography.regularBody + ) + } - if (!seasonsCount.isNullOrBlank()) { - Text( - text = seasonsCount, - color = PrimaryWhite, - style = typography.regularBody - ) - } + if (!seasonsCount.isNullOrBlank()) { + Text( + text = seasonsCount, + color = PrimaryWhite, + style = typography.regularBody + ) + } - if (!quality.isNullOrBlank()) { - CloneflixVideoQualityBadge(quality = quality) + if (!quality.isNullOrBlank()) { + CloneflixVideoQualityBadge(quality = quality) + } } } - } - // Line 2: Rating & Content Advisories (rendered only if available) - if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - if (!maturityRating.isNullOrBlank()) { - CloneflixMaturityRating(rating = maturityRating) - } - if (!advisories.isNullOrBlank()) { - Text( - text = advisories, - color = Grey100, - style = typography.regularCaption1 - ) + // Line 2: Rating & Content Advisories (rendered only if available) + if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (!maturityRating.isNullOrBlank()) { + CloneflixMaturityRating(rating = maturityRating) + } + if (!advisories.isNullOrBlank()) { + Text( + text = advisories, + color = Grey100, + style = typography.regularCaption1 + ) + } } } - } - // Line 3: Top 10 Rank Badge (rendered only if available) - if (!top10RankText.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - modifier = Modifier.padding(vertical = dimens.spacingXs) - ) { - Box( - modifier = Modifier + // Line 3: Top 10 Rank Badge (rendered only if available) + if (!top10RankText.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + modifier = Modifier.padding(vertical = dimens.spacingXs) + ) { + Box( + modifier = Modifier .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) .background(PrimaryRed) .padding(horizontal = 6.dp, vertical = 2.dp) - ) { + ) { + Text( + text = "TOP 10", + style = typography.regularCaption2, + color = PrimaryWhite, + fontWeight = FontWeight.Black + ) + } Text( - text = "TOP 10", - style = typography.regularCaption2, - color = PrimaryWhite, - fontWeight = FontWeight.Black + text = top10RankText, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite ) } + } + + // Line 4: Synopsis + if (synopsis.isNotBlank()) { Text( - text = top10RankText, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite + text = synopsis, + style = typography.regularBody, + color = Grey50, + lineHeight = 22.sp ) } } - // Line 4: Synopsis - if (synopsis.isNotBlank()) { - Text( - text = synopsis, - style = typography.regularBody, - color = Grey50, - lineHeight = 22.sp - ) - } - } - - // Right Column: Cast, Genres, Mood Tags (rendered only if metadata exists) - if (hasRightColumn) { - Column( - modifier = Modifier - .weight(0.35f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - // Cast Row - if (castList.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Cast", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = castList.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) + // Right Column: Cast, Genres, Mood Tags (rendered only if metadata exists) + if (hasRightColumn) { + Column( + modifier = Modifier + .weight(0.35f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + // Cast Row + if (castList.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Cast", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = castList.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } } - } - // Genres Row - if (genres.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Genres", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = genres.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite - ) + // Genres Row + if (genres.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Genres", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = genres.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite + ) + } } - } - // This show is (Moods) - if (moodTags.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "This show is", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = moodTags.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite - ) + // This show is (Moods) + if (moodTags.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "This show is", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = moodTags.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite + ) + } } } } } - } - - if (episodesToDisplay.isNotEmpty() || recommendationCards.isNotEmpty() || trailersList.isNotEmpty()) { - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } - // ========================================== - // 3. EPISODES & SEASONS SECTION (Figma 121:4895) - // ========================================== - if (episodesToDisplay.isNotEmpty()) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Episodes", - style = typography.boldTitle2, - fontSize = 24.sp, - color = PrimaryWhite - ) + if (episodesToDisplay.isNotEmpty() || recommendationCards.isNotEmpty() || trailersList.isNotEmpty()) { + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } - // Season Selection Dropdown - if (seasonOptions.size > 1) { - CloneflixDropdown( - options = seasonOptions, - selectedOption = selectedSeasonText, - onOptionSelected = { seasonStr -> - selectedSeasonText = seasonStr - val idx = seasonOptions.indexOf(seasonStr) - if (idx >= 0) onSeasonSelect?.invoke(idx) - }, - width = 180.dp + // ========================================== + // 3. EPISODES & SEASONS SECTION (Figma 121:4895) + // ========================================== + if (episodesToDisplay.isNotEmpty()) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Episodes", + style = typography.boldTitle2, + fontSize = 24.sp, + color = PrimaryWhite ) + + // Season Selection Dropdown + if (seasonOptions.size > 1) { + CloneflixDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { seasonStr -> + selectedSeasonText = seasonStr + val idx = seasonOptions.indexOf(seasonStr) + if (idx >= 0) onSeasonSelect?.invoke(idx) + }, + width = 180.dp + ) + } } - } - Spacer(modifier = Modifier.height(dimens.spacingL)) + Spacer(modifier = Modifier.height(dimens.spacingL)) - // Episodes List - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - episodesToDisplay.forEach { ep -> - CloneflixDynamicEpisodeRow( - episode = ep, - onClick = { - if (onEpisodeClick != null) onEpisodeClick(ep) - else showToast("Playing ${ep.headerName}: ${ep.name}") - }, - onDownloadClick = { - if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) - else showToast("Downloading Episode ${ep.episode}") - } - ) + // Episodes List + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + episodesToDisplay.forEach { ep -> + CloneflixDynamicEpisodeRow( + episode = ep, + onClick = { + if (onEpisodeClick != null) onEpisodeClick(ep) + else showToast("Playing ${ep.headerName}: ${ep.name}") + }, + onDownloadClick = { + if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) + else showToast("Downloading Episode ${ep.episode}") + } + ) + } } - } - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } - // ========================================== - // 4. MORE LIKE THIS SECTION (Figma 121:4909) - // ========================================== - if (recommendationCards.isNotEmpty()) { - Text( - text = "More Like This", - style = typography.boldTitle2, - fontSize = 22.sp, - color = PrimaryWhite - ) + // ========================================== + // 4. MORE LIKE THIS SECTION (Figma 121:4909) + // ========================================== + if (recommendationCards.isNotEmpty()) { + Text( + text = "More Like This", + style = typography.boldTitle2, + fontSize = 22.sp, + color = PrimaryWhite + ) - Spacer(modifier = Modifier.height(dimens.spacingL)) + Spacer(modifier = Modifier.height(dimens.spacingL)) - // 3-Column Grid of Recommendation Cards - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - val chunkedRecommendations = recommendationCards.chunked(3) - chunkedRecommendations.forEach { rowItems -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - rowItems.forEachIndexed { idx, cardItem -> - Box(modifier = Modifier.weight(1f)) { - CloneflixMovieCard( - title = cardItem.title, - type = cardItem.type, - size = CloneflixMovieCardSize.MEDIUM, - badge = cardItem.badge, - runtime = cardItem.runtime, - posterUrl = cardItem.posterUrl, - showLogo = cardItem.showLogo, - onClick = { - val rec = dynamicRecommendations?.find { it.name == cardItem.title } - if (rec != null && onRecommendationClick != null) { - onRecommendationClick(rec) - } else { - showToast("Opened recommendation: ${cardItem.title}") - } - }, - modifier = Modifier.fillMaxWidth() - ) + // 3-Column Grid of Recommendation Cards + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + val chunkedRecommendations = recommendationCards.chunked(3) + chunkedRecommendations.forEach { rowItems -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + rowItems.forEachIndexed { idx, cardItem -> + Box(modifier = Modifier.weight(1f)) { + CloneflixMovieCard( + title = cardItem.title, + type = cardItem.type, + size = CloneflixMovieCardSize.MEDIUM, + badge = cardItem.badge, + runtime = cardItem.runtime, + posterUrl = cardItem.posterUrl, + showLogo = cardItem.showLogo, + onClick = { + val rec = dynamicRecommendations?.find { it.name == cardItem.title } + if (rec != null && onRecommendationClick != null) { + onRecommendationClick(rec) + } else { + showToast("Opened recommendation: ${cardItem.title}") + } + }, + modifier = Modifier.fillMaxWidth() + ) + } } - } - if (rowItems.size < 3) { - repeat(3 - rowItems.size) { - Spacer(modifier = Modifier.weight(1f)) + if (rowItems.size < 3) { + repeat(3 - rowItems.size) { + Spacer(modifier = Modifier.weight(1f)) + } } } } } - } - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } - // ========================================== - // 5. TRAILERS & MORE SECTION (Figma 121:4925) - // ========================================== - if (trailersList.isNotEmpty()) { - Text( - text = "Trailers & More", - style = typography.boldTitle2, - fontSize = 22.sp, - color = PrimaryWhite - ) + // ========================================== + // 5. TRAILERS & MORE SECTION (Figma 121:4925) + // ========================================== + if (trailersList.isNotEmpty()) { + Text( + text = "Trailers & More", + style = typography.boldTitle2, + fontSize = 22.sp, + color = PrimaryWhite + ) - Spacer(modifier = Modifier.height(dimens.spacingL)) + Spacer(modifier = Modifier.height(dimens.spacingL)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - trailersList.forEach { trailer -> - Box(modifier = Modifier.weight(1f)) { - CloneflixTrailerItemCard( - trailer = trailer, - onClick = { - if (hasTrailers) onTrailerClick() - else showToast("Playing ${trailer.title}") - }, - modifier = Modifier.fillMaxWidth() - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + trailersList.forEach { trailer -> + Box(modifier = Modifier.weight(1f)) { + CloneflixTrailerItemCard( + trailer = trailer, + onClick = { + if (hasTrailers) onTrailerClick() + else showToast("Playing ${trailer.title}") + }, + modifier = Modifier.fillMaxWidth() + ) + } } } + + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) } - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } + // ========================================== + // 6. ABOUT SECTION (Figma 121:4932) + // ========================================== + val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || + writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || + !maturityRating.isNullOrBlank() - // ========================================== - // 6. ABOUT SECTION (Figma 121:4932) - // ========================================== - val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || - writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || - !maturityRating.isNullOrBlank() - - if (hasAboutContent) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(Grey850) - .padding(dimens.spacing2Xl), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = "About $title", - style = typography.boldTitle2, - fontSize = 20.sp, - color = PrimaryWhite - ) + if (hasAboutContent) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(Grey850) + .padding(dimens.spacing2Xl), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = "About $title", + style = typography.boldTitle2, + fontSize = 20.sp, + color = PrimaryWhite + ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) + Spacer(modifier = Modifier.height(dimens.spacingXs)) - if (!creator.isNullOrBlank()) { - CloneflixAboutMetadataRow(label = "Creator", value = creator) - } - if (castList.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) - } - if (writers.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) - } - if (genres.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) - } - if (moodTags.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) - } - if (!maturityRating.isNullOrBlank()) { - val ratingDescription = if (!advisories.isNullOrBlank()) { - "$maturityRating Recommended for ages 16 and up. Contains $advisories." - } else { - maturityRating + if (!creator.isNullOrBlank()) { + CloneflixAboutMetadataRow(label = "Creator", value = creator) + } + if (castList.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) + } + if (writers.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) + } + if (genres.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) + } + if (moodTags.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) + } + if (!maturityRating.isNullOrBlank()) { + val ratingDescription = if (!advisories.isNullOrBlank()) { + "$maturityRating Recommended for ages 16 and up. Contains $advisories." + } else { + maturityRating + } + CloneflixAboutMetadataRow( + label = "Maturity Rating", + value = ratingDescription + ) } - CloneflixAboutMetadataRow( - label = "Maturity Rating", - value = ratingDescription - ) } - } - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + } } } } From f2bd09869109dece8d9c403de05e557a284848bc Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 00:30:25 +0700 Subject: [PATCH 11/35] fix(ui): update recommendations to poster layout with title overlay, make about card focusable, and ensure full hero banner visibility --- .../compose/components/CloneflixDefaults.kt | 4 ++ .../compose/components/CloneflixMovieCard.kt | 38 +++++++++++++ .../CloneflixMovieDetailsComposeScreen.kt | 55 ++++++++++++------- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt index 699f6e359df..96053a9bb75 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt @@ -44,6 +44,7 @@ object CloneflixMovieCardDefaults { val WidthPlayerPreview: Dp = 245.dp val WidthContinueWatchingMedium: Dp = 218.dp val WidthContinueWatchingSmall: Dp = 160.dp + val WidthPoster: Dp = 150.dp val HeightMedium: Dp = 123.dp val HeightSmall: Dp = 72.dp @@ -57,6 +58,7 @@ object CloneflixMovieCardDefaults { val HeightPlayerPreview: Dp = 152.dp val HeightContinueWatchingMedium: Dp = 133.dp val HeightContinueWatchingSmall: Dp = 98.dp + val HeightPoster: Dp = 225.dp val RankBoxWidth: Dp = 90.dp val RankFontSize: TextUnit = 110.sp @@ -76,6 +78,7 @@ object CloneflixMovieCardDefaults { val ActionButtonSize: Dp = 40.dp fun cardWidth(type: CloneflixMovieCardType, size: CloneflixMovieCardSize): Dp = when (type) { + CloneflixMovieCardType.POSTER -> if (size == CloneflixMovieCardSize.MEDIUM) WidthPoster else 110.dp CloneflixMovieCardType.TOP10 -> if (size == CloneflixMovieCardSize.MEDIUM) WidthTop10Medium else WidthTop10Small CloneflixMovieCardType.EPISODE -> if (size == CloneflixMovieCardSize.SMALL) WidthEpisodeSmall else WidthEpisodeMedium CloneflixMovieCardType.MORE_LIKE_THIS, @@ -87,6 +90,7 @@ object CloneflixMovieCardDefaults { } fun cardHeight(type: CloneflixMovieCardType, size: CloneflixMovieCardSize): Dp = when (type) { + CloneflixMovieCardType.POSTER -> if (size == CloneflixMovieCardSize.MEDIUM) HeightPoster else 165.dp CloneflixMovieCardType.TOP10 -> if (size == CloneflixMovieCardSize.MEDIUM) HeightTop10Medium else HeightTop10Small CloneflixMovieCardType.EPISODE -> if (size == CloneflixMovieCardSize.SMALL) HeightEpisodeSmall else HeightEpisodeMedium CloneflixMovieCardType.MORE_LIKE_THIS, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt index f95d8c56c7c..b01c9603a7c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt @@ -76,6 +76,7 @@ enum class CloneflixMovieCardSize { enum class CloneflixMovieCardType { DEFAULT, + POSTER, MORE_LIKE_THIS, MORE_LIKE_THIS_WITH_PLAY, EPISODE, @@ -97,6 +98,7 @@ fun CloneflixMovieCard( size: CloneflixMovieCardSize = CloneflixMovieCardSize.MEDIUM, badge: CloneflixBadgeType? = null, showLogo: Boolean = false, + showBottomTitle: Boolean = false, top10Rank: Int? = null, progress: Float? = null, runtime: String? = null, @@ -191,6 +193,7 @@ fun CloneflixMovieCard( MovieCardSurface( title = title, showLogo = showLogo, + showBottomTitle = showBottomTitle, badge = badge, runtime = runtime, timestamp = timestamp, @@ -213,6 +216,7 @@ fun CloneflixMovieCard( MovieCardSurface( title = title, showLogo = showLogo, + showBottomTitle = showBottomTitle, badge = badge, runtime = runtime, timestamp = timestamp, @@ -247,6 +251,7 @@ private fun MovieCardSurface( timestamp: String?, progress: Float?, showCenterPlay: Boolean, + showBottomTitle: Boolean = false, posterUrl: String? = null ) { val typography = CloneflixTheme.typography @@ -389,6 +394,37 @@ private fun MovieCardSurface( ) } } + + // Bottom Title Overlay with Black Background Gradient + if (showBottomTitle && !title.isNullOrBlank()) { + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomStart) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color(0xD9000000), + Color(0xF2000000), + Color(0xFF000000) + ) + ) + ) + .padding(horizontal = 8.dp, vertical = 6.dp) + ) { + Text( + text = title, + style = typography.regularCaption1, + fontWeight = FontWeight.Bold, + color = PrimaryWhite, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + fontSize = 12.sp, + lineHeight = 15.sp + ) + } + } } } @@ -569,6 +605,7 @@ fun CloneflixMovieBlockRow( size = item.size, badge = item.badge, showLogo = item.showLogo, + showBottomTitle = item.showBottomTitle, top10Rank = item.top10Rank, progress = item.progress, runtime = item.runtime, @@ -588,6 +625,7 @@ data class CloneflixMovieCardItem( val size: CloneflixMovieCardSize = CloneflixMovieCardSize.MEDIUM, val badge: CloneflixBadgeType? = null, val showLogo: Boolean = false, + val showBottomTitle: Boolean = false, val top10Rank: Int? = null, val progress: Float? = null, val runtime: String? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index 92c0c3159da..5372405a9e0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -85,6 +86,7 @@ import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 import com.lagradost.cloudstream3.utils.ImageLoader.loadImage +import kotlinx.coroutines.launch /** * Data representation for trailer / extra clips in Figma Node 121:4925. @@ -148,6 +150,7 @@ fun CloneflixMovieDetailsComposeScreen( val playButtonFocusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { + scrollState.scrollTo(0) playButtonFocusRequester.requestFocus() } @@ -182,9 +185,10 @@ fun CloneflixMovieDetailsComposeScreen( dynamicRecommendations?.map { rec -> CloneflixMovieCardItem( title = rec.name, - type = CloneflixMovieCardType.MORE_LIKE_THIS, + type = CloneflixMovieCardType.POSTER, posterUrl = rec.posterUrl, - showLogo = true + showLogo = false, + showBottomTitle = true ) } ?: emptyList() } @@ -203,7 +207,7 @@ fun CloneflixMovieDetailsComposeScreen( Box( modifier = Modifier .fillMaxWidth() - .height(500.dp) + .height(440.dp) .background(PrimaryBlack) ) { // Live Backdrop Image filling full banner width & height @@ -225,7 +229,7 @@ fun CloneflixMovieDetailsComposeScreen( Box( modifier = Modifier .fillMaxWidth() - .height(140.dp) + .height(130.dp) .align(Alignment.TopCenter) .background( Brush.verticalGradient( @@ -278,8 +282,8 @@ fun CloneflixMovieDetailsComposeScreen( if (!logoUrl.isNullOrBlank()) { Box( modifier = Modifier - .height(72.dp) - .width(260.dp) + .height(68.dp) + .width(240.dp) ) { AndroidView( factory = { ctx -> @@ -299,7 +303,7 @@ fun CloneflixMovieDetailsComposeScreen( Text( text = title, style = typography.boldTitle1, - fontSize = 34.sp, + fontSize = 32.sp, color = PrimaryWhite, maxLines = 2, overflow = TextOverflow.Ellipsis @@ -317,7 +321,13 @@ fun CloneflixMovieDetailsComposeScreen( CloneflixHeroPlayButton( onClick = onPlayClick, text = "Play", - modifier = Modifier.focusRequester(playButtonFocusRequester) + modifier = Modifier + .focusRequester(playButtonFocusRequester) + .onFocusChanged { state -> + if (state.isFocused && scrollState.value > 0) { + // Keep Hero banner fully visible when play button is focused + } + } ) // Add to My List (+) @@ -596,7 +606,7 @@ fun CloneflixMovieDetailsComposeScreen( } // ========================================== - // 4. MORE LIKE THIS SECTION (Figma 121:4909) + // 4. MORE LIKE THIS SECTION (Figma 121:4909 - Poster Layout) // ========================================== if (recommendationCards.isNotEmpty()) { Text( @@ -608,27 +618,28 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 3-Column Grid of Recommendation Cards + // Responsive Poster Grid (6 columns per row for TV immersion) Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { - val chunkedRecommendations = recommendationCards.chunked(3) + val chunkedRecommendations = recommendationCards.chunked(6) chunkedRecommendations.forEach { rowItems -> Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - rowItems.forEachIndexed { idx, cardItem -> + rowItems.forEach { cardItem -> Box(modifier = Modifier.weight(1f)) { CloneflixMovieCard( title = cardItem.title, - type = cardItem.type, + type = CloneflixMovieCardType.POSTER, size = CloneflixMovieCardSize.MEDIUM, badge = cardItem.badge, runtime = cardItem.runtime, posterUrl = cardItem.posterUrl, - showLogo = cardItem.showLogo, + showLogo = false, + showBottomTitle = true, onClick = { val rec = dynamicRecommendations?.find { it.name == cardItem.title } if (rec != null && onRecommendationClick != null) { @@ -641,8 +652,8 @@ fun CloneflixMovieDetailsComposeScreen( ) } } - if (rowItems.size < 3) { - repeat(3 - rowItems.size) { + if (rowItems.size < 6) { + repeat(6 - rowItems.size) { Spacer(modifier = Modifier.weight(1f)) } } @@ -688,18 +699,24 @@ fun CloneflixMovieDetailsComposeScreen( } // ========================================== - // 6. ABOUT SECTION (Figma 121:4932) + // 6. ABOUT SECTION (Figma 121:4932 - Focusable Container for D-Pad) // ========================================== val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || !maturityRating.isNullOrBlank() if (hasAboutContent) { + val aboutInteractionSource = remember { MutableInteractionSource() } + val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() + val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(1.dp, Color(0xFF333333)) + Column( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(Grey850) + .background(if (isAboutFocused) Color(0xFF262626) else Grey850) + .border(aboutBorder, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .focusable(interactionSource = aboutInteractionSource) .padding(dimens.spacing2Xl), verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { From 5165e49184b11dfb95330408e4937d93a20f8e1c Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 01:50:45 +0700 Subject: [PATCH 12/35] ui(detail): move CloneflixLogoView to the left of exit button in hero banner --- .../CloneflixMovieDetailsComposeScreen.kt | 144 +++++++++++------- 1 file changed, 90 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index 5372405a9e0..037f13a535c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -37,12 +37,14 @@ 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.draw.clipToBounds import androidx.compose.ui.draw.scale import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role @@ -75,10 +77,13 @@ import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTokens import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoQualityBadge import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent +import com.lagradost.cloudstream3.ui.revamp.compose.theme.getRatingScoreColor import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey750 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack @@ -106,6 +111,7 @@ data class CloneflixTrailerData( fun CloneflixMovieDetailsComposeScreen( modifier: Modifier = Modifier, title: String = CloneflixSampleData.SAMPLE_TITLE_HOUSE_OF_NINJAS, + providerName: String? = null, backdropUrl: String? = null, posterUrl: String? = null, logoUrl: String? = null, @@ -142,6 +148,10 @@ fun CloneflixMovieDetailsComposeScreen( onCloseClick: () -> Unit = {} ) { val context = LocalContext.current + val configuration = LocalConfiguration.current + val screenHeight = configuration.screenHeightDp.dp + val heroHeight = (screenHeight * 0.65f).coerceAtLeast(380.dp) + val colors = CloneflixTheme.colors val typography = CloneflixTheme.typography val dimens = CloneflixTheme.dimens @@ -202,13 +212,14 @@ fun CloneflixMovieDetailsComposeScreen( .verticalScroll(scrollState) ) { // ========================================== - // 1. HERO BANNER SECTION (Figma 121:4893 - Edge to Edge Full Banner) + // 1. HERO BANNER SECTION (Figma 121:4893 - 60% Screen Height Crop) // ========================================== Box( modifier = Modifier .fillMaxWidth() - .height(440.dp) - .background(PrimaryBlack) + .height(heroHeight) + .clipToBounds() + .background(colors.background) ) { // Live Backdrop Image filling full banner width & height if (!backdropUrl.isNullOrBlank()) { @@ -216,32 +227,19 @@ fun CloneflixMovieDetailsComposeScreen( factory = { ctx -> ImageView(ctx).apply { scaleType = ImageView.ScaleType.CENTER_CROP + adjustViewBounds = false + clipToOutline = true } }, update = { imageView -> imageView.loadImage(backdropUrl) }, - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() + .clipToBounds() ) } - // Top Header Gradient Overlay (darkens top area for close button & status bar) - Box( - modifier = Modifier - .fillMaxWidth() - .height(130.dp) - .align(Alignment.TopCenter) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Black.copy(alpha = 0.85f), - TransparentBlack60, - Color.Transparent - ) - ) - ) - ) - // Bottom Hero Gradient Overlay (fades smoothly down into the page background) Box( modifier = Modifier @@ -252,25 +250,34 @@ fun CloneflixMovieDetailsComposeScreen( Brush.verticalGradient( colors = listOf( Color.Transparent, - TransparentBlack60, - PrimaryBlack.copy(alpha = 0.92f), - PrimaryBlack + colors.background.copy(alpha = 0.5f), + colors.background.copy(alpha = 0.92f), + colors.background ) ) ) ) - // Top-Right Close Button - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_close), - contentDescription = "Close", - onClick = { - onCloseClick() - }, + // Top-Right: Logo + Close Button + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), modifier = Modifier .align(Alignment.TopEnd) .padding(dimens.spacing2Xl) - ) + ) { + CloneflixLogoView( + variant = CloneflixLogoVariant.WORDMARK_MEDIUM, + text = providerName + ) + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_close), + contentDescription = "Close", + onClick = { + onCloseClick() + } + ) + } // Title, Wordmark, and Action Buttons aligned at the bottom Column( @@ -278,7 +285,7 @@ fun CloneflixMovieDetailsComposeScreen( .align(Alignment.BottomStart) .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) ) { - // Wordmark / Logo / Title + // Movie Title Logo or Title Text if (!logoUrl.isNullOrBlank()) { Box( modifier = Modifier @@ -298,8 +305,6 @@ fun CloneflixMovieDetailsComposeScreen( ) } } else { - CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) - Spacer(modifier = Modifier.height(dimens.spacingS)) Text( text = title, style = typography.boldTitle1, @@ -312,24 +317,26 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Action Buttons Row (Play, Add/List, Thumbs Up, Trailer) + // Primary Play Button (Separate dedicated section) + CloneflixHeroPlayButton( + onClick = onPlayClick, + text = "Play", + modifier = Modifier + .focusRequester(playButtonFocusRequester) + .onFocusChanged { state -> + if (state.isFocused && scrollState.value > 0) { + // Keep Hero banner fully visible when play button is focused + } + } + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + // Secondary Action Buttons Row (Add to List, Like, Trailer) below Play button Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - // White Primary Play Button - CloneflixHeroPlayButton( - onClick = onPlayClick, - text = "Play", - modifier = Modifier - .focusRequester(playButtonFocusRequester) - .onFocusChanged { state -> - if (state.isFocused && scrollState.value > 0) { - // Keep Hero banner fully visible when play button is focused - } - } - ) - // Add to My List (+) CloneflixCircleActionButton( icon = painterResource( @@ -397,7 +404,7 @@ fun CloneflixMovieDetailsComposeScreen( if (!matchScore.isNullOrBlank()) { Text( text = matchScore, - color = GreenAccent, + color = getRatingScoreColor(matchScore), style = typography.mediumBody, fontWeight = FontWeight.Bold ) @@ -708,13 +715,13 @@ fun CloneflixMovieDetailsComposeScreen( if (hasAboutContent) { val aboutInteractionSource = remember { MutableInteractionSource() } val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() - val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(1.dp, Color(0xFF333333)) + val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(1.dp, Grey600) Column( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(if (isAboutFocused) Color(0xFF262626) else Grey850) + .background(if (isAboutFocused) Grey750 else Grey850) .border(aboutBorder, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) .focusable(interactionSource = aboutInteractionSource) .padding(dimens.spacing2Xl), @@ -1037,10 +1044,39 @@ fun CloneflixAboutMetadataRow( } } -@Preview(device = Devices.TV_1080p, showBackground = true) +@Preview( + name = "TV 1080p Landscape", + device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape", + showBackground = true, + backgroundColor = 0xFF141414 +) +@Preview( + name = "TV 720p Landscape", + device = "spec:width=1280dp,height=720dp,dpi=213,orientation=landscape", + showBackground = true, + backgroundColor = 0xFF141414 +) @Composable private fun CloneflixMovieDetailsComposeScreenPreview() { CloneflixTheme { - CloneflixMovieDetailsComposeScreen() + CloneflixMovieDetailsComposeScreen( + title = "HOUSE OF NINJAS", + providerName = "SUPERSTREAM", + matchScore = "98% Match", + releaseYear = "2024", + seasonsCount = "1 Season", + quality = "4K Ultra HD", + maturityRating = "TV-MA", + advisories = "smoking, violence, language", + top10RankText = "#2 in TV Shows Today", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + cast = listOf("Kento Kaku", "Yosuke Eguchi", "Tae Kimura", "Kengo Kora"), + genres = listOf("TV Dramas", "Action", "Japanese", "Suspenseful"), + moodTags = listOf("Dark", "Suspenseful", "Exciting"), + creator = "Dave Boyle", + writers = listOf("Dave Boyle", "Masahiro Yamaura", "Kota Oishi"), + isInWatchList = false, + hasTrailers = true + ) } } From a3f8e0a43de72f7b27da18dc8c98cfc8a8275a03 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 01:51:00 +0700 Subject: [PATCH 13/35] fix(dropdown): remove redundant .focusable() to fix double Enter press on TV DPAD --- .../ui/revamp/compose/components/CloneflixDropdown.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt index 9385bbeb299..22075d27233 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt @@ -130,7 +130,6 @@ fun CloneflixDropdown( role = Role.DropdownList, onClick = { expanded = !expanded } ) - .focusable(interactionSource = interactionSource) .padding(horizontal = dimens.spacingM, vertical = dimens.spacingS) .semantics { role = Role.DropdownList @@ -204,8 +203,8 @@ fun CloneflixDropdown( modifier = Modifier .fillMaxWidth() .height(40.dp) - .background(if (isItemFocused) Color(0xFF333333) else Color.Transparent) - .focusable(interactionSource = itemInteractionSource) + .background(if (isItemFocused) Color(0xFF333333) else Color.Transparent), + interactionSource = itemInteractionSource ) } } From 40d9899985f803b170df521e80aed0ea899d2e69 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 01:54:33 +0700 Subject: [PATCH 14/35] feat(design-system): refine Cloneflix UI components and theme - CloneflixComposeTheme: expand theme tokens with new color palette entries - CloneflixBadgeComponents: adjust badge styling and layout tweaks - CloneflixMovieCard: refine card layout and visual treatment - CloneflixMovieInfoComponents: minor info layout improvements - CloneflixHeroBannersComposeScreen: hero banner UI enhancements - CloneflixMoviePreviewComposeScreen: preview screen polish - ResultFragmentTv: minor additions for TV result fragment --- .../ui/result/ResultFragmentTv.kt | 2 ++ .../components/CloneflixBadgeComponents.kt | 20 +++++++---- .../compose/components/CloneflixMovieCard.kt | 14 +++++--- .../CloneflixMovieInfoComponents.kt | 5 +-- .../CloneflixHeroBannersComposeScreen.kt | 7 +++- .../CloneflixMoviePreviewComposeScreen.kt | 7 +++- .../compose/theme/CloneflixComposeTheme.kt | 33 ++++++++++++++++++- 7 files changed, 71 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 2ba77ff28b3..fe6dc99b27c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -147,6 +147,7 @@ class ResultFragmentTv : BaseFragment( if (page is Resource.Success) { val d = page.value val title = d.titleText.asStringNull(context) ?: d.title + val provider = d.apiName.asStringNull(context) ?: storedData.apiName val plot = d.plotText.asStringNull(context) ?: "" val year = d.yearText?.asStringNull(context) val duration = d.durationText?.asStringNull(context) @@ -157,6 +158,7 @@ class ResultFragmentTv : BaseFragment( CloneflixMovieDetailsComposeScreen( title = title, + providerName = provider, backdropUrl = d.posterBackgroundImage ?: d.posterImage, posterUrl = d.posterImage, logoUrl = d.logoUrl, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt index 637e09684e1..f5a711c6843 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt @@ -325,36 +325,40 @@ enum class CloneflixLogoVariant { /** * Cloneflix Brand Logo Composable (Wordmark & Lettermark). + * Optionally displays provider/plugin name or custom text in brand styling. */ @Composable fun CloneflixLogoView( variant: CloneflixLogoVariant = CloneflixLogoVariant.WORDMARK_MEDIUM, + text: String? = null, modifier: Modifier = Modifier ) { + val logoText = (if (!text.isNullOrBlank()) text else "CLONEFLIX").uppercase() when (variant) { CloneflixLogoVariant.WORDMARK_MEDIUM -> { Text( - text = "CLONEFLIX", + text = logoText, color = PrimaryRed, fontSize = 28.sp, fontWeight = FontWeight.Black, fontFamily = FontFamily.SansSerif, letterSpacing = 1.5.sp, - modifier = modifier.semantics { contentDescription = "Cloneflix Logo" } + modifier = modifier.semantics { contentDescription = "$logoText Logo" } ) } CloneflixLogoVariant.WORDMARK_SMALL -> { Text( - text = "CLONEFLIX", + text = logoText, color = PrimaryRed, fontSize = 18.sp, fontWeight = FontWeight.Black, fontFamily = FontFamily.SansSerif, letterSpacing = 1.sp, - modifier = modifier.semantics { contentDescription = "Cloneflix Logo Small" } + modifier = modifier.semantics { contentDescription = "$logoText Logo Small" } ) } CloneflixLogoVariant.LETTERMARK_LARGE -> { + val letter = logoText.firstOrNull()?.toString() ?: "C" Box( modifier = modifier .size(48.dp) @@ -363,7 +367,7 @@ fun CloneflixLogoView( contentAlignment = Alignment.Center ) { Text( - text = "C", + text = letter, color = PrimaryRed, fontSize = 38.sp, fontWeight = FontWeight.Black, @@ -372,6 +376,7 @@ fun CloneflixLogoView( } } CloneflixLogoVariant.LETTERMARK_MEDIUM -> { + val letter = logoText.firstOrNull()?.toString() ?: "C" Box( modifier = modifier .size(36.dp) @@ -380,7 +385,7 @@ fun CloneflixLogoView( contentAlignment = Alignment.Center ) { Text( - text = "C", + text = letter, color = PrimaryRed, fontSize = 28.sp, fontWeight = FontWeight.Black, @@ -389,6 +394,7 @@ fun CloneflixLogoView( } } CloneflixLogoVariant.LETTERMARK_SMALL -> { + val letter = logoText.firstOrNull()?.toString() ?: "C" Box( modifier = modifier .size(24.dp) @@ -397,7 +403,7 @@ fun CloneflixLogoView( contentAlignment = Alignment.Center ) { Text( - text = "C", + text = letter, color = PrimaryRed, fontSize = 18.sp, fontWeight = FontWeight.Black, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt index b01c9603a7c..b625879fb71 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.res.stringResource import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent +import com.lagradost.cloudstream3.ui.revamp.compose.theme.getRatingScoreColor import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 @@ -395,23 +396,26 @@ private fun MovieCardSurface( } } - // Bottom Title Overlay with Black Background Gradient + // Bottom Title Overlay with Black Background Gradient (30% of card height) if (showBottomTitle && !title.isNullOrBlank()) { Box( modifier = Modifier .fillMaxWidth() + .fillMaxHeight(0.30f) .align(Alignment.BottomStart) .background( Brush.verticalGradient( colors = listOf( Color.Transparent, - Color(0xD9000000), - Color(0xF2000000), + Color(0x99000000), + Color(0xCC000000), + Color(0xEE000000), Color(0xFF000000) ) ) ) - .padding(horizontal = 8.dp, vertical = 6.dp) + .padding(horizontal = 8.dp, vertical = 6.dp), + contentAlignment = Alignment.BottomStart ) { Text( text = title, @@ -746,7 +750,7 @@ fun CloneflixExpandedMoviePreview( ) { Text( text = matchScore, - color = GreenAccent, + color = getRatingScoreColor(matchScore), style = typography.mediumBody, fontWeight = FontWeight.Bold ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt index 60ed79c8364..e1b4eff89e3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt @@ -43,6 +43,7 @@ import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.revamp.compose.theme.getRatingScoreColor /** * Text / MovieInfo Inline Row Pattern from Figma Section 12. @@ -65,7 +66,7 @@ fun CloneflixMovieInfoRow( ) { Text( text = matchText, - color = GreenAccent, + color = getRatingScoreColor(matchText), style = typography.mediumBody, fontWeight = FontWeight.Bold ) @@ -119,7 +120,7 @@ fun CloneflixMovieInfoBlock( ) { Text( text = matchScore, - color = GreenAccent, + color = getRatingScoreColor(matchScore), style = typography.mediumBody, fontWeight = FontWeight.Bold ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt index 6f8d699ac2c..3f6870215e1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt @@ -426,7 +426,12 @@ fun CloneflixHeroBannersComposeScreen( } } -@Preview(name = "Hero Banners Screen Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Preview( + name = "Hero Banners Screen Preview", + showBackground = true, + backgroundColor = 0xFF141414, + device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape" +) @Composable private fun HeroBannersScreenPreview() { CloneflixTheme { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt index 344ef04153e..29eb42ac8d4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt @@ -546,7 +546,12 @@ fun CloneflixMoreLikeThisDetailCard( } @Preview(name = "Phone Movie Preview Screen", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Movie Preview Screen", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Preview( + name = "TV Movie Preview Screen", + showBackground = true, + backgroundColor = 0xFF141414, + device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape" +) @Composable private fun CloneflixMoviePreviewScreenPreview() { CloneflixTheme { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt index c1b9d122367..176f593a8c9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt @@ -48,6 +48,8 @@ val Blue200 = Color(0xFF448EF4) val Blue300 = Color(0xFF54B9C5) val GreenAccent = Color(0xFF46D369) +val YellowAccent = Color(0xFFFFB800) +val OrangeAccent = Color(0xFFFF8C00) val Grey10 = Color(0xFFE5E5E5) val Grey20 = Color(0xFFDCDCDC) @@ -111,9 +113,38 @@ data class CloneflixColors( val blueAccent100: Color = Blue100, val blueAccent200: Color = Blue200, val blueAccent300: Color = Blue300, - val greenAccent: Color = GreenAccent + val greenAccent: Color = GreenAccent, + val yellowAccent: Color = YellowAccent, + val orangeAccent: Color = OrangeAccent ) +/** + * Resolves color dynamically based on match score / rating quality: + * - High score (>= 70% or >= 7.0 / 10, or "New"): GreenAccent (0xFF46D369) + * - Moderate / average score (50% - 69% or 5.0 - 6.9 / 10): YellowAccent (0xFFFFB800) + * - Low score (< 50% or < 5.0 / 10): Red100 (0xFFEB3942) + */ +fun getRatingScoreColor(scoreText: String?): Color { + if (scoreText.isNullOrBlank()) return GreenAccent + val text = scoreText.trim() + if (text.equals("New", ignoreCase = true)) return GreenAccent + + val match = Regex("""(\d+(\.\d+)?)""").find(text) + if (match != null) { + val num = match.value.toDoubleOrNull() ?: return GreenAccent + val isPercentage = text.contains("%") || num > 10.0 + val percentage = if (isPercentage) num else (num * 10.0) + + return when { + percentage >= 70.0 -> GreenAccent + percentage >= 50.0 -> YellowAccent + else -> Red100 + } + } + + return GreenAccent +} + @Immutable data class CloneflixTypography( val regularCaption2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 11.sp, lineHeight = 14.sp), From 0616f318bd622a1f0be02d6c43daf43c3e6c577b Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 07:54:33 +0700 Subject: [PATCH 15/35] feat(revamp): strip comments, refine TV focus behaviors, and add dedicated Play Trailer button --- .../CloneflixAudioSubtitlesSelector.kt | 5 - .../components/CloneflixAvatarComponents.kt | 55 +--- .../components/CloneflixBadgeComponents.kt | 41 +-- .../components/CloneflixButtonPatterns.kt | 5 - .../components/CloneflixComposeComponents.kt | 1 - .../CloneflixContentBlockComponents.kt | 91 ------ .../compose/components/CloneflixDefaults.kt | 16 - .../compose/components/CloneflixDropdown.kt | 3 - .../components/CloneflixEpisodesDrawer.kt | 6 - .../CloneflixHeroBannerComponents.kt | 167 +++++----- .../compose/components/CloneflixMovieCard.kt | 195 +++++------ .../CloneflixMovieInfoComponents.kt | 13 - .../CloneflixNextEpisodeComponents.kt | 6 - .../CloneflixPlaybackSpeedSelector.kt | 5 - .../compose/components/CloneflixSampleData.kt | 4 - .../components/CloneflixVideoPlayerPattern.kt | 23 -- .../CloneflixVideoProgressIndicator.kt | 6 - .../components/CloneflixVolumeSlider.kt | 3 - .../screens/CloneflixAvatarsComposeScreen.kt | 5 - .../screens/CloneflixButtonsComposeScreen.kt | 24 -- .../CloneflixContentBlocksComposeScreen.kt | 18 -- .../CloneflixHeroBannersComposeScreen.kt | 18 -- .../CloneflixIconsLabelsComposeScreen.kt | 7 - .../CloneflixMovieDetailsComposeScreen.kt | 302 +++++++++--------- .../CloneflixMoviePreviewComposeScreen.kt | 12 - .../CloneflixVideoPlayerComposeScreen.kt | 13 - .../compose/theme/CloneflixComposeTheme.kt | 6 - 27 files changed, 340 insertions(+), 710 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt index a6998a48555..998fee2d282 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt @@ -155,7 +155,6 @@ fun CloneflixAudioSubtitlesSelector( .fillMaxWidth() .padding(24.dp) ) { - // Header with Close Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, @@ -187,14 +186,12 @@ fun CloneflixAudioSubtitlesSelector( Spacer(modifier = Modifier.height(20.dp)) - // Two Columns: Audio (Left) and Subtitles (Right) Row( modifier = Modifier .fillMaxWidth() .height(300.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) ) { - // Column 1: Audio Column( modifier = Modifier .weight(1f) @@ -225,7 +222,6 @@ fun CloneflixAudioSubtitlesSelector( } } - // Vertical Divider line Box( modifier = Modifier .width(1.dp) @@ -233,7 +229,6 @@ fun CloneflixAudioSubtitlesSelector( .background(Color(0x26FFFFFF)) ) - // Column 2: Subtitles Column( modifier = Modifier .weight(1f) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt index 1e1a349bc8e..1dd612e4900 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt @@ -153,9 +153,6 @@ object CloneflixOfficialAvatars { } } -/** - * High-fidelity vector graphic renderer for Cloneflix avatar faces. - */ @Composable fun CloneflixAvatarGraphic( avatar: CloneflixAvatarModel, @@ -165,7 +162,6 @@ fun CloneflixAvatarGraphic( val w = size.width val h = size.height - // Background gradient drawRoundRect( brush = Brush.verticalGradient( colors = listOf(avatar.topColor, avatar.bottomColor), @@ -176,7 +172,6 @@ fun CloneflixAvatarGraphic( cornerRadius = CornerRadius(w * 0.08f, h * 0.08f) ) - // Draw facial features based on AvatarFaceStyle when (avatar.faceStyle) { AvatarFaceStyle.SMILEY -> drawSmileyFace(w, h, PrimaryWhite) AvatarFaceStyle.WINK -> drawWinkFace(w, h, PrimaryWhite) @@ -202,11 +197,9 @@ private fun DrawScope.drawSmileyFace(w: Float, h: Float, color: Color) { val leftEyeX = w * 0.35f val rightEyeX = w * 0.65f - // Eyes drawCircle(color = color, radius = eyeRadius, center = Offset(leftEyeX, eyeY)) drawCircle(color = color, radius = eyeRadius, center = Offset(rightEyeX, eyeY)) - // Smile Arc val mouthPath = Path().apply { moveTo(w * 0.30f, h * 0.56f) quadraticTo(w * 0.50f, h * 0.74f, w * 0.70f, h * 0.56f) @@ -222,17 +215,14 @@ private fun DrawScope.drawWinkFace(w: Float, h: Float, color: Color) { val eyeRadius = w * 0.065f val eyeY = h * 0.38f - // Left eye (open) drawCircle(color = color, radius = eyeRadius, center = Offset(w * 0.35f, eyeY)) - // Right eye (wink) val winkPath = Path().apply { moveTo(w * 0.60f, eyeY) quadraticTo(w * 0.67f, eyeY - h * 0.04f, w * 0.74f, eyeY) } drawPath(path = winkPath, color = color, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) - // Smirk val mouthPath = Path().apply { moveTo(w * 0.34f, h * 0.58f) quadraticTo(w * 0.52f, h * 0.72f, w * 0.72f, h * 0.52f) @@ -241,7 +231,6 @@ private fun DrawScope.drawWinkFace(w: Float, h: Float, color: Color) { } private fun DrawScope.drawAngryFace(w: Float, h: Float, color: Color) { - // Brows val leftBrow = Path().apply { moveTo(w * 0.22f, h * 0.28f) lineTo(w * 0.44f, h * 0.36f) @@ -253,11 +242,9 @@ private fun DrawScope.drawAngryFace(w: Float, h: Float, color: Color) { drawPath(leftBrow, color = color, style = Stroke(width = w * 0.07f, cap = StrokeCap.Round)) drawPath(rightBrow, color = color, style = Stroke(width = w * 0.07f, cap = StrokeCap.Round)) - // Eyes drawCircle(color = color, radius = w * 0.055f, center = Offset(w * 0.34f, h * 0.42f)) drawCircle(color = color, radius = w * 0.055f, center = Offset(w * 0.66f, h * 0.42f)) - // Frown val mouthPath = Path().apply { moveTo(w * 0.32f, h * 0.68f) quadraticTo(w * 0.50f, h * 0.56f, w * 0.68f, h * 0.68f) @@ -266,7 +253,6 @@ private fun DrawScope.drawAngryFace(w: Float, h: Float, color: Color) { } private fun DrawScope.drawKidsFace(w: Float, h: Float, color: Color) { - // Big round eyes drawCircle(color = color, radius = w * 0.10f, center = Offset(w * 0.35f, h * 0.38f)) drawCircle(color = color, radius = w * 0.10f, center = Offset(w * 0.65f, h * 0.38f)) drawCircle(color = PrimaryBlack, radius = w * 0.05f, center = Offset(w * 0.35f, h * 0.38f)) @@ -274,7 +260,6 @@ private fun DrawScope.drawKidsFace(w: Float, h: Float, color: Color) { drawCircle(color = PrimaryWhite, radius = w * 0.02f, center = Offset(w * 0.37f, h * 0.36f)) drawCircle(color = PrimaryWhite, radius = w * 0.02f, center = Offset(w * 0.67f, h * 0.36f)) - // Big happy smile with tongue val mouthPath = Path().apply { moveTo(w * 0.28f, h * 0.58f) quadraticTo(w * 0.50f, h * 0.78f, w * 0.72f, h * 0.58f) @@ -283,17 +268,14 @@ private fun DrawScope.drawKidsFace(w: Float, h: Float, color: Color) { } private fun DrawScope.drawMonsterGrey(w: Float, h: Float) { - // Horns drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.25f, h * 0.20f)) drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.75f, h * 0.20f)) - // Eyes drawCircle(color = PrimaryWhite, radius = w * 0.07f, center = Offset(w * 0.35f, h * 0.40f)) drawCircle(color = PrimaryWhite, radius = w * 0.07f, center = Offset(w * 0.65f, h * 0.40f)) drawCircle(color = PrimaryBlack, radius = w * 0.035f, center = Offset(w * 0.35f, h * 0.40f)) drawCircle(color = PrimaryBlack, radius = w * 0.035f, center = Offset(w * 0.65f, h * 0.40f)) - // Teeth smile val mouthPath = Path().apply { moveTo(w * 0.30f, h * 0.60f) lineTo(w * 0.70f, h * 0.60f) @@ -305,19 +287,17 @@ private fun DrawScope.drawMonsterGrey(w: Float, h: Float) { } private fun DrawScope.drawMonsterBlue(w: Float, h: Float) { - // Single Cyclops Eye drawCircle(color = PrimaryWhite, radius = w * 0.13f, center = Offset(w * 0.50f, h * 0.36f)) drawCircle(color = Color(0xFF0984E3), radius = w * 0.07f, center = Offset(w * 0.50f, h * 0.36f)) drawCircle(color = PrimaryBlack, radius = w * 0.04f, center = Offset(w * 0.50f, h * 0.36f)) drawCircle(color = PrimaryWhite, radius = w * 0.018f, center = Offset(w * 0.52f, h * 0.34f)) - // Cute fangs mouth val mouthPath = Path().apply { moveTo(w * 0.32f, h * 0.60f) quadraticTo(w * 0.50f, h * 0.76f, w * 0.68f, h * 0.60f) } drawPath(path = mouthPath, color = PrimaryWhite, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) - // Fang + val fang = Path().apply { moveTo(w * 0.40f, h * 0.60f) lineTo(w * 0.43f, h * 0.68f) @@ -328,7 +308,6 @@ private fun DrawScope.drawMonsterBlue(w: Float, h: Float) { } private fun DrawScope.drawMonsterRed(w: Float, h: Float) { - // Sharp eyes val leftEye = Path().apply { moveTo(w * 0.28f, h * 0.36f) lineTo(w * 0.44f, h * 0.40f) @@ -344,7 +323,6 @@ private fun DrawScope.drawMonsterRed(w: Float, h: Float) { drawPath(leftEye, color = Color(0xFFFFD32A)) drawPath(rightEye, color = Color(0xFFFFD32A)) - // Wide grin val mouth = Path().apply { moveTo(w * 0.26f, h * 0.56f) quadraticTo(w * 0.50f, h * 0.78f, w * 0.74f, h * 0.56f) @@ -359,18 +337,15 @@ private fun DrawScope.drawMonsterYellow(w: Float, h: Float) { } private fun DrawScope.drawChickenFace(w: Float, h: Float) { - // Comb on head drawCircle(color = Color(0xFFFF4757), radius = w * 0.09f, center = Offset(w * 0.50f, h * 0.16f)) drawCircle(color = Color(0xFFFF4757), radius = w * 0.07f, center = Offset(w * 0.38f, h * 0.19f)) drawCircle(color = Color(0xFFFF4757), radius = w * 0.07f, center = Offset(w * 0.62f, h * 0.19f)) - // Eyes drawCircle(color = PrimaryWhite, radius = w * 0.06f, center = Offset(w * 0.35f, h * 0.40f)) drawCircle(color = PrimaryWhite, radius = w * 0.06f, center = Offset(w * 0.65f, h * 0.40f)) drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.35f, h * 0.40f)) drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.65f, h * 0.40f)) - // Beak val beak = Path().apply { moveTo(w * 0.42f, h * 0.48f) lineTo(w * 0.58f, h * 0.48f) @@ -381,18 +356,15 @@ private fun DrawScope.drawChickenFace(w: Float, h: Float) { } private fun DrawScope.drawZombieFace(w: Float, h: Float) { - // Mismatched eyes drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.35f, h * 0.38f)) drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.35f, h * 0.38f)) - // X eye val xPath = Path().apply { moveTo(w * 0.60f, h * 0.34f); lineTo(w * 0.70f, h * 0.42f) moveTo(w * 0.70f, h * 0.34f); lineTo(w * 0.60f, h * 0.42f) } drawPath(xPath, color = PrimaryWhite, style = Stroke(width = w * 0.04f, cap = StrokeCap.Round)) - // Stitched mouth val mouthLine = Path().apply { moveTo(w * 0.30f, h * 0.62f); lineTo(w * 0.70f, h * 0.62f) moveTo(w * 0.38f, h * 0.56f); lineTo(w * 0.38f, h * 0.68f) @@ -403,17 +375,14 @@ private fun DrawScope.drawZombieFace(w: Float, h: Float) { } private fun DrawScope.drawPandaFace(w: Float, h: Float) { - // Black ears drawCircle(color = Color(0xFF2F3640), radius = w * 0.11f, center = Offset(w * 0.22f, h * 0.22f)) drawCircle(color = Color(0xFF2F3640), radius = w * 0.11f, center = Offset(w * 0.78f, h * 0.22f)) - // Eye patches drawCircle(color = Color(0xFF2F3640), radius = w * 0.09f, center = Offset(w * 0.36f, h * 0.44f)) - drawCircle(color = Color(0xFF2F3640), radius = w * 0.09f, center = Offset(w * 0.64f, h * 0.44f)) + drawCircle(color = Color(0xFF2F3640), radius = w * 0.64f, center = Offset(w * 0.64f, h * 0.44f)) drawCircle(color = PrimaryWhite, radius = w * 0.035f, center = Offset(w * 0.36f, h * 0.43f)) drawCircle(color = PrimaryWhite, radius = w * 0.035f, center = Offset(w * 0.64f, h * 0.43f)) - // Nose & smile drawCircle(color = Color(0xFF2F3640), radius = w * 0.04f, center = Offset(w * 0.50f, h * 0.56f)) val mouthPath = Path().apply { moveTo(w * 0.42f, h * 0.64f) @@ -423,7 +392,6 @@ private fun DrawScope.drawPandaFace(w: Float, h: Float) { } private fun DrawScope.drawCoolFace(w: Float, h: Float, color: Color) { - // Sunglasses val glasses = Path().apply { moveTo(w * 0.22f, h * 0.36f) lineTo(w * 0.78f, h * 0.36f) @@ -436,7 +404,6 @@ private fun DrawScope.drawCoolFace(w: Float, h: Float, color: Color) { } drawPath(glasses, color = PrimaryBlack) - // Smile val mouthPath = Path().apply { moveTo(w * 0.34f, h * 0.62f) quadraticTo(w * 0.50f, h * 0.76f, w * 0.68f, h * 0.58f) @@ -454,9 +421,6 @@ private fun DrawScope.drawGeometricFace(w: Float, h: Float, text: String) { drawSmileyFace(w, h, PrimaryWhite) } -/** - * Small Avatar component (UserProfilePicture or UserProfileMenu) - */ @Composable fun CloneflixSmallAvatar( avatar: CloneflixAvatarModel, @@ -530,9 +494,6 @@ fun CloneflixSmallAvatar( } } -/** - * Large Avatar component (UserProfileAvatar or AddUserProfile) with hover/TV D-pad focus states. - */ @Composable fun CloneflixLargeAvatar( name: String, @@ -591,7 +552,6 @@ fun CloneflixLargeAvatar( contentAlignment = Alignment.Center ) { if (isAddProfile) { - // Circle Plus Icon for Add Profile Box( modifier = Modifier .size(size * 0.54f) @@ -632,9 +592,6 @@ fun CloneflixLargeAvatar( } } -/** - * Sample "Who's Watching?" profile selector row with D-Pad traversal and selection state. - */ @Composable fun CloneflixUserProfilesRow( profiles: List>, @@ -674,9 +631,6 @@ private val PolygonShape = GenericShape { size, _ -> close() } -/** - * Interactive Profile Dropdown Menu matching the Figma design popover. - */ @Composable fun CloneflixProfileMenuDropdown( currentProfiles: List>, @@ -694,7 +648,6 @@ fun CloneflixProfileMenuDropdown( horizontalAlignment = Alignment.End, modifier = modifier.width(224.dp) ) { - // Pointer Polygon Arrow on top Box( modifier = Modifier .padding(end = 24.dp) @@ -704,7 +657,6 @@ fun CloneflixProfileMenuDropdown( .border(BorderStroke(1.dp, Grey600), PolygonShape) ) - // Dropdown Menu Card Surface( color = Color(0xFF141414), shape = RoundedCornerShape(4.dp), @@ -715,7 +667,6 @@ fun CloneflixProfileMenuDropdown( .offset(y = (-1).dp) ) { Column(modifier = Modifier.padding(vertical = 8.dp)) { - // Profiles List currentProfiles.forEach { (name, avatar) -> ProfileMenuItem( icon = { @@ -736,7 +687,6 @@ fun CloneflixProfileMenuDropdown( CloneflixDivider(modifier = Modifier.padding(horizontal = 8.dp)) Spacer(modifier = Modifier.height(4.dp)) - // Action Items ProfileMenuItem( iconRes = R.drawable.cloneflix_ic_edit, label = "Manage Profiles", @@ -765,7 +715,6 @@ fun CloneflixProfileMenuDropdown( CloneflixDivider(modifier = Modifier.padding(horizontal = 8.dp)) Spacer(modifier = Modifier.height(4.dp)) - // Sign Out Link Box( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt index f5a711c6843..29fe93fac59 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt @@ -46,9 +46,6 @@ import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red100 import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red300 -/** - * Maturity Rating Badges from Figma Design System (TV-Y, TV-Y7, G, TV-G, PG, TV-PG, PG-13, TV-14, R, TV-MA, NC-17). - */ @Composable fun CloneflixMaturityRating( rating: String, @@ -58,16 +55,17 @@ fun CloneflixMaturityRating( val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() - val borderColor = if (isFocused) PrimaryWhite else Grey25 - val borderWidth = if (isFocused) 1.8.dp else 1.dp - val textColor = if (isFocused) PrimaryWhite else Grey25 + val isInteractive = onClick != null + val borderColor = if (isInteractive && isFocused) PrimaryWhite else Grey25 + val borderWidth = if (isInteractive && isFocused) 1.8.dp else 1.dp + val textColor = if (isInteractive && isFocused) PrimaryWhite else Grey25 Box( modifier = modifier .height(22.dp) .clip(RoundedCornerShape(2.dp)) .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(2.dp)) - .background(if (isFocused) Grey300T40 else Color.Transparent) + .background(if (isInteractive && isFocused) Grey300T40 else Color.Transparent) .then( if (onClick != null) { Modifier @@ -79,7 +77,7 @@ fun CloneflixMaturityRating( ) .focusable(interactionSource = interactionSource) } else { - Modifier.focusable(interactionSource = interactionSource) + Modifier } ) .padding(horizontal = 6.5.dp), @@ -99,9 +97,6 @@ fun CloneflixMaturityRating( } } -/** - * Video Quality Badges (HD, 4K, HDR, UltraHD 4K, Dolby Vision). - */ @Composable fun CloneflixVideoQualityBadge( quality: String, @@ -111,16 +106,17 @@ fun CloneflixVideoQualityBadge( val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() - val borderColor = if (isFocused) PrimaryWhite else Grey200 - val borderWidth = if (isFocused) 1.5.dp else 1.dp - val textColor = if (isFocused) PrimaryWhite else Grey10 + val isInteractive = onClick != null + val borderColor = if (isInteractive && isFocused) PrimaryWhite else Grey200 + val borderWidth = if (isInteractive && isFocused) 1.5.dp else 1.dp + val textColor = if (isInteractive && isFocused) PrimaryWhite else Grey10 Box( modifier = modifier .height(18.dp) .clip(RoundedCornerShape(4.dp)) .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(4.dp)) - .background(if (isFocused) Grey300T40 else Color.Transparent) + .background(if (isInteractive && isFocused) Grey300T40 else Color.Transparent) .then( if (onClick != null) { Modifier @@ -132,7 +128,7 @@ fun CloneflixVideoQualityBadge( ) .focusable(interactionSource = interactionSource) } else { - Modifier.focusable(interactionSource = interactionSource) + Modifier } ) .padding(horizontal = 6.dp), @@ -158,9 +154,6 @@ enum class CloneflixTop10Size { SMALL } -/** - * Iconic Top 10 Red Stacked Badge. - */ @Composable fun CloneflixTop10Badge( modifier: Modifier = Modifier, @@ -216,9 +209,6 @@ fun CloneflixTop10Badge( } } -/** - * Top 10 with Rank text (e.g. "#2 in TV Shows Today"). - */ @Composable fun CloneflixTop10RankBanner( rankText: String, @@ -278,9 +268,6 @@ enum class CloneflixBadgeType(@androidx.annotation.StringRes val stringRes: Int) LEAVING_SOON(com.lagradost.cloudstream3.R.string.cloneflix_badge_leaving_soon) } -/** - * Content Status Pill Badges (Recently Added, New Season, Top 10, Leaving Soon). - */ @Composable fun CloneflixContentBadge( type: CloneflixBadgeType, @@ -323,10 +310,6 @@ enum class CloneflixLogoVariant { LETTERMARK_SMALL } -/** - * Cloneflix Brand Logo Composable (Wordmark & Lettermark). - * Optionally displays provider/plugin name or custom text in brand styling. - */ @Composable fun CloneflixLogoView( variant: CloneflixLogoVariant = CloneflixLogoVariant.WORDMARK_MEDIUM, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt index fc268b5c65e..2fe2eaf7573 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt @@ -165,7 +165,6 @@ fun CloneflixVideoPlayerControlBar( .background(Color(0xDF141414)) .padding(dimens.spacingL) ) { - // Scrubber / Progress Bar Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically @@ -200,13 +199,11 @@ fun CloneflixVideoPlayerControlBar( Spacer(modifier = Modifier.height(dimens.spacingM)) - // Control buttons row Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - // Left Group Row( horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), verticalAlignment = Alignment.CenterVertically @@ -237,7 +234,6 @@ fun CloneflixVideoPlayerControlBar( ) } - // Center Title Text( text = title, style = typography.mediumBody, @@ -245,7 +241,6 @@ fun CloneflixVideoPlayerControlBar( color = PrimaryWhite ) - // Right Group Row( horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), verticalAlignment = Alignment.CenterVertically diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt index ad63cd8a532..c774ab9041c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt @@ -827,4 +827,3 @@ private fun CloneflixButtonsPreview() { } } } - diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt index 78d31a1b23a..b67242717ad 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt @@ -66,13 +66,6 @@ import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 -// ============================================================================ -// 1. HEADERS (Landing Page & Home Page) -// ============================================================================ - -/** - * Landing Page Header with Logo, Language selector dropdown, and Sign In action button. - */ @Composable fun CloneflixLandingHeader( onSignInClick: () -> Unit = {}, @@ -92,7 +85,6 @@ fun CloneflixLandingHeader( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - // Logo Text( text = "CLONEFLIX", style = typography.logoBebas, @@ -100,7 +92,6 @@ fun CloneflixLandingHeader( color = PrimaryRed ) - // Actions (Language dropdown + Sign In button) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) @@ -123,9 +114,6 @@ fun CloneflixLandingHeader( } } -/** - * Home Page Header with Logo, Navigation Links, Search, Notifications, and Profile Avatar. - */ @Composable fun CloneflixHomeHeader( selectedNavIndex: Int = 0, @@ -147,7 +135,6 @@ fun CloneflixHomeHeader( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - // Left side: Logo & Navigation Items Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingXl) @@ -187,7 +174,6 @@ fun CloneflixHomeHeader( } } - // Right side: Search, Notifications, Avatar with dropdown arrow Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) @@ -216,7 +202,6 @@ fun CloneflixHomeHeader( ) } - // Profile Avatar with dropdown arrow Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), @@ -251,13 +236,6 @@ fun CloneflixHomeHeader( } } -// ============================================================================ -// 2. FAQ ACCORDION (Interactive Expandable Component) -// ============================================================================ - -/** - * Individual FAQ Question Item with smooth accordion expansion. - */ @Composable fun CloneflixFaqItem( question: String, @@ -289,7 +267,6 @@ fun CloneflixFaqItem( .fillMaxWidth() .animateContentSize(animationSpec = tween(durationMillis = 250)) ) { - // Question Header Card Surface( onClick = onToggle, color = itemBackgroundColor, @@ -326,7 +303,6 @@ fun CloneflixFaqItem( } } - // Answer Body Card AnimatedVisibility(visible = isOpen) { Column { Spacer(modifier = Modifier.height(2.dp)) @@ -349,9 +325,6 @@ fun CloneflixFaqItem( } } -/** - * Full FAQ Section containing multiple interactive FAQ items. - */ @Composable fun CloneflixFaqSection( modifier: Modifier = Modifier @@ -402,13 +375,6 @@ fun CloneflixFaqSection( } } -// ============================================================================ -// 3. SCREENS PREVIEWS (TV, Mac, iPhone with Download Card, Kids) -// ============================================================================ - -/** - * TV Screen Mockup Preview. - */ @Composable fun CloneflixTvPreviewCard( modifier: Modifier = Modifier @@ -423,7 +389,6 @@ fun CloneflixTvPreviewCard( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { - // TV Bezel Box( modifier = Modifier .fillMaxWidth() @@ -433,7 +398,6 @@ fun CloneflixTvPreviewCard( .border(2.dp, Grey700, RoundedCornerShape(8.dp)), contentAlignment = Alignment.Center ) { - // TV Screen Content Simulation Box( modifier = Modifier .fillMaxSize() @@ -465,7 +429,6 @@ fun CloneflixTvPreviewCard( } } - // TV Stand / Base Box( modifier = Modifier .width(60.dp) @@ -482,9 +445,6 @@ fun CloneflixTvPreviewCard( } } -/** - * Mac / Computer Screen Mockup Preview. - */ @Composable fun CloneflixMacPreviewCard( modifier: Modifier = Modifier @@ -499,7 +459,6 @@ fun CloneflixMacPreviewCard( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { - // Screen Bezel Box( modifier = Modifier .fillMaxWidth() @@ -509,7 +468,6 @@ fun CloneflixMacPreviewCard( .border(1.5.dp, Grey700, RoundedCornerShape(6.dp)) ) { Column(modifier = Modifier.fillMaxSize()) { - // Top Bar (Window dots) Row( modifier = Modifier .fillMaxWidth() @@ -524,7 +482,6 @@ fun CloneflixMacPreviewCard( Box(modifier = Modifier.size(6.dp).background(Color(0xFF27C93F), CircleShape)) } - // Content Box( modifier = Modifier .weight(1f) @@ -554,7 +511,6 @@ fun CloneflixMacPreviewCard( } } - // Laptop base Box( modifier = Modifier .fillMaxWidth(0.95f) @@ -565,9 +521,6 @@ fun CloneflixMacPreviewCard( } } -/** - * Standalone / Floating Download Progress Card (Stranger Things badge). - */ @Composable fun CloneflixDownloadProgressCard( title: String = "Stranger Things", @@ -586,7 +539,6 @@ fun CloneflixDownloadProgressCard( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(14.dp) ) { - // Movie Poster Thumbnail Placeholder Box( modifier = Modifier .width(36.dp) @@ -607,7 +559,6 @@ fun CloneflixDownloadProgressCard( ) } - // Title & Status Column(modifier = Modifier.weight(1f)) { Text( text = title, @@ -626,7 +577,6 @@ fun CloneflixDownloadProgressCard( ) } - // Download Animation / Indicator Box( modifier = Modifier.size(24.dp), contentAlignment = Alignment.Center @@ -641,9 +591,6 @@ fun CloneflixDownloadProgressCard( } } -/** - * Mobile Phone Mockup Preview featuring the Stranger Things download card. - */ @Composable fun CloneflixMobilePreviewCard( modifier: Modifier = Modifier @@ -663,7 +610,6 @@ fun CloneflixMobilePreviewCard( .background(Color(0xFF1E1E1E)) .border(2.dp, Grey700, RoundedCornerShape(24.dp)) ) { - // Screen Wallpaper / Content Box( modifier = Modifier .fillMaxSize() @@ -674,7 +620,6 @@ fun CloneflixMobilePreviewCard( ) .padding(16.dp) ) { - // Top status bar notch Box( modifier = Modifier .align(Alignment.TopCenter) @@ -683,7 +628,6 @@ fun CloneflixMobilePreviewCard( .background(Color.Black, RoundedCornerShape(bottomStart = 8.dp, bottomEnd = 8.dp)) ) - // Downloading Card overlay CloneflixDownloadProgressCard( modifier = Modifier .align(Alignment.BottomCenter) @@ -695,9 +639,6 @@ fun CloneflixMobilePreviewCard( } } -/** - * Kids Profile & Content Screen Preview. - */ @Composable fun CloneflixKidsPreviewCard( modifier: Modifier = Modifier @@ -725,7 +666,6 @@ fun CloneflixKidsPreviewCard( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { - // Kids Avatar Row Row( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically @@ -776,18 +716,11 @@ fun CloneflixKidsPreviewCard( } } -// ============================================================================ -// 4. LANDING PAGE BLOCKS (Headline + Image / Image + Headline) -// ============================================================================ - enum class CloneflixBlockLayout { HEADLINE_IMAGE, IMAGE_HEADLINE } -/** - * Reusable Landing Page Block with Title, Description, and Device Preview Slot. - */ @Composable fun CloneflixFeatureBlock( title: String, @@ -865,13 +798,6 @@ fun CloneflixFeatureBlock( } } -// ============================================================================ -// 5. FOOTERS (Landing Page, Authentication Page, Home Page) -// ============================================================================ - -/** - * Landing Page Footer with Questions hotline, 4-column links grid, and Language selector. - */ @OptIn(ExperimentalLayoutApi::class) @Composable fun CloneflixLandingFooter( @@ -897,7 +823,6 @@ fun CloneflixLandingFooter( .background(PrimaryBlack) .padding(horizontal = dimens.spacing2Xl, vertical = 48.dp) ) { - // Hotline Text( text = "Questions? Call 1-844-505-2993", style = typography.regularBody, @@ -907,7 +832,6 @@ fun CloneflixLandingFooter( .padding(bottom = dimens.spacingXl) ) - // 4-Column Responsive Grid of Links FlowRow( modifier = Modifier.fillMaxWidth(), maxItemsInEachRow = 4, @@ -930,7 +854,6 @@ fun CloneflixLandingFooter( Spacer(modifier = Modifier.height(28.dp)) - // Language Dropdown CloneflixDropdown( options = listOf("English", "Español", "Français", "Deutsch"), selectedOption = selectedLanguage, @@ -949,9 +872,6 @@ fun CloneflixLandingFooter( } } -/** - * Authentication Page Footer (Streamlined). - */ @OptIn(ExperimentalLayoutApi::class) @Composable fun CloneflixAuthFooter( @@ -1016,9 +936,6 @@ fun CloneflixAuthFooter( } } -/** - * Home Page Footer with Social Media Icons, Links Grid, and Service Code Button. - */ @OptIn(ExperimentalLayoutApi::class) @Composable fun CloneflixHomeFooter( @@ -1043,7 +960,6 @@ fun CloneflixHomeFooter( .background(PrimaryBlack) .padding(horizontal = dimens.spacing2Xl, vertical = 40.dp) ) { - // Social Media Icons Row( horizontalArrangement = Arrangement.spacedBy(20.dp), verticalAlignment = Alignment.CenterVertically, @@ -1071,7 +987,6 @@ fun CloneflixHomeFooter( } } - // Links Grid FlowRow( modifier = Modifier.fillMaxWidth(), maxItemsInEachRow = 4, @@ -1094,7 +1009,6 @@ fun CloneflixHomeFooter( Spacer(modifier = Modifier.height(24.dp)) - // Service Code Button Surface( onClick = onServiceCodeClick, shape = RoundedCornerShape(2.dp), @@ -1111,7 +1025,6 @@ fun CloneflixHomeFooter( ) } - // Copyright Text( text = "© 1997 - 2024 Netflix, Inc.", style = typography.regularCaption2, @@ -1121,10 +1034,6 @@ fun CloneflixHomeFooter( } } -// ============================================================================ -// PREVIEWS -// ============================================================================ - @Preview(name = "Landing Header Preview", showBackground = true, backgroundColor = 0xFF000000) @Composable private fun LandingHeaderPreview() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt index 96053a9bb75..11254a387af 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt @@ -5,33 +5,23 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -/** - * Central design tokens and defaults for Cloneflix components. - * Eliminates magic numbers across Jetpack Compose UI. - */ object CloneflixTokens { - // Focus & Motion Tokens const val FOCUS_SCALE_FACTOR = 1.05f const val FOCUS_SCALE_FACTOR_LARGE = 1.08f const val FOCUS_SCALE_FACTOR_SUBTLE = 1.02f - // Alpha / Opacity Tokens const val ALPHA_MUTED = 0.3f const val ALPHA_SUBTLE = 0.4f const val ALPHA_MEDIUM = 0.6f const val ALPHA_HIGH = 0.7f const val ALPHA_NEAR_OPAQUE = 0.9f - // Common Radii val RadiusPill: Dp = 999.dp val RadiusCard: Dp = 4.dp val RadiusCardMedium: Dp = 8.dp val RadiusCardLarge: Dp = 12.dp } -/** - * Defaults and dimension formulas for Cloneflix Movie Cards. - */ object CloneflixMovieCardDefaults { val WidthMedium: Dp = 218.dp val WidthSmall: Dp = 128.dp @@ -102,9 +92,6 @@ object CloneflixMovieCardDefaults { } } -/** - * Defaults for Video Player and Media Control Overlays. - */ object CloneflixPlayerDefaults { val ControlButtonSize: Dp = 48.dp val ControlButtonSizeLarge: Dp = 64.dp @@ -118,9 +105,6 @@ object CloneflixPlayerDefaults { val SpeedItemWidth: Dp = 72.dp } -/** - * Defaults for Episodes and Drawer Panels. - */ object CloneflixDrawerDefaults { val DrawerWidth: Dp = 380.dp val EpisodeItemCornerRadius: Dp = 6.dp diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt index 22075d27233..380f631d4d5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt @@ -212,9 +212,6 @@ fun CloneflixDropdown( } } -/** - * Static showcase container for displaying dropdown state variants. - */ @Composable fun CloneflixDropdownStaticShowcase( title: String, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt index 740bdc83380..e2623db1d34 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt @@ -110,7 +110,6 @@ fun CloneflixEpisodeCard( color = if (isFocused) Color(0xFF282828) else Color(0xEB1C1C1C) ) { Column(modifier = Modifier.padding(dimens.spacingS)) { - // Thumbnail with progress bar Box( modifier = Modifier .fillMaxWidth() @@ -147,7 +146,6 @@ fun CloneflixEpisodeCard( ) } - // Duration badge Box( modifier = Modifier .align(Alignment.BottomEnd) @@ -162,7 +160,6 @@ fun CloneflixEpisodeCard( ) } - // Progress line if (item.progress > 0f) { Box( modifier = Modifier @@ -183,7 +180,6 @@ fun CloneflixEpisodeCard( Spacer(modifier = Modifier.height(dimens.spacingS)) - // Episode title + badge Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, @@ -236,7 +232,6 @@ fun CloneflixEpisodesDrawer( .fillMaxWidth() .padding(vertical = dimens.spacingL) ) { - // Header Row( modifier = Modifier .fillMaxWidth() @@ -276,7 +271,6 @@ fun CloneflixEpisodesDrawer( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Episodes carousel LazyRow( state = listState, contentPadding = PaddingValues(horizontal = dimens.spacing2Xl), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt index 4c52225efdf..231e5f15baf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.revamp.compose.components import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.BorderStroke @@ -36,13 +37,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.zIndex import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices @@ -86,9 +91,6 @@ enum class CloneflixHeroBannerType { MOVIE_PREVIEW } -/** - * Standard Play Button used in Hero Banners and Title Previews. - */ @Composable fun CloneflixHeroPlayButton( onClick: () -> Unit, @@ -98,15 +100,16 @@ fun CloneflixHeroPlayButton( val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + label = "heroPlayBtnScale" + ) + val background = if (isFocused) PrimaryWhite else PrimaryWhite.copy(alpha = 0.95f) val border = if (isFocused) BorderStroke(2.dp, PrimaryRed) else null - Row( + Box( modifier = modifier - .height(42.dp) - .clip(RoundedCornerShape(4.dp)) - .background(background) - .then(if (border != null) Modifier.border(border, RoundedCornerShape(4.dp)) else Modifier) .clickable( interactionSource = interactionSource, indication = null, @@ -114,31 +117,42 @@ fun CloneflixHeroPlayButton( onClick = onClick ) .focusable(interactionSource = interactionSource) - .padding(horizontal = 24.dp, vertical = 8.dp) .semantics { contentDescription = "$text Video" }, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) + contentAlignment = Alignment.Center ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryBlack, - modifier = Modifier.size(20.dp) - ) - Text( - text = text, - color = PrimaryBlack, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) + Row( + modifier = Modifier + .height(42.dp) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .clip(RoundedCornerShape(4.dp)) + .background(background) + .then(if (border != null) Modifier.border(border, RoundedCornerShape(4.dp)) else Modifier) + .padding(horizontal = 24.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryBlack, + modifier = Modifier.size(20.dp) + ) + Text( + text = text, + color = PrimaryBlack, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } } } -/** - * Standard More Info Button used in Hero Banners and Title Previews. - */ @Composable fun CloneflixHeroMoreInfoButton( onClick: () -> Unit, @@ -186,9 +200,59 @@ fun CloneflixHeroMoreInfoButton( } } -/** - * Circular Action Button used in Hero Banners (Replay, Volume, Plus, ThumbUp, Close). - */ +@Composable +fun CloneflixHeroTrailerButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: String = "Play Trailer" +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = CloneflixTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, + label = "heroTrailerBtnScale" + ) + + val background = if (isFocused) TransparentWhite20 else TransparentBlack60 + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, PrimaryWhite) + } else { + BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_HIGH)) + } + + Box( + modifier = modifier + .height(40.dp) + .scale(scale) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(background) + .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + .padding(horizontal = 16.dp, vertical = 8.dp) + .semantics { + role = Role.Button + contentDescription = text + }, + contentAlignment = Alignment.Center + ) { + Text( + text = text, + color = PrimaryWhite, + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + style = CloneflixTheme.typography.mediumBody + ) + } +} + @Composable fun CloneflixHeroCircleButton( iconRes: Int, @@ -235,9 +299,6 @@ fun CloneflixHeroCircleButton( } } -/** - * Hero Banner Right-Edge Maturity Rating Badge (e.g. "16+", "TV-MA"). - */ @Composable fun CloneflixHeroMaturityRating( rating: String = "16+", @@ -254,7 +315,6 @@ fun CloneflixHeroMaturityRating( shape = RoundedCornerShape(topStart = 2.dp, bottomStart = 2.dp) ) .drawBehind { - // Red left accent bar drawRect( color = PrimaryRed, size = androidx.compose.ui.geometry.Size(3.dp.toPx(), size.height) @@ -273,9 +333,6 @@ fun CloneflixHeroMaturityRating( } } -/** - * Title Preview Component from Figma Section 15 (Sizes: Large, Medium, Small1, Small2, Small3). - */ @Composable fun CloneflixTitlePreview( size: CloneflixTitlePreviewSize = CloneflixTitlePreviewSize.LARGE, @@ -296,10 +353,8 @@ fun CloneflixTitlePreview( modifier = modifier.widthIn(max = 540.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - // Title Logo Box CloneflixTitleLogoView(title = title, subtitle = subtitle) - // Top 10 Rank Banner if (!top10RankText.isNullOrBlank()) { CloneflixTop10RankBanner( rankText = top10RankText, @@ -307,7 +362,6 @@ fun CloneflixTitlePreview( ) } - // Synopsis Text( text = synopsis, style = typography.regularBody, @@ -317,7 +371,6 @@ fun CloneflixTitlePreview( lineHeight = 22.sp ) - // Action Buttons Row Row( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically @@ -409,9 +462,6 @@ fun CloneflixTitlePreview( } } -/** - * Stylized Cinematic Title Logo View. - */ @Composable fun CloneflixTitleLogoView( title: String, @@ -444,9 +494,6 @@ fun CloneflixTitleLogoView( } } -/** - * Versatile Hero Banner Component supporting LandingPage, AuthenticationPage, HomePage, and MoviePreview. - */ @Composable fun CloneflixHeroBanner( type: CloneflixHeroBannerType = CloneflixHeroBannerType.HOME_PAGE, @@ -472,7 +519,6 @@ fun CloneflixHeroBanner( .clip(RoundedCornerShape(8.dp)) .background(Color(0xFF181C24)) ) { - // Background artistic backdrop gradient simulating hero wallpaper Box( modifier = Modifier .fillMaxSize() @@ -484,7 +530,6 @@ fun CloneflixHeroBanner( ) ) - // Top Vignette Gradient (for navigation bar contrast) Box( modifier = Modifier .fillMaxWidth() @@ -496,7 +541,6 @@ fun CloneflixHeroBanner( ) ) - // Left Scrim Gradient (for title readability) Box( modifier = Modifier .fillMaxSize() @@ -511,7 +555,6 @@ fun CloneflixHeroBanner( ) ) - // Bottom Fade Scrim Gradient (seamless merge into movie rows) Box( modifier = Modifier .fillMaxWidth() @@ -524,7 +567,6 @@ fun CloneflixHeroBanner( ) ) - // Title Preview Overlay (Left) Box( modifier = Modifier .align(Alignment.BottomStart) @@ -540,7 +582,6 @@ fun CloneflixHeroBanner( ) } - // Controls Overlay (Right: Audio toggle + Maturity Rating) Row( modifier = Modifier .align(Alignment.BottomEnd) @@ -570,7 +611,6 @@ fun CloneflixHeroBanner( .clip(RoundedCornerShape(8.dp)) .background(Color(0xFF181C24)) ) { - // Backdrop Gradient Box( modifier = Modifier .fillMaxSize() @@ -582,7 +622,6 @@ fun CloneflixHeroBanner( ) ) - // Bottom Fade Scrim Box( modifier = Modifier .fillMaxWidth() @@ -595,7 +634,6 @@ fun CloneflixHeroBanner( ) ) - // Top Right Close Button if (onCloseClick != null) { Box( modifier = Modifier @@ -613,7 +651,6 @@ fun CloneflixHeroBanner( } } - // Bottom-Left Title & Actions Column( modifier = Modifier .align(Alignment.BottomStart) @@ -652,7 +689,6 @@ fun CloneflixHeroBanner( } } - // Bottom-Right Audio Toggle if (onAudioToggle != null) { Box( modifier = Modifier @@ -678,7 +714,6 @@ fun CloneflixHeroBanner( .clip(RoundedCornerShape(8.dp)) .background(Color(0xFF0F1115)) ) { - // Vignette background Box( modifier = Modifier .fillMaxSize() @@ -690,7 +725,6 @@ fun CloneflixHeroBanner( ) ) - // Header Navigation Row( modifier = Modifier .fillMaxWidth() @@ -715,7 +749,6 @@ fun CloneflixHeroBanner( ) } - // Central Hero Content Column( modifier = Modifier .align(Alignment.Center) @@ -772,7 +805,6 @@ fun CloneflixHeroBanner( .clip(RoundedCornerShape(8.dp)) .background(Color(0xFF0F1115)) ) { - // Vignette background Box( modifier = Modifier .fillMaxSize() @@ -784,7 +816,6 @@ fun CloneflixHeroBanner( ) ) - // Header Row( modifier = Modifier .fillMaxWidth() @@ -801,7 +832,6 @@ fun CloneflixHeroBanner( ) } - // Auth Card in Center Box( modifier = Modifier .align(Alignment.Center) @@ -841,10 +871,6 @@ fun CloneflixHeroBanner( } } -/** - * Full Composed Home Page Hero Pattern from Figma Section 15 (Frame 314). - * Includes Top App Navigation Bar, Hero Backdrop with Title Preview, Rating, and Bottom Shelf Preview. - */ @Composable fun CloneflixHomePageHeroPattern( onPlayClick: () -> Unit = {}, @@ -865,7 +891,6 @@ fun CloneflixHomePageHeroPattern( .clip(RoundedCornerShape(8.dp)) .background(Color(0xFF141414)) ) { - // 1. Top Navigation Bar Row( modifier = Modifier .fillMaxWidth() @@ -878,7 +903,6 @@ fun CloneflixHomePageHeroPattern( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - // Left: Logo & Nav items Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(20.dp) @@ -928,7 +952,6 @@ fun CloneflixHomePageHeroPattern( } } - // Right: Actions (Search, Notification, Profile) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) @@ -951,7 +974,6 @@ fun CloneflixHomePageHeroPattern( .clickable(onClick = onNotificationClick) ) - // Profile Avatar with Dropdown Arrow Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), @@ -981,7 +1003,6 @@ fun CloneflixHomePageHeroPattern( } } - // 2. Hero Banner Body CloneflixHeroBanner( type = CloneflixHeroBannerType.HOME_PAGE, title = "HOUSE OF NINJAS", @@ -994,7 +1015,6 @@ fun CloneflixHomePageHeroPattern( isMuted = isMuted ) - // 3. Bottom Trending Shelf Row Preview Column( modifier = Modifier .fillMaxWidth() @@ -1047,7 +1067,6 @@ fun CloneflixHomePageHeroPattern( .focusable(interactionSource = interactionSource) .padding(8.dp) ) { - // Badge Tag Box( modifier = Modifier .align(Alignment.TopStart) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt index b625879fb71..ad03bab339b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt @@ -37,8 +37,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.zIndex import android.widget.ImageView import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import androidx.compose.ui.res.painterResource @@ -87,10 +89,6 @@ enum class CloneflixMovieCardType { CONTINUE_WATCHING } -/** - * Main Movie Card Composable from Figma Node 202:17654. - * Optimized for Google TV / Android TV D-Pad traversal and Mobile touch. - */ @Composable fun CloneflixMovieCard( title: String, @@ -128,10 +126,9 @@ fun CloneflixMovieCard( BorderStroke(dimens.borderSubtle, colors.border) } - Column( + Box( modifier = modifier .width(cardWidth) - .scale(scale) .then( if (onClick != null) { Modifier @@ -146,47 +143,77 @@ fun CloneflixMovieCard( Modifier.focusable(interactionSource = interactionSource) } ) - .semantics { contentDescription = title } + .semantics { contentDescription = title }, + contentAlignment = Alignment.Center ) { - if (type == CloneflixMovieCardType.TOP10 && top10Rank != null) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(cardHeight), - verticalAlignment = Alignment.Bottom - ) { - // Large Stylized Rank Number (1..10) - Box( + Column( + modifier = Modifier + .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scale + scaleY = scale + } + ) { + if (type == CloneflixMovieCardType.TOP10 && top10Rank != null) { + Row( modifier = Modifier - .width(CloneflixMovieCardDefaults.RankBoxWidth) - .fillMaxHeight(), - contentAlignment = Alignment.BottomCenter + .fillMaxWidth() + .height(cardHeight), + verticalAlignment = Alignment.Bottom ) { - Text( - text = top10Rank.toString(), - fontSize = CloneflixMovieCardDefaults.RankFontSize, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - color = Grey800, - lineHeight = CloneflixMovieCardDefaults.RankLineHeight, - modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankShadowOffsetX) - ) - Text( - text = top10Rank.toString(), - fontSize = CloneflixMovieCardDefaults.RankFontSize, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - color = PrimaryBlack, - lineHeight = CloneflixMovieCardDefaults.RankLineHeight, - modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankForegroundOffsetX) - ) - } + Box( + modifier = Modifier + .width(CloneflixMovieCardDefaults.RankBoxWidth) + .fillMaxHeight(), + contentAlignment = Alignment.BottomCenter + ) { + Text( + text = top10Rank.toString(), + fontSize = CloneflixMovieCardDefaults.RankFontSize, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + color = Grey800, + lineHeight = CloneflixMovieCardDefaults.RankLineHeight, + modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankShadowOffsetX) + ) + Text( + text = top10Rank.toString(), + fontSize = CloneflixMovieCardDefaults.RankFontSize, + fontWeight = FontWeight.Black, + fontFamily = FontFamily.SansSerif, + color = PrimaryBlack, + lineHeight = CloneflixMovieCardDefaults.RankLineHeight, + modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankForegroundOffsetX) + ) + } - // Poster Card Thumbnail + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(Grey800) + .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) + ) { + MovieCardSurface( + title = title, + showLogo = showLogo, + showBottomTitle = showBottomTitle, + badge = badge, + runtime = runtime, + timestamp = timestamp, + progress = progress, + showCenterPlay = false, + posterUrl = posterUrl + ) + } + } + } else { Box( modifier = Modifier - .weight(1f) - .fillMaxHeight() + .fillMaxWidth() + .height(if (type == CloneflixMovieCardType.TRAILER) CloneflixMovieCardDefaults.HeightTrailerThumbnail else cardHeight) .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) .background(Grey800) .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) @@ -199,45 +226,22 @@ fun CloneflixMovieCard( runtime = runtime, timestamp = timestamp, progress = progress, - showCenterPlay = false, + showCenterPlay = type == CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY || + type == CloneflixMovieCardType.EPISODE, posterUrl = posterUrl ) } - } - } else { - // Standard Card Surface - Box( - modifier = Modifier - .fillMaxWidth() - .height(if (type == CloneflixMovieCardType.TRAILER) CloneflixMovieCardDefaults.HeightTrailerThumbnail else cardHeight) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(Grey800) - .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) - ) { - MovieCardSurface( - title = title, - showLogo = showLogo, - showBottomTitle = showBottomTitle, - badge = badge, - runtime = runtime, - timestamp = timestamp, - progress = progress, - showCenterPlay = type == CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY || - type == CloneflixMovieCardType.EPISODE, - posterUrl = posterUrl - ) - } - // Trailer Title/Subtitle below thumbnail - if (type == CloneflixMovieCardType.TRAILER && !subtitle.isNullOrBlank()) { - Spacer(modifier = Modifier.height(dimens.spacingS)) - Text( - text = subtitle, - style = typography.mediumSmallBody, - color = if (isFocused) PrimaryWhite else Grey100, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) + if (type == CloneflixMovieCardType.TRAILER && !subtitle.isNullOrBlank()) { + Spacer(modifier = Modifier.height(dimens.spacingS)) + Text( + text = subtitle, + style = typography.mediumSmallBody, + color = if (isFocused) PrimaryWhite else Grey100, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } } } } @@ -257,7 +261,6 @@ private fun MovieCardSurface( ) { val typography = CloneflixTheme.typography - // Background Box( modifier = Modifier .fillMaxSize() @@ -284,7 +287,6 @@ private fun MovieCardSurface( modifier = Modifier.fillMaxSize() ) } else { - // Title Text in Mockup Center Text( text = title, style = typography.regularCaption1, @@ -298,7 +300,6 @@ private fun MovieCardSurface( ) } - // Top-Left Logo / Lettermark if (showLogo) { Box( modifier = Modifier @@ -309,7 +310,6 @@ private fun MovieCardSurface( } } - // Top-Right Badge (e.g. Recently Added, New Season) if (badge != null) { Box( modifier = Modifier @@ -320,15 +320,13 @@ private fun MovieCardSurface( } } - // Center Play Circle Button Overlay if (showCenterPlay) { Box( modifier = Modifier .size(CloneflixMovieCardDefaults.PlayButtonOverlaySize) .clip(CircleShape) .background(TransparentBlack60) - .border(BorderStroke(CloneflixTheme.dimens.borderDefault, PrimaryWhite), CircleShape) - .align(Alignment.Center), + .border(BorderStroke(CloneflixTheme.dimens.borderDefault, PrimaryWhite), CircleShape), contentAlignment = Alignment.Center ) { Icon( @@ -340,7 +338,6 @@ private fun MovieCardSurface( } } - // Bottom-Right Runtime Tag (e.g. "2h 18m") if (!runtime.isNullOrBlank()) { Box( modifier = Modifier @@ -359,7 +356,6 @@ private fun MovieCardSurface( } } - // Bottom-Right Timestamp Tag for Player Preview (e.g. "51:29") if (!timestamp.isNullOrBlank()) { Box( modifier = Modifier @@ -378,7 +374,6 @@ private fun MovieCardSurface( } } - // Bottom Progress Indicator (Continue Watching) if (progress != null) { Box( modifier = Modifier @@ -396,7 +391,6 @@ private fun MovieCardSurface( } } - // Bottom Title Overlay with Black Background Gradient (30% of card height) if (showBottomTitle && !title.isNullOrBlank()) { Box( modifier = Modifier @@ -432,10 +426,6 @@ private fun MovieCardSurface( } } -/** - * List of Episodes Item Pattern from Figma Section 120:3230. - * Supports Current (active/highlighted) vs Other episode states with D-pad navigation. - */ @Composable fun CloneflixEpisodeItem( episodeNumber: Int, @@ -490,7 +480,6 @@ fun CloneflixEpisodeItem( .padding(dimens.spacingL), verticalAlignment = Alignment.CenterVertically ) { - // Episode Number (e.g. "1", "2") Text( text = episodeNumber.toString(), style = typography.boldTitle2, @@ -500,7 +489,6 @@ fun CloneflixEpisodeItem( Spacer(modifier = Modifier.width(dimens.spacingM)) - // Episode Thumbnail with Play Button Overlay (128x72dp) Box( modifier = Modifier .width(CloneflixMovieCardDefaults.EpisodeThumbnailWidth) @@ -516,7 +504,6 @@ fun CloneflixEpisodeItem( color = Grey50 ) - // Play circle button overlay Box( modifier = Modifier .size(CloneflixMovieCardDefaults.EpisodeItemPlayButtonSize) @@ -536,7 +523,6 @@ fun CloneflixEpisodeItem( Spacer(modifier = Modifier.width(dimens.spacingL)) - // Episode Title, Duration & Description Column(modifier = Modifier.weight(1f)) { Row( modifier = Modifier.fillMaxWidth(), @@ -574,9 +560,6 @@ fun CloneflixEpisodeItem( } } -/** - * Movie Block Carousel / Row Pattern (Figma Section 202:19015). - */ @Composable fun CloneflixMovieBlockRow( title: String, @@ -638,9 +621,6 @@ data class CloneflixMovieCardItem( val posterUrl: String? = null ) -/** - * Expanded Movie Preview Banner / Card (Figma Section 109:9726). - */ @Composable fun CloneflixExpandedMoviePreview( title: String = CloneflixSampleData.SAMPLE_TITLE_HOUSE_OF_NINJAS, @@ -668,7 +648,6 @@ fun CloneflixExpandedMoviePreview( .border(BorderStroke(dimens.borderDefault, colors.border), RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) ) { Column(modifier = Modifier.fillMaxWidth()) { - // Backdrop Header with Action Buttons Box( modifier = Modifier .fillMaxWidth() @@ -683,7 +662,6 @@ fun CloneflixExpandedMoviePreview( ) ) ) { - // Backdrop Title & Logo Column( modifier = Modifier .align(Alignment.BottomStart) @@ -700,12 +678,10 @@ fun CloneflixExpandedMoviePreview( Spacer(modifier = Modifier.height(dimens.spacingM)) - // Circular Action Buttons (Play, Add, ThumbUp, Mute) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - // Play Button CloneflixCircleActionButton( icon = painterResource(id = R.drawable.cloneflix_ic_play), contentDescription = stringResource(R.string.cloneflix_cd_play), @@ -713,21 +689,18 @@ fun CloneflixExpandedMoviePreview( onClick = onPlayClick ) - // Add to List Button (+) CloneflixCircleActionButton( icon = painterResource(id = R.drawable.cloneflix_ic_plus), contentDescription = stringResource(R.string.cloneflix_cd_add_to_list), onClick = onAddClick ) - // Thumb Up Button CloneflixCircleActionButton( icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), contentDescription = stringResource(R.string.cloneflix_cd_like), onClick = onThumbUpClick ) - // Mute / Volume Button CloneflixCircleActionButton( icon = painterResource(id = R.drawable.cloneflix_ic_mute), contentDescription = stringResource(R.string.cloneflix_cd_mute), @@ -737,13 +710,11 @@ fun CloneflixExpandedMoviePreview( } } - // Info Body Column( modifier = Modifier .fillMaxWidth() .padding(dimens.spacing2Xl) ) { - // Metadata Row (Match, Rating, Duration, Quality) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) @@ -768,7 +739,6 @@ fun CloneflixExpandedMoviePreview( Spacer(modifier = Modifier.height(dimens.spacingM)) - // Genres separated by dots Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) @@ -803,9 +773,6 @@ fun CloneflixExpandedMoviePreview( } } -/** - * Circular action button for media preview overlays. - */ @Composable fun CloneflixCircleActionButton( icon: Painter, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt index e1b4eff89e3..e6c6f4c6a50 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt @@ -45,9 +45,6 @@ import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.revamp.compose.theme.getRatingScoreColor -/** - * Text / MovieInfo Inline Row Pattern from Figma Section 12. - */ @Composable fun CloneflixMovieInfoRow( matchText: String = "New", @@ -92,9 +89,6 @@ fun CloneflixMovieInfoRow( } } -/** - * MovieInfo Block Pattern from Figma Section 13 (Detailed header, advisories, Top 10 rank). - */ @Composable fun CloneflixMovieInfoBlock( matchScore: String = "98% Match", @@ -113,7 +107,6 @@ fun CloneflixMovieInfoBlock( modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - // Line 1: Match Score, Year, Duration, Quality, AD Icon Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) @@ -149,7 +142,6 @@ fun CloneflixMovieInfoBlock( } } - // Line 2: Rating badge with advisory keywords Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) @@ -165,7 +157,6 @@ fun CloneflixMovieInfoBlock( } } - // Line 3: Top 10 rank banner if present if (!top10RankText.isNullOrBlank()) { CloneflixTop10RankBanner( rankText = top10RankText, @@ -175,9 +166,6 @@ fun CloneflixMovieInfoBlock( } } -/** - * Complete MovieInfo Overview Card from Figma Section 14 (Synopsis, Block & Metadata columns). - */ @Composable fun CloneflixMovieInfoOverview( title: String = "House of Ninjas", @@ -253,7 +241,6 @@ fun CloneflixMovieInfoOverview( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Metadata columns: Cast, Genres, Mood tags Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { Text( text = buildAnnotatedString { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt index 7702d93a8ce..41d41bb2aa6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt @@ -96,7 +96,6 @@ fun CloneflixNextEpisodeCard( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - // Header row with countdown and close button Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, @@ -148,13 +147,11 @@ fun CloneflixNextEpisodeCard( } } - // Thumbnail preview + Episode Info row Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.Top ) { - // Thumbnail Box Box( modifier = Modifier .width(130.dp) @@ -183,7 +180,6 @@ fun CloneflixNextEpisodeCard( ) } - // Duration pill Box( modifier = Modifier .align(Alignment.BottomEnd) @@ -200,7 +196,6 @@ fun CloneflixNextEpisodeCard( } } - // Info Column Column( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp) @@ -227,7 +222,6 @@ fun CloneflixNextEpisodeCard( } } - // Action button "Play Next" val playNextInteraction = remember { MutableInteractionSource() } val isPlayNextFocused by playNextInteraction.collectIsFocusedAsState() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt index b8e6e41516f..4474782238d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt @@ -111,7 +111,6 @@ fun CloneflixPlaybackSpeedSelector( modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - // Top Row with Title & optional Close Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, @@ -145,14 +144,12 @@ fun CloneflixPlaybackSpeedSelector( Spacer(modifier = Modifier.height(28.dp)) - // Speed step track with points Box( modifier = Modifier .fillMaxWidth() .height(48.dp), contentAlignment = Alignment.Center ) { - // Connecting line Canvas( modifier = Modifier .fillMaxWidth() @@ -168,7 +165,6 @@ fun CloneflixPlaybackSpeedSelector( ) } - // Steps row Row( modifier = Modifier .fillMaxWidth() @@ -221,7 +217,6 @@ fun CloneflixPlaybackSpeedSelector( Spacer(modifier = Modifier.height(8.dp)) - // Speed labels row Row( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt index ff4561e8d7d..637ce260254 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt @@ -1,9 +1,5 @@ package com.lagradost.cloudstream3.ui.revamp.compose.components -/** - * Centralized mock/sample data for Cloneflix Previews and Showcases. - * Keeps production Composable parameter signatures clean from hardcoded sample strings. - */ object CloneflixSampleData { const val SAMPLE_TITLE_HOUSE_OF_NINJAS = "House of Ninjas" const val SAMPLE_MATCH_SCORE = "98% Match" diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt index 7e42c07ed8c..88fbc34aca6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt @@ -116,7 +116,6 @@ fun CloneflixVideoPlayerPattern( .clip(RoundedCornerShape(12.dp)) .background(Color(0xFF0F0F0F)) ) { - // 1. Mock Video Content / Cinematic Gradient Backdrop Box( modifier = Modifier .fillMaxSize() @@ -127,7 +126,6 @@ fun CloneflixVideoPlayerPattern( ) ) ) { - // Subtle cinematic film grain / center title watermark Text( text = title.uppercase(), style = CloneflixTheme.typography.logoBebas, @@ -137,7 +135,6 @@ fun CloneflixVideoPlayerPattern( ) } - // 2. Scrim Gradients (Top and Bottom) Box( modifier = Modifier .fillMaxWidth() @@ -162,7 +159,6 @@ fun CloneflixVideoPlayerPattern( ) ) - // 3. TOP BAR Row( modifier = Modifier .fillMaxWidth() @@ -175,7 +171,6 @@ fun CloneflixVideoPlayerPattern( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(14.dp) ) { - // Back Button Box( modifier = Modifier .size(40.dp) @@ -192,7 +187,6 @@ fun CloneflixVideoPlayerPattern( ) } - // Title + Episode Header Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) @@ -216,7 +210,6 @@ fun CloneflixVideoPlayerPattern( } } - // Flag / Options Button Box( modifier = Modifier .size(40.dp) @@ -234,13 +227,11 @@ fun CloneflixVideoPlayerPattern( } } - // 4. CENTER PLAY/PAUSE/REPLAY/FORWARD CONTROLS Row( modifier = Modifier.align(Alignment.Center), horizontalArrangement = Arrangement.spacedBy(28.dp), verticalAlignment = Alignment.CenterVertically ) { - // Replay 10s CloneflixPlayerControlButton( action = CloneflixPlayerControlAction.REPLAY_10, onClick = { currentProgress = (currentProgress - 0.05f).coerceAtLeast(0f) }, @@ -248,7 +239,6 @@ fun CloneflixVideoPlayerPattern( iconSize = 24.dp ) - // Play / Pause main button Box( modifier = Modifier .size(64.dp) @@ -265,7 +255,6 @@ fun CloneflixVideoPlayerPattern( ) } - // Forward 10s CloneflixPlayerControlButton( action = CloneflixPlayerControlAction.FORWARD_10, onClick = { currentProgress = (currentProgress + 0.05f).coerceAtMost(1f) }, @@ -274,7 +263,6 @@ fun CloneflixVideoPlayerPattern( ) } - // 5. SEEK / SCROLL PREVIEW TOOLTIP OVERLAY if (overlayState == CloneflixPlayerOverlayState.SCROLL_PREVIEW) { Column( modifier = Modifier @@ -321,7 +309,6 @@ fun CloneflixVideoPlayerPattern( } } - // 6. VOLUME SLIDER POPOVER OVERLAY if (overlayState == CloneflixPlayerOverlayState.SOUND_CHANGE) { Box( modifier = Modifier @@ -337,7 +324,6 @@ fun CloneflixVideoPlayerPattern( } } - // 7. BOTTOM CONTROLS & PROGRESS BAR Column( modifier = Modifier .fillMaxWidth() @@ -345,7 +331,6 @@ fun CloneflixVideoPlayerPattern( .padding(horizontal = 20.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(6.dp) ) { - // Progress Bar CloneflixVideoProgressIndicator( progress = currentProgress, bufferedProgress = 0.65f, @@ -356,13 +341,11 @@ fun CloneflixVideoPlayerPattern( showTimestamps = false ) - // Bottom Buttons Bar Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - // Left group: Play/Pause, 10s Replay, 10s Forward, Volume Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp) @@ -399,7 +382,6 @@ fun CloneflixVideoPlayerPattern( ) } - // Middle Info Label Text( text = "$title • $episodeLabel", style = CloneflixTheme.typography.mediumBody, @@ -409,7 +391,6 @@ fun CloneflixVideoPlayerPattern( overflow = TextOverflow.Ellipsis ) - // Right group: Next Episode, Episodes, Subtitles, Speed, Fullscreen Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp) @@ -472,7 +453,6 @@ fun CloneflixVideoPlayerPattern( } } - // 8. NEXT EPISODE FLOATING PROMPT OVERLAY if (overlayState == CloneflixPlayerOverlayState.NEXT_PREVIEW) { Box( modifier = Modifier @@ -496,7 +476,6 @@ fun CloneflixVideoPlayerPattern( } } - // 9. EPISODES DRAWER OVERLAY if (overlayState == CloneflixPlayerOverlayState.LIST_PREVIEW) { Box( modifier = Modifier @@ -517,7 +496,6 @@ fun CloneflixVideoPlayerPattern( } } - // 10. PLAYBACK SPEED SELECTOR OVERLAY if (overlayState == CloneflixPlayerOverlayState.SPEED_CHANGE) { Box( modifier = Modifier @@ -539,7 +517,6 @@ fun CloneflixVideoPlayerPattern( } } - // 11. AUDIO & SUBTITLES MODAL DIALOG OVERLAY if (overlayState == CloneflixPlayerOverlayState.SUBTITLES_CHANGE) { Box( modifier = Modifier diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt index dc541cc41a9..4117cd2992c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt @@ -200,7 +200,6 @@ fun CloneflixVideoProgressIndicator( val trackHeightPx = trackHeight.toPx() val cornerRadius = CornerRadius(trackHeightPx / 2f, trackHeightPx / 2f) - // 1. Background (Remaining) track drawRoundRect( color = Grey450, topLeft = Offset(0f, centerY - trackHeightPx / 2f), @@ -208,7 +207,6 @@ fun CloneflixVideoProgressIndicator( cornerRadius = cornerRadius ) - // 2. Buffered progress track val bufferedWidth = (bufferedProgress.coerceIn(0f, 1f) * totalWidth) if (bufferedWidth > 0f) { drawRoundRect( @@ -219,7 +217,6 @@ fun CloneflixVideoProgressIndicator( ) } - // 3. Played progress track val playedWidth = (animatedProgress * totalWidth) if (playedWidth > 0f) { drawRoundRect( @@ -230,7 +227,6 @@ fun CloneflixVideoProgressIndicator( ) } - // 4. Halo ring on hover/focus if (haloRadius.value > 0f) { drawCircle( color = PrimaryRed.copy(alpha = 0.25f), @@ -239,14 +235,12 @@ fun CloneflixVideoProgressIndicator( ) } - // 5. Scrubber thumb handle drawCircle( color = PrimaryRed, radius = thumbRadius.toPx(), center = Offset(playedWidth, centerY) ) - // 6. Center dot highlight in thumb if (isHovered) { drawCircle( color = PrimaryWhite, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt index 2b018ee0b1a..f533e633d87 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt @@ -136,7 +136,6 @@ fun CloneflixVerticalVolumeBar( val barX = (totalW - barW) / 2f val corner = CornerRadius(barW / 2f, barW / 2f) - // Background bar (grey) drawRoundRect( color = Grey450, topLeft = Offset(barX, 0f), @@ -144,7 +143,6 @@ fun CloneflixVerticalVolumeBar( cornerRadius = corner ) - // Active bar (red) from bottom upwards val fillH = animatedVolume * totalH if (fillH > 0f) { drawRoundRect( @@ -155,7 +153,6 @@ fun CloneflixVerticalVolumeBar( ) } - // Scrubber thumb circle val thumbY = totalH - fillH val thumbRadius = 7.dp.toPx() drawCircle( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt index d395d7afe21..c518dfe85b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt @@ -107,7 +107,6 @@ fun CloneflixAvatarsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // Section 1: Small Avatar Variants Text( text = "SMALL AVATAR (32DP)", style = typography.sectionHeader, @@ -170,7 +169,6 @@ fun CloneflixAvatarsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // Section 2: Large Avatar Variants & States Text( text = "LARGE AVATAR & FOCUS STATES (144DP)", style = typography.sectionHeader, @@ -244,7 +242,6 @@ fun CloneflixAvatarsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // Section 3: Sample "Who's Watching?" Profile Selector Text( text = "SAMPLE: WHO'S WATCHING? (PROFILE SELECTOR)", style = typography.sectionHeader, @@ -289,7 +286,6 @@ fun CloneflixAvatarsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // Section 4: Sample Profile Menu Popover Dropdown Text( text = "SAMPLE: PROFILE MENU (HEADER POPOVER)", style = typography.sectionHeader, @@ -345,7 +341,6 @@ fun CloneflixAvatarsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // Section 5: Official Avatars Catalog Text( text = "OFFICIAL AVATARS CATALOG", style = typography.sectionHeader, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt index 77590b3e6ee..f97f21540cf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt @@ -74,7 +74,6 @@ fun CloneflixButtonsComposeScreen( .verticalScroll(scrollState) .padding(dimens.spacing2Xl) ) { - // Top Header CloneflixHeader( title = "Buttons", subtitle = "Action Buttons • Media Control Buttons • Compositions & Patterns", @@ -83,9 +82,6 @@ fun CloneflixButtonsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // ========================================== - // SECTION 1: ACTION BUTTONS - // ========================================== Text( text = "ACTION BUTTONS", style = typography.sectionHeader, @@ -101,7 +97,6 @@ fun CloneflixButtonsComposeScreen( .padding(dimens.spacingL), verticalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { - // Sign In Buttons (Large & Small) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Sign In (Primary Red • Large & Small)", @@ -129,7 +124,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Use Sign-In Code Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Secondary / Dark Action", @@ -146,7 +140,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Play & More Info Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Hero CTA Actions (Play & More Info)", @@ -176,7 +169,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Get Started Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Get Started Action (Onboarding)", @@ -195,7 +187,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Manage Profiles (Outlined) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Manage Profiles (Outlined Border)", @@ -212,7 +203,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Checkbox (Remember Me) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Checkbox Button (Remember Me)", @@ -230,9 +220,6 @@ fun CloneflixButtonsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // ========================================== - // SECTION 2: MEDIA CONTROL BUTTONS - // ========================================== Text( text = "MEDIA CONTROL BUTTONS", style = typography.sectionHeader, @@ -248,7 +235,6 @@ fun CloneflixButtonsComposeScreen( .padding(dimens.spacingL), verticalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { - // Video Player Action Bar Controls Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { Text( text = "Video Player Controls (48dp / D-pad focusable)", @@ -310,7 +296,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Movie Preview Circular Actions Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { Text( text = "Movie Preview Action Buttons (40dp)", @@ -345,7 +330,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Multi-reaction Reaction Pill Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { Text( text = "Thumbs Multi-Reaction Pill (Dislike • Like • Love)", @@ -361,7 +345,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Hero Banner Controls Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { Text( text = "Hero Banner Video Controls (35dp Sound Toggle & Replay)", @@ -389,9 +372,6 @@ fun CloneflixButtonsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // ========================================== - // SECTION 3: PATTERNS & COMPOSITIONS - // ========================================== Text( text = "PATTERNS & COMPOSITIONS", style = typography.sectionHeader, @@ -407,7 +387,6 @@ fun CloneflixButtonsComposeScreen( .padding(dimens.spacingL), verticalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { - // Pattern 1: Hero Actions Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Pattern: Hero CTAs (Play + More Info)", @@ -422,7 +401,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Pattern 2: Preview Card Actions Row Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Pattern: Movie Card Quick Actions", @@ -440,7 +418,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Pattern 3: Hero Banner Rating & Sound Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Pattern: Hero Banner Rating + Sound", @@ -456,7 +433,6 @@ fun CloneflixButtonsComposeScreen( CloneflixDivider() - // Pattern 4: Full Video Player Control Bar Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "Pattern: Video Player Bottom Control Bar", diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt index a19dc7f27ee..2590a4522cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt @@ -71,9 +71,6 @@ fun CloneflixContentBlocksComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // -------------------------------------------------------------------- - // 1. HEADERS - // -------------------------------------------------------------------- Text( text = "HEADERS", style = typography.sectionHeader, @@ -125,9 +122,6 @@ fun CloneflixContentBlocksComposeScreen( CloneflixDivider() Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // -------------------------------------------------------------------- - // 2. LANDING PAGE BLOCKS - // -------------------------------------------------------------------- Text( text = "LANDING PAGE BLOCKS", style = typography.sectionHeader, @@ -137,7 +131,6 @@ fun CloneflixContentBlocksComposeScreen( CloneflixCard(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.padding(dimens.spacingL)) { - // Block 1: Headline + Image (TV) CloneflixFeatureBlock( title = "Enjoy on your TV", description = "Watch on Smart TVs, Playstation, Xbox, Chromecast, Apple TV, Blu-ray players, and more.", @@ -149,7 +142,6 @@ fun CloneflixContentBlocksComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Block 2: Image + Headline (Devices) CloneflixFeatureBlock( title = "Watch everywhere", description = "Stream unlimited movies and TV shows on your phone, tablet, laptop, and TV.", @@ -161,7 +153,6 @@ fun CloneflixContentBlocksComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Block 3: Headline + Image (Kids) CloneflixFeatureBlock( title = "Create profiles for kids", description = "Send kids on adventures with their favorite characters in a space made just for them — free with your membership.", @@ -177,9 +168,6 @@ fun CloneflixContentBlocksComposeScreen( CloneflixDivider() Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // -------------------------------------------------------------------- - // 3. SCREENS PREVIEWS - // -------------------------------------------------------------------- Text( text = "SCREENS PREVIEWS", style = typography.sectionHeader, @@ -257,9 +245,6 @@ fun CloneflixContentBlocksComposeScreen( CloneflixDivider() Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // -------------------------------------------------------------------- - // 4. FAQ ACCORDION - // -------------------------------------------------------------------- Text( text = "FAQ ACCORDION", style = typography.sectionHeader, @@ -275,9 +260,6 @@ fun CloneflixContentBlocksComposeScreen( CloneflixDivider() Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // -------------------------------------------------------------------- - // 5. FOOTERS - // -------------------------------------------------------------------- Text( text = "FOOTERS", style = typography.sectionHeader, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt index 3f6870215e1..509517f5c61 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt @@ -68,9 +68,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // ========================================== - // SECTION 1: TITLE PREVIEW SIZES - // ========================================== Text( text = "TITLE PREVIEW SIZES", style = typography.sectionHeader, @@ -79,7 +76,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Size: Large CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -108,7 +104,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Size: Medium CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -136,7 +131,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Size: Small 1, Small 2, Small 3 CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -193,9 +187,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // ========================================== - // SECTION 2: HERO BANNER VARIANTS - // ========================================== Text( text = "HERO BANNER VARIANTS", style = typography.sectionHeader, @@ -204,7 +195,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 1. Home Page Hero Banner CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -240,7 +230,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 2. Movie Preview Hero Banner CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -275,7 +264,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 3. Landing Page Hero CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -301,7 +289,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 4. Authentication Page Hero CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -327,9 +314,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // ========================================== - // SECTION 3: COMPOSED PATTERNS (HeroBanner+) - // ========================================== Text( text = "COMPOSED PATTERNS (HERO BANNER+)", style = typography.sectionHeader, @@ -338,7 +322,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 1. Full Composed Home Page Pattern CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -368,7 +351,6 @@ fun CloneflixHeroBannersComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 2. Full Composed Movie Preview Modal Pattern CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt index 17f6e1e7dbb..a6cc806bdf1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt @@ -135,7 +135,6 @@ fun CloneflixIconsLabelsComposeScreen( .verticalScroll(scrollState) .padding(dimens.spacing2Xl) ) { - // Section 0: Header CloneflixHeader( title = "Icons & Labels", subtitle = "Design System 2024 • Website & TV Reference", @@ -144,7 +143,6 @@ fun CloneflixIconsLabelsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // Section 1: Icons Text( text = "ICONS (24)", style = typography.sectionHeader, @@ -168,7 +166,6 @@ fun CloneflixIconsLabelsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // Section 2: Maturity Ratings Text( text = "MATURITY RATINGS", style = typography.sectionHeader, @@ -196,7 +193,6 @@ fun CloneflixIconsLabelsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // Section 3: Video Quality Text( text = "VIDEO QUALITY", style = typography.sectionHeader, @@ -224,7 +220,6 @@ fun CloneflixIconsLabelsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // Section 4: Labels & Badges Text( text = "LABELS & BADGES", style = typography.sectionHeader, @@ -299,7 +294,6 @@ fun CloneflixIconsLabelsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // Section 5: Logos Text( text = "LOGO VARIANTS", style = typography.sectionHeader, @@ -350,7 +344,6 @@ fun CloneflixIconsLabelsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // Section 6: Movie Info Patterns & Live Blocks Text( text = "PATTERNS & PREVIEWS", style = typography.sectionHeader, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index 037f13a535c..caccdf1b10b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -4,6 +4,7 @@ import android.widget.ImageView import android.widget.Toast import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -22,6 +23,8 @@ 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.relocation.BringIntoViewResponder +import androidx.compose.foundation.relocation.bringIntoViewResponder import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -33,6 +36,7 @@ import androidx.compose.runtime.LaunchedEffect 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.Alignment import androidx.compose.ui.Modifier @@ -42,8 +46,11 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.zIndex import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -65,6 +72,7 @@ import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeTyp import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCircleActionButton import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdown import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroPlayButton +import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroTrailerButton import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoVariant import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoView import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMaturityRating @@ -93,20 +101,13 @@ import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import kotlinx.coroutines.launch -/** - * Data representation for trailer / extra clips in Figma Node 121:4925. - */ data class CloneflixTrailerData( val title: String, val runtime: String, val rawTrailer: Any? = null ) -/** - * Movie Details Page / Details Only Screen based on Figma Node 121:4892. - * Fully dynamic and optimized for Google TV / Android TV D-Pad traversal and Mobile touch. - * Only renders sections and elements where real data is available; hides dummy/unsupported UI. - */ +@OptIn(ExperimentalFoundationApi::class) @Composable fun CloneflixMovieDetailsComposeScreen( modifier: Modifier = Modifier, @@ -156,6 +157,7 @@ fun CloneflixMovieDetailsComposeScreen( val typography = CloneflixTheme.typography val dimens = CloneflixTheme.dimens val scrollState = rememberScrollState() + val coroutineScope = rememberCoroutineScope() val playButtonFocusRequester = remember { FocusRequester() } @@ -174,7 +176,6 @@ fun CloneflixMovieDetailsComposeScreen( mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "" }) } - // Keep selectedSeasonText in sync when selectedSeasonIndex prop updates from ViewModel LaunchedEffect(selectedSeasonIndex, seasonOptions) { if (seasonOptions.isNotEmpty()) { selectedSeasonText = seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.first() } @@ -211,17 +212,25 @@ fun CloneflixMovieDetailsComposeScreen( .background(colors.background) .verticalScroll(scrollState) ) { - // ========================================== - // 1. HERO BANNER SECTION (Figma 121:4893 - 60% Screen Height Crop) - // ========================================== + val heroBringIntoViewResponder = remember { + object : BringIntoViewResponder { + override fun calculateRectForParent(localRect: Rect): Rect { + return Rect(0f, 0f, localRect.right, localRect.bottom) + } + + override suspend fun bringChildIntoView(localRect: () -> Rect?) { + } + } + } + Box( modifier = Modifier .fillMaxWidth() .height(heroHeight) + .bringIntoViewResponder(heroBringIntoViewResponder) .clipToBounds() .background(colors.background) ) { - // Live Backdrop Image filling full banner width & height if (!backdropUrl.isNullOrBlank()) { AndroidView( factory = { ctx -> @@ -240,7 +249,6 @@ fun CloneflixMovieDetailsComposeScreen( ) } - // Bottom Hero Gradient Overlay (fades smoothly down into the page background) Box( modifier = Modifier .fillMaxWidth() @@ -258,7 +266,6 @@ fun CloneflixMovieDetailsComposeScreen( ) ) - // Top-Right: Logo + Close Button Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), @@ -279,13 +286,11 @@ fun CloneflixMovieDetailsComposeScreen( ) } - // Title, Wordmark, and Action Buttons aligned at the bottom Column( modifier = Modifier .align(Alignment.BottomStart) .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) ) { - // Movie Title Logo or Title Text if (!logoUrl.isNullOrBlank()) { Box( modifier = Modifier @@ -317,7 +322,6 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Primary Play Button (Separate dedicated section) CloneflixHeroPlayButton( onClick = onPlayClick, text = "Play", @@ -325,42 +329,52 @@ fun CloneflixMovieDetailsComposeScreen( .focusRequester(playButtonFocusRequester) .onFocusChanged { state -> if (state.isFocused && scrollState.value > 0) { - // Keep Hero banner fully visible when play button is focused + coroutineScope.launch { + scrollState.animateScrollTo(0) + } } } ) Spacer(modifier = Modifier.height(dimens.spacingM)) - // Secondary Action Buttons Row (Add to List, Like, Trailer) below Play button Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - // Add to My List (+) - CloneflixCircleActionButton( - icon = painterResource( - id = if (inMyListState) R.drawable.cloneflix_ic_check else R.drawable.cloneflix_ic_plus - ), - contentDescription = if (inMyListState) "In My List" else "Add to My List", - onClick = { - inMyListState = !inMyListState - onAddToListClick() + modifier = Modifier.onFocusChanged { state -> + if (state.hasFocus && scrollState.value > 0) { + coroutineScope.launch { + scrollState.animateScrollTo(0) + } } - ) + } + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CloneflixCircleActionButton( + icon = painterResource( + id = if (inMyListState) R.drawable.cloneflix_ic_check else R.drawable.cloneflix_ic_plus + ), + contentDescription = if (inMyListState) "In My List" else "Add to My List", + onClick = { + inMyListState = !inMyListState + onAddToListClick() + } + ) - // Thumbs Up / Like - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), - contentDescription = "Like", - onClick = onLikeClick - ) + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), + contentDescription = "Like", + onClick = onLikeClick + ) + } - // Trailer Toggle (shown only if trailer links available) if (hasTrailers) { - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = "Trailer", + Spacer(modifier = Modifier.width(dimens.spacingL)) + + CloneflixHeroTrailerButton( + text = "Trailer", onClick = onTrailerClick ) } @@ -368,31 +382,23 @@ fun CloneflixMovieDetailsComposeScreen( } } - // ========================================== - // CONTENT SECTIONS WRAPPER WITH SAFE MARGINS - // ========================================== Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) ) { - // ========================================== - // 2. MOVIE INFO & METADATA SECTION (Figma 121:4894) - // ========================================== val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) ) { - // Left Column: Badges, Top 10, Synopsis Column( modifier = Modifier .weight(if (hasRightColumn) 0.65f else 1f) .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - // Line 1: Match Score, Year, Duration, Quality (rendered only if available) val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() @@ -432,7 +438,6 @@ fun CloneflixMovieDetailsComposeScreen( } } - // Line 2: Rating & Content Advisories (rendered only if available) if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -451,7 +456,6 @@ fun CloneflixMovieDetailsComposeScreen( } } - // Line 3: Top 10 Rank Badge (rendered only if available) if (!top10RankText.isNullOrBlank()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -480,18 +484,36 @@ fun CloneflixMovieDetailsComposeScreen( } } - // Line 4: Synopsis if (synopsis.isNotBlank()) { - Text( - text = synopsis, - style = typography.regularBody, - color = Grey50, - lineHeight = 22.sp - ) + val synopsisInteractionSource = remember { MutableInteractionSource() } + val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(if (isSynopsisFocused) Grey800.copy(alpha = 0.5f) else Color.Transparent) + .then( + if (isSynopsisFocused) { + Modifier.border( + BorderStroke(dimens.borderFocus, PrimaryWhite), + RoundedCornerShape(CloneflixTokens.RadiusCard) + ) + } else Modifier + ) + .focusable(interactionSource = synopsisInteractionSource) + .padding(dimens.spacingS) + ) { + Text( + text = synopsis, + style = typography.regularBody, + color = if (isSynopsisFocused) PrimaryWhite else Grey50, + lineHeight = 22.sp + ) + } } } - // Right Column: Cast, Genres, Mood Tags (rendered only if metadata exists) if (hasRightColumn) { Column( modifier = Modifier @@ -499,7 +521,6 @@ fun CloneflixMovieDetailsComposeScreen( .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - // Cast Row if (castList.isNotEmpty()) { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( @@ -517,7 +538,6 @@ fun CloneflixMovieDetailsComposeScreen( } } - // Genres Row if (genres.isNotEmpty()) { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( @@ -533,7 +553,6 @@ fun CloneflixMovieDetailsComposeScreen( } } - // This show is (Moods) if (moodTags.isNotEmpty()) { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( @@ -556,13 +575,10 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) } - // ========================================== - // 3. EPISODES & SEASONS SECTION (Figma 121:4895) - // ========================================== if (episodesToDisplay.isNotEmpty()) { Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), verticalAlignment = Alignment.CenterVertically ) { Text( @@ -572,7 +588,6 @@ fun CloneflixMovieDetailsComposeScreen( color = PrimaryWhite ) - // Season Selection Dropdown if (seasonOptions.size > 1) { CloneflixDropdown( options = seasonOptions, @@ -589,7 +604,6 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Episodes List Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(dimens.spacingL) @@ -612,9 +626,6 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) } - // ========================================== - // 4. MORE LIKE THIS SECTION (Figma 121:4909 - Poster Layout) - // ========================================== if (recommendationCards.isNotEmpty()) { Text( text = "More Like This", @@ -625,7 +636,6 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Responsive Poster Grid (6 columns per row for TV immersion) Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(dimens.spacingL) @@ -671,9 +681,6 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) } - // ========================================== - // 5. TRAILERS & MORE SECTION (Figma 121:4925) - // ========================================== if (trailersList.isNotEmpty()) { Text( text = "Trailers & More", @@ -705,9 +712,6 @@ fun CloneflixMovieDetailsComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) } - // ========================================== - // 6. ABOUT SECTION (Figma 121:4932 - Focusable Container for D-Pad) - // ========================================== val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || !maturityRating.isNullOrBlank() @@ -770,9 +774,6 @@ fun CloneflixMovieDetailsComposeScreen( } } -/** - * Dynamic Episode item row inside the Details Page (Figma Node 121:4895). - */ @Composable fun CloneflixDynamicEpisodeRow( episode: ResultEpisode, @@ -794,25 +795,33 @@ fun CloneflixDynamicEpisodeRow( val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null val progress = remember(episode) { episode.getWatchProgress() } - Row( + Box( modifier = modifier .fillMaxWidth() - .scale(scale) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(background) - .then(if (border != null) Modifier.border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) else Modifier) .clickable( interactionSource = interactionSource, indication = null, role = Role.Button, onClick = onClick ) - .focusable(interactionSource = interactionSource) - .padding(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + .focusable(interactionSource = interactionSource), + contentAlignment = Alignment.Center ) { - // Episode Number Index + Row( + modifier = Modifier + .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(background) + .then(if (border != null) Modifier.border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) else Modifier) + .padding(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { Text( text = episode.episode.toString(), style = typography.boldTitle1, @@ -821,7 +830,6 @@ fun CloneflixDynamicEpisodeRow( modifier = Modifier.width(32.dp) ) - // 16:9 Episode Thumbnail with Play Overlay & Progress Bar Box( modifier = Modifier .width(160.dp) @@ -843,7 +851,6 @@ fun CloneflixDynamicEpisodeRow( ) } - // Play Icon Overlay Box( modifier = Modifier .size(36.dp) @@ -862,7 +869,6 @@ fun CloneflixDynamicEpisodeRow( ) } - // Watched Progress Bar (if watched partially) if (progress > 0.05f) { Box( modifier = Modifier @@ -881,7 +887,6 @@ fun CloneflixDynamicEpisodeRow( } } - // Title, Duration, and Description Column( modifier = Modifier .weight(1f) @@ -920,7 +925,6 @@ fun CloneflixDynamicEpisodeRow( } } - // Download Action Button CloneflixCircleActionButton( icon = painterResource(id = R.drawable.netflix_download), contentDescription = "Download Episode ${episode.episode}", @@ -929,10 +933,8 @@ fun CloneflixDynamicEpisodeRow( ) } } +} -/** - * Trailer item card in Trailers & More (Figma Node 121:4925). - */ @Composable fun CloneflixTrailerItemCard( trailer: CloneflixTrailerData, @@ -951,72 +953,78 @@ fun CloneflixTrailerItemCard( val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(dimens.borderSubtle, Grey700) - Column( + Box( modifier = modifier - .scale(scale) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(Grey850) - .border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) .clickable( interactionSource = interactionSource, indication = null, role = Role.Button, onClick = onClick ) - .focusable(interactionSource = interactionSource) + .focusable(interactionSource = interactionSource), + contentAlignment = Alignment.Center ) { - Box( + Column( modifier = Modifier .fillMaxWidth() - .height(130.dp) - .background(Grey700) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(Grey850) + .border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) ) { - // Play overlay Box( modifier = Modifier - .size(42.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) - .align(Alignment.Center) + .fillMaxWidth() + .height(130.dp) + .background(Grey700) ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryWhite, + Box( modifier = Modifier - .size(20.dp) + .size(42.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) .align(Alignment.Center) - ) + ) { + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier + .size(20.dp) + .align(Alignment.Center) + ) + } } - } - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingM), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = trailer.title, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = trailer.runtime, - style = typography.regularCaption2, - color = Grey200 - ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingM), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = trailer.title, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = trailer.runtime, + style = typography.regularCaption2, + color = Grey200 + ) + } } } } -/** - * Metadata row inside the About section (Figma Node 121:4932). - */ @Composable fun CloneflixAboutMetadataRow( label: String, @@ -1050,12 +1058,6 @@ fun CloneflixAboutMetadataRow( showBackground = true, backgroundColor = 0xFF141414 ) -@Preview( - name = "TV 720p Landscape", - device = "spec:width=1280dp,height=720dp,dpi=213,orientation=landscape", - showBackground = true, - backgroundColor = 0xFF141414 -) @Composable private fun CloneflixMovieDetailsComposeScreenPreview() { CloneflixTheme { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt index 29eb42ac8d4..f91dff3d716 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt @@ -119,7 +119,6 @@ fun CloneflixMoviePreviewComposeScreen( .verticalScroll(scrollState) .padding(dimens.spacing2Xl) ) { - // Header CloneflixHeader( title = "Movie Preview", subtitle = "Movie Cards • Samples • Carousels • Episode Lists • Detail Previews", @@ -128,7 +127,6 @@ fun CloneflixMoviePreviewComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // SECTION 1: Movie Cards Text( text = "MOVIE CARDS (VARIANTS)", style = typography.sectionHeader, @@ -240,7 +238,6 @@ fun CloneflixMoviePreviewComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // SECTION 2: Samples - Home Page Movie Cards Text( text = "SAMPLES (HOME PAGE CARDS)", style = typography.sectionHeader, @@ -308,7 +305,6 @@ fun CloneflixMoviePreviewComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // SECTION 3: Patterns - Movie Blocks (Carousels) Text( text = "PATTERNS: MOVIE BLOCKS", style = typography.sectionHeader, @@ -349,7 +345,6 @@ fun CloneflixMoviePreviewComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // SECTION 4: Patterns - List of Episodes Text( text = "PATTERNS: LIST OF EPISODES", style = typography.sectionHeader, @@ -405,7 +400,6 @@ fun CloneflixMoviePreviewComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - // SECTION 5: Patterns - Expanded Movie Preview & Detail Card Text( text = "PATTERNS: MOVIE PREVIEW (EXPANDED & DETAIL)", style = typography.sectionHeader, @@ -452,9 +446,6 @@ fun CloneflixMoviePreviewComposeScreen( } } -/** - * More Like This Card with metadata and Add button from Figma 121:3161. - */ @Composable fun CloneflixMoreLikeThisDetailCard( title: String, @@ -478,7 +469,6 @@ fun CloneflixMoreLikeThisDetailCard( .border(BorderStroke(0.5.dp, colors.border), RoundedCornerShape(6.dp)) ) { Column(modifier = Modifier.fillMaxWidth()) { - // Thumbnail with Logo & Runtime CloneflixMovieCard( title = title, type = CloneflixMovieCardType.MORE_LIKE_THIS, @@ -487,7 +477,6 @@ fun CloneflixMoreLikeThisDetailCard( modifier = Modifier.fillMaxWidth() ) - // Metadata row with + button Column( modifier = Modifier .fillMaxWidth() @@ -511,7 +500,6 @@ fun CloneflixMoreLikeThisDetailCard( ) } - // Circle Add Button Box( modifier = Modifier .size(32.dp) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt index 00ba233f558..80f11ecc252 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt @@ -100,7 +100,6 @@ fun CloneflixVideoPlayerComposeScreen( .verticalScroll(scrollState) .padding(dimens.spacing2Xl) ) { - // Section Header matching Figma CloneflixHeader( title = "Video Player", subtitle = "Video Progress Indicator • Volume Slider • Speed • Subtitles • Patterns", @@ -109,9 +108,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // ========================================== - // SECTION 1: COMPONENTS - // ========================================== Text( text = "COMPONENTS", style = typography.sectionHeader, @@ -120,7 +116,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 1. Video Progress Indicator CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -204,7 +199,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 2. Volume Slider (High, Middle, Mute) CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -275,7 +269,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 3. Next Episode Preview & Playback Speed CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -326,7 +319,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // 4. Subtitles & Audio Selector CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE @@ -360,9 +352,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - // ========================================== - // SECTION 2: PATTERNS (FULL VIDEO PLAYER) - // ========================================== Text( text = "PATTERNS", style = typography.sectionHeader, @@ -371,7 +360,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Interactive pattern state switcher ScrollableTabRow( selectedTabIndex = patternTabs.indexOfFirst { it.second == selectedPatternState }.coerceAtLeast(0), containerColor = colors.surfaceElevated, @@ -406,7 +394,6 @@ fun CloneflixVideoPlayerComposeScreen( Spacer(modifier = Modifier.height(dimens.spacingL)) - // Full Composed Video Player Pattern Card CloneflixCard( modifier = Modifier.fillMaxWidth(), elevation = CloneflixCardElevation.SURFACE diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt index 176f593a8c9..921fe0205eb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt @@ -118,12 +118,6 @@ data class CloneflixColors( val orangeAccent: Color = OrangeAccent ) -/** - * Resolves color dynamically based on match score / rating quality: - * - High score (>= 70% or >= 7.0 / 10, or "New"): GreenAccent (0xFF46D369) - * - Moderate / average score (50% - 69% or 5.0 - 6.9 / 10): YellowAccent (0xFFFFB800) - * - Low score (< 50% or < 5.0 / 10): Red100 (0xFFEB3942) - */ fun getRatingScoreColor(scoreText: String?): Color { if (scoreText.isNullOrBlank()) return GreenAccent val text = scoreText.trim() From 884fa2b3786a2cde9cd1bae245650c8c96d2cff1 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 08:24:35 +0700 Subject: [PATCH 16/35] feat(compose): optimize TV details scroll with placeholder hierarchy and row recycling --- .../CloneflixHeroBannerComponents.kt | 16 +- .../compose/components/CloneflixMovieCard.kt | 8 +- .../CloneflixMovieDetailsComposeScreen.kt | 1179 +++++++++-------- 3 files changed, 652 insertions(+), 551 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt index 231e5f15baf..c25b58fddd2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt @@ -95,9 +95,10 @@ enum class CloneflixHeroBannerType { fun CloneflixHeroPlayButton( onClick: () -> Unit, modifier: Modifier = Modifier, - text: String = "Play" + text: String = "Play", + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true ) { - val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val scale by animateFloatAsState( @@ -113,10 +114,11 @@ fun CloneflixHeroPlayButton( .clickable( interactionSource = interactionSource, indication = null, + enabled = enabled, role = Role.Button, onClick = onClick ) - .focusable(interactionSource = interactionSource) + .focusable(enabled = enabled, interactionSource = interactionSource) .semantics { contentDescription = "$text Video" }, @@ -204,9 +206,10 @@ fun CloneflixHeroMoreInfoButton( fun CloneflixHeroTrailerButton( onClick: () -> Unit, modifier: Modifier = Modifier, - text: String = "Play Trailer" + text: String = "Play Trailer", + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true ) { - val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val dimens = CloneflixTheme.dimens @@ -232,10 +235,11 @@ fun CloneflixHeroTrailerButton( .clickable( interactionSource = interactionSource, indication = null, + enabled = enabled, role = Role.Button, onClick = onClick ) - .focusable(interactionSource = interactionSource) + .focusable(enabled = enabled, interactionSource = interactionSource) .padding(horizontal = 16.dp, vertical = 8.dp) .semantics { role = Role.Button diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt index ad03bab339b..04b99f075f7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt @@ -779,9 +779,10 @@ fun CloneflixCircleActionButton( contentDescription: String, onClick: () -> Unit, modifier: Modifier = Modifier, - isPrimary: Boolean = false + isPrimary: Boolean = false, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true ) { - val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val dimens = CloneflixTheme.dimens @@ -807,10 +808,11 @@ fun CloneflixCircleActionButton( .clickable( interactionSource = interactionSource, indication = null, + enabled = enabled, role = Role.Button, onClick = onClick ) - .focusable(interactionSource = interactionSource), + .focusable(enabled = enabled, interactionSource = interactionSource), contentAlignment = Alignment.Center ) { Icon( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index caccdf1b10b..8f070923cc0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -2,7 +2,9 @@ package com.lagradost.cloudstream3.ui.revamp.compose.screens import android.widget.ImageView import android.widget.Toast +import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -23,15 +25,18 @@ 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.relocation.BringIntoViewResponder -import androidx.compose.foundation.relocation.bringIntoViewResponder -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.gestures.BringIntoViewSpec +import androidx.compose.foundation.gestures.LocalBringIntoViewSpec +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -156,13 +161,18 @@ fun CloneflixMovieDetailsComposeScreen( val colors = CloneflixTheme.colors val typography = CloneflixTheme.typography val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() + val lazyListState = rememberLazyListState() val coroutineScope = rememberCoroutineScope() + val playInteractionSource = remember { MutableInteractionSource() } + val inMyListInteractionSource = remember { MutableInteractionSource() } + val likeInteractionSource = remember { MutableInteractionSource() } + val trailerInteractionSource = remember { MutableInteractionSource() } + val playButtonFocusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { - scrollState.scrollTo(0) + lazyListState.scrollToItem(0) playButtonFocusRequester.requestFocus() } @@ -204,411 +214,480 @@ fun CloneflixMovieDetailsComposeScreen( } ?: emptyList() } - val trailersList = dynamicTrailers ?: emptyList() + val chunkedRecommendations = remember(recommendationCards) { + recommendationCards.chunked(6) + } - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - ) { - val heroBringIntoViewResponder = remember { - object : BringIntoViewResponder { - override fun calculateRectForParent(localRect: Rect): Rect { - return Rect(0f, 0f, localRect.right, localRect.bottom) - } + val trailersList = dynamicTrailers ?: emptyList() - override suspend fun bringChildIntoView(localRect: () -> Rect?) { - } + val tvBringIntoViewSpec = remember { + object : BringIntoViewSpec { + override fun calculateScrollDistance( + offset: Float, + size: Float, + containerSize: Float + ): Float { + val parentFraction = 0.30f + val targetOffset = (parentFraction * containerSize) + return offset - targetOffset } } + } - Box( - modifier = Modifier - .fillMaxWidth() - .height(heroHeight) - .bringIntoViewResponder(heroBringIntoViewResponder) - .clipToBounds() + CompositionLocalProvider(LocalBringIntoViewSpec provides tvBringIntoViewSpec) { + LazyColumn( + state = lazyListState, + modifier = modifier + .fillMaxSize() .background(colors.background) ) { - if (!backdropUrl.isNullOrBlank()) { - AndroidView( - factory = { ctx -> - ImageView(ctx).apply { - scaleType = ImageView.ScaleType.CENTER_CROP - adjustViewBounds = false - clipToOutline = true - } - }, - update = { imageView -> - imageView.loadImage(backdropUrl) - }, + item(key = "hero_banner", contentType = "hero_banner") { + Box( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() + .height(heroHeight) .clipToBounds() - ) - } - - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.85f) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - colors.background.copy(alpha = 0.5f), - colors.background.copy(alpha = 0.92f), - colors.background - ) - ) - ) - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - modifier = Modifier - .align(Alignment.TopEnd) - .padding(dimens.spacing2Xl) - ) { - CloneflixLogoView( - variant = CloneflixLogoVariant.WORDMARK_MEDIUM, - text = providerName - ) - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_close), - contentDescription = "Close", - onClick = { - onCloseClick() - } - ) - } - - Column( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) - ) { - if (!logoUrl.isNullOrBlank()) { - Box( - modifier = Modifier - .height(68.dp) - .width(240.dp) - ) { + .background(colors.background) + ) { + if (!backdropUrl.isNullOrBlank()) { AndroidView( factory = { ctx -> ImageView(ctx).apply { - scaleType = ImageView.ScaleType.FIT_START + scaleType = ImageView.ScaleType.CENTER_CROP + adjustViewBounds = false + clipToOutline = true } }, update = { imageView -> - imageView.loadImage(logoUrl) + imageView.loadImage(backdropUrl) }, - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() + .clipToBounds() ) } - } else { - Text( - text = title, - style = typography.boldTitle1, - fontSize = 32.sp, - color = PrimaryWhite, - maxLines = 2, - overflow = TextOverflow.Ellipsis + + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.85f) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + colors.background.copy(alpha = 0.5f), + colors.background.copy(alpha = 0.92f), + colors.background + ) + ) + ) ) - } - Spacer(modifier = Modifier.height(dimens.spacingL)) + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) + .zIndex(50f), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Box( + modifier = Modifier + .height(42.dp) + .width(110.dp) + .focusRequester(playButtonFocusRequester) + .focusable(interactionSource = playInteractionSource) + .clickable( + interactionSource = playInteractionSource, + indication = null, + role = Role.Button, + onClick = onPlayClick + ) + ) - CloneflixHeroPlayButton( - onClick = onPlayClick, - text = "Play", - modifier = Modifier - .focusRequester(playButtonFocusRequester) - .onFocusChanged { state -> - if (state.isFocused && scrollState.value > 0) { - coroutineScope.launch { - scrollState.animateScrollTo(0) - } - } - } - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Box( + modifier = Modifier + .size(40.dp) + .focusable(interactionSource = inMyListInteractionSource) + .clickable( + interactionSource = inMyListInteractionSource, + indication = null, + role = Role.Button + ) { + inMyListState = !inMyListState + onAddToListClick() + } + ) - Spacer(modifier = Modifier.height(dimens.spacingM)) + Box( + modifier = Modifier + .size(40.dp) + .focusable(interactionSource = likeInteractionSource) + .clickable( + interactionSource = likeInteractionSource, + indication = null, + role = Role.Button, + onClick = onLikeClick + ) + ) + } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.onFocusChanged { state -> - if (state.hasFocus && scrollState.value > 0) { - coroutineScope.launch { - scrollState.animateScrollTo(0) + if (hasTrailers) { + Box( + modifier = Modifier + .height(40.dp) + .width(120.dp) + .focusable(interactionSource = trailerInteractionSource) + .clickable( + interactionSource = trailerInteractionSource, + indication = null, + role = Role.Button, + onClick = onTrailerClick + ) + ) } } } - ) { + + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(dimens.spacing2Xl) + ) { + CloneflixLogoView( + variant = CloneflixLogoVariant.WORDMARK_MEDIUM, + text = providerName + ) + } + Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom ) { - CloneflixCircleActionButton( - icon = painterResource( - id = if (inMyListState) R.drawable.cloneflix_ic_check else R.drawable.cloneflix_ic_plus - ), - contentDescription = if (inMyListState) "In My List" else "Add to My List", - onClick = { - inMyListState = !inMyListState - onAddToListClick() + Column { + if (!logoUrl.isNullOrBlank()) { + Box( + modifier = Modifier + .height(68.dp) + .width(240.dp) + ) { + AndroidView( + factory = { ctx -> + ImageView(ctx).apply { + scaleType = ImageView.ScaleType.FIT_START + } + }, + update = { imageView -> + imageView.loadImage(logoUrl) + }, + modifier = Modifier.fillMaxSize() + ) + } + } else { + Text( + text = title, + style = typography.boldTitle1, + fontSize = 32.sp, + color = PrimaryWhite, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) } - ) - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), - contentDescription = "Like", - onClick = onLikeClick - ) - } + Spacer(modifier = Modifier.height(dimens.spacingL)) - if (hasTrailers) { - Spacer(modifier = Modifier.width(dimens.spacingL)) + CloneflixHeroPlayButton( + onClick = onPlayClick, + text = "Play", + interactionSource = playInteractionSource, + enabled = false + ) - CloneflixHeroTrailerButton( - text = "Trailer", - onClick = onTrailerClick - ) + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CloneflixCircleActionButton( + icon = painterResource( + id = if (inMyListState) R.drawable.cloneflix_ic_check else R.drawable.cloneflix_ic_plus + ), + contentDescription = if (inMyListState) "In My List" else "Add to My List", + onClick = { + inMyListState = !inMyListState + onAddToListClick() + }, + interactionSource = inMyListInteractionSource, + enabled = false + ) + + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), + contentDescription = "Like", + onClick = onLikeClick, + interactionSource = likeInteractionSource, + enabled = false + ) + } + } + + if (hasTrailers) { + CloneflixHeroTrailerButton( + text = "Play Trailer", + onClick = onTrailerClick, + interactionSource = trailerInteractionSource, + enabled = false + ) + } } } } - } - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL) - ) { - val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() + item(key = "movie_info_synopsis", contentType = "movie_info") { + val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) - ) { - Column( + Row( modifier = Modifier - .weight(if (hasRightColumn) 0.65f else 1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) ) { - val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || - !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() + Column( + modifier = Modifier + .weight(if (hasRightColumn) 0.65f else 1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() - if (hasLine1) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (!matchScore.isNullOrBlank()) { - Text( - text = matchScore, - color = getRatingScoreColor(matchScore), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - } + if (hasLine1) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (!matchScore.isNullOrBlank()) { + Text( + text = matchScore, + color = getRatingScoreColor(matchScore), + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + } - if (!releaseYear.isNullOrBlank()) { - Text( - text = releaseYear, - color = PrimaryWhite, - style = typography.regularBody - ) - } + if (!releaseYear.isNullOrBlank()) { + Text( + text = releaseYear, + color = PrimaryWhite, + style = typography.regularBody + ) + } - if (!seasonsCount.isNullOrBlank()) { - Text( - text = seasonsCount, - color = PrimaryWhite, - style = typography.regularBody - ) - } + if (!seasonsCount.isNullOrBlank()) { + Text( + text = seasonsCount, + color = PrimaryWhite, + style = typography.regularBody + ) + } - if (!quality.isNullOrBlank()) { - CloneflixVideoQualityBadge(quality = quality) + if (!quality.isNullOrBlank()) { + CloneflixVideoQualityBadge(quality = quality) + } } } - } - if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - if (!maturityRating.isNullOrBlank()) { - CloneflixMaturityRating(rating = maturityRating) + if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (!maturityRating.isNullOrBlank()) { + CloneflixMaturityRating(rating = maturityRating) + } + if (!advisories.isNullOrBlank()) { + Text( + text = advisories, + color = Grey100, + style = typography.regularCaption1 + ) + } } - if (!advisories.isNullOrBlank()) { + } + + if (!top10RankText.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + modifier = Modifier.padding(vertical = dimens.spacingXs) + ) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(PrimaryRed) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = "TOP 10", + style = typography.regularCaption2, + color = PrimaryWhite, + fontWeight = FontWeight.Black + ) + } Text( - text = advisories, - color = Grey100, - style = typography.regularCaption1 + text = top10RankText, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite ) } } - } - if (!top10RankText.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - modifier = Modifier.padding(vertical = dimens.spacingXs) - ) { + if (synopsis.isNotBlank()) { + val synopsisInteractionSource = remember { MutableInteractionSource() } + val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() + Box( modifier = Modifier - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(PrimaryRed) - .padding(horizontal = 6.dp, vertical = 2.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(if (isSynopsisFocused) Grey800.copy(alpha = 0.5f) else Color.Transparent) + .then( + if (isSynopsisFocused) { + Modifier.border( + BorderStroke(dimens.borderFocus, PrimaryWhite), + RoundedCornerShape(CloneflixTokens.RadiusCard) + ) + } else Modifier + ) + .focusable(interactionSource = synopsisInteractionSource) + .padding(dimens.spacingS) ) { Text( - text = "TOP 10", - style = typography.regularCaption2, - color = PrimaryWhite, - fontWeight = FontWeight.Black + text = synopsis, + style = typography.regularBody, + color = if (isSynopsisFocused) PrimaryWhite else Grey50, + lineHeight = 22.sp ) } - Text( - text = top10RankText, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) } } - if (synopsis.isNotBlank()) { - val synopsisInteractionSource = remember { MutableInteractionSource() } - val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() - - Box( + if (hasRightColumn) { + Column( modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(if (isSynopsisFocused) Grey800.copy(alpha = 0.5f) else Color.Transparent) - .then( - if (isSynopsisFocused) { - Modifier.border( - BorderStroke(dimens.borderFocus, PrimaryWhite), - RoundedCornerShape(CloneflixTokens.RadiusCard) - ) - } else Modifier - ) - .focusable(interactionSource = synopsisInteractionSource) - .padding(dimens.spacingS) + .weight(0.35f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - Text( - text = synopsis, - style = typography.regularBody, - color = if (isSynopsisFocused) PrimaryWhite else Grey50, - lineHeight = 22.sp - ) - } - } - } - - if (hasRightColumn) { - Column( - modifier = Modifier - .weight(0.35f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (castList.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Cast", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = castList.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) + if (castList.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Cast", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = castList.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } } - } - if (genres.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Genres", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = genres.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite - ) + if (genres.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "Genres", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = genres.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite + ) + } } - } - if (moodTags.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "This show is", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = moodTags.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite - ) + if (moodTags.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "This show is", + style = typography.regularCaption2, + color = Grey200 + ) + Text( + text = moodTags.joinToString(", "), + style = typography.regularCaption1, + color = PrimaryWhite + ) + } } } } } } - if (episodesToDisplay.isNotEmpty() || recommendationCards.isNotEmpty() || trailersList.isNotEmpty()) { - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } - if (episodesToDisplay.isNotEmpty()) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Episodes", - style = typography.boldTitle2, - fontSize = 24.sp, - color = PrimaryWhite - ) - - if (seasonOptions.size > 1) { - CloneflixDropdown( - options = seasonOptions, - selectedOption = selectedSeasonText, - onOptionSelected = { seasonStr -> - selectedSeasonText = seasonStr - val idx = seasonOptions.indexOf(seasonStr) - if (idx >= 0) onSeasonSelect?.invoke(idx) - }, - width = 180.dp + item(key = "episodes_header", contentType = "episodes_header") { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Episodes", + style = typography.boldTitle2, + fontSize = 24.sp, + color = PrimaryWhite ) + + if (seasonOptions.size > 1) { + CloneflixDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { seasonStr -> + selectedSeasonText = seasonStr + val idx = seasonOptions.indexOf(seasonStr) + if (idx >= 0) onSeasonSelect?.invoke(idx) + }, + width = 180.dp + ) + } } } - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - episodesToDisplay.forEach { ep -> + items( + items = episodesToDisplay, + key = { ep -> "episode_${ep.id}" }, + contentType = { "episode_row" } + ) { ep -> + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp) + ) { CloneflixDynamicEpisodeRow( episode = ep, onClick = { @@ -622,94 +701,101 @@ fun CloneflixMovieDetailsComposeScreen( ) } } + } + + if (chunkedRecommendations.isNotEmpty()) { + item(key = "recommendations_header", contentType = "recommendations_header") { + Text( + text = "More Like This", + style = typography.boldTitle2, + fontSize = 22.sp, + color = PrimaryWhite, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) + ) + } - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) + itemsIndexed( + items = chunkedRecommendations, + key = { index, _ -> "rec_row_$index" }, + contentType = { _, _ -> "recommendation_row" } + ) { _, rowItems -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + rowItems.forEach { cardItem -> + Box(modifier = Modifier.weight(1f)) { + CloneflixMovieCard( + title = cardItem.title, + type = CloneflixMovieCardType.POSTER, + size = CloneflixMovieCardSize.MEDIUM, + badge = cardItem.badge, + runtime = cardItem.runtime, + posterUrl = cardItem.posterUrl, + showLogo = false, + showBottomTitle = true, + onClick = { + val rec = dynamicRecommendations?.find { it.name == cardItem.title } + if (rec != null && onRecommendationClick != null) { + onRecommendationClick(rec) + } else { + showToast("Opened recommendation: ${cardItem.title}") + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + } + if (rowItems.size < 6) { + repeat(6 - rowItems.size) { + Spacer(modifier = Modifier.weight(1f)) + } + } + } + } } - if (recommendationCards.isNotEmpty()) { - Text( - text = "More Like This", - style = typography.boldTitle2, - fontSize = 22.sp, - color = PrimaryWhite - ) + if (trailersList.isNotEmpty()) { + item(key = "trailers_section", contentType = "trailers_section") { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) + ) { + Text( + text = "Trailers & More", + style = typography.boldTitle2, + fontSize = 22.sp, + color = PrimaryWhite + ) - Spacer(modifier = Modifier.height(dimens.spacingL)) + Spacer(modifier = Modifier.height(dimens.spacingL)) - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - val chunkedRecommendations = recommendationCards.chunked(6) - chunkedRecommendations.forEach { rowItems -> Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { - rowItems.forEach { cardItem -> + trailersList.forEach { trailer -> Box(modifier = Modifier.weight(1f)) { - CloneflixMovieCard( - title = cardItem.title, - type = CloneflixMovieCardType.POSTER, - size = CloneflixMovieCardSize.MEDIUM, - badge = cardItem.badge, - runtime = cardItem.runtime, - posterUrl = cardItem.posterUrl, - showLogo = false, - showBottomTitle = true, + CloneflixTrailerItemCard( + trailer = trailer, onClick = { - val rec = dynamicRecommendations?.find { it.name == cardItem.title } - if (rec != null && onRecommendationClick != null) { - onRecommendationClick(rec) - } else { - showToast("Opened recommendation: ${cardItem.title}") - } + if (hasTrailers) onTrailerClick() + else showToast("Playing ${trailer.title}") }, modifier = Modifier.fillMaxWidth() ) } } - if (rowItems.size < 6) { - repeat(6 - rowItems.size) { - Spacer(modifier = Modifier.weight(1f)) - } - } } } } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } - - if (trailersList.isNotEmpty()) { - Text( - text = "Trailers & More", - style = typography.boldTitle2, - fontSize = 22.sp, - color = PrimaryWhite - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - trailersList.forEach { trailer -> - Box(modifier = Modifier.weight(1f)) { - CloneflixTrailerItemCard( - trailer = trailer, - onClick = { - if (hasTrailers) onTrailerClick() - else showToast("Playing ${trailer.title}") - }, - modifier = Modifier.fillMaxWidth() - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) } val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || @@ -717,58 +803,65 @@ fun CloneflixMovieDetailsComposeScreen( !maturityRating.isNullOrBlank() if (hasAboutContent) { - val aboutInteractionSource = remember { MutableInteractionSource() } - val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() - val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(1.dp, Grey600) + item(key = "about_section", contentType = "about_section") { + val aboutInteractionSource = remember { MutableInteractionSource() } + val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() + val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(1.dp, Grey600) - Column( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(if (isAboutFocused) Grey750 else Grey850) - .border(aboutBorder, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .focusable(interactionSource = aboutInteractionSource) - .padding(dimens.spacing2Xl), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = "About $title", - style = typography.boldTitle2, - fontSize = 20.sp, - color = PrimaryWhite - ) + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacing3Xl) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .background(if (isAboutFocused) Grey750 else Grey850) + .border(aboutBorder, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .focusable(interactionSource = aboutInteractionSource) + .padding(dimens.spacing2Xl), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = "About $title", + style = typography.boldTitle2, + fontSize = 20.sp, + color = PrimaryWhite + ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) + Spacer(modifier = Modifier.height(dimens.spacingXs)) - if (!creator.isNullOrBlank()) { - CloneflixAboutMetadataRow(label = "Creator", value = creator) - } - if (castList.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) - } - if (writers.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) - } - if (genres.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) - } - if (moodTags.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) - } - if (!maturityRating.isNullOrBlank()) { - val ratingDescription = if (!advisories.isNullOrBlank()) { - "$maturityRating Recommended for ages 16 and up. Contains $advisories." - } else { - maturityRating + if (!creator.isNullOrBlank()) { + CloneflixAboutMetadataRow(label = "Creator", value = creator) + } + if (castList.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) + } + if (writers.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) + } + if (genres.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) + } + if (moodTags.isNotEmpty()) { + CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) + } + if (!maturityRating.isNullOrBlank()) { + val ratingDescription = if (!advisories.isNullOrBlank()) { + "$maturityRating Recommended for ages 16 and up. Contains $advisories." + } else { + maturityRating + } + CloneflixAboutMetadataRow( + label = "Maturity Rating", + value = ratingDescription + ) + } } - CloneflixAboutMetadataRow( - label = "Maturity Rating", - value = ratingDescription - ) } } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) } } } @@ -788,6 +881,7 @@ fun CloneflixDynamicEpisodeRow( val scale by animateFloatAsState( targetValue = if (isFocused) 1.02f else 1f, + animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), label = "episodeScale" ) @@ -822,118 +916,118 @@ fun CloneflixDynamicEpisodeRow( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { - Text( - text = episode.episode.toString(), - style = typography.boldTitle1, - fontSize = 24.sp, - color = Grey200, - modifier = Modifier.width(32.dp) - ) - - Box( - modifier = Modifier - .width(160.dp) - .height(90.dp) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(Grey700) - ) { - if (!episode.poster.isNullOrBlank()) { - AndroidView( - factory = { ctx -> - ImageView(ctx).apply { - scaleType = ImageView.ScaleType.CENTER_CROP - } - }, - update = { imageView -> - imageView.loadImage(episode.poster) - }, - modifier = Modifier.fillMaxSize() - ) - } + Text( + text = episode.episode.toString(), + style = typography.boldTitle1, + fontSize = 24.sp, + color = Grey200, + modifier = Modifier.width(32.dp) + ) Box( modifier = Modifier - .size(36.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) - .align(Alignment.Center) + .width(160.dp) + .height(90.dp) + .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .background(Grey700) ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(16.dp) - .align(Alignment.Center) - ) - } + if (!episode.poster.isNullOrBlank()) { + AndroidView( + factory = { ctx -> + ImageView(ctx).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + } + }, + update = { imageView -> + imageView.loadImage(episode.poster) + }, + modifier = Modifier.fillMaxSize() + ) + } - if (progress > 0.05f) { Box( modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .align(Alignment.BottomStart) - .background(Grey800) + .size(36.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) ) { - Box( + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryWhite, modifier = Modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(PrimaryRed) + .size(16.dp) + .align(Alignment.Center) ) } + + if (progress > 0.05f) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .align(Alignment.BottomStart) + .background(Grey800) + ) { + Box( + modifier = Modifier + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(PrimaryRed) + ) + } + } } - } - Column( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) ) { - Text( - text = "${episode.episode}. ${episode.name ?: episode.headerName}", - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = if (episode.runTime != null) "${episode.runTime}m" else "", - style = typography.regularCaption1, - color = Grey200 - ) - } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${episode.episode}. ${episode.name ?: episode.headerName}", + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = if (episode.runTime != null) "${episode.runTime}m" else "", + style = typography.regularCaption1, + color = Grey200 + ) + } - if (!episode.description.isNullOrBlank()) { - Text( - text = episode.description, - style = typography.regularCaption1, - color = Grey100, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - lineHeight = 18.sp - ) + if (!episode.description.isNullOrBlank()) { + Text( + text = episode.description, + style = typography.regularCaption1, + color = Grey100, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + lineHeight = 18.sp + ) + } } - } - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.netflix_download), - contentDescription = "Download Episode ${episode.episode}", - onClick = onDownloadClick, - modifier = Modifier.size(36.dp) - ) + CloneflixCircleActionButton( + icon = painterResource(id = R.drawable.netflix_download), + contentDescription = "Download Episode ${episode.episode}", + onClick = onDownloadClick, + modifier = Modifier.size(36.dp) + ) + } } } -} @Composable fun CloneflixTrailerItemCard( @@ -948,6 +1042,7 @@ fun CloneflixTrailerItemCard( val scale by animateFloatAsState( targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, + animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), label = "trailerScale" ) From b8053652c2844e0f2107f8254ebb35b1b647bc2f Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 08:49:12 +0700 Subject: [PATCH 17/35] perf(compose): eliminate scroll lag with AsyncImage, deferred state reads, and stable models --- .../compose/components/CloneflixDefaults.kt | 13 + .../CloneflixHeroBannerComponents.kt | 23 +- .../compose/components/CloneflixMovieCard.kt | 27 +- .../CloneflixMovieDetailsComposeScreen.kt | 313 +++++++++--------- gradle/libs.versions.toml | 3 +- 5 files changed, 196 insertions(+), 183 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt index 11254a387af..468ca196499 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt @@ -1,5 +1,11 @@ package com.lagradost.cloudstream3.ui.revamp.compose.components +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.TweenSpec +import androidx.compose.animation.core.tween +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp @@ -20,6 +26,13 @@ object CloneflixTokens { val RadiusCard: Dp = 4.dp val RadiusCardMedium: Dp = 8.dp val RadiusCardLarge: Dp = 12.dp + + val ShapeCardSmall = RoundedCornerShape(4.dp) + val ShapeCardMedium = RoundedCornerShape(8.dp) + val ShapeCardLarge = RoundedCornerShape(12.dp) + val ShapePill = RoundedCornerShape(999.dp) + + val FastFocusAnimationSpec: TweenSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing) } object CloneflixMovieCardDefaults { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt index c25b58fddd2..d78d416d0ee 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt @@ -101,8 +101,9 @@ fun CloneflixHeroPlayButton( ) { val isFocused by interactionSource.collectIsFocusedAsState() - val scale by animateFloatAsState( + val scaleState = animateFloatAsState( targetValue = if (isFocused) 1.05f else 1f, + animationSpec = CloneflixTokens.FastFocusAnimationSpec, label = "heroPlayBtnScale" ) @@ -129,12 +130,12 @@ fun CloneflixHeroPlayButton( .height(42.dp) .zIndex(if (isFocused) 10f else 0f) .graphicsLayer { - scaleX = scale - scaleY = scale + scaleX = scaleState.value + scaleY = scaleState.value } - .clip(RoundedCornerShape(4.dp)) + .clip(CloneflixTokens.ShapeCardSmall) .background(background) - .then(if (border != null) Modifier.border(border, RoundedCornerShape(4.dp)) else Modifier) + .then(if (border != null) Modifier.border(border, CloneflixTokens.ShapeCardSmall) else Modifier) .padding(horizontal = 24.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp) @@ -213,8 +214,9 @@ fun CloneflixHeroTrailerButton( val isFocused by interactionSource.collectIsFocusedAsState() val dimens = CloneflixTheme.dimens - val scale by animateFloatAsState( + val scaleState = animateFloatAsState( targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, + animationSpec = CloneflixTokens.FastFocusAnimationSpec, label = "heroTrailerBtnScale" ) @@ -228,10 +230,13 @@ fun CloneflixHeroTrailerButton( Box( modifier = modifier .height(40.dp) - .scale(scale) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(CloneflixTokens.ShapeCardSmall) .background(background) - .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) + .border(border, CloneflixTokens.ShapeCardSmall) .clickable( interactionSource = interactionSource, indication = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt index 04b99f075f7..1bb809fcf3b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -39,10 +40,9 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.zIndex -import android.widget.ImageView -import com.lagradost.cloudstream3.utils.ImageLoader.loadImage +import coil3.compose.AsyncImage import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription @@ -112,8 +112,9 @@ fun CloneflixMovieCard( val typography = CloneflixTheme.typography val dimens = CloneflixTheme.dimens - val scale by animateFloatAsState( + val scaleState = animateFloatAsState( targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, + animationSpec = CloneflixTokens.FastFocusAnimationSpec, label = "cardScale" ) @@ -151,8 +152,8 @@ fun CloneflixMovieCard( .fillMaxWidth() .zIndex(if (isFocused) 10f else 0f) .graphicsLayer { - scaleX = scale - scaleY = scale + scaleX = scaleState.value + scaleY = scaleState.value } ) { if (type == CloneflixMovieCardType.TOP10 && top10Rank != null) { @@ -275,15 +276,10 @@ private fun MovieCardSurface( ) ) { if (!posterUrl.isNullOrBlank()) { - AndroidView( - factory = { ctx -> - ImageView(ctx).apply { - scaleType = ImageView.ScaleType.CENTER_CROP - } - }, - update = { imageView -> - imageView.loadImage(posterUrl) - }, + AsyncImage( + model = posterUrl, + contentDescription = title, + contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize() ) } else { @@ -606,6 +602,7 @@ fun CloneflixMovieBlockRow( } } +@Immutable data class CloneflixMovieCardItem( val title: String, val type: CloneflixMovieCardType = CloneflixMovieCardType.DEFAULT, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt index 8f070923cc0..be208064be9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3.ui.revamp.compose.screens -import android.widget.ImageView import android.widget.Toast import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateFloatAsState @@ -37,6 +36,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -55,6 +55,7 @@ import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.zIndex import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext @@ -67,7 +68,7 @@ import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.viewinterop.AndroidView +import coil3.compose.AsyncImage import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse @@ -103,15 +104,21 @@ import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 -import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import kotlinx.coroutines.launch +@Immutable data class CloneflixTrailerData( val title: String, val runtime: String, val rawTrailer: Any? = null ) +@Immutable +data class CloneflixRecommendationRow( + val rowIndex: Int, + val items: List +) + @OptIn(ExperimentalFoundationApi::class) @Composable fun CloneflixMovieDetailsComposeScreen( @@ -215,7 +222,9 @@ fun CloneflixMovieDetailsComposeScreen( } val chunkedRecommendations = remember(recommendationCards) { - recommendationCards.chunked(6) + recommendationCards.chunked(6).mapIndexed { idx, list -> + CloneflixRecommendationRow(idx, list) + } } val trailersList = dynamicTrailers ?: emptyList() @@ -250,17 +259,10 @@ fun CloneflixMovieDetailsComposeScreen( .background(colors.background) ) { if (!backdropUrl.isNullOrBlank()) { - AndroidView( - factory = { ctx -> - ImageView(ctx).apply { - scaleType = ImageView.ScaleType.CENTER_CROP - adjustViewBounds = false - clipToOutline = true - } - }, - update = { imageView -> - imageView.loadImage(backdropUrl) - }, + AsyncImage( + model = backdropUrl, + contentDescription = null, + contentScale = ContentScale.Crop, modifier = Modifier .fillMaxSize() .clipToBounds() @@ -385,15 +387,10 @@ fun CloneflixMovieDetailsComposeScreen( .height(68.dp) .width(240.dp) ) { - AndroidView( - factory = { ctx -> - ImageView(ctx).apply { - scaleType = ImageView.ScaleType.FIT_START - } - }, - update = { imageView -> - imageView.loadImage(logoUrl) - }, + AsyncImage( + model = logoUrl, + contentDescription = title, + contentScale = ContentScale.Fit, modifier = Modifier.fillMaxSize() ) } @@ -683,23 +680,26 @@ fun CloneflixMovieDetailsComposeScreen( key = { ep -> "episode_${ep.id}" }, contentType = { "episode_row" } ) { ep -> - Box( + val onEpClick = remember(ep) { + { + if (onEpisodeClick != null) onEpisodeClick(ep) + else showToast("Playing ${ep.headerName}: ${ep.name}") + } + } + val onEpDownload = remember(ep) { + { + if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) + else showToast("Downloading Episode ${ep.episode}") + } + } + CloneflixDynamicEpisodeRow( + episode = ep, + onClick = onEpClick, + onDownloadClick = onEpDownload, modifier = Modifier .fillMaxWidth() .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp) - ) { - CloneflixDynamicEpisodeRow( - episode = ep, - onClick = { - if (onEpisodeClick != null) onEpisodeClick(ep) - else showToast("Playing ${ep.headerName}: ${ep.name}") - }, - onDownloadClick = { - if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) - else showToast("Downloading Episode ${ep.episode}") - } - ) - } + ) } } @@ -717,46 +717,22 @@ fun CloneflixMovieDetailsComposeScreen( ) } - itemsIndexed( + items( items = chunkedRecommendations, - key = { index, _ -> "rec_row_$index" }, - contentType = { _, _ -> "recommendation_row" } - ) { _, rowItems -> - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - rowItems.forEach { cardItem -> - Box(modifier = Modifier.weight(1f)) { - CloneflixMovieCard( - title = cardItem.title, - type = CloneflixMovieCardType.POSTER, - size = CloneflixMovieCardSize.MEDIUM, - badge = cardItem.badge, - runtime = cardItem.runtime, - posterUrl = cardItem.posterUrl, - showLogo = false, - showBottomTitle = true, - onClick = { - val rec = dynamicRecommendations?.find { it.name == cardItem.title } - if (rec != null && onRecommendationClick != null) { - onRecommendationClick(rec) - } else { - showToast("Opened recommendation: ${cardItem.title}") - } - }, - modifier = Modifier.fillMaxWidth() - ) - } - } - if (rowItems.size < 6) { - repeat(6 - rowItems.size) { - Spacer(modifier = Modifier.weight(1f)) + key = { row -> "rec_row_${row.rowIndex}" }, + contentType = { "recommendation_row" } + ) { row -> + CloneflixRecommendationRowView( + row = row, + onCardClick = { cardItem -> + val rec = dynamicRecommendations?.find { it.name == cardItem.title } + if (rec != null && onRecommendationClick != null) { + onRecommendationClick(rec) + } else { + showToast("Opened recommendation: ${cardItem.title}") } } - } + ) } } @@ -879,9 +855,9 @@ fun CloneflixDynamicEpisodeRow( val typography = CloneflixTheme.typography val dimens = CloneflixTheme.dimens - val scale by animateFloatAsState( + val scaleState = animateFloatAsState( targetValue = if (isFocused) 1.02f else 1f, - animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), + animationSpec = CloneflixTokens.FastFocusAnimationSpec, label = "episodeScale" ) @@ -889,33 +865,28 @@ fun CloneflixDynamicEpisodeRow( val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null val progress = remember(episode) { episode.getWatchProgress() } - Box( + Row( modifier = modifier .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(CloneflixTokens.ShapeCardMedium) + .background(background) + .then(if (border != null) Modifier.border(border, CloneflixTokens.ShapeCardMedium) else Modifier) .clickable( interactionSource = interactionSource, indication = null, role = Role.Button, onClick = onClick ) - .focusable(interactionSource = interactionSource), - contentAlignment = Alignment.Center + .focusable(interactionSource = interactionSource) + .padding(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { - Row( - modifier = Modifier - .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scale - scaleY = scale - } - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(background) - .then(if (border != null) Modifier.border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) else Modifier) - .padding(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { Text( text = episode.episode.toString(), style = typography.boldTitle1, @@ -928,19 +899,14 @@ fun CloneflixDynamicEpisodeRow( modifier = Modifier .width(160.dp) .height(90.dp) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) + .clip(CloneflixTokens.ShapeCardSmall) .background(Grey700) ) { if (!episode.poster.isNullOrBlank()) { - AndroidView( - factory = { ctx -> - ImageView(ctx).apply { - scaleType = ImageView.ScaleType.CENTER_CROP - } - }, - update = { imageView -> - imageView.loadImage(episode.poster) - }, + AsyncImage( + model = episode.poster, + contentDescription = episode.name, + contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize() ) } @@ -1026,7 +992,6 @@ fun CloneflixDynamicEpisodeRow( modifier = Modifier.size(36.dp) ) } - } } @Composable @@ -1040,83 +1005,78 @@ fun CloneflixTrailerItemCard( val typography = CloneflixTheme.typography val dimens = CloneflixTheme.dimens - val scale by animateFloatAsState( + val scaleState = animateFloatAsState( targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, - animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), + animationSpec = CloneflixTokens.FastFocusAnimationSpec, label = "trailerScale" ) val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(dimens.borderSubtle, Grey700) - Box( + Column( modifier = modifier + .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(CloneflixTokens.ShapeCardMedium) + .background(Grey850) + .border(border, CloneflixTokens.ShapeCardMedium) .clickable( interactionSource = interactionSource, indication = null, role = Role.Button, onClick = onClick ) - .focusable(interactionSource = interactionSource), - contentAlignment = Alignment.Center + .focusable(interactionSource = interactionSource) ) { - Column( + Box( modifier = Modifier .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scale - scaleY = scale - } - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(Grey850) - .border(border, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) + .height(130.dp) + .background(Grey700) ) { Box( modifier = Modifier - .fillMaxWidth() - .height(130.dp) - .background(Grey700) + .size(42.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) ) { - Box( + Icon( + painter = painterResource(id = R.drawable.cloneflix_ic_play), + contentDescription = null, + tint = PrimaryWhite, modifier = Modifier - .size(42.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) + .size(20.dp) .align(Alignment.Center) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(20.dp) - .align(Alignment.Center) - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingM), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = trailer.title, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = trailer.runtime, - style = typography.regularCaption2, - color = Grey200 ) } } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingM), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = trailer.title, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = PrimaryWhite, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = trailer.runtime, + style = typography.regularCaption2, + color = Grey200 + ) + } } } @@ -1147,6 +1107,43 @@ fun CloneflixAboutMetadataRow( } } +@Composable +fun CloneflixRecommendationRowView( + row: CloneflixRecommendationRow, + onCardClick: (CloneflixMovieCardItem) -> Unit, + modifier: Modifier = Modifier +) { + val dimens = CloneflixTheme.dimens + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + row.items.forEach { cardItem -> + Box(modifier = Modifier.weight(1f)) { + CloneflixMovieCard( + title = cardItem.title, + type = CloneflixMovieCardType.POSTER, + size = CloneflixMovieCardSize.MEDIUM, + badge = cardItem.badge, + runtime = cardItem.runtime, + posterUrl = cardItem.posterUrl, + showLogo = false, + showBottomTitle = true, + onClick = { onCardClick(cardItem) }, + modifier = Modifier.fillMaxWidth() + ) + } + } + if (row.items.size < 6) { + repeat(6 - row.items.size) { + Spacer(modifier = Modifier.weight(1f)) + } + } + } +} + @Preview( name = "TV 1080p Landscape", device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index af0884db010..9fe11ed4d7d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -74,6 +74,7 @@ annotation = { module = "androidx.annotation:annotation", version.ref = "annotat appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } +coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" } conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = "conscryptAndroid" } @@ -160,7 +161,7 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko [bundles] -coil = ["coil", "coil-network-okhttp"] +coil = ["coil", "coil-network-okhttp", "coil-compose"] cryptography = ["cryptography-core", "cryptography-provider-optimal"] lifecycle = ["lifecycle-livedata-ktx", "lifecycle-viewmodel-ktx"] media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"] From 883e557f8e1c2d102bc0f3d612b0391ce86b96d0 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 09:10:10 +0700 Subject: [PATCH 18/35] feat(detail): initial final release detail revamp --- app/src/main/AndroidManifest.xml | 12 - .../ui/result/ResultFragmentTv.kt | 8 +- .../compose/MovieDetailsComposeScreen.kt | 1139 ++++++++++++++++ .../components/MovieDetailsComponents.kt | 702 ++++++++++ .../result/compose/theme/MovieDetailsTheme.kt | 236 ++++ .../ui/revamp/RevampDesignSystemActivity.kt | 23 - .../ui/revamp/RevampDesignSystemFragment.kt | 60 - .../ui/revamp/about/CloneflixAboutCardView.kt | 43 - .../ui/revamp/about/CloneflixAboutFragment.kt | 60 - .../revamp/colors/CloneflixColorsAdapter.kt | 43 - .../revamp/colors/CloneflixColorsFragment.kt | 44 - .../ui/revamp/components/CloneflixButton.kt | 133 -- .../ui/revamp/components/CloneflixCardView.kt | 56 - .../components/CloneflixColorSwatchView.kt | 50 - .../components/CloneflixComponentsFragment.kt | 53 - .../ui/revamp/components/CloneflixDivider.kt | 25 - .../revamp/components/CloneflixHeaderView.kt | 58 - .../revamp/components/CloneflixInputField.kt | 29 - .../ui/revamp/components/CloneflixTextView.kt | 69 - .../compose/CloneflixComposeActivity.kt | 19 - .../CloneflixAudioSubtitlesSelector.kt | 305 ----- .../components/CloneflixAvatarComponents.kt | 786 ----------- .../components/CloneflixBadgeComponents.kt | 427 ------ .../components/CloneflixButtonPatterns.kt | 297 ----- .../components/CloneflixComposeComponents.kt | 829 ------------ .../CloneflixContentBlockComponents.kt | 1082 --------------- .../compose/components/CloneflixDefaults.kt | 125 -- .../compose/components/CloneflixDropdown.kt | 291 ---- .../components/CloneflixEpisodesDrawer.kt | 330 ----- .../CloneflixHeroBannerComponents.kt | 1119 ---------------- .../CloneflixMediaControlButtons.kt | 371 ------ .../compose/components/CloneflixMovieCard.kt | 857 ------------ .../CloneflixMovieInfoComponents.kt | 295 ----- .../CloneflixNextEpisodeComponents.kt | 290 ---- .../CloneflixPlaybackSpeedSelector.kt | 261 ---- .../compose/components/CloneflixSampleData.kt | 51 - .../components/CloneflixVideoPlayerPattern.kt | 559 -------- .../CloneflixVideoProgressIndicator.kt | 316 ----- .../components/CloneflixVolumeSlider.kt | 258 ---- .../screens/CloneflixAboutComposeScreen.kt | 149 --- .../screens/CloneflixAvatarsComposeScreen.kt | 479 ------- .../screens/CloneflixButtonsComposeScreen.kt | 470 ------- .../screens/CloneflixColorsComposeScreen.kt | 81 -- .../CloneflixComponentsComposeScreen.kt | 195 --- .../CloneflixContentBlocksComposeScreen.kt | 323 ----- .../screens/CloneflixDropdownComposeScreen.kt | 295 ----- .../CloneflixHeroBannersComposeScreen.kt | 422 ------ .../CloneflixIconsLabelsComposeScreen.kt | 495 ------- .../CloneflixInputFieldsComposeScreen.kt | 198 --- .../CloneflixMovieDetailsComposeScreen.kt | 1176 ----------------- .../CloneflixMoviePreviewComposeScreen.kt | 548 -------- .../screens/CloneflixShowcaseComposeScreen.kt | 163 --- .../CloneflixTypographyComposeScreen.kt | 222 ---- .../CloneflixVideoPlayerComposeScreen.kt | 432 ------ .../compose/theme/CloneflixComposeTheme.kt | 282 ---- .../icons/CloneflixIconsLabelsFragment.kt | 31 - .../ui/revamp/theme/CloneflixColors.kt | 162 --- .../ui/revamp/theme/CloneflixDimens.kt | 61 - .../ui/revamp/theme/CloneflixTheme.kt | 16 - .../ui/revamp/theme/CloneflixTypography.kt | 78 -- .../typography/CloneflixTypographyAdapter.kt | 55 - .../typography/CloneflixTypographyFragment.kt | 85 -- .../main/res/drawable/cloneflix_bg_card.xml | 9 - .../drawable/cloneflix_bg_card_elevated.xml | 9 - .../res/drawable/cloneflix_bg_header_icon.xml | 8 - .../res/drawable/cloneflix_bg_input_field.xml | 21 - .../drawable/cloneflix_bg_swatch_preview.xml | 8 - .../main/res/drawable/cloneflix_btn_ghost.xml | 10 - .../res/drawable/cloneflix_btn_outline.xml | 13 - .../res/drawable/cloneflix_btn_primary.xml | 10 - .../res/drawable/cloneflix_btn_secondary.xml | 10 - .../main/res/drawable/cloneflix_ic_about.xml | 9 - .../res/drawable/cloneflix_ic_account.xml | 9 - app/src/main/res/drawable/cloneflix_ic_ad.xml | 9 - .../res/drawable/cloneflix_ic_arrow_down.xml | 9 - .../res/drawable/cloneflix_ic_arrow_left.xml | 9 - .../res/drawable/cloneflix_ic_arrow_right.xml | 9 - .../cloneflix_ic_arrow_right_narrow.xml | 9 - .../res/drawable/cloneflix_ic_arrow_up.xml | 9 - .../main/res/drawable/cloneflix_ic_check.xml | 9 - .../drawable/cloneflix_ic_chevron_right.xml | 13 - .../drawable/cloneflix_ic_circle_error.xml | 17 - .../main/res/drawable/cloneflix_ic_close.xml | 9 - .../main/res/drawable/cloneflix_ic_colors.xml | 9 - .../main/res/drawable/cloneflix_ic_copy.xml | 9 - .../drawable/cloneflix_ic_double_thumb_up.xml | 12 - .../res/drawable/cloneflix_ic_dropdown.xml | 9 - .../main/res/drawable/cloneflix_ic_edit.xml | 9 - .../drawable/cloneflix_ic_error_circle.xml | 9 - .../res/drawable/cloneflix_ic_feedback.xml | 9 - .../main/res/drawable/cloneflix_ic_flag.xml | 9 - .../main/res/drawable/cloneflix_ic_help.xml | 9 - .../main/res/drawable/cloneflix_ic_info.xml | 9 - .../res/drawable/cloneflix_ic_input_field.xml | 12 - .../main/res/drawable/cloneflix_ic_labels.xml | 9 - .../main/res/drawable/cloneflix_ic_mute.xml | 9 - .../drawable/cloneflix_ic_notification.xml | 9 - .../res/drawable/cloneflix_ic_open_in_new.xml | 9 - .../main/res/drawable/cloneflix_ic_person.xml | 9 - .../main/res/drawable/cloneflix_ic_play.xml | 9 - .../main/res/drawable/cloneflix_ic_plus.xml | 9 - .../main/res/drawable/cloneflix_ic_replay.xml | 9 - .../drawable/cloneflix_ic_replay_arrow.xml | 9 - .../main/res/drawable/cloneflix_ic_search.xml | 9 - .../res/drawable/cloneflix_ic_social_fb.xml | 9 - .../res/drawable/cloneflix_ic_social_ig.xml | 9 - .../res/drawable/cloneflix_ic_social_x.xml | 9 - .../res/drawable/cloneflix_ic_social_yt.xml | 9 - .../res/drawable/cloneflix_ic_thumb_down.xml | 9 - .../res/drawable/cloneflix_ic_thumb_up.xml | 9 - .../res/drawable/cloneflix_ic_typography.xml | 9 - .../res/drawable/cloneflix_ic_videoplayer.xml | 9 - .../res/drawable/cloneflix_ic_visibility.xml | 9 - .../drawable/cloneflix_ic_visibility_off.xml | 9 - .../main/res/drawable/cloneflix_ic_volume.xml | 9 - app/src/main/res/font/netflix_sans_black.ttf | Bin 147116 -> 0 bytes .../res/font/netflix_sans_black_italic.ttf | Bin 129048 -> 0 bytes app/src/main/res/font/netflix_sans_bold.ttf | Bin 146248 -> 0 bytes .../res/font/netflix_sans_bold_italic.ttf | Bin 143256 -> 0 bytes app/src/main/res/font/netflix_sans_italic.ttf | Bin 150520 -> 0 bytes app/src/main/res/font/netflix_sans_light.ttf | Bin 146064 -> 0 bytes .../res/font/netflix_sans_light_italic.ttf | Bin 149336 -> 0 bytes app/src/main/res/font/netflix_sans_medium.ttf | Bin 146636 -> 0 bytes .../res/font/netflix_sans_medium_italic.ttf | Bin 147660 -> 0 bytes .../main/res/font/netflix_sans_regular.ttf | Bin 147476 -> 0 bytes app/src/main/res/font/netflix_sans_thin.ttf | Bin 146064 -> 0 bytes .../res/font/netflix_sans_thin_italic.ttf | Bin 148228 -> 0 bytes .../layout/revamp_activity_design_system.xml | 6 - .../main/res/layout/revamp_fragment_about.xml | 69 - .../res/layout/revamp_fragment_colors.xml | 35 - .../res/layout/revamp_fragment_components.xml | 203 --- .../layout/revamp_fragment_design_system.xml | 63 - .../res/layout/revamp_fragment_typography.xml | 105 -- .../res/layout/revamp_item_color_section.xml | 25 - .../res/layout/revamp_item_color_swatch.xml | 58 - .../layout/revamp_item_typography_style.xml | 48 - .../res/layout/revamp_view_about_card.xml | 86 -- .../main/res/layout/revamp_view_header.xml | 68 - app/src/main/res/values/cloneflix_attrs.xml | 70 - app/src/main/res/values/cloneflix_colors.xml | 57 - app/src/main/res/values/cloneflix_dimens.xml | 88 -- app/src/main/res/values/cloneflix_strings.xml | 68 - app/src/main/res/values/cloneflix_styles.xml | 270 ---- app/src/main/res/values/strings.xml | 8 + .../ui/result/MovieDetailsComposeTest.kt | 71 + .../ui/revamp/CloneflixDesignSystemTest.kt | 75 -- 146 files changed, 2160 insertions(+), 17954 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt delete mode 100644 app/src/main/res/drawable/cloneflix_bg_card.xml delete mode 100644 app/src/main/res/drawable/cloneflix_bg_card_elevated.xml delete mode 100644 app/src/main/res/drawable/cloneflix_bg_header_icon.xml delete mode 100644 app/src/main/res/drawable/cloneflix_bg_input_field.xml delete mode 100644 app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml delete mode 100644 app/src/main/res/drawable/cloneflix_btn_ghost.xml delete mode 100644 app/src/main/res/drawable/cloneflix_btn_outline.xml delete mode 100644 app/src/main/res/drawable/cloneflix_btn_primary.xml delete mode 100644 app/src/main/res/drawable/cloneflix_btn_secondary.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_about.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_account.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_ad.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_down.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_left.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_right.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_arrow_up.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_check.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_chevron_right.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_circle_error.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_close.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_colors.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_copy.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_dropdown.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_edit.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_error_circle.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_feedback.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_flag.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_help.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_info.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_input_field.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_labels.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_mute.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_notification.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_open_in_new.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_person.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_play.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_plus.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_replay.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_search.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_social_fb.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_social_ig.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_social_x.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_social_yt.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_thumb_down.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_thumb_up.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_typography.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_videoplayer.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_visibility.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_visibility_off.xml delete mode 100644 app/src/main/res/drawable/cloneflix_ic_volume.xml delete mode 100644 app/src/main/res/font/netflix_sans_black.ttf delete mode 100644 app/src/main/res/font/netflix_sans_black_italic.ttf delete mode 100644 app/src/main/res/font/netflix_sans_bold.ttf delete mode 100644 app/src/main/res/font/netflix_sans_bold_italic.ttf delete mode 100644 app/src/main/res/font/netflix_sans_italic.ttf delete mode 100644 app/src/main/res/font/netflix_sans_light.ttf delete mode 100644 app/src/main/res/font/netflix_sans_light_italic.ttf delete mode 100644 app/src/main/res/font/netflix_sans_medium.ttf delete mode 100644 app/src/main/res/font/netflix_sans_medium_italic.ttf delete mode 100644 app/src/main/res/font/netflix_sans_regular.ttf delete mode 100644 app/src/main/res/font/netflix_sans_thin.ttf delete mode 100644 app/src/main/res/font/netflix_sans_thin_italic.ttf delete mode 100644 app/src/main/res/layout/revamp_activity_design_system.xml delete mode 100644 app/src/main/res/layout/revamp_fragment_about.xml delete mode 100644 app/src/main/res/layout/revamp_fragment_colors.xml delete mode 100644 app/src/main/res/layout/revamp_fragment_components.xml delete mode 100644 app/src/main/res/layout/revamp_fragment_design_system.xml delete mode 100644 app/src/main/res/layout/revamp_fragment_typography.xml delete mode 100644 app/src/main/res/layout/revamp_item_color_section.xml delete mode 100644 app/src/main/res/layout/revamp_item_color_swatch.xml delete mode 100644 app/src/main/res/layout/revamp_item_typography_style.xml delete mode 100644 app/src/main/res/layout/revamp_view_about_card.xml delete mode 100644 app/src/main/res/layout/revamp_view_header.xml delete mode 100644 app/src/main/res/values/cloneflix_attrs.xml delete mode 100644 app/src/main/res/values/cloneflix_colors.xml delete mode 100644 app/src/main/res/values/cloneflix_dimens.xml delete mode 100644 app/src/main/res/values/cloneflix_strings.xml delete mode 100644 app/src/main/res/values/cloneflix_styles.xml create mode 100644 app/src/test/java/com/lagradost/cloudstream3/ui/result/MovieDetailsComposeTest.kt delete mode 100644 app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d3489a33c40..71ce4f515f4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -275,18 +275,6 @@ android:name=".ui.ControllerActivity" android:exported="false" /> - - - - ( var comingSoon = false binding.resultComposeView.setContent { - CloneflixTheme { + MovieDetailsTheme { val page = composePageState if (page is Resource.Success) { val d = page.value @@ -156,7 +156,7 @@ class ResultFragmentTv : BaseFragment( val genres = d.tags val actors = composeActorsState.ifEmpty { d.actors ?: emptyList() } - CloneflixMovieDetailsComposeScreen( + MovieDetailsComposeScreen( title = title, providerName = provider, backdropUrl = d.posterBackgroundImage ?: d.posterImage, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt new file mode 100644 index 00000000000..61c0b95710f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -0,0 +1,1139 @@ +package com.lagradost.cloudstream3.ui.result.compose + +import android.widget.Toast +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.BringIntoViewSpec +import androidx.compose.foundation.gestures.LocalBringIntoViewSpec +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.scale +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.ui.result.compose.components.CircleActionButton +import com.lagradost.cloudstream3.ui.result.compose.components.DetailsLogoVariant +import com.lagradost.cloudstream3.ui.result.compose.components.DetailsLogoView +import com.lagradost.cloudstream3.ui.result.compose.components.HeroPlayButton +import com.lagradost.cloudstream3.ui.result.compose.components.HeroTrailerButton +import com.lagradost.cloudstream3.ui.result.compose.components.MaturityRatingBadge +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardItem +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardSize +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardType +import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsMovieCard +import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens +import com.lagradost.cloudstream3.ui.result.compose.components.SeasonDropdown +import com.lagradost.cloudstream3.ui.result.compose.components.VideoQualityBadge +import com.lagradost.cloudstream3.ui.result.compose.theme.GreenAccent +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey500 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey600 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey750 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor +import com.lagradost.cloudstream3.ui.result.getWatchProgress +import kotlinx.coroutines.launch + +@Immutable +data class MovieTrailerData( + val title: String, + val runtime: String, + val rawTrailer: Any? = null +) + +@Immutable +data class MovieRecommendationRow( + val rowIndex: Int, + val items: List +) + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun MovieDetailsComposeScreen( + modifier: Modifier = Modifier, + title: String = "", + providerName: String? = null, + backdropUrl: String? = null, + posterUrl: String? = null, + logoUrl: String? = null, + matchScore: String? = null, + releaseYear: String? = null, + seasonsCount: String? = null, + quality: String? = null, + maturityRating: String? = null, + advisories: String? = null, + top10RankText: String? = null, + synopsis: String = "", + cast: List = emptyList(), + genres: List = emptyList(), + moodTags: List = emptyList(), + creator: String? = null, + writers: List = emptyList(), + dynamicEpisodes: List? = null, + dynamicRecommendations: List? = null, + dynamicActors: List? = null, + dynamicSeasons: List? = null, + dynamicTrailers: List? = null, + selectedSeasonIndex: Int = 0, + isInWatchList: Boolean = false, + hasTrailers: Boolean = false, + onPlayClick: () -> Unit = {}, + onEpisodeClick: ((ResultEpisode) -> Unit)? = null, + onEpisodeDownloadClick: ((ResultEpisode) -> Unit)? = null, + onSeasonSelect: ((Int) -> Unit)? = null, + onAddToListClick: () -> Unit = {}, + onLikeClick: () -> Unit = {}, + onTrailerClick: () -> Unit = {}, + onActorClick: ((String) -> Unit)? = null, + onRecommendationClick: ((SearchResponse) -> Unit)? = null, + onCloseClick: () -> Unit = {} +) { + val context = LocalContext.current + val configuration = LocalConfiguration.current + val screenHeight = configuration.screenHeightDp.dp + val heroHeight = (screenHeight * 0.65f).coerceAtLeast(380.dp) + + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val lazyListState = rememberLazyListState() + val coroutineScope = rememberCoroutineScope() + + val playInteractionSource = remember { MutableInteractionSource() } + val inMyListInteractionSource = remember { MutableInteractionSource() } + val likeInteractionSource = remember { MutableInteractionSource() } + val trailerInteractionSource = remember { MutableInteractionSource() } + + val playButtonFocusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + lazyListState.scrollToItem(0) + playButtonFocusRequester.requestFocus() + } + + var inMyListState by remember(isInWatchList) { mutableStateOf(isInWatchList) } + + val seasonOptions = remember(dynamicSeasons) { + dynamicSeasons ?: emptyList() + } + + var selectedSeasonText by remember(selectedSeasonIndex, seasonOptions) { + mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "" }) + } + + LaunchedEffect(selectedSeasonIndex, seasonOptions) { + if (seasonOptions.isNotEmpty()) { + selectedSeasonText = seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.first() } + } + } + + val episodesToDisplay = dynamicEpisodes ?: emptyList() + + val castList = remember(dynamicActors, cast) { + dynamicActors?.map { it.actor.name }?.filter { it.isNotBlank() }?.ifEmpty { cast } ?: cast + } + + val recommendationCards = remember(dynamicRecommendations) { + dynamicRecommendations?.map { rec -> + MovieCardItem( + title = rec.name, + type = MovieCardType.POSTER, + posterUrl = rec.posterUrl, + showLogo = false, + showBottomTitle = true + ) + } ?: emptyList() + } + + val chunkedRecommendations = remember(recommendationCards) { + recommendationCards.chunked(6).mapIndexed { idx, list -> + MovieRecommendationRow(idx, list) + } + } + + val trailersList = dynamicTrailers ?: emptyList() + + val tvBringIntoViewSpec = remember { + object : BringIntoViewSpec { + override fun calculateScrollDistance( + offset: Float, + size: Float, + containerSize: Float + ): Float { + val parentFraction = 0.30f + val targetOffset = (parentFraction * containerSize) + return offset - targetOffset + } + } + } + + CompositionLocalProvider(LocalBringIntoViewSpec provides tvBringIntoViewSpec) { + LazyColumn( + state = lazyListState, + modifier = modifier + .fillMaxSize() + .background(colors.background) + ) { + item(key = "hero_banner") { + Box( + modifier = Modifier + .fillMaxWidth() + .height(heroHeight) + .clipToBounds() + ) { + val imageUrl = backdropUrl ?: posterUrl + if (!imageUrl.isNullOrBlank()) { + AsyncImage( + model = imageUrl, + contentDescription = title, + contentScale = ContentScale.Crop, + alignment = Alignment.TopCenter, + modifier = Modifier + .fillMaxSize() + .graphicsLayer { alpha = 0.85f } + ) + } + + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + PrimaryBlack.copy(alpha = 0.3f), + PrimaryBlack.copy(alpha = 0.8f), + colors.background + ), + startY = 0f + ) + ) + ) + + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.horizontalGradient( + colors = listOf( + PrimaryBlack.copy(alpha = 0.7f), + Color.Transparent + ), + startX = 0f, + endX = 800f + ) + ) + ) + + Row( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = dimens.spacing2Xl, end = dimens.spacing2Xl), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CircleActionButton( + icon = painterResource(id = R.drawable.ic_baseline_close_24), + contentDescription = "Close", + onClick = onCloseClick + ) + } + + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding( + start = dimens.spacing3Xl, + bottom = dimens.spacing2Xl, + end = dimens.spacing3Xl + ) + .fillMaxWidth(0.85f) + ) { + if (!providerName.isNullOrBlank()) { + Text( + text = providerName.uppercase(), + style = typography.boldTitle2, + color = PrimaryRed, + letterSpacing = 2.sp, + modifier = Modifier.padding(bottom = dimens.spacingS) + ) + } + + DetailsLogoView( + logoUrl = logoUrl, + titleFallback = title, + variant = DetailsLogoVariant.FULL_COLOR, + height = 64.dp + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), + verticalAlignment = Alignment.CenterVertically + ) { + HeroPlayButton( + onClick = onPlayClick, + text = "Play", + interactionSource = playInteractionSource, + modifier = Modifier.focusRequester(playButtonFocusRequester) + ) + + if (hasTrailers) { + HeroTrailerButton( + onClick = onTrailerClick, + text = "Play Trailer", + interactionSource = trailerInteractionSource + ) + } + + CircleActionButton( + icon = painterResource( + id = if (inMyListState) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 + ), + contentDescription = if (inMyListState) "In My List" else "Add to My List", + onClick = { + inMyListState = !inMyListState + onAddToListClick() + }, + interactionSource = inMyListInteractionSource + ) + + CircleActionButton( + icon = painterResource(id = R.drawable.ic_baseline_favorite_24), + contentDescription = "Favorite", + onClick = onLikeClick, + interactionSource = likeInteractionSource + ) + } + } + } + } + + item(key = "meta_info_section") { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing3Xl) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacing4Xl) + ) { + Column(modifier = Modifier.weight(0.65f)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (!matchScore.isNullOrBlank()) { + Text( + text = matchScore, + color = getRatingScoreColor(matchScore), + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + } + + if (!releaseYear.isNullOrBlank()) { + Text( + text = releaseYear, + color = Grey200, + style = typography.regularBody + ) + } + + if (!maturityRating.isNullOrBlank()) { + MaturityRatingBadge(rating = maturityRating) + } + + if (!seasonsCount.isNullOrBlank()) { + Text( + text = seasonsCount, + color = Grey200, + style = typography.regularBody + ) + } + + if (!quality.isNullOrBlank()) { + VideoQualityBadge(quality = quality) + } + } + + if (!advisories.isNullOrBlank()) { + Spacer(modifier = Modifier.height(dimens.spacingS)) + Row( + modifier = Modifier + .clip(RoundedCornerShape(2.dp)) + .border(BorderStroke(1.dp, Grey700), RoundedCornerShape(2.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + if (!maturityRating.isNullOrBlank()) { + Text( + text = maturityRating, + color = PrimaryWhite, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "•", + color = Grey500, + fontSize = 10.sp + ) + } + Text( + text = advisories, + color = Grey200, + fontSize = 11.sp + ) + } + } + + if (!top10RankText.isNullOrBlank()) { + Spacer(modifier = Modifier.height(dimens.spacingM)) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + Box( + modifier = Modifier + .size(24.dp) + .clip(RoundedCornerShape(2.dp)) + .background(PrimaryRed), + contentAlignment = Alignment.Center + ) { + Text( + text = "TOP\n10", + color = PrimaryWhite, + fontSize = 8.sp, + fontWeight = FontWeight.Black, + lineHeight = 9.sp + ) + } + Text( + text = top10RankText, + color = PrimaryWhite, + style = typography.boldTitle2, + fontSize = 16.sp + ) + } + } + + if (synopsis.isNotBlank()) { + Spacer(modifier = Modifier.height(dimens.spacingL)) + Text( + text = synopsis, + style = typography.regularBody, + color = PrimaryWhite, + lineHeight = 22.sp + ) + } + } + + Column( + modifier = Modifier.weight(0.35f), + verticalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (castList.isNotEmpty()) { + Row { + Text( + text = "Cast: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = castList.take(4).joinToString(", "), + style = typography.regularSmallBody, + color = PrimaryWhite, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + + if (genres.isNotEmpty()) { + Row { + Text( + text = "Genres: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = genres.joinToString(", "), + style = typography.regularSmallBody, + color = PrimaryWhite, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + + if (moodTags.isNotEmpty()) { + Row { + Text( + text = "This show is: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = moodTags.joinToString(", "), + style = typography.regularSmallBody, + color = PrimaryWhite, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + + if (!creator.isNullOrBlank()) { + Row { + Text( + text = "Creator: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = creator, + style = typography.regularSmallBody, + color = PrimaryWhite + ) + } + } + + if (writers.isNotEmpty()) { + Row { + Text( + text = "Writers: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = writers.joinToString(", "), + style = typography.regularSmallBody, + color = PrimaryWhite, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } + } + } + + if (episodesToDisplay.isNotEmpty() || seasonOptions.isNotEmpty()) { + item(key = "episodes_section_header") { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = dimens.spacing3Xl, end = dimens.spacing3Xl, top = dimens.spacing3Xl) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Episodes", + style = typography.boldTitle1, + fontSize = 24.sp, + color = PrimaryWhite + ) + + if (seasonOptions.size > 1) { + SeasonDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { chosen -> + selectedSeasonText = chosen + val idx = seasonOptions.indexOf(chosen) + if (idx >= 0) { + onSeasonSelect?.invoke(idx) + } + }, + width = 160.dp + ) + } + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + } + } + + itemsIndexed( + items = episodesToDisplay, + key = { _, ep -> "ep_${ep.id}_${ep.episode}" } + ) { index, ep -> + EpisodeRowItem( + episode = ep, + index = index + 1, + onEpisodeClick = { onEpisodeClick?.invoke(ep) }, + onDownloadClick = { onEpisodeDownloadClick?.invoke(ep) } + ) + } + } + + if (trailersList.isNotEmpty()) { + item(key = "trailers_section_header") { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + start = dimens.spacing3Xl, + end = dimens.spacing3Xl, + top = dimens.spacing3Xl, + bottom = dimens.spacingL + ) + ) { + Text( + text = "Trailers & More", + style = typography.boldTitle1, + fontSize = 24.sp, + color = PrimaryWhite + ) + } + } + + items( + items = trailersList, + key = { "trailer_${it.title}" } + ) { trailer -> + TrailerRowItem(trailer = trailer) + } + } + + if (chunkedRecommendations.isNotEmpty()) { + item(key = "recommendations_section_header") { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + start = dimens.spacing3Xl, + end = dimens.spacing3Xl, + top = dimens.spacing3Xl, + bottom = dimens.spacingL + ) + ) { + Text( + text = "More Like This", + style = typography.boldTitle1, + fontSize = 24.sp, + color = PrimaryWhite + ) + } + } + + items( + items = chunkedRecommendations, + key = { "rec_row_${it.rowIndex}" } + ) { row -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing3Xl, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + row.items.forEach { cardItem -> + val rawRec = cardItem.rawItem as? SearchResponse + ?: dynamicRecommendations?.firstOrNull { it.name == cardItem.title } + + MovieDetailsMovieCard( + title = cardItem.title, + type = MovieCardType.POSTER, + size = MovieCardSize.MEDIUM, + posterUrl = cardItem.posterUrl, + backdropUrl = cardItem.backdropUrl, + showBottomTitle = true, + onClick = { + if (rawRec != null) { + onRecommendationClick?.invoke(rawRec) + } + } + ) + } + } + } + } + + item(key = "about_details_card") { + val hasAboutData = title.isNotBlank() || castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() + if (hasAboutData) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + start = dimens.spacing3Xl, + end = dimens.spacing3Xl, + top = dimens.spacing4Xl, + bottom = dimens.spacing4Xl + ) + ) { + val aboutInteractionSource = remember { MutableInteractionSource() } + val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() + + val border = if (isAboutFocused) { + BorderStroke(dimens.borderFocus, PrimaryWhite) + } else { + BorderStroke(dimens.borderSubtle, Grey750) + } + + val scale by animateFloatAsState( + targetValue = if (isAboutFocused) 1.01f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "aboutCardScale" + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .scale(scale) + .clip(RoundedCornerShape(8.dp)) + .background(Grey850) + .border(border, RoundedCornerShape(8.dp)) + .clickable( + interactionSource = aboutInteractionSource, + indication = null, + onClick = {} + ) + .focusable(interactionSource = aboutInteractionSource) + .padding(dimens.spacing2Xl) + ) { + Column { + Text( + text = "About $title", + style = typography.boldTitle2, + fontSize = 20.sp, + color = PrimaryWhite + ) + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + if (!creator.isNullOrBlank()) { + Text( + text = "Creator: $creator", + style = typography.regularSmallBody, + color = Grey200, + modifier = Modifier.padding(bottom = 6.dp) + ) + } + + if (castList.isNotEmpty()) { + Row(modifier = Modifier.padding(bottom = 6.dp)) { + Text( + text = "Cast: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = castList.joinToString(", "), + style = typography.regularSmallBody, + color = PrimaryWhite + ) + } + } + + if (genres.isNotEmpty()) { + Row(modifier = Modifier.padding(bottom = 6.dp)) { + Text( + text = "Genres: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = genres.joinToString(", "), + style = typography.regularSmallBody, + color = PrimaryWhite + ) + } + } + + if (moodTags.isNotEmpty()) { + Row(modifier = Modifier.padding(bottom = 6.dp)) { + Text( + text = "This show is: ", + style = typography.regularSmallBody, + color = Grey200 + ) + Text( + text = moodTags.joinToString(", "), + style = typography.regularSmallBody, + color = PrimaryWhite + ) + } + } + + if (!maturityRating.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(top = 4.dp) + ) { + Text( + text = "Maturity Rating: ", + style = typography.regularSmallBody, + color = Grey200 + ) + MaturityRatingBadge(rating = maturityRating) + if (!advisories.isNullOrBlank()) { + Text( + text = advisories, + style = typography.regularSmallBody, + color = Grey100 + ) + } + } + } + } + } + } + } + } + } + } +} + +@Composable +private fun EpisodeRowItem( + episode: ResultEpisode, + index: Int, + onEpisodeClick: () -> Unit, + onDownloadClick: () -> Unit, + modifier: Modifier = Modifier +) { + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.015f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "epRowScale" + ) + + val background = when { + isFocused -> Grey750 + else -> Color.Transparent + } + + val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null + + val progress = remember(episode) { + val posDur = DataStoreHelper.getViewPos(episode.id) + if (posDur != null && posDur.duration > 0) { + posDur.position.toFloat() / posDur.duration.toFloat() + } else null + } + + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing3Xl, vertical = 4.dp) + .scale(scale) + .clip(RoundedCornerShape(6.dp)) + .background(background) + .then(if (border != null) Modifier.border(border, RoundedCornerShape(6.dp)) else Modifier) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onEpisodeClick + ) + .focusable(interactionSource = interactionSource) + .padding(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${episode.episode}", + style = typography.boldTitle1, + fontSize = 22.sp, + color = if (isFocused) PrimaryWhite else Grey200, + modifier = Modifier.width(36.dp) + ) + + Box( + modifier = Modifier + .width(130.dp) + .height(74.dp) + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(Grey800) + ) { + val epPoster = episode.poster + if (!epPoster.isNullOrBlank()) { + AsyncImage( + model = epPoster, + contentDescription = episode.name, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(TransparentBlack60), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(if (isFocused) PrimaryRed else PrimaryWhite.copy(alpha = 0.85f)), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = "Play Episode", + tint = if (isFocused) PrimaryWhite else PrimaryBlack, + modifier = Modifier.size(18.dp) + ) + } + } + + if (progress != null && progress > 0f) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(3.dp) + .align(Alignment.BottomCenter) + .background(Grey700) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .background(PrimaryRed) + ) + } + } + } + + Spacer(modifier = Modifier.width(dimens.spacingL)) + + Column(modifier = Modifier.weight(1f)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = episode.name ?: "Episode ${episode.episode}", + style = typography.mediumBody, + color = PrimaryWhite, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) + ) + + if (episode.runTime != null && episode.runTime > 0) { + Text( + text = "${episode.runTime}m", + style = typography.regularCaption1, + color = Grey200, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + + if (!episode.description.isNullOrBlank()) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = episode.description, + style = typography.regularSmallBody, + color = Grey100, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + lineHeight = 18.sp + ) + } + } + + Spacer(modifier = Modifier.width(dimens.spacingL)) + + CircleActionButton( + icon = painterResource(id = R.drawable.baseline_downloading_24), + contentDescription = "Download Episode ${episode.episode}", + onClick = onDownloadClick, + modifier = Modifier.size(36.dp) + ) + } +} + +@Composable +private fun TrailerRowItem( + trailer: MovieTrailerData, + modifier: Modifier = Modifier +) { + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.015f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "trailerRowScale" + ) + + val background = if (isFocused) Grey750 else Color.Transparent + val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null + + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing3Xl, vertical = 4.dp) + .scale(scale) + .clip(RoundedCornerShape(6.dp)) + .background(background) + .then(if (border != null) Modifier.border(border, RoundedCornerShape(6.dp)) else Modifier) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = {} + ) + .focusable(interactionSource = interactionSource) + .padding(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .width(130.dp) + .height(74.dp) + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(Grey800), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(if (isFocused) PrimaryRed else PrimaryWhite.copy(alpha = 0.85f)), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = "Play Trailer", + tint = if (isFocused) PrimaryWhite else PrimaryBlack, + modifier = Modifier.size(18.dp) + ) + } + } + + Spacer(modifier = Modifier.width(dimens.spacingL)) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = trailer.title, + style = typography.mediumBody, + color = PrimaryWhite, + fontWeight = FontWeight.SemiBold + ) + + if (trailer.runtime.isNotBlank()) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = trailer.runtime, + style = typography.regularCaption1, + color = Grey200 + ) + } + } + } +} + +@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) +@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Composable +private fun MovieDetailsScreenPreview() { + MovieDetailsTheme { + MovieDetailsComposeScreen( + title = "House of Ninjas", + providerName = "CloudStream", + matchScore = "98% Match", + releaseYear = "2024", + seasonsCount = "1 Season", + quality = "UltraHD 4K", + maturityRating = "TV-MA", + advisories = "violence, language", + top10RankText = "#1 in TV Shows Today", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats." + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt new file mode 100644 index 00000000000..05c1119c4b6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt @@ -0,0 +1,702 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.aspectRatio +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +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.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.GreenAccent +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey10 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey100 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey200 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey50 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey500 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey600 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey700 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey800 +import com.lagradost.cloudstream3.ui.result.compose.theme.Grey850 +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryRed +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack90 +import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor + +object MovieDetailsTokens { + val ShapeCardSmall = RoundedCornerShape(4.dp) + val ShapeCardMedium = RoundedCornerShape(8.dp) + val ShapeCardLarge = RoundedCornerShape(12.dp) + + val FastFocusAnimationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing) + + const val FOCUS_SCALE_FACTOR = 1.05f + const val FOCUS_SCALE_FACTOR_LARGE = 1.10f + + const val ALPHA_HIGH = 0.85f + const val ALPHA_MEDIUM = 0.60f + const val ALPHA_LOW = 0.35f +} + +enum class MovieCardSize { + MEDIUM, + SMALL +} + +enum class MovieCardType { + DEFAULT, + POSTER, + MORE_LIKE_THIS, + MORE_LIKE_THIS_WITH_PLAY, + EPISODE, + TRAILER, + PLAYER_PREVIEW, + TOP10, + CONTINUE_WATCHING +} + +enum class MovieBadgeType(val stringRes: Int) { + TOP_10(R.string.badge_top_10), + RECENTLY_ADDED(R.string.badge_recently_added), + NEW_SEASON(R.string.badge_new_season), + NEW_EPISODES(R.string.badge_new_episodes), + LEAVING_SOON(R.string.badge_leaving_soon), + MUST_WATCH(R.string.badge_must_watch) +} + +@Immutable +data class MovieCardItem( + val title: String, + val type: MovieCardType = MovieCardType.DEFAULT, + val posterUrl: String? = null, + val backdropUrl: String? = null, + val progress: Float? = null, + val badge: MovieBadgeType? = null, + val matchScore: String? = null, + val maturityRating: String? = null, + val duration: String? = null, + val quality: String? = null, + val top10Rank: Int? = null, + val showLogo: Boolean = false, + val showBottomTitle: Boolean = false, + val synopsis: String? = null, + val rawItem: Any? = null +) + +object MovieCardDefaults { + val CardAspectRatioDefault = 16f / 9f + val CardAspectRatioPoster = 2f / 3f + + val ActionButtonSize = 36.dp + + fun cardWidth(type: MovieCardType, size: MovieCardSize): Dp = when (type) { + MovieCardType.POSTER -> when (size) { + MovieCardSize.MEDIUM -> 140.dp + MovieCardSize.SMALL -> 110.dp + } + else -> when (size) { + MovieCardSize.MEDIUM -> 218.dp + MovieCardSize.SMALL -> 128.dp + } + } +} + +@Composable +fun HeroPlayButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: String = "Play", + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "heroPlayBtnScale" + ) + + val background = if (isFocused) PrimaryWhite else PrimaryWhite.copy(alpha = 0.95f) + val border = if (isFocused) BorderStroke(2.dp, PrimaryRed) else null + + Box( + modifier = modifier + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .semantics { + contentDescription = "$text Video" + }, + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier + .height(42.dp) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(background) + .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardSmall) else Modifier) + .padding(horizontal = 24.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = null, + tint = PrimaryBlack, + modifier = Modifier.size(20.dp) + ) + Text( + text = text, + color = PrimaryBlack, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Composable +fun HeroTrailerButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: String = "Trailer", + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "heroTrailerBtnScale" + ) + + val background = if (isFocused) Grey600.copy(alpha = 0.9f) else Grey700.copy(alpha = 0.6f) + val border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(1.dp, PrimaryWhite.copy(alpha = 0.3f)) + + Box( + modifier = modifier + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .semantics { + contentDescription = "$text Video" + }, + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier + .height(42.dp) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(background) + .border(border, MovieDetailsTokens.ShapeCardSmall) + .padding(horizontal = 20.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_ondemand_video_24), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier.size(18.dp) + ) + Text( + text = text, + color = PrimaryWhite, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold + ) + } + } +} + +@Composable +fun CircleActionButton( + icon: Painter, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + isPrimary: Boolean = false, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = MovieDetailsTheme.dimens + + val bgColor = when { + isFocused -> PrimaryWhite.copy(alpha = 0.3f) + isPrimary -> PrimaryWhite + else -> TransparentBlack60 + } + + val iconTint = if (isPrimary && !isFocused) PrimaryBlack else PrimaryWhite + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, PrimaryWhite) + } else { + BorderStroke(dimens.borderDefault, PrimaryWhite.copy(alpha = MovieDetailsTokens.ALPHA_HIGH)) + } + + Box( + modifier = modifier + .size(MovieCardDefaults.ActionButtonSize) + .clip(CircleShape) + .background(bgColor) + .border(border, CircleShape) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource), + contentAlignment = Alignment.Center + ) { + Icon( + painter = icon, + contentDescription = contentDescription, + tint = iconTint, + modifier = Modifier.size(dimens.iconM) + ) + } +} + +@Composable +fun MaturityRatingBadge( + rating: String, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .clip(RoundedCornerShape(2.dp)) + .background(Grey800) + .border(BorderStroke(1.dp, Grey500), RoundedCornerShape(2.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = rating, + color = PrimaryWhite, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold + ) + } +} + +@Composable +fun VideoQualityBadge( + quality: String, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .clip(RoundedCornerShape(2.dp)) + .border(BorderStroke(1.dp, Grey500), RoundedCornerShape(2.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = quality, + color = Grey200, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + } +} + +enum class DetailsLogoVariant { + FULL_COLOR, + WHITE_MONO, + MINIMAL_ICON +} + +@Composable +fun DetailsLogoView( + modifier: Modifier = Modifier, + logoUrl: String? = null, + titleFallback: String = "", + variant: DetailsLogoVariant = DetailsLogoVariant.FULL_COLOR, + height: Dp = 48.dp +) { + val typography = MovieDetailsTheme.typography + + if (!logoUrl.isNullOrBlank()) { + AsyncImage( + model = logoUrl, + contentDescription = titleFallback, + contentScale = ContentScale.Fit, + alignment = Alignment.CenterStart, + modifier = modifier + .height(height) + .fillMaxWidth(0.6f) + ) + } else { + Text( + text = titleFallback, + style = typography.boldTitle1, + color = when (variant) { + DetailsLogoVariant.FULL_COLOR -> PrimaryRed + DetailsLogoVariant.WHITE_MONO -> PrimaryWhite + DetailsLogoVariant.MINIMAL_ICON -> PrimaryWhite + }, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = modifier + ) + } +} + +@Composable +fun SeasonDropdown( + options: List, + selectedOption: String, + onOptionSelected: (String) -> Unit, + modifier: Modifier = Modifier, + width: Dp? = null, + enabled: Boolean = true +) { + var expanded by remember { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + label = "DropdownScale" + ) + + val rotationAngle by animateFloatAsState( + targetValue = if (expanded) 180f else 0f, + label = "DropdownChevronRotation" + ) + + val borderStroke = when { + isFocused || expanded -> BorderStroke(2.dp, PrimaryWhite) + else -> BorderStroke(1.dp, Color(0xFF666666)) + } + + val backgroundColor = when { + isFocused || expanded -> Color(0xFF333333) + else -> Color(0xFF1E1E1E) + } + + val baseModifier = if (width != null) modifier.width(width) else modifier + + Box( + modifier = baseModifier + .scale(scale) + .clip(RoundedCornerShape(4.dp)) + .background(backgroundColor) + .border(borderStroke, RoundedCornerShape(4.dp)) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.DropdownList, + onClick = { expanded = !expanded } + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .padding(horizontal = 14.dp, vertical = 8.dp), + contentAlignment = Alignment.CenterStart + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = selectedOption.ifEmpty { "Select Season" }, + color = PrimaryWhite, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + + Icon( + painter = painterResource(id = R.drawable.ic_baseline_keyboard_arrow_down_24), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier + .size(18.dp) + .rotate(rotationAngle) + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .background(Color(0xFF232323)) + .border(BorderStroke(1.dp, Color(0xFF444444)), RoundedCornerShape(4.dp)) + .heightIn(max = 280.dp) + ) { + options.forEach { option -> + val isSelected = option == selectedOption + DropdownMenuItem( + text = { + Text( + text = option, + color = if (isSelected) PrimaryRed else PrimaryWhite, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + fontSize = 14.sp + ) + }, + onClick = { + onOptionSelected(option) + expanded = false + }, + colors = MenuDefaults.itemColors( + textColor = PrimaryWhite + ) + ) + } + } + } +} + +@Composable +fun MovieDetailsMovieCard( + title: String, + modifier: Modifier = Modifier, + type: MovieCardType = MovieCardType.POSTER, + size: MovieCardSize = MovieCardSize.MEDIUM, + posterUrl: String? = null, + backdropUrl: String? = null, + progress: Float? = null, + badge: MovieBadgeType? = null, + matchScore: String? = null, + maturityRating: String? = null, + duration: String? = null, + quality: String? = null, + showBottomTitle: Boolean = false, + onClick: () -> Unit = {}, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = MovieDetailsTheme.dimens + + val scale by animateFloatAsState( + targetValue = if (isFocused) MovieDetailsTokens.FOCUS_SCALE_FACTOR else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "cardScale" + ) + + val aspectRatio = if (type == MovieCardType.POSTER) { + MovieCardDefaults.CardAspectRatioPoster + } else { + MovieCardDefaults.CardAspectRatioDefault + } + + val cardWidth = MovieCardDefaults.cardWidth(type, size) + val imageUrl = if (type == MovieCardType.POSTER) (posterUrl ?: backdropUrl) else (backdropUrl ?: posterUrl) + + Column( + modifier = modifier + .width(cardWidth) + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .zIndex(if (isFocused) 5f else 0f) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .semantics { + contentDescription = title + } + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(aspectRatio) + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(Grey850) + .then( + if (isFocused) { + Modifier.border(BorderStroke(dimens.borderFocus, PrimaryWhite), MovieDetailsTokens.ShapeCardSmall) + } else { + Modifier.border(BorderStroke(dimens.borderSubtle, Color.Transparent), MovieDetailsTokens.ShapeCardSmall) + } + ) + ) { + if (!imageUrl.isNullOrBlank()) { + AsyncImage( + model = imageUrl, + contentDescription = title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .background(Grey800), + contentAlignment = Alignment.Center + ) { + Text( + text = title.take(2).uppercase(), + color = Grey200, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } + } + + if (type == MovieCardType.POSTER && showBottomTitle) { + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, TransparentBlack90), + startY = 0f + ) + ) + .padding(horizontal = 8.dp, vertical = 6.dp) + ) { + Text( + text = title, + color = PrimaryWhite, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + + if (progress != null && progress > 0f) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(dimens.progressHeight) + .align(Alignment.BottomCenter) + .background(Grey700) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .background(PrimaryRed) + ) + } + } + + if (badge != null) { + Box( + modifier = Modifier + .align(Alignment.TopStart) + .padding(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(PrimaryRed) + .padding(horizontal = 5.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(id = badge.stringRes), + color = PrimaryWhite, + fontSize = 9.sp, + fontWeight = FontWeight.Bold + ) + } + } + } + + if (type != MovieCardType.POSTER && showBottomTitle) { + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = title, + color = if (isFocused) PrimaryWhite else Grey200, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt new file mode 100644 index 00000000000..ab4b7d0d900 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt @@ -0,0 +1,236 @@ +package com.lagradost.cloudstream3.ui.result.compose.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +val AppFontFamily: FontFamily = FontFamily.Default + +val PrimaryRed = Color(0xFFE50914) +val PrimaryBlack = Color(0xFF000000) +val PrimaryWhite = Color(0xFFFFFFFF) + +val Red100 = Color(0xFFEB3942) +val Red200 = Color(0xFFC11119) +val Red300 = Color(0xFFF50723) + +val GreenAccent = Color(0xFF46D369) +val YellowAccent = Color(0xFFFFB800) +val OrangeAccent = Color(0xFFFF8C00) + +val Grey10 = Color(0xFFE5E5E5) +val Grey50 = Color(0xFFBCBCBC) +val Grey100 = Color(0xFFB3B3B3) +val Grey200 = Color(0xFF808080) +val Grey400 = Color(0xFF414141) +val Grey500 = Color(0xFF3A3A3A) +val Grey600 = Color(0xFF333333) +val Grey700 = Color(0xFF2A2A2A) +val Grey750 = Color(0xFF262626) +val Grey800 = Color(0xFF232323) +val Grey850 = Color(0xFF181818) +val Grey900 = Color(0xFF141414) + +val TransparentWhite15 = Color(0x26FFFFFF) +val TransparentWhite20 = Color(0x33FFFFFF) +val TransparentWhite30 = Color(0x4CFFFFFF) +val TransparentWhite50 = Color(0x7FFFFFFF) +val TransparentWhite70 = Color(0xB2FFFFFF) + +val TransparentBlack30 = Color(0x4C000000) +val TransparentBlack60 = Color(0x99000000) +val TransparentBlack90 = Color(0xE5000000) + +val SurfaceDark = Color(0xFF232323) +val SurfaceElevated = Color(0xFF181818) +val CanvasBackground = Color(0xFF141414) +val BorderSubtle = Color(0x33FFFFFF) +val DividerColor = Color(0x26FFFFFF) + +@Immutable +data class MovieDetailsColors( + val primary: Color = PrimaryRed, + val onPrimary: Color = PrimaryWhite, + val background: Color = CanvasBackground, + val onBackground: Color = PrimaryWhite, + val surface: Color = SurfaceDark, + val onSurface: Color = PrimaryWhite, + val surfaceElevated: Color = SurfaceElevated, + val border: Color = BorderSubtle, + val divider: Color = DividerColor, + val textPrimary: Color = PrimaryWhite, + val textSecondary: Color = Grey200, + val textMuted: Color = Grey100, + val textDimmed: Color = Grey50, + val greenAccent: Color = GreenAccent, + val yellowAccent: Color = YellowAccent, + val orangeAccent: Color = OrangeAccent +) + +fun getRatingScoreColor(scoreText: String?): Color { + if (scoreText.isNullOrBlank()) return GreenAccent + val text = scoreText.trim() + if (text.equals("New", ignoreCase = true)) return GreenAccent + + val match = Regex("""(\d+(\.\d+)?)""").find(text) + if (match != null) { + val num = match.value.toDoubleOrNull() ?: return GreenAccent + val isPercentage = text.contains("%") || num > 10.0 + val percentage = if (isPercentage) num else (num * 10.0) + + return when { + percentage >= 70.0 -> GreenAccent + percentage >= 50.0 -> YellowAccent + else -> Red100 + } + } + + return GreenAccent +} + +@Immutable +data class MovieDetailsTypography( + val regularCaption2: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 11.sp, lineHeight = 14.sp), + val regularCaption1: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 16.sp), + val regularSmallBody: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 18.sp), + val regularBody: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 20.sp), + val regularHeadline2: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 17.sp, lineHeight = 22.sp), + val regularHeadline1: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 18.sp, lineHeight = 22.sp), + val regularTitle4: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 20.sp, lineHeight = 30.sp), + val regularTitle3: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 21.sp, lineHeight = 25.sp), + val regularTitle2: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 24.sp, lineHeight = 30.sp), + val regularTitle1: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 27.sp, lineHeight = 36.sp), + val regularLargeTitle: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Normal, fontSize = 50.sp, lineHeight = 64.sp), + + val mediumCaption2: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 15.sp), + val mediumCaption1: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 13.sp, lineHeight = 17.sp), + val mediumSmallBody: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 18.sp), + val mediumBody: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 16.sp, lineHeight = 24.sp), + val mediumHeadline2: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 20.sp, lineHeight = 30.sp), + val mediumHeadline1: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 21.sp, lineHeight = 25.sp), + val mediumTitle4: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 22.sp, lineHeight = 22.sp), + val mediumTitle3: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 24.sp, lineHeight = 31.sp), + val mediumTitle2: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 28.sp, lineHeight = 45.sp), + val mediumTitle1: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 30.sp, lineHeight = 45.sp), + val mediumLargeTitle: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Medium, fontSize = 33.sp, lineHeight = 42.sp), + + val boldTitle2: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Bold, fontSize = 20.sp, lineHeight = 30.sp), + val boldTitle1: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Bold, fontSize = 48.sp, lineHeight = 62.sp), + val boldLargeTitle: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Bold, fontSize = 55.sp, lineHeight = 70.sp), + + val headerDisplay: TextStyle = TextStyle(fontFamily = AppFontFamily, fontWeight = FontWeight.Bold, fontSize = 36.sp, lineHeight = 44.sp), + val sectionHeader: TextStyle = TextStyle(fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Bold, fontSize = 24.sp, lineHeight = 30.sp, color = PrimaryRed), + val logoBebas: TextStyle = TextStyle(fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Bold, fontSize = 32.sp, lineHeight = 38.sp, color = PrimaryRed) +) + +@Immutable +data class MovieDetailsDimens( + val spacingXxs: Dp = 2.dp, + val spacingXs: Dp = 4.dp, + val spacingS: Dp = 8.dp, + val spacingM: Dp = 12.dp, + val spacingL: Dp = 16.dp, + val spacingXl: Dp = 20.dp, + val spacing2Xl: Dp = 24.dp, + val spacing3Xl: Dp = 32.dp, + val spacing4Xl: Dp = 40.dp, + val spacing5Xl: Dp = 48.dp, + + val radiusXs: Dp = 4.dp, + val radiusS: Dp = 8.dp, + val radiusM: Dp = 12.dp, + val radiusL: Dp = 16.dp, + val radiusXl: Dp = 24.dp, + val radiusFull: Dp = 999.dp, + + val buttonHeightLarge: Dp = 48.dp, + val buttonHeightMedium: Dp = 40.dp, + val buttonHeightSmall: Dp = 32.dp, + + val iconXs: Dp = 14.dp, + val iconS: Dp = 16.dp, + val iconM: Dp = 24.dp, + val iconL: Dp = 32.dp, + val iconXl: Dp = 40.dp, + + val borderSubtle: Dp = 0.5.dp, + val borderDefault: Dp = 1.dp, + val borderFocus: Dp = 2.dp, + val progressHeight: Dp = 3.5.dp +) + +val LocalMovieDetailsColors = staticCompositionLocalOf { MovieDetailsColors() } +val LocalMovieDetailsTypography = staticCompositionLocalOf { MovieDetailsTypography() } +val LocalMovieDetailsDimens = staticCompositionLocalOf { MovieDetailsDimens() } + +val MovieDetailsShapes = Shapes( + extraSmall = RoundedCornerShape(4.dp), + small = RoundedCornerShape(8.dp), + medium = RoundedCornerShape(12.dp), + large = RoundedCornerShape(16.dp), + extraLarge = RoundedCornerShape(24.dp) +) + +object MovieDetailsTheme { + val colors: MovieDetailsColors + @Composable + @ReadOnlyComposable + get() = LocalMovieDetailsColors.current + + val typography: MovieDetailsTypography + @Composable + @ReadOnlyComposable + get() = LocalMovieDetailsTypography.current + + val dimens: MovieDetailsDimens + @Composable + @ReadOnlyComposable + get() = LocalMovieDetailsDimens.current + + val shapes: Shapes + @Composable + @ReadOnlyComposable + get() = MaterialTheme.shapes +} + +@Composable +fun MovieDetailsTheme( + colors: MovieDetailsColors = MovieDetailsColors(), + typography: MovieDetailsTypography = MovieDetailsTypography(), + dimens: MovieDetailsDimens = MovieDetailsDimens(), + content: @Composable () -> Unit +) { + val materialColors = darkColorScheme( + primary = colors.primary, + onPrimary = colors.onPrimary, + background = colors.background, + onBackground = colors.onBackground, + surface = colors.surface, + onSurface = colors.onSurface + ) + + CompositionLocalProvider( + LocalMovieDetailsColors provides colors, + LocalMovieDetailsTypography provides typography, + LocalMovieDetailsDimens provides dimens + ) { + MaterialTheme( + colorScheme = materialColors, + shapes = MovieDetailsShapes, + content = content + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt deleted file mode 100644 index 1849d06e4e9..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemActivity.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.databinding.RevampActivityDesignSystemBinding - -class RevampDesignSystemActivity : AppCompatActivity() { - - private lateinit var binding: RevampActivityDesignSystemBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = RevampActivityDesignSystemBinding.inflate(layoutInflater) - setContentView(binding.root) - - if (savedInstanceState == null) { - supportFragmentManager.beginTransaction() - .replace(R.id.container, RevampDesignSystemFragment.newInstance()) - .commit() - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt deleted file mode 100644 index 0bb5fb7cebd..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/RevampDesignSystemFragment.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.viewpager2.adapter.FragmentStateAdapter -import com.google.android.material.tabs.TabLayoutMediator -import com.lagradost.cloudstream3.databinding.RevampFragmentDesignSystemBinding -import com.lagradost.cloudstream3.ui.revamp.about.CloneflixAboutFragment -import com.lagradost.cloudstream3.ui.revamp.colors.CloneflixColorsFragment -import com.lagradost.cloudstream3.ui.revamp.components.CloneflixComponentsFragment -import com.lagradost.cloudstream3.ui.revamp.icons.CloneflixIconsLabelsFragment -import com.lagradost.cloudstream3.ui.revamp.typography.CloneflixTypographyFragment - -class RevampDesignSystemFragment : Fragment() { - - private var _binding: RevampFragmentDesignSystemBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = RevampFragmentDesignSystemBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val fragments = listOf( - CloneflixAboutFragment.newInstance() to "About", - CloneflixTypographyFragment.newInstance() to "Typography", - CloneflixColorsFragment.newInstance() to "Colors", - CloneflixComponentsFragment.newInstance() to "Components", - CloneflixIconsLabelsFragment.newInstance() to "Icons & Labels" - ) - - binding.viewPager.adapter = object : FragmentStateAdapter(this) { - override fun getItemCount(): Int = fragments.size - override fun createFragment(position: Int): Fragment = fragments[position].first - } - - TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> - tab.text = fragments[position].second - }.attach() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - companion object { - fun newInstance() = RevampDesignSystemFragment() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt deleted file mode 100644 index 36e6daed972..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutCardView.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.about - -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.FrameLayout -import android.widget.Toast -import com.lagradost.cloudstream3.databinding.RevampViewAboutCardBinding - -class CloneflixAboutCardView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - - val binding: RevampViewAboutCardBinding = - RevampViewAboutCardBinding.inflate(LayoutInflater.from(context), this, true) - - init { - binding.btnConnectX.setOnClickListener { - openUrl("https://x.com/ivannaheraskina") - } - - binding.btnFeedback.setOnClickListener { - Toast.makeText( - context, - "Feedback submitted! Thank you for using Cloneflix Design System 2024.", - Toast.LENGTH_LONG - ).show() - } - } - - private fun openUrl(url: String) { - try { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - context.startActivity(intent) - } catch (_: Exception) { - Toast.makeText(context, "Could not open $url", Toast.LENGTH_SHORT).show() - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt deleted file mode 100644 index aff2f6a59ca..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/about/CloneflixAboutFragment.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.about - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import com.lagradost.cloudstream3.databinding.RevampFragmentAboutBinding - -class CloneflixAboutFragment : Fragment() { - - private var _binding: RevampFragmentAboutBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = RevampFragmentAboutBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.aboutCard.btnConnectX.setOnClickListener { - openUrl("https://x.com/ivannaheraskina") - } - - binding.aboutCard.btnFeedback.setOnClickListener { - Toast.makeText( - requireContext(), - "Thank you for supporting Cloneflix Design System 2024!", - Toast.LENGTH_SHORT - ).show() - } - } - - private fun openUrl(url: String) { - try { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) - } catch (_: Exception) { - Toast.makeText(requireContext(), "Could not open $url", Toast.LENGTH_SHORT).show() - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - companion object { - fun newInstance() = CloneflixAboutFragment() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt deleted file mode 100644 index cffc7306424..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsAdapter.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.colors - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.databinding.RevampItemColorSectionBinding -import com.lagradost.cloudstream3.ui.revamp.components.CloneflixColorSwatchView -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors - -class CloneflixColorsAdapter( - private val sections: Map> -) : RecyclerView.Adapter() { - - private val sectionList = sections.toList() - - class ViewHolder(val binding: RevampItemColorSectionBinding) : - RecyclerView.ViewHolder(binding.root) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = RevampItemColorSectionBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val (categoryName, tokens) = sectionList[position] - holder.binding.sectionTitle.text = categoryName.uppercase() - holder.binding.swatchesContainer.removeAllViews() - - val context = holder.itemView.context - for (token in tokens) { - val swatchView = CloneflixColorSwatchView(context).apply { - bind(token) - } - holder.binding.swatchesContainer.addView(swatchView) - } - } - - override fun getItemCount(): Int = sectionList.size -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt deleted file mode 100644 index 0cb873ce9ce..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/colors/CloneflixColorsFragment.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.colors - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import com.lagradost.cloudstream3.databinding.RevampFragmentColorsBinding -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors - -class CloneflixColorsFragment : Fragment() { - - private var _binding: RevampFragmentColorsBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = RevampFragmentColorsBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val tokens = CloneflixColors.getAllTokens() - val sections = tokens.groupBy { it.category } - - binding.rvColorSections.layoutManager = LinearLayoutManager(requireContext()) - binding.rvColorSections.adapter = CloneflixColorsAdapter(sections) - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - companion object { - fun newInstance() = CloneflixColorsFragment() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt deleted file mode 100644 index 997de0ba8e1..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixButton.kt +++ /dev/null @@ -1,133 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.content.Context -import android.util.AttributeSet -import androidx.core.content.ContextCompat -import com.google.android.material.button.MaterialButton -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography - -class CloneflixButton @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = com.google.android.material.R.attr.materialButtonStyle -) : MaterialButton(context, attrs, defStyleAttr) { - - enum class Variant { - PRIMARY, - SECONDARY, - OUTLINE, - GHOST - } - - enum class Size { - LARGE, - MEDIUM, - SMALL - } - - private var currentVariant: Variant = Variant.PRIMARY - private var currentSize: Size = Size.MEDIUM - - init { - isAllCaps = false - typeface = CloneflixTypography.getFontTypeface(context, CloneflixTypography.Weight.BOLD) - - if (attrs != null) { - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixButton) - val variantIndex = typedArray.getInt(R.styleable.CloneflixButton_cloneflixButtonVariant, 0) - val sizeIndex = typedArray.getInt(R.styleable.CloneflixButton_cloneflixButtonSize, 1) - val iconRes = typedArray.getResourceId(R.styleable.CloneflixButton_cloneflixIcon, 0) - val btnText = typedArray.getString(R.styleable.CloneflixButton_cloneflixText) - typedArray.recycle() - - currentVariant = when (variantIndex) { - 1 -> Variant.SECONDARY - 2 -> Variant.OUTLINE - 3 -> Variant.GHOST - else -> Variant.PRIMARY - } - - currentSize = when (sizeIndex) { - 0 -> Size.LARGE - 2 -> Size.SMALL - else -> Size.MEDIUM - } - - if (iconRes != 0) { - setIconResource(iconRes) - } - if (btnText != null) { - text = btnText - } - } - - applyVariant(currentVariant) - applySize(currentSize) - } - - fun setVariant(variant: Variant) { - currentVariant = variant - applyVariant(variant) - } - - fun setButtonSize(size: Size) { - currentSize = size - applySize(size) - } - - private fun applyVariant(variant: Variant) { - when (variant) { - Variant.PRIMARY -> { - setBackgroundResource(R.drawable.cloneflix_btn_primary) - setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) - iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_white) - strokeWidth = 0 - } - Variant.SECONDARY -> { - setBackgroundResource(R.drawable.cloneflix_btn_secondary) - setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_black)) - iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_black) - strokeWidth = 0 - } - Variant.OUTLINE -> { - setBackgroundResource(R.drawable.cloneflix_btn_outline) - setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) - iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_white) - } - Variant.GHOST -> { - setBackgroundResource(R.drawable.cloneflix_btn_ghost) - setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) - iconTint = ContextCompat.getColorStateList(context, R.color.cloneflix_primary_white) - strokeWidth = 0 - } - } - } - - private fun applySize(size: Size) { - when (size) { - Size.LARGE -> { - minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_button_height_large) - setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_headline1)) - val padH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_2xl) - val padV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_m) - setPadding(padH, padV, padH, padV) - } - Size.MEDIUM -> { - minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_button_height_medium) - setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_body)) - val padH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_l) - val padV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_s) - setPadding(padH, padV, padH, padV) - } - Size.SMALL -> { - minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_button_height_small) - setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_smallbody)) - val padH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_m) - val padV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_xs) - setPadding(padH, padV, padH, padV) - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt deleted file mode 100644 index 7dc78557ac0..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixCardView.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.content.Context -import android.util.AttributeSet -import android.widget.FrameLayout -import androidx.core.content.ContextCompat -import com.lagradost.cloudstream3.R - -class CloneflixCardView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - - enum class ElevationLevel { - FLAT, - SURFACE, - ELEVATED - } - - init { - var elevationLevel = ElevationLevel.SURFACE - if (attrs != null) { - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixCardView) - val levelIndex = typedArray.getInt(R.styleable.CloneflixCardView_cardElevationLevel, 1) - typedArray.recycle() - - elevationLevel = when (levelIndex) { - 0 -> ElevationLevel.FLAT - 2 -> ElevationLevel.ELEVATED - else -> ElevationLevel.SURFACE - } - } - - applyElevationLevel(elevationLevel) - clipToOutline = true - } - - fun setElevationLevel(level: ElevationLevel) { - applyElevationLevel(level) - } - - private fun applyElevationLevel(level: ElevationLevel) { - when (level) { - ElevationLevel.FLAT -> { - setBackgroundColor(ContextCompat.getColor(context, R.color.cloneflix_canvas_background)) - } - ElevationLevel.SURFACE -> { - setBackgroundResource(R.drawable.cloneflix_bg_card) - } - ElevationLevel.ELEVATED -> { - setBackgroundResource(R.drawable.cloneflix_bg_card_elevated) - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt deleted file mode 100644 index f3534b5aca8..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixColorSwatchView.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.content.Context -import android.graphics.drawable.GradientDrawable -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.FrameLayout -import androidx.core.content.ContextCompat -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.databinding.RevampItemColorSwatchBinding -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTheme - -class CloneflixColorSwatchView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - - private val binding: RevampItemColorSwatchBinding = - RevampItemColorSwatchBinding.inflate(LayoutInflater.from(context), this, true) - - private var currentToken: CloneflixColors.ColorToken? = null - - fun bind(token: CloneflixColors.ColorToken) { - currentToken = token - binding.colorName.text = token.name - binding.colorHexAndRgb.text = "${token.hex} • ${token.rgbDescription}" - - val previewDrawable = GradientDrawable().apply { - shape = GradientDrawable.RECTANGLE - cornerRadius = resources.getDimension(R.dimen.cloneflix_radius_s) - setColor(token.colorInt) - setStroke( - resources.getDimensionPixelSize(R.dimen.cloneflix_divider_height), - ContextCompat.getColor(context, R.color.cloneflix_border_subtle) - ) - } - binding.colorPreview.background = previewDrawable - - setOnClickListener { - CloneflixTheme.copyToClipboard(context, token.name, token.hex) - binding.copyButton.setImageResource(R.drawable.cloneflix_ic_check) - postDelayed({ - binding.copyButton.setImageResource(R.drawable.cloneflix_ic_copy) - }, 1200) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt deleted file mode 100644 index 37ccdea14b0..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixComponentsFragment.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import com.lagradost.cloudstream3.databinding.RevampFragmentComponentsBinding - -class CloneflixComponentsFragment : Fragment() { - - private var _binding: RevampFragmentComponentsBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = RevampFragmentComponentsBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.btnDemoPrimary.setOnClickListener { - Toast.makeText(requireContext(), "Primary Red Button Clicked!", Toast.LENGTH_SHORT).show() - } - - binding.btnDemoSecondary.setOnClickListener { - Toast.makeText(requireContext(), "Secondary White Button Clicked!", Toast.LENGTH_SHORT).show() - } - - binding.btnDemoOutline.setOnClickListener { - Toast.makeText(requireContext(), "Outline Button Clicked!", Toast.LENGTH_SHORT).show() - } - - binding.btnDemoGhost.setOnClickListener { - Toast.makeText(requireContext(), "Ghost Button Clicked!", Toast.LENGTH_SHORT).show() - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - companion object { - fun newInstance() = CloneflixComponentsFragment() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt deleted file mode 100644 index 763ea39788b..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixDivider.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.content.Context -import android.util.AttributeSet -import android.view.View -import androidx.core.content.ContextCompat -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens - -class CloneflixDivider @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : View(context, attrs, defStyleAttr) { - - init { - setBackgroundColor(ContextCompat.getColor(context, R.color.cloneflix_border_subtle)) - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val height = resources.getDimensionPixelSize(R.dimen.cloneflix_divider_height) - val finalHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) - super.onMeasure(widthMeasureSpec, finalHeightMeasureSpec) - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt deleted file mode 100644 index 23f9ea3c7c6..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixHeaderView.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.FrameLayout -import androidx.annotation.DrawableRes -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.databinding.RevampViewHeaderBinding - -class CloneflixHeaderView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - - val binding: RevampViewHeaderBinding = - RevampViewHeaderBinding.inflate(LayoutInflater.from(context), this, true) - - init { - if (attrs != null) { - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixHeaderView) - val title = typedArray.getString(R.styleable.CloneflixHeaderView_headerTitle) - val subtitle = typedArray.getString(R.styleable.CloneflixHeaderView_headerSubtitle) - val iconRes = typedArray.getResourceId(R.styleable.CloneflixHeaderView_headerIcon, 0) - val showDivider = typedArray.getBoolean(R.styleable.CloneflixHeaderView_showDivider, true) - typedArray.recycle() - - title?.let { setTitle(it) } - subtitle?.let { setSubtitle(it) } - if (iconRes != 0) { - setIcon(iconRes) - } - setDividerVisible(showDivider) - } - } - - fun setTitle(title: CharSequence) { - binding.headerTitle.text = title - } - - fun setSubtitle(subtitle: CharSequence?) { - if (!subtitle.isNullOrBlank()) { - binding.headerSubtitle.text = subtitle - binding.headerSubtitle.visibility = VISIBLE - } else { - binding.headerSubtitle.visibility = GONE - } - } - - fun setIcon(@DrawableRes iconRes: Int) { - binding.headerIcon.setImageResource(iconRes) - } - - fun setDividerVisible(visible: Boolean) { - binding.headerDivider.visibility = if (visible) VISIBLE else GONE - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt deleted file mode 100644 index d33abd2930e..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixInputField.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.content.Context -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatEditText -import androidx.core.content.ContextCompat -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography - -class CloneflixInputField @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = android.R.attr.editTextStyle -) : AppCompatEditText(context, attrs, defStyleAttr) { - - init { - setBackgroundResource(R.drawable.cloneflix_bg_input_field) - setTextColor(ContextCompat.getColor(context, R.color.cloneflix_primary_white)) - setHintTextColor(ContextCompat.getColor(context, R.color.cloneflix_grey_200)) - typeface = CloneflixTypography.getFontTypeface(context, CloneflixTypography.Weight.REGULAR) - setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.cloneflix_text_regular_body)) - - val paddingH = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_l) - val paddingV = resources.getDimensionPixelSize(R.dimen.cloneflix_spacing_m) - setPadding(paddingH, paddingV, paddingH, paddingV) - minHeight = resources.getDimensionPixelSize(R.dimen.cloneflix_input_height) - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt deleted file mode 100644 index b03198c0b8c..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/components/CloneflixTextView.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.components - -import android.content.Context -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatTextView -import androidx.core.widget.TextViewCompat -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography - -class CloneflixTextView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : AppCompatTextView(context, attrs, defStyleAttr) { - - init { - if (attrs != null) { - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CloneflixTextView) - val styleEnum = typedArray.getInt(R.styleable.CloneflixTextView_cloneflixTextStyle, -1) - typedArray.recycle() - - if (styleEnum >= 0) { - applyStyleEnum(styleEnum) - } - } - } - - fun setCloneflixStyle(level: CloneflixTypography.StyleLevel) { - TextViewCompat.setTextAppearance(this, level.styleResId) - typeface = CloneflixTypography.getFontTypeface(context, level.weight) - } - - private fun applyStyleEnum(index: Int) { - val level = when (index) { - 0 -> CloneflixTypography.StyleLevel.REGULAR_CAPTION2 - 1 -> CloneflixTypography.StyleLevel.REGULAR_CAPTION1 - 2 -> CloneflixTypography.StyleLevel.REGULAR_SMALL_BODY - 3 -> CloneflixTypography.StyleLevel.REGULAR_BODY - 4 -> CloneflixTypography.StyleLevel.REGULAR_HEADLINE2 - 5 -> CloneflixTypography.StyleLevel.REGULAR_HEADLINE1 - 6 -> CloneflixTypography.StyleLevel.REGULAR_TITLE4 - 7 -> CloneflixTypography.StyleLevel.REGULAR_TITLE3 - 8 -> CloneflixTypography.StyleLevel.REGULAR_TITLE2 - 9 -> CloneflixTypography.StyleLevel.REGULAR_TITLE1 - 10 -> CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE - - 11 -> CloneflixTypography.StyleLevel.MEDIUM_CAPTION2 - 12 -> CloneflixTypography.StyleLevel.MEDIUM_CAPTION1 - 13 -> CloneflixTypography.StyleLevel.MEDIUM_SMALL_BODY - 14 -> CloneflixTypography.StyleLevel.MEDIUM_BODY - 15 -> CloneflixTypography.StyleLevel.MEDIUM_HEADLINE2 - 16 -> CloneflixTypography.StyleLevel.MEDIUM_HEADLINE1 - 17 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE4 - 18 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE3 - 19 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE2 - 20 -> CloneflixTypography.StyleLevel.MEDIUM_TITLE1 - 21 -> CloneflixTypography.StyleLevel.MEDIUM_LARGE_TITLE - - 22 -> CloneflixTypography.StyleLevel.BOLD_TITLE2 - 23 -> CloneflixTypography.StyleLevel.BOLD_TITLE1 - 24 -> CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE - - 25 -> CloneflixTypography.StyleLevel.LOGO_BEBAS - 26 -> CloneflixTypography.StyleLevel.HEADER_DISPLAY - else -> null - } - level?.let { setCloneflixStyle(it) } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt deleted file mode 100644 index 5f4af267bd6..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/CloneflixComposeActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import com.lagradost.cloudstream3.ui.revamp.compose.screens.CloneflixShowcaseComposeScreen -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -class CloneflixComposeActivity : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - CloneflixTheme { - CloneflixShowcaseComposeScreen() - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt deleted file mode 100644 index 998fee2d282..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAudioSubtitlesSelector.kt +++ /dev/null @@ -1,305 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.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.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.draw.scale -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -@Composable -fun CloneflixTrackOptionItem( - title: String, - isSelected: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.03f else 1f, - label = "trackItemScale" - ) - - val backgroundColor = when { - isFocused -> TransparentWhite20 - isSelected -> Color(0x1AE50914) - else -> Color.Transparent - } - - Row( - modifier = modifier - .fillMaxWidth() - .scale(scale) - .clip(RoundedCornerShape(8.dp)) - .background(backgroundColor) - .then( - if (isFocused) Modifier.border(1.5.dp, PrimaryWhite, RoundedCornerShape(8.dp)) - else Modifier - ) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .padding(horizontal = 12.dp, vertical = 10.dp) - .semantics { - role = Role.RadioButton - contentDescription = "$title, ${if (isSelected) "selected" else "not selected"}" - }, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - Box( - modifier = Modifier.size(18.dp), - contentAlignment = Alignment.Center - ) { - if (isSelected) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_check), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier.size(16.dp) - ) - } - } - - Text( - text = title, - style = CloneflixTheme.typography.regularBody, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - color = if (isSelected) PrimaryWhite else Grey200, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} - -@Composable -fun CloneflixAudioSubtitlesSelector( - audioTracks: List, - selectedAudio: String, - onAudioSelected: (String) -> Unit, - subtitleTracks: List, - selectedSubtitle: String, - onSubtitleSelected: (String) -> Unit, - onCloseClick: () -> Unit, - modifier: Modifier = Modifier -) { - val audioListState = rememberLazyListState() - val subtitleListState = rememberLazyListState() - - Surface( - modifier = modifier - .width(640.dp) - .border(BorderStroke(1.dp, Color(0x33FFFFFF)), RoundedCornerShape(16.dp)), - shape = RoundedCornerShape(16.dp), - color = TransparentBlack90 - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(24.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Audio & Subtitles", - style = CloneflixTheme.typography.mediumHeadline2, - fontSize = 20.sp, - color = PrimaryWhite - ) - - Box( - modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(Color(0x33FFFFFF)) - .clickable(onClick = onCloseClick), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_close), - contentDescription = "Close", - tint = PrimaryWhite, - modifier = Modifier.size(16.dp) - ) - } - } - - Spacer(modifier = Modifier.height(20.dp)) - - Row( - modifier = Modifier - .fillMaxWidth() - .height(300.dp), - horizontalArrangement = Arrangement.spacedBy(24.dp) - ) { - Column( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - ) { - Text( - text = "AUDIO", - style = CloneflixTheme.typography.mediumHeadline2, - fontSize = 14.sp, - color = Grey100, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp) - ) - - Spacer(modifier = Modifier.height(8.dp)) - - LazyColumn( - state = audioListState, - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - items(audioTracks) { track -> - CloneflixTrackOptionItem( - title = track, - isSelected = track == selectedAudio, - onClick = { onAudioSelected(track) } - ) - } - } - } - - Box( - modifier = Modifier - .width(1.dp) - .fillMaxHeight() - .background(Color(0x26FFFFFF)) - ) - - Column( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - ) { - Text( - text = "SUBTITLES", - style = CloneflixTheme.typography.mediumHeadline2, - fontSize = 14.sp, - color = Grey100, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp) - ) - - Spacer(modifier = Modifier.height(8.dp)) - - LazyColumn( - state = subtitleListState, - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - items(subtitleTracks) { track -> - CloneflixTrackOptionItem( - title = track, - isSelected = track == selectedSubtitle, - onClick = { onSubtitleSelected(track) } - ) - } - } - } - } - } - } -} - -@Preview(name = "Audio Subtitles Dialog Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun AudioSubtitlesDialogPreview() { - CloneflixTheme { - val audioTracks = listOf( - "Japanese [Original]", - "English", - "Spanish (Latin America)", - "French", - "German" - ) - val subtitleTracks = listOf( - "Off", - "English (CC)", - "Japanese (CC)", - "Spanish", - "Simplified Chinese", - "Traditional Chinese", - "Arabic", - "French", - "German" - ) - - Box( - modifier = Modifier - .background(Color(0xFF141414)) - .padding(24.dp) - ) { - CloneflixAudioSubtitlesSelector( - audioTracks = audioTracks, - selectedAudio = "Japanese [Original]", - onAudioSelected = {}, - subtitleTracks = subtitleTracks, - selectedSubtitle = "English (CC)", - onSubtitleSelected = {}, - onCloseClick = {} - ) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt deleted file mode 100644 index 1dd612e4900..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixAvatarComponents.kt +++ /dev/null @@ -1,786 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.GenericShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.draw.rotate -import androidx.compose.ui.draw.scale -import androidx.compose.ui.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey350 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite - -enum class AvatarFaceStyle { - SMILEY, - WINK, - ANGRY, - KIDS, - MONSTER_GREY, - MONSTER_BLUE, - MONSTER_RED, - MONSTER_YELLOW, - CHICKEN, - ZOMBIE, - PANDA, - COOL, - SURPRISED, - GEOMETRIC -} - -data class CloneflixAvatarModel( - val id: String, - val name: String, - val topColor: Color, - val bottomColor: Color, - val faceStyle: AvatarFaceStyle = AvatarFaceStyle.SMILEY, - val accentColor: Color = PrimaryWhite -) - -object CloneflixOfficialAvatars { - val Red = CloneflixAvatarModel("red", "Red Classic", Color(0xFFE50914), Color(0xFF8B0000), AvatarFaceStyle.SMILEY) - val Green = CloneflixAvatarModel("green", "Green Classic", Color(0xFF2ECC71), Color(0xFF1B8246), AvatarFaceStyle.SMILEY) - val DarkBlue = CloneflixAvatarModel("dark blue", "Dark Blue", Color(0xFF1E3799), Color(0xFF0C2461), AvatarFaceStyle.SMILEY) - val Blue = CloneflixAvatarModel("blue", "Blue Classic", Color(0xFF3867D6), Color(0xFF1B3B6F), AvatarFaceStyle.SMILEY) - val Purple = CloneflixAvatarModel("purple", "Purple", Color(0xFF8854D0), Color(0xFF5B2C98), AvatarFaceStyle.SMILEY) - val Pink = CloneflixAvatarModel("pink", "Pink Classic", Color(0xFFFD79A8), Color(0xFFD63031), AvatarFaceStyle.SMILEY) - val Yellow = CloneflixAvatarModel("yellow", "Yellow Gold", Color(0xFFF1C40F), Color(0xFFD35400), AvatarFaceStyle.SMILEY) - val Kids = CloneflixAvatarModel("kids", "Kids", Color(0xFFF39C12), Color(0xFFE67E22), AvatarFaceStyle.KIDS) - val AngryMan = CloneflixAvatarModel("angryman", "Angry Man", Color(0xFFE74C3C), Color(0xFF7B1113), AvatarFaceStyle.ANGRY) - val FluffyGrey = CloneflixAvatarModel("fluffygrey", "Fluffy Grey", Color(0xFF7F8C8D), Color(0xFF34495E), AvatarFaceStyle.MONSTER_GREY) - val FluffyBlue = CloneflixAvatarModel("fluffyblue", "Fluffy Blue", Color(0xFF00A8FF), Color(0xFF0097E6), AvatarFaceStyle.MONSTER_BLUE) - val FluffyRed = CloneflixAvatarModel("fluffyred", "Fluffy Red", Color(0xFFEA2027), Color(0xFFB53471), AvatarFaceStyle.MONSTER_RED) - val FluffyYellow = CloneflixAvatarModel("fluffyyellow", "Fluffy Yellow", Color(0xFFFFC312), Color(0xFFF79F1F), AvatarFaceStyle.MONSTER_YELLOW) - val Chicken = CloneflixAvatarModel("chicken", "Chicken", Color(0xFFEE5253), Color(0xFFEA2027), AvatarFaceStyle.CHICKEN) - val Zombie = CloneflixAvatarModel("zombi", "Zombie", Color(0xFF10AC84), Color(0xFF1DD1A1), AvatarFaceStyle.ZOMBIE) - val Panda = CloneflixAvatarModel("panda", "Panda", Color(0xFFECEFF1), Color(0xFFCFD8DC), AvatarFaceStyle.PANDA) - - val Popular = listOf( - Red, Green, DarkBlue, Blue, - Purple, Pink, Yellow, Kids, - AngryMan, FluffyGrey, FluffyBlue, FluffyRed, - FluffyYellow, Chicken, Zombie, Panda - ) - - private val Palette = listOf( - Pair(Color(0xFFE50914), Color(0xFF9E0000)), - Pair(Color(0xFF0984E3), Color(0xFF1B3B6F)), - Pair(Color(0xFF00B894), Color(0xFF006266)), - Pair(Color(0xFF6C5CE7), Color(0xFF4834D4)), - Pair(Color(0xFFFD79A8), Color(0xFFE84393)), - Pair(Color(0xFFFDCB6E), Color(0xFFE17055)), - Pair(Color(0xFF00CEC9), Color(0xFF0984E3)), - Pair(Color(0xFFE17055), Color(0xFFD63031)), - Pair(Color(0xFF2C3E50), Color(0xFF1E272E)), - Pair(Color(0xFFFF7675), Color(0xFFD63031)), - Pair(Color(0xFF55EFC4), Color(0xFF00B894)), - Pair(Color(0xFFA29BFE), Color(0xFF6C5CE7)) - ) - - fun getAvatarByIndex(index: Int): CloneflixAvatarModel { - val numStr = String.format("%02d", index.coerceAtLeast(1)) - val colorPair = Palette[(index - 1) % Palette.size] - val styles = AvatarFaceStyle.values() - val faceStyle = styles[(index - 1) % styles.size] - return CloneflixAvatarModel( - id = numStr, - name = numStr, - topColor = colorPair.first, - bottomColor = colorPair.second, - faceStyle = faceStyle - ) - } - - fun getAllOthers(count: Int = 36): List { - return (1..count).map { getAvatarByIndex(it) } - } -} - -@Composable -fun CloneflixAvatarGraphic( - avatar: CloneflixAvatarModel, - modifier: Modifier = Modifier -) { - Canvas(modifier = modifier.fillMaxSize()) { - val w = size.width - val h = size.height - - drawRoundRect( - brush = Brush.verticalGradient( - colors = listOf(avatar.topColor, avatar.bottomColor), - startY = 0f, - endY = h - ), - size = Size(w, h), - cornerRadius = CornerRadius(w * 0.08f, h * 0.08f) - ) - - when (avatar.faceStyle) { - AvatarFaceStyle.SMILEY -> drawSmileyFace(w, h, PrimaryWhite) - AvatarFaceStyle.WINK -> drawWinkFace(w, h, PrimaryWhite) - AvatarFaceStyle.ANGRY -> drawAngryFace(w, h, PrimaryWhite) - AvatarFaceStyle.KIDS -> drawKidsFace(w, h, PrimaryWhite) - AvatarFaceStyle.MONSTER_GREY -> drawMonsterGrey(w, h) - AvatarFaceStyle.MONSTER_BLUE -> drawMonsterBlue(w, h) - AvatarFaceStyle.MONSTER_RED -> drawMonsterRed(w, h) - AvatarFaceStyle.MONSTER_YELLOW -> drawMonsterYellow(w, h) - AvatarFaceStyle.CHICKEN -> drawChickenFace(w, h) - AvatarFaceStyle.ZOMBIE -> drawZombieFace(w, h) - AvatarFaceStyle.PANDA -> drawPandaFace(w, h) - AvatarFaceStyle.COOL -> drawCoolFace(w, h, PrimaryWhite) - AvatarFaceStyle.SURPRISED -> drawSurprisedFace(w, h, PrimaryWhite) - AvatarFaceStyle.GEOMETRIC -> drawGeometricFace(w, h, avatar.id) - } - } -} - -private fun DrawScope.drawSmileyFace(w: Float, h: Float, color: Color) { - val eyeRadius = w * 0.065f - val eyeY = h * 0.38f - val leftEyeX = w * 0.35f - val rightEyeX = w * 0.65f - - drawCircle(color = color, radius = eyeRadius, center = Offset(leftEyeX, eyeY)) - drawCircle(color = color, radius = eyeRadius, center = Offset(rightEyeX, eyeY)) - - val mouthPath = Path().apply { - moveTo(w * 0.30f, h * 0.56f) - quadraticTo(w * 0.50f, h * 0.74f, w * 0.70f, h * 0.56f) - } - drawPath( - path = mouthPath, - color = color, - style = Stroke(width = w * 0.065f, cap = StrokeCap.Round) - ) -} - -private fun DrawScope.drawWinkFace(w: Float, h: Float, color: Color) { - val eyeRadius = w * 0.065f - val eyeY = h * 0.38f - - drawCircle(color = color, radius = eyeRadius, center = Offset(w * 0.35f, eyeY)) - - val winkPath = Path().apply { - moveTo(w * 0.60f, eyeY) - quadraticTo(w * 0.67f, eyeY - h * 0.04f, w * 0.74f, eyeY) - } - drawPath(path = winkPath, color = color, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) - - val mouthPath = Path().apply { - moveTo(w * 0.34f, h * 0.58f) - quadraticTo(w * 0.52f, h * 0.72f, w * 0.72f, h * 0.52f) - } - drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.065f, cap = StrokeCap.Round)) -} - -private fun DrawScope.drawAngryFace(w: Float, h: Float, color: Color) { - val leftBrow = Path().apply { - moveTo(w * 0.22f, h * 0.28f) - lineTo(w * 0.44f, h * 0.36f) - } - val rightBrow = Path().apply { - moveTo(w * 0.78f, h * 0.28f) - lineTo(w * 0.56f, h * 0.36f) - } - drawPath(leftBrow, color = color, style = Stroke(width = w * 0.07f, cap = StrokeCap.Round)) - drawPath(rightBrow, color = color, style = Stroke(width = w * 0.07f, cap = StrokeCap.Round)) - - drawCircle(color = color, radius = w * 0.055f, center = Offset(w * 0.34f, h * 0.42f)) - drawCircle(color = color, radius = w * 0.055f, center = Offset(w * 0.66f, h * 0.42f)) - - val mouthPath = Path().apply { - moveTo(w * 0.32f, h * 0.68f) - quadraticTo(w * 0.50f, h * 0.56f, w * 0.68f, h * 0.68f) - } - drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.065f, cap = StrokeCap.Round)) -} - -private fun DrawScope.drawKidsFace(w: Float, h: Float, color: Color) { - drawCircle(color = color, radius = w * 0.10f, center = Offset(w * 0.35f, h * 0.38f)) - drawCircle(color = color, radius = w * 0.10f, center = Offset(w * 0.65f, h * 0.38f)) - drawCircle(color = PrimaryBlack, radius = w * 0.05f, center = Offset(w * 0.35f, h * 0.38f)) - drawCircle(color = PrimaryBlack, radius = w * 0.05f, center = Offset(w * 0.65f, h * 0.38f)) - drawCircle(color = PrimaryWhite, radius = w * 0.02f, center = Offset(w * 0.37f, h * 0.36f)) - drawCircle(color = PrimaryWhite, radius = w * 0.02f, center = Offset(w * 0.67f, h * 0.36f)) - - val mouthPath = Path().apply { - moveTo(w * 0.28f, h * 0.58f) - quadraticTo(w * 0.50f, h * 0.78f, w * 0.72f, h * 0.58f) - } - drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.065f, cap = StrokeCap.Round)) -} - -private fun DrawScope.drawMonsterGrey(w: Float, h: Float) { - drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.25f, h * 0.20f)) - drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.75f, h * 0.20f)) - - drawCircle(color = PrimaryWhite, radius = w * 0.07f, center = Offset(w * 0.35f, h * 0.40f)) - drawCircle(color = PrimaryWhite, radius = w * 0.07f, center = Offset(w * 0.65f, h * 0.40f)) - drawCircle(color = PrimaryBlack, radius = w * 0.035f, center = Offset(w * 0.35f, h * 0.40f)) - drawCircle(color = PrimaryBlack, radius = w * 0.035f, center = Offset(w * 0.65f, h * 0.40f)) - - val mouthPath = Path().apply { - moveTo(w * 0.30f, h * 0.60f) - lineTo(w * 0.70f, h * 0.60f) - lineTo(w * 0.65f, h * 0.70f) - lineTo(w * 0.35f, h * 0.70f) - close() - } - drawPath(mouthPath, color = PrimaryWhite) -} - -private fun DrawScope.drawMonsterBlue(w: Float, h: Float) { - drawCircle(color = PrimaryWhite, radius = w * 0.13f, center = Offset(w * 0.50f, h * 0.36f)) - drawCircle(color = Color(0xFF0984E3), radius = w * 0.07f, center = Offset(w * 0.50f, h * 0.36f)) - drawCircle(color = PrimaryBlack, radius = w * 0.04f, center = Offset(w * 0.50f, h * 0.36f)) - drawCircle(color = PrimaryWhite, radius = w * 0.018f, center = Offset(w * 0.52f, h * 0.34f)) - - val mouthPath = Path().apply { - moveTo(w * 0.32f, h * 0.60f) - quadraticTo(w * 0.50f, h * 0.76f, w * 0.68f, h * 0.60f) - } - drawPath(path = mouthPath, color = PrimaryWhite, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) - - val fang = Path().apply { - moveTo(w * 0.40f, h * 0.60f) - lineTo(w * 0.43f, h * 0.68f) - lineTo(w * 0.46f, h * 0.60f) - close() - } - drawPath(fang, color = PrimaryWhite) -} - -private fun DrawScope.drawMonsterRed(w: Float, h: Float) { - val leftEye = Path().apply { - moveTo(w * 0.28f, h * 0.36f) - lineTo(w * 0.44f, h * 0.40f) - lineTo(w * 0.32f, h * 0.44f) - close() - } - val rightEye = Path().apply { - moveTo(w * 0.72f, h * 0.36f) - lineTo(w * 0.56f, h * 0.40f) - lineTo(w * 0.68f, h * 0.44f) - close() - } - drawPath(leftEye, color = Color(0xFFFFD32A)) - drawPath(rightEye, color = Color(0xFFFFD32A)) - - val mouth = Path().apply { - moveTo(w * 0.26f, h * 0.56f) - quadraticTo(w * 0.50f, h * 0.78f, w * 0.74f, h * 0.56f) - quadraticTo(w * 0.50f, h * 0.64f, w * 0.26f, h * 0.56f) - close() - } - drawPath(mouth, color = PrimaryWhite) -} - -private fun DrawScope.drawMonsterYellow(w: Float, h: Float) { - drawSmileyFace(w, h, Color(0xFF2C3E50)) -} - -private fun DrawScope.drawChickenFace(w: Float, h: Float) { - drawCircle(color = Color(0xFFFF4757), radius = w * 0.09f, center = Offset(w * 0.50f, h * 0.16f)) - drawCircle(color = Color(0xFFFF4757), radius = w * 0.07f, center = Offset(w * 0.38f, h * 0.19f)) - drawCircle(color = Color(0xFFFF4757), radius = w * 0.07f, center = Offset(w * 0.62f, h * 0.19f)) - - drawCircle(color = PrimaryWhite, radius = w * 0.06f, center = Offset(w * 0.35f, h * 0.40f)) - drawCircle(color = PrimaryWhite, radius = w * 0.06f, center = Offset(w * 0.65f, h * 0.40f)) - drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.35f, h * 0.40f)) - drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.65f, h * 0.40f)) - - val beak = Path().apply { - moveTo(w * 0.42f, h * 0.48f) - lineTo(w * 0.58f, h * 0.48f) - lineTo(w * 0.50f, h * 0.66f) - close() - } - drawPath(beak, color = Color(0xFFFFA502)) -} - -private fun DrawScope.drawZombieFace(w: Float, h: Float) { - drawCircle(color = PrimaryWhite, radius = w * 0.08f, center = Offset(w * 0.35f, h * 0.38f)) - drawCircle(color = PrimaryBlack, radius = w * 0.03f, center = Offset(w * 0.35f, h * 0.38f)) - - val xPath = Path().apply { - moveTo(w * 0.60f, h * 0.34f); lineTo(w * 0.70f, h * 0.42f) - moveTo(w * 0.70f, h * 0.34f); lineTo(w * 0.60f, h * 0.42f) - } - drawPath(xPath, color = PrimaryWhite, style = Stroke(width = w * 0.04f, cap = StrokeCap.Round)) - - val mouthLine = Path().apply { - moveTo(w * 0.30f, h * 0.62f); lineTo(w * 0.70f, h * 0.62f) - moveTo(w * 0.38f, h * 0.56f); lineTo(w * 0.38f, h * 0.68f) - moveTo(w * 0.50f, h * 0.56f); lineTo(w * 0.50f, h * 0.68f) - moveTo(w * 0.62f, h * 0.56f); lineTo(w * 0.62f, h * 0.68f) - } - drawPath(mouthLine, color = PrimaryWhite, style = Stroke(width = w * 0.04f, cap = StrokeCap.Round)) -} - -private fun DrawScope.drawPandaFace(w: Float, h: Float) { - drawCircle(color = Color(0xFF2F3640), radius = w * 0.11f, center = Offset(w * 0.22f, h * 0.22f)) - drawCircle(color = Color(0xFF2F3640), radius = w * 0.11f, center = Offset(w * 0.78f, h * 0.22f)) - - drawCircle(color = Color(0xFF2F3640), radius = w * 0.09f, center = Offset(w * 0.36f, h * 0.44f)) - drawCircle(color = Color(0xFF2F3640), radius = w * 0.64f, center = Offset(w * 0.64f, h * 0.44f)) - drawCircle(color = PrimaryWhite, radius = w * 0.035f, center = Offset(w * 0.36f, h * 0.43f)) - drawCircle(color = PrimaryWhite, radius = w * 0.035f, center = Offset(w * 0.64f, h * 0.43f)) - - drawCircle(color = Color(0xFF2F3640), radius = w * 0.04f, center = Offset(w * 0.50f, h * 0.56f)) - val mouthPath = Path().apply { - moveTo(w * 0.42f, h * 0.64f) - quadraticTo(w * 0.50f, h * 0.70f, w * 0.58f, h * 0.64f) - } - drawPath(path = mouthPath, color = Color(0xFF2F3640), style = Stroke(width = w * 0.04f, cap = StrokeCap.Round)) -} - -private fun DrawScope.drawCoolFace(w: Float, h: Float, color: Color) { - val glasses = Path().apply { - moveTo(w * 0.22f, h * 0.36f) - lineTo(w * 0.78f, h * 0.36f) - lineTo(w * 0.74f, h * 0.48f) - lineTo(w * 0.54f, h * 0.48f) - lineTo(w * 0.50f, h * 0.40f) - lineTo(w * 0.46f, h * 0.48f) - lineTo(w * 0.26f, h * 0.48f) - close() - } - drawPath(glasses, color = PrimaryBlack) - - val mouthPath = Path().apply { - moveTo(w * 0.34f, h * 0.62f) - quadraticTo(w * 0.50f, h * 0.76f, w * 0.68f, h * 0.58f) - } - drawPath(path = mouthPath, color = color, style = Stroke(width = w * 0.06f, cap = StrokeCap.Round)) -} - -private fun DrawScope.drawSurprisedFace(w: Float, h: Float, color: Color) { - drawCircle(color = color, radius = w * 0.065f, center = Offset(w * 0.35f, h * 0.36f)) - drawCircle(color = color, radius = w * 0.065f, center = Offset(w * 0.65f, h * 0.36f)) - drawCircle(color = color, radius = w * 0.08f, center = Offset(w * 0.50f, h * 0.62f)) -} - -private fun DrawScope.drawGeometricFace(w: Float, h: Float, text: String) { - drawSmileyFace(w, h, PrimaryWhite) -} - -@Composable -fun CloneflixSmallAvatar( - avatar: CloneflixAvatarModel, - modifier: Modifier = Modifier, - showMenuArrow: Boolean = false, - size: Dp = 32.dp, - onClick: (() -> Unit)? = null, - isExpanded: Boolean = false -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val rotationAngle by animateFloatAsState( - targetValue = if (isExpanded) 180f else 0f, - label = "SmallAvatarArrowRotation" - ) - - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.08f else 1.0f, - label = "SmallAvatarScale" - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = modifier - .scale(scale) - .clip(RoundedCornerShape(4.dp)) - .then( - if (isFocused) Modifier.border(BorderStroke(2.dp, PrimaryWhite), RoundedCornerShape(4.dp)) - else Modifier - ) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - } else { - Modifier.focusable(interactionSource = interactionSource) - } - ) - .padding(if (showMenuArrow) 4.dp else 0.dp) - .semantics { - role = Role.Button - contentDescription = "User profile ${avatar.name}${if (showMenuArrow) ", menu dropdown" else ""}" - } - ) { - Box( - modifier = Modifier - .size(size) - .clip(RoundedCornerShape(3.dp)) - ) { - CloneflixAvatarGraphic(avatar = avatar) - } - - if (showMenuArrow) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_arrow_down), - contentDescription = null, - tint = if (isFocused) PrimaryWhite else Grey100, - modifier = Modifier - .size(16.dp) - .rotate(rotationAngle) - ) - } - } -} - -@Composable -fun CloneflixLargeAvatar( - name: String, - modifier: Modifier = Modifier, - avatar: CloneflixAvatarModel? = null, - isAddProfile: Boolean = false, - size: Dp = 144.dp, - isHoverState: Boolean = false, - onClick: () -> Unit = {} -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocusedByInteraction by interactionSource.collectIsFocusedAsState() - val isFocused = isFocusedByInteraction || isHoverState - - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.06f else 1.0f, - label = "LargeAvatarScale" - ) - - val borderWidth = if (isFocused) 2.5.dp else 0.dp - val borderColor = if (isFocused) PrimaryWhite else Color.Transparent - - val textColor = when { - isFocused -> PrimaryWhite - isAddProfile -> Grey200 - else -> colors.textPrimary - } - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .width(size + 16.dp) - .scale(scale) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .padding(vertical = 6.dp) - .semantics { - role = Role.Button - contentDescription = if (isAddProfile) "Add Profile" else "Profile $name" - } - ) { - Box( - modifier = Modifier - .size(size) - .clip(RoundedCornerShape(6.dp)) - .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(6.dp)) - .background(if (isAddProfile) Grey800 else Color.Transparent), - contentAlignment = Alignment.Center - ) { - if (isAddProfile) { - Box( - modifier = Modifier - .size(size * 0.54f) - .clip(CircleShape) - .background(if (isFocused) Grey700 else Grey850) - .border( - BorderStroke( - 1.5.dp, - if (isFocused) PrimaryWhite else Grey350 - ), - CircleShape - ), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_plus), - contentDescription = null, - tint = if (isFocused) PrimaryWhite else Grey200, - modifier = Modifier.size(size * 0.30f) - ) - } - } else if (avatar != null) { - CloneflixAvatarGraphic(avatar = avatar) - } - } - - Spacer(modifier = Modifier.height(12.dp)) - - Text( - text = name, - style = if (isFocused) typography.mediumBody.copy(fontWeight = FontWeight.Bold) else typography.mediumBody, - fontSize = 16.sp, - color = textColor, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} - -@Composable -fun CloneflixUserProfilesRow( - profiles: List>, - selectedProfileName: String, - onProfileClick: (String, CloneflixAvatarModel) -> Unit, - onAddProfileClick: () -> Unit, - modifier: Modifier = Modifier -) { - Row( - horizontalArrangement = Arrangement.spacedBy(24.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - ) { - profiles.forEach { (profileName, avatar) -> - CloneflixLargeAvatar( - name = profileName, - avatar = avatar, - isAddProfile = false, - isHoverState = profileName == selectedProfileName, - onClick = { onProfileClick(profileName, avatar) } - ) - } - - CloneflixLargeAvatar( - name = "Add Profile", - avatar = null, - isAddProfile = true, - onClick = onAddProfileClick - ) - } -} - -private val PolygonShape = GenericShape { size, _ -> - moveTo(size.width / 2f, 0f) - lineTo(size.width, size.height) - lineTo(0f, size.height) - close() -} - -@Composable -fun CloneflixProfileMenuDropdown( - currentProfiles: List>, - onProfileSelected: (String) -> Unit, - onManageProfilesClick: () -> Unit, - onTransferProfilesClick: () -> Unit, - onAccountClick: () -> Unit, - onHelpCenterClick: () -> Unit, - onSignOutClick: () -> Unit, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - - Column( - horizontalAlignment = Alignment.End, - modifier = modifier.width(224.dp) - ) { - Box( - modifier = Modifier - .padding(end = 24.dp) - .size(width = 16.dp, height = 10.dp) - .clip(PolygonShape) - .background(Grey800) - .border(BorderStroke(1.dp, Grey600), PolygonShape) - ) - - Surface( - color = Color(0xFF141414), - shape = RoundedCornerShape(4.dp), - border = BorderStroke(1.dp, Grey700), - shadowElevation = 12.dp, - modifier = Modifier - .fillMaxWidth() - .offset(y = (-1).dp) - ) { - Column(modifier = Modifier.padding(vertical = 8.dp)) { - currentProfiles.forEach { (name, avatar) -> - ProfileMenuItem( - icon = { - Box( - modifier = Modifier - .size(32.dp) - .clip(RoundedCornerShape(3.dp)) - ) { - CloneflixAvatarGraphic(avatar = avatar) - } - }, - label = name, - onClick = { onProfileSelected(name) } - ) - } - - Spacer(modifier = Modifier.height(4.dp)) - CloneflixDivider(modifier = Modifier.padding(horizontal = 8.dp)) - Spacer(modifier = Modifier.height(4.dp)) - - ProfileMenuItem( - iconRes = R.drawable.cloneflix_ic_edit, - label = "Manage Profiles", - onClick = onManageProfilesClick - ) - - ProfileMenuItem( - iconRes = R.drawable.cloneflix_ic_person, - label = "Transfer Profiles", - onClick = onTransferProfilesClick - ) - - ProfileMenuItem( - iconRes = R.drawable.cloneflix_ic_account, - label = "Account", - onClick = onAccountClick - ) - - ProfileMenuItem( - iconRes = R.drawable.cloneflix_ic_help, - label = "Help Center", - onClick = onHelpCenterClick - ) - - Spacer(modifier = Modifier.height(4.dp)) - CloneflixDivider(modifier = Modifier.padding(horizontal = 8.dp)) - Spacer(modifier = Modifier.height(4.dp)) - - Box( - modifier = Modifier - .fillMaxWidth() - .clickable(role = Role.Button, onClick = onSignOutClick) - .padding(horizontal = 16.dp, vertical = 12.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = "Sign out of Cloneflix", - style = typography.mediumSmallBody, - color = PrimaryWhite, - fontSize = 13.sp, - textAlign = TextAlign.Center - ) - } - } - } - } -} - -@Composable -private fun ProfileMenuItem( - label: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - iconRes: Int? = null, - icon: (@Composable () -> Unit)? = null -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val typography = CloneflixTheme.typography - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .background(if (isFocused) Grey800 else Color.Transparent) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .padding(horizontal = 14.dp, vertical = 8.dp) - ) { - if (icon != null) { - icon() - } else if (iconRes != null) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = null, - tint = if (isFocused) PrimaryWhite else Grey100, - modifier = Modifier.size(20.dp) - ) - } - - Spacer(modifier = Modifier.width(12.dp)) - - Text( - text = label, - style = typography.regularBody, - fontSize = 13.sp, - color = if (isFocused) PrimaryWhite else Grey100, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt deleted file mode 100644 index 29fe93fac59..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixBadgeComponents.kt +++ /dev/null @@ -1,427 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.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.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.graphics.Color -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey10 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey25 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T40 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red300 - -@Composable -fun CloneflixMaturityRating( - rating: String, - modifier: Modifier = Modifier, - onClick: (() -> Unit)? = null -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val isInteractive = onClick != null - val borderColor = if (isInteractive && isFocused) PrimaryWhite else Grey25 - val borderWidth = if (isInteractive && isFocused) 1.8.dp else 1.dp - val textColor = if (isInteractive && isFocused) PrimaryWhite else Grey25 - - Box( - modifier = modifier - .height(22.dp) - .clip(RoundedCornerShape(2.dp)) - .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(2.dp)) - .background(if (isInteractive && isFocused) Grey300T40 else Color.Transparent) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - } else { - Modifier - } - ) - .padding(horizontal = 6.5.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = rating, - color = textColor, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - lineHeight = 16.sp, - textAlign = TextAlign.Center, - modifier = Modifier.semantics { - contentDescription = "Maturity Rating $rating" - } - ) - } -} - -@Composable -fun CloneflixVideoQualityBadge( - quality: String, - modifier: Modifier = Modifier, - onClick: (() -> Unit)? = null -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val isInteractive = onClick != null - val borderColor = if (isInteractive && isFocused) PrimaryWhite else Grey200 - val borderWidth = if (isInteractive && isFocused) 1.5.dp else 1.dp - val textColor = if (isInteractive && isFocused) PrimaryWhite else Grey10 - - Box( - modifier = modifier - .height(18.dp) - .clip(RoundedCornerShape(4.dp)) - .border(BorderStroke(borderWidth, borderColor), RoundedCornerShape(4.dp)) - .background(if (isInteractive && isFocused) Grey300T40 else Color.Transparent) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - } else { - Modifier - } - ) - .padding(horizontal = 6.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = quality, - color = textColor, - fontSize = 11.sp, - fontWeight = FontWeight.Bold, - lineHeight = 14.sp, - textAlign = TextAlign.Center, - modifier = Modifier.semantics { - contentDescription = "Video Quality $quality" - } - ) - } -} - -enum class CloneflixTop10Size { - LARGE, - MEDIUM, - SMALL -} - -@Composable -fun CloneflixTop10Badge( - modifier: Modifier = Modifier, - size: CloneflixTop10Size = CloneflixTop10Size.MEDIUM -) { - val badgeSize: Dp = when (size) { - CloneflixTop10Size.LARGE -> 32.dp - CloneflixTop10Size.MEDIUM -> 28.dp - CloneflixTop10Size.SMALL -> 24.dp - } - - val topTextSize = when (size) { - CloneflixTop10Size.LARGE -> 10.sp - CloneflixTop10Size.MEDIUM -> 9.sp - CloneflixTop10Size.SMALL -> 7.5.sp - } - - val numTextSize = when (size) { - CloneflixTop10Size.LARGE -> 16.sp - CloneflixTop10Size.MEDIUM -> 14.sp - CloneflixTop10Size.SMALL -> 11.5.sp - } - - Box( - modifier = modifier - .size(badgeSize) - .clip(RoundedCornerShape(4.dp)) - .background(PrimaryRed) - .semantics { contentDescription = "Top 10 Badge" }, - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = "TOP", - color = PrimaryWhite, - fontSize = topTextSize, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - lineHeight = topTextSize - ) - Text( - text = "10", - color = PrimaryWhite, - fontSize = numTextSize, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - lineHeight = numTextSize - ) - } - } -} - -@Composable -fun CloneflixTop10RankBanner( - rankText: String, - modifier: Modifier = Modifier, - size: CloneflixTop10Size = CloneflixTop10Size.MEDIUM, - onClick: (() -> Unit)? = null -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val textFontSize = when (size) { - CloneflixTop10Size.LARGE -> 22.sp - CloneflixTop10Size.MEDIUM -> 18.sp - CloneflixTop10Size.SMALL -> 15.sp - } - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .clip(RoundedCornerShape(8.dp)) - .border( - if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(0.dp, Color.Transparent), - RoundedCornerShape(8.dp) - ) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - } else { - Modifier.focusable(interactionSource = interactionSource) - } - ) - .padding(4.dp) - ) { - CloneflixTop10Badge(size = size) - Spacer(modifier = Modifier.width(10.dp)) - Text( - text = rankText, - color = PrimaryWhite, - fontSize = textFontSize, - fontWeight = FontWeight.Bold, - fontFamily = CloneflixTheme.typography.regularBody.fontFamily - ) - } -} - -enum class CloneflixBadgeType(@androidx.annotation.StringRes val stringRes: Int) { - RECENTLY_ADDED(com.lagradost.cloudstream3.R.string.cloneflix_badge_recently_added), - NEW_SEASON(com.lagradost.cloudstream3.R.string.cloneflix_badge_new_season), - TOP_10(com.lagradost.cloudstream3.R.string.cloneflix_badge_top_10), - LEAVING_SOON(com.lagradost.cloudstream3.R.string.cloneflix_badge_leaving_soon) -} - -@Composable -fun CloneflixContentBadge( - type: CloneflixBadgeType, - modifier: Modifier = Modifier, - customText: String? = null -) { - val text = customText ?: androidx.compose.ui.res.stringResource(id = type.stringRes) - - val backgroundColor = when (type) { - CloneflixBadgeType.RECENTLY_ADDED -> Red100 - CloneflixBadgeType.NEW_SEASON -> PrimaryRed - CloneflixBadgeType.TOP_10 -> Red300 - CloneflixBadgeType.LEAVING_SOON -> Red300 - } - - Box( - modifier = modifier - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(backgroundColor) - .padding(horizontal = CloneflixTheme.dimens.spacingS, vertical = CloneflixTheme.dimens.spacingXxs), - contentAlignment = Alignment.Center - ) { - Text( - text = text, - color = PrimaryWhite, - fontSize = 11.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.SansSerif, - lineHeight = 13.sp, - textAlign = TextAlign.Center - ) - } -} - -enum class CloneflixLogoVariant { - WORDMARK_MEDIUM, - WORDMARK_SMALL, - LETTERMARK_LARGE, - LETTERMARK_MEDIUM, - LETTERMARK_SMALL -} - -@Composable -fun CloneflixLogoView( - variant: CloneflixLogoVariant = CloneflixLogoVariant.WORDMARK_MEDIUM, - text: String? = null, - modifier: Modifier = Modifier -) { - val logoText = (if (!text.isNullOrBlank()) text else "CLONEFLIX").uppercase() - when (variant) { - CloneflixLogoVariant.WORDMARK_MEDIUM -> { - Text( - text = logoText, - color = PrimaryRed, - fontSize = 28.sp, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - letterSpacing = 1.5.sp, - modifier = modifier.semantics { contentDescription = "$logoText Logo" } - ) - } - CloneflixLogoVariant.WORDMARK_SMALL -> { - Text( - text = logoText, - color = PrimaryRed, - fontSize = 18.sp, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - letterSpacing = 1.sp, - modifier = modifier.semantics { contentDescription = "$logoText Logo Small" } - ) - } - CloneflixLogoVariant.LETTERMARK_LARGE -> { - val letter = logoText.firstOrNull()?.toString() ?: "C" - Box( - modifier = modifier - .size(48.dp) - .clip(RoundedCornerShape(8.dp)) - .background(PrimaryBlack), - contentAlignment = Alignment.Center - ) { - Text( - text = letter, - color = PrimaryRed, - fontSize = 38.sp, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif - ) - } - } - CloneflixLogoVariant.LETTERMARK_MEDIUM -> { - val letter = logoText.firstOrNull()?.toString() ?: "C" - Box( - modifier = modifier - .size(36.dp) - .clip(RoundedCornerShape(6.dp)) - .background(PrimaryBlack), - contentAlignment = Alignment.Center - ) { - Text( - text = letter, - color = PrimaryRed, - fontSize = 28.sp, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif - ) - } - } - CloneflixLogoVariant.LETTERMARK_SMALL -> { - val letter = logoText.firstOrNull()?.toString() ?: "C" - Box( - modifier = modifier - .size(24.dp) - .clip(RoundedCornerShape(4.dp)) - .background(PrimaryBlack), - contentAlignment = Alignment.Center - ) { - Text( - text = letter, - color = PrimaryRed, - fontSize = 18.sp, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif - ) - } - } - } -} - -@Preview(name = "Badges & Logos Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun BadgesAndLogosPreview() { - CloneflixTheme { - Column(modifier = Modifier.padding(16.dp)) { - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - CloneflixMaturityRating(rating = "TV-MA") - CloneflixMaturityRating(rating = "PG-13") - CloneflixVideoQualityBadge(quality = "4K") - CloneflixVideoQualityBadge(quality = "HD") - CloneflixVideoQualityBadge(quality = "Dolby Vision") - } - Spacer(modifier = Modifier.height(16.dp)) - CloneflixTop10RankBanner(rankText = "#2 in TV Shows Today") - Spacer(modifier = Modifier.height(16.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - CloneflixContentBadge(type = CloneflixBadgeType.RECENTLY_ADDED) - CloneflixContentBadge(type = CloneflixBadgeType.NEW_SEASON) - CloneflixContentBadge(type = CloneflixBadgeType.TOP_10) - } - Spacer(modifier = Modifier.height(16.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) { - CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) - CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_MEDIUM) - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt deleted file mode 100644 index 2fe2eaf7573..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixButtonPatterns.kt +++ /dev/null @@ -1,297 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -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.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.Slider -import androidx.compose.material3.SliderDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -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 -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 - -@Composable -fun CloneflixHeroActionPattern( - onPlayClick: () -> Unit = {}, - onMoreInfoClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val dimens = CloneflixTheme.dimens - - Row( - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixButton( - text = stringResource(R.string.cloneflix_btn_play), - onClick = onPlayClick, - variant = CloneflixButtonVariant.SECONDARY, - size = CloneflixButtonSize.MEDIUM, - icon = painterResource(id = R.drawable.cloneflix_ic_play) - ) - - CloneflixButton( - text = stringResource(R.string.cloneflix_btn_more_info), - onClick = onMoreInfoClick, - variant = CloneflixButtonVariant.MORE_INFO, - size = CloneflixButtonSize.MEDIUM, - icon = painterResource(id = R.drawable.cloneflix_ic_info) - ) - } -} - -@Composable -fun CloneflixPreviewActionsPattern( - isAdded: Boolean = false, - onPlayClick: () -> Unit = {}, - onAddClick: () -> Unit = {}, - onThumbUpClick: () -> Unit = {}, - onExpandClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val dimens = CloneflixTheme.dimens - - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixCircleActionButton( - type = CloneflixCircleActionType.PLAY, - onClick = onPlayClick - ) - CloneflixCircleActionButton( - type = if (isAdded) CloneflixCircleActionType.ADDED else CloneflixCircleActionType.ADD, - onClick = onAddClick - ) - CloneflixCircleActionButton( - type = CloneflixCircleActionType.THUMB_UP, - onClick = onThumbUpClick - ) - } - - CloneflixCircleActionButton( - type = CloneflixCircleActionType.ARROW_DOWN, - onClick = onExpandClick - ) - } -} - -@Composable -fun CloneflixHeroRatingPattern( - isMuted: Boolean = true, - rating: String = "TV-MA", - onToggleMute: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val dimens = CloneflixTheme.dimens - - Row( - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixHeroSoundButton( - isMuted = isMuted, - onToggleMute = onToggleMute, - size = CloneflixMovieCardDefaults.PlayButtonOverlaySize - ) - - CloneflixMaturityRating( - rating = rating - ) - } -} - -@Composable -fun CloneflixVideoPlayerControlBar( - title: String = "S1:E3 • The Shadow Protocol", - currentTimeText: String = "14:20", - totalTimeText: String = "45:00", - progress: Float = 0.32f, - isPlaying: Boolean = true, - isMuted: Boolean = false, - onPlayPauseToggle: () -> Unit = {}, - onSeekBack: () -> Unit = {}, - onSeekForward: () -> Unit = {}, - onToggleMute: () -> Unit = {}, - onNextEpisode: () -> Unit = {}, - onSubtitlesClick: () -> Unit = {}, - onSpeedClick: () -> Unit = {}, - onEpisodesClick: () -> Unit = {}, - onFullscreenToggle: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - var currentProgress by remember { mutableFloatStateOf(progress) } - - Column( - modifier = modifier - .fillMaxWidth() - .clip(RoundedCornerShape(dimens.radiusM)) - .background(Color(0xDF141414)) - .padding(dimens.spacingL) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = currentTimeText, - style = typography.regularCaption1, - color = PrimaryWhite - ) - - Spacer(modifier = Modifier.width(dimens.spacingM)) - - Slider( - value = currentProgress, - onValueChange = { currentProgress = it }, - colors = SliderDefaults.colors( - thumbColor = PrimaryRed, - activeTrackColor = PrimaryRed, - inactiveTrackColor = Color(0x66FFFFFF) - ), - modifier = Modifier.weight(1f) - ) - - Spacer(modifier = Modifier.width(dimens.spacingM)) - - Text( - text = totalTimeText, - style = typography.regularCaption1, - color = Grey200 - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixPlayerControlButton( - action = if (isPlaying) CloneflixPlayerControlAction.PLAY else CloneflixPlayerControlAction.PAUSE, - onClick = onPlayPauseToggle, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.REPLAY_10, - onClick = onSeekBack, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.FORWARD_10, - onClick = onSeekForward, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - CloneflixPlayerControlButton( - action = if (isMuted) CloneflixPlayerControlAction.VOLUME_OFF else CloneflixPlayerControlAction.VOLUME_UP, - onClick = onToggleMute, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - } - - Text( - text = title, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.NEXT_EPISODE, - onClick = onNextEpisode, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.EPISODES, - onClick = onEpisodesClick, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.SUBTITLES, - onClick = onSubtitlesClick, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.SPEED, - onClick = onSpeedClick, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.FULLSCREEN, - onClick = onFullscreenToggle, - size = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize = dimens.iconM - ) - } - } - } -} - -@Preview(name = "Button Patterns Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun ButtonPatternsPreview() { - CloneflixTheme { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(20.dp) - ) { - CloneflixHeroActionPattern() - CloneflixPreviewActionsPattern() - CloneflixHeroRatingPattern() - CloneflixVideoPlayerControlBar() - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt deleted file mode 100644 index c774ab9041c..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixComposeComponents.kt +++ /dev/null @@ -1,829 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -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.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Surface -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.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red300 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors - -enum class CloneflixButtonVariant { - PRIMARY, - SECONDARY, - DARK_SECONDARY, - MORE_INFO, - OUTLINE, - GHOST -} - -enum class CloneflixButtonSize { - LARGE, - MEDIUM, - SMALL -} - -@Composable -fun CloneflixButton( - text: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - variant: CloneflixButtonVariant = CloneflixButtonVariant.PRIMARY, - size: CloneflixButtonSize = CloneflixButtonSize.MEDIUM, - icon: Painter? = null, - enabled: Boolean = true -) { - val dimens = CloneflixTheme.dimens - val colors = CloneflixTheme.colors - - val height = when (size) { - CloneflixButtonSize.LARGE -> dimens.buttonHeightLarge - CloneflixButtonSize.MEDIUM -> dimens.buttonHeightMedium - CloneflixButtonSize.SMALL -> dimens.buttonHeightSmall - } - - val textSize = when (size) { - CloneflixButtonSize.LARGE -> 18.sp - CloneflixButtonSize.MEDIUM -> 16.sp - CloneflixButtonSize.SMALL -> 14.sp - } - - val contentPadding = when (size) { - CloneflixButtonSize.LARGE -> PaddingValues(horizontal = dimens.spacing2Xl, vertical = dimens.spacingM) - CloneflixButtonSize.MEDIUM -> PaddingValues(horizontal = dimens.spacingL, vertical = dimens.spacingS) - CloneflixButtonSize.SMALL -> PaddingValues(horizontal = dimens.spacingM, vertical = dimens.spacingXs) - } - - val containerColor = when (variant) { - CloneflixButtonVariant.PRIMARY -> colors.primary - CloneflixButtonVariant.SECONDARY -> PrimaryWhite - CloneflixButtonVariant.DARK_SECONDARY -> TransparentWhite20 - CloneflixButtonVariant.MORE_INFO -> Color(0xB3333333) - CloneflixButtonVariant.OUTLINE -> Color.Transparent - CloneflixButtonVariant.GHOST -> Color.Transparent - } - - val contentColor = when (variant) { - CloneflixButtonVariant.PRIMARY -> colors.onPrimary - CloneflixButtonVariant.SECONDARY -> PrimaryBlack - CloneflixButtonVariant.DARK_SECONDARY -> PrimaryWhite - CloneflixButtonVariant.MORE_INFO -> PrimaryWhite - CloneflixButtonVariant.OUTLINE -> colors.textPrimary - CloneflixButtonVariant.GHOST -> colors.textPrimary - } - - val border = when (variant) { - CloneflixButtonVariant.OUTLINE -> BorderStroke(1.dp, Grey200) - else -> null - } - - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val activeBorder = if (isFocused) { - BorderStroke(2.dp, PrimaryWhite) - } else { - border - } - - Button( - onClick = onClick, - enabled = enabled, - interactionSource = interactionSource, - modifier = modifier - .height(height) - .semantics { role = Role.Button }, - shape = CloneflixTheme.shapes.extraSmall, - colors = ButtonDefaults.buttonColors( - containerColor = containerColor, - contentColor = contentColor, - disabledContainerColor = containerColor.copy(alpha = 0.5f), - disabledContentColor = contentColor.copy(alpha = 0.5f) - ), - border = activeBorder, - contentPadding = contentPadding - ) { - if (icon != null) { - Icon( - painter = icon, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = contentColor - ) - Spacer(modifier = Modifier.width(dimens.spacingS)) - } - Text( - text = text, - fontSize = textSize, - fontWeight = FontWeight.Bold, - color = contentColor - ) - } -} - -@Composable -fun CloneflixCheckbox( - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - modifier: Modifier = Modifier, - label: String? = null, - enabled: Boolean = true -) { - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val activeBorder = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else null - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .clip(CloneflixTheme.shapes.extraSmall) - .then(if (activeBorder != null) Modifier.border(activeBorder, CloneflixTheme.shapes.extraSmall) else Modifier) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - onClick = { onCheckedChange(!checked) } - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .padding(vertical = dimens.spacingXs, horizontal = dimens.spacingXs) - .semantics { role = Role.Checkbox } - ) { - Checkbox( - checked = checked, - onCheckedChange = null, - enabled = enabled, - colors = CheckboxDefaults.colors( - checkedColor = PrimaryWhite, - checkmarkColor = PrimaryBlack, - uncheckedColor = Grey200, - disabledCheckedColor = PrimaryWhite.copy(alpha = 0.5f), - disabledUncheckedColor = Grey200.copy(alpha = 0.5f) - ), - modifier = Modifier.size(20.dp) - ) - - if (!label.isNullOrBlank()) { - Spacer(modifier = Modifier.width(dimens.spacingS)) - Text( - text = label, - style = typography.regularBody, - color = if (isFocused) PrimaryWhite else colors.textPrimary - ) - } - } -} - -enum class CloneflixCardElevation { - SURFACE, - ELEVATED -} - -@Composable -fun CloneflixCard( - modifier: Modifier = Modifier, - elevation: CloneflixCardElevation = CloneflixCardElevation.SURFACE, - content: @Composable () -> Unit -) { - val colors = CloneflixTheme.colors - val shapes = CloneflixTheme.shapes - val bgColor = if (elevation == CloneflixCardElevation.SURFACE) colors.surface else colors.surfaceElevated - - Surface( - modifier = modifier.clip(shapes.large), - color = bgColor, - shape = shapes.large, - border = BorderStroke(1.dp, colors.border), - content = content - ) -} - -@Composable -fun CloneflixHeader( - title: String, - subtitle: String? = null, - iconRes: Int = R.drawable.cloneflix_ic_about, - modifier: Modifier = Modifier -) { - val dimens = CloneflixTheme.dimens - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - - Column(modifier = modifier.fillMaxWidth()) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = dimens.spacing2Xl) - ) { - Box( - modifier = Modifier - .size(dimens.headerIconSize) - .clip(CircleShape) - .background(colors.surface) - .border(dimens.headerIconStroke, PrimaryWhite, CircleShape), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = "$title Icon", - tint = colors.textPrimary, - modifier = Modifier.size(36.dp) - ) - } - - Spacer(modifier = Modifier.width(dimens.spacing2Xl)) - - Column { - Text( - text = title, - style = typography.headerDisplay, - color = colors.textPrimary - ) - if (!subtitle.isNullOrBlank()) { - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = subtitle, - style = typography.regularSmallBody, - color = colors.textSecondary - ) - } - } - } - - CloneflixDivider() - } -} - -@Composable -fun CloneflixDivider( - modifier: Modifier = Modifier, - thickness: Dp = CloneflixTheme.dimens.dividerThickness, - color: Color = CloneflixTheme.colors.divider -) { - Box( - modifier = modifier - .fillMaxWidth() - .height(thickness) - .background(color) - ) -} - -@Composable -fun CloneflixColorSwatch( - token: CloneflixColors.ColorToken, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val typography = CloneflixTheme.typography - - var isFocused by remember { mutableStateOf(false) } - val activeBorder = if (isFocused) { - BorderStroke(2.dp, PrimaryWhite) - } else { - BorderStroke(1.dp, colors.border) - } - - Row( - modifier = modifier - .fillMaxWidth() - .clip(CloneflixTheme.shapes.large) - .background(colors.surface) - .border(activeBorder, CloneflixTheme.shapes.large) - .onFocusChanged { isFocused = it.isFocused } - .clickable( - onClick = onClick, - role = Role.Button - ) - .focusable() - .padding(dimens.spacingL) - .semantics { - contentDescription = "${token.name}, Hex: ${token.hex}, ${token.rgbDescription}" - }, - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(dimens.swatchSize) - .clip(CloneflixTheme.shapes.small) - .background(Color(token.colorInt)) - .border(1.dp, colors.border, CloneflixTheme.shapes.small) - ) - - Spacer(modifier = Modifier.width(dimens.spacingL)) - - Column(modifier = Modifier.weight(1f)) { - Text( - text = token.name, - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXxs)) - Text( - text = "${token.hex} • ${token.rgbDescription}", - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_copy), - contentDescription = "Copy Color Hex", - tint = colors.textSecondary, - modifier = Modifier.size(24.dp) - ) - } -} - -enum class CloneflixInputFieldSize { - LARGE, - MEDIUM -} - -enum class CloneflixInputFieldState { - DEFAULT, - FOCUSED, - ERROR -} - -@Composable -fun CloneflixInputField( - value: String, - onValueChange: (String) -> Unit, - label: String, - modifier: Modifier = Modifier, - size: CloneflixInputFieldSize = CloneflixInputFieldSize.LARGE, - isError: Boolean = false, - errorMessage: String? = null, - isPassword: Boolean = false, - enabled: Boolean = true, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, - keyboardActions: KeyboardActions = KeyboardActions.Default, - leadingIcon: (@Composable () -> Unit)? = null, - trailingIcon: (@Composable () -> Unit)? = null -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - var isFocused by remember { mutableStateOf(false) } - var passwordVisible by remember { mutableStateOf(!isPassword) } - - val hasFloating = isFocused || value.isNotEmpty() - - val containerHeight = when (size) { - CloneflixInputFieldSize.LARGE -> 56.dp - CloneflixInputFieldSize.MEDIUM -> 48.dp - } - - val labelOffsetY by animateDpAsState( - targetValue = if (hasFloating) 7.dp else if (size == CloneflixInputFieldSize.LARGE) 16.dp else 12.dp, - animationSpec = tween(durationMillis = 150), - label = "labelOffsetY" - ) - - val labelFontSize by animateFloatAsState( - targetValue = if (hasFloating) 12f else 16f, - animationSpec = tween(durationMillis = 150), - label = "labelFontSize" - ) - - val labelColor by animateColorAsState( - targetValue = when { - isError -> Red100 - isFocused -> Grey50 - else -> Grey50 - }, - animationSpec = tween(durationMillis = 150), - label = "labelColor" - ) - - val borderColor = when { - isError -> Red100 - isFocused -> PrimaryWhite - else -> Grey200 - } - - val borderWidth = if (isFocused) 2.dp else 1.dp - val shape = RoundedCornerShape(4.dp) - - val visualTransformation = if (isPassword && !passwordVisible) { - PasswordVisualTransformation() - } else { - VisualTransformation.None - } - - Column(modifier = modifier.fillMaxWidth()) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(containerHeight) - .clip(shape) - .background(Color(0x80000000)) - .border(BorderStroke(borderWidth, borderColor), shape) - .focusable() - .onFocusChanged { isFocused = it.isFocused } - .padding(horizontal = 16.dp), - contentAlignment = Alignment.CenterStart - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - if (leadingIcon != null) { - leadingIcon() - Spacer(modifier = Modifier.width(12.dp)) - } - - Box( - modifier = Modifier - .weight(1f) - .height(containerHeight) - ) { - Text( - text = label, - color = labelColor, - fontFamily = CloneflixFontFamily, - fontWeight = if (hasFloating) FontWeight.Medium else FontWeight.Normal, - fontSize = labelFontSize.sp, - modifier = Modifier - .offset(y = labelOffsetY) - ) - - BasicTextField( - value = value, - onValueChange = onValueChange, - enabled = enabled, - singleLine = true, - visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - textStyle = TextStyle( - fontFamily = CloneflixFontFamily, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - color = PrimaryWhite - ), - cursorBrush = SolidColor(PrimaryWhite), - modifier = Modifier - .fillMaxWidth() - .padding(top = if (hasFloating) 20.dp else 0.dp) - .align(if (hasFloating) Alignment.TopStart else Alignment.CenterStart) - ) - } - - if (isPassword) { - IconButton( - onClick = { passwordVisible = !passwordVisible }, - modifier = Modifier.size(24.dp) - ) { - Icon( - painter = painterResource( - id = if (passwordVisible) R.drawable.cloneflix_ic_visibility else R.drawable.cloneflix_ic_visibility_off - ), - contentDescription = if (passwordVisible) "Hide password" else "Show password", - tint = Grey50, - modifier = Modifier.size(20.dp) - ) - } - } else if (trailingIcon != null) { - trailingIcon() - } - } - } - - if (isError && !errorMessage.isNullOrBlank()) { - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(start = dimens.spacingXs) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_circle_error), - contentDescription = "Error", - tint = Red100, - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = errorMessage, - style = typography.regularCaption1, - color = Red100 - ) - } - } - } -} - -@Composable -fun CloneflixTextField( - value: String, - onValueChange: (String) -> Unit, - label: String, - modifier: Modifier = Modifier, - isError: Boolean = false, - errorMessage: String? = null -) { - CloneflixInputField( - value = value, - onValueChange = onValueChange, - label = label, - modifier = modifier, - isError = isError, - errorMessage = errorMessage - ) -} - -@Composable -fun CloneflixGetStartedRow( - emailValue: String, - onEmailChange: (String) -> Unit, - onGetStartedClick: () -> Unit, - modifier: Modifier = Modifier, - isError: Boolean = false, - errorMessage: String? = null -) { - val dimens = CloneflixTheme.dimens - - Row( - modifier = modifier.fillMaxWidth(), - verticalAlignment = Alignment.Top - ) { - CloneflixInputField( - value = emailValue, - onValueChange = onEmailChange, - label = "Email address", - size = CloneflixInputFieldSize.LARGE, - isError = isError, - errorMessage = errorMessage, - modifier = Modifier.weight(1f) - ) - - Spacer(modifier = Modifier.width(dimens.spacingS)) - - CloneflixButton( - text = "Get Started", - onClick = onGetStartedClick, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.LARGE, - icon = painterResource(id = R.drawable.cloneflix_ic_chevron_right), - modifier = Modifier.height(56.dp) - ) - } -} - -@Composable -fun CloneflixSignInCard( - modifier: Modifier = Modifier, - initialEmail: String = "", - onSignInClick: (String, String) -> Unit = { _, _ -> }, - onUseCodeClick: () -> Unit = {}, - onForgotPasswordClick: () -> Unit = {}, - onSignUpClick: () -> Unit = {} -) { - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - - var emailOrPhone by remember { mutableStateOf(initialEmail) } - var password by remember { mutableStateOf("") } - var rememberMe by remember { mutableStateOf(true) } - var passwordError by remember { mutableStateOf(null) } - - CloneflixCard( - modifier = modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.ELEVATED - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacing3Xl) - ) { - Text( - text = "Sign In", - style = typography.boldTitle1, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - CloneflixInputField( - value = emailOrPhone, - onValueChange = { emailOrPhone = it }, - label = "Email or phone number", - size = CloneflixInputFieldSize.LARGE - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixInputField( - value = password, - onValueChange = { - password = it - if (it.isNotEmpty() && (it.length < 4 || it.length > 60)) { - passwordError = "Your password must contain between 4 and 60 characters." - } else { - passwordError = null - } - }, - label = "Password", - isPassword = true, - size = CloneflixInputFieldSize.LARGE, - isError = passwordError != null, - errorMessage = passwordError - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - CloneflixButton( - text = "Sign In", - onClick = { - if (password.length < 4 || password.length > 60) { - passwordError = "Your password must contain between 4 and 60 characters." - } else { - onSignInClick(emailOrPhone, password) - } - }, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.LARGE, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "OR", - style = typography.regularBody, - color = colors.textSecondary, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Button( - onClick = onUseCodeClick, - modifier = Modifier - .fillMaxWidth() - .height(44.dp), - shape = RoundedCornerShape(4.dp), - colors = ButtonDefaults.buttonColors( - containerColor = TransparentWhite20, - contentColor = PrimaryWhite - ) - ) { - Text( - text = "Use a Sign-In Code", - style = typography.mediumBody, - color = PrimaryWhite - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Forgot password?", - style = typography.mediumBody, - color = PrimaryWhite, - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = onForgotPasswordClick) - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Checkbox( - checked = rememberMe, - onCheckedChange = { rememberMe = it }, - colors = CheckboxDefaults.colors( - checkedColor = PrimaryWhite, - checkmarkColor = PrimaryBlack, - uncheckedColor = Grey200 - ) - ) - Text( - text = "Remember me", - style = typography.regularCaption1, - color = colors.textPrimary, - modifier = Modifier.clickable { rememberMe = !rememberMe } - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = "New to Cloneflix? ", - style = typography.regularBody, - color = colors.textSecondary - ) - Text( - text = "Sign up now.", - style = typography.mediumBody, - color = PrimaryWhite, - modifier = Modifier.clickable(onClick = onSignUpClick) - ) - } - } - } -} - -@Preview(name = "Cloneflix Buttons Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun CloneflixButtonsPreview() { - CloneflixTheme { - Column(modifier = Modifier.padding(16.dp)) { - CloneflixButton(text = "Primary Button", onClick = {}, modifier = Modifier.fillMaxWidth()) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixButton(text = "Secondary Button", onClick = {}, variant = CloneflixButtonVariant.SECONDARY, modifier = Modifier.fillMaxWidth()) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixButton(text = "Outline Button", onClick = {}, variant = CloneflixButtonVariant.OUTLINE, modifier = Modifier.fillMaxWidth()) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt deleted file mode 100644 index b67242717ad..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixContentBlockComponents.kt +++ /dev/null @@ -1,1082 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -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.widthIn -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -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.draw.rotate -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Blue100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey10 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey400 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -@Composable -fun CloneflixLandingHeader( - onSignInClick: () -> Unit = {}, - selectedLanguage: String = "English", - onLanguageSelected: (String) -> Unit = {}, - languageOptions: List = listOf("English", "Español", "Français", "Deutsch", "日本語"), - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - Row( - modifier = modifier - .fillMaxWidth() - .background(Color.Black.copy(alpha = 0.85f)) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = "CLONEFLIX", - style = typography.logoBebas, - fontSize = 28.sp, - color = PrimaryRed - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CloneflixDropdown( - options = languageOptions, - selectedOption = selectedLanguage, - onOptionSelected = onLanguageSelected, - variant = CloneflixDropdownVariant.COMPACT, - width = 120.dp - ) - - CloneflixButton( - text = "Sign In", - onClick = onSignInClick, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.SMALL - ) - } - } -} - -@Composable -fun CloneflixHomeHeader( - selectedNavIndex: Int = 0, - onNavItemSelected: (Int) -> Unit = {}, - onSearchClick: () -> Unit = {}, - onNotificationsClick: () -> Unit = {}, - onProfileClick: () -> Unit = {}, - navItems: List = listOf("Home", "TV Shows", "Movies", "New & Popular", "My List", "Browse by Languages"), - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - Row( - modifier = modifier - .fillMaxWidth() - .background(Color.Black.copy(alpha = 0.95f)) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingM), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingXl) - ) { - Text( - text = "CLONEFLIX", - style = typography.logoBebas, - fontSize = 24.sp, - color = PrimaryRed - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - navItems.forEachIndexed { index, item -> - val isSelected = selectedNavIndex == index - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - Text( - text = item, - style = if (isSelected) typography.mediumBody else typography.regularSmallBody, - color = when { - isSelected -> PrimaryWhite - isFocused -> PrimaryWhite - else -> Grey10 - }, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - modifier = Modifier - .clip(RoundedCornerShape(4.dp)) - .focusable(interactionSource = interactionSource) - .clickable { onNavItemSelected(index) } - .padding(horizontal = 4.dp, vertical = 2.dp) - ) - } - } - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - IconButton( - onClick = onSearchClick, - modifier = Modifier.size(36.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_search), - contentDescription = "Search", - tint = PrimaryWhite, - modifier = Modifier.size(20.dp) - ) - } - - IconButton( - onClick = onNotificationsClick, - modifier = Modifier.size(36.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_notification), - contentDescription = "Notifications", - tint = PrimaryWhite, - modifier = Modifier.size(20.dp) - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp), - modifier = Modifier - .clip(RoundedCornerShape(4.dp)) - .clickable { onProfileClick() } - .padding(4.dp) - ) { - Box( - modifier = Modifier - .size(32.dp) - .clip(RoundedCornerShape(4.dp)) - .background(PrimaryRed), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_person), - contentDescription = "Profile Avatar", - tint = PrimaryWhite, - modifier = Modifier.size(20.dp) - ) - } - - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_arrow_down), - contentDescription = "Profile Menu", - tint = PrimaryWhite, - modifier = Modifier.size(14.dp) - ) - } - } - } -} - -@Composable -fun CloneflixFaqItem( - question: String, - answer: String, - isOpen: Boolean, - onToggle: () -> Unit, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val rotationAngle by animateFloatAsState( - targetValue = if (isOpen) 45f else 0f, - animationSpec = tween(durationMillis = 250), - label = "FaqIconRotation" - ) - - val itemBackgroundColor = when { - isFocused -> Color(0xFF414141) - isOpen -> Color(0xFF2D2D2D) - else -> Color(0xFF2D2D2D) - } - - val border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else null - - Column( - modifier = modifier - .fillMaxWidth() - .animateContentSize(animationSpec = tween(durationMillis = 250)) - ) { - Surface( - onClick = onToggle, - color = itemBackgroundColor, - shape = RoundedCornerShape(2.dp), - border = border, - interactionSource = interactionSource, - modifier = Modifier - .fillMaxWidth() - .focusable(interactionSource = interactionSource) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 22.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = question, - style = typography.mediumTitle3, - fontSize = 20.sp, - color = PrimaryWhite, - modifier = Modifier.weight(1f) - ) - - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_plus), - contentDescription = if (isOpen) "Collapse answer" else "Expand answer", - tint = PrimaryWhite, - modifier = Modifier - .size(24.dp) - .rotate(rotationAngle) - ) - } - } - - AnimatedVisibility(visible = isOpen) { - Column { - Spacer(modifier = Modifier.height(2.dp)) - Surface( - color = Color(0xFF2D2D2D), - shape = RoundedCornerShape(2.dp), - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = answer, - style = typography.regularBody, - fontSize = 18.sp, - lineHeight = 26.sp, - color = PrimaryWhite, - modifier = Modifier.padding(24.dp) - ) - } - } - } - } -} - -@Composable -fun CloneflixFaqSection( - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - - val faqList = remember { - listOf( - "What is Netflix?" to "Netflix is a streaming service that offers a wide variety of award-winning TV shows, movies, anime, documentaries, and more on thousands of internet-connected devices.\n\nYou can watch as much as you want, whenever you want – all for one low monthly price. There's always something new to discover and new TV shows and movies are added every week!", - "How much does Netflix cost?" to "Watch Netflix on your smartphone, tablet, Smart TV, laptop, or streaming device, all for one fixed monthly fee. Plans range from standard with ads to premium. No extra costs, no contracts.", - "Where can I watch?" to "Watch anywhere, anytime. Sign in with your Netflix account to watch instantly on the web at netflix.com from your personal computer or on any internet-connected device that offers the Netflix app, including smart TVs, smartphones, tablets, streaming media players and game consoles.\n\nYou can also download your favorite shows with the iOS or Android app. Use downloads to watch while you're on the go and without an internet connection.", - "How do I cancel?" to "Netflix is flexible. There are no pesky contracts and no commitments. You can easily cancel your account online in two clicks. There are no cancellation fees – start or stop your account anytime.", - "What can I watch on Netflix?" to "Netflix has an extensive library of feature films, documentaries, TV shows, anime, award-winning Netflix originals, and more. Watch as much as you want, anytime you want.", - "Is Netflix good for kids?" to "The Netflix Kids experience is included in your membership to give parents control while kids enjoy family-friendly TV shows and movies in their own space.\n\nKids profiles come with PIN-protected parental controls that let you restrict the maturity rating of content kids can watch and block specific titles you don’t want kids to see." - ) - } - - var openIndex by remember { mutableIntStateOf(0) } - - Column( - modifier = modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "Frequently Asked Questions", - style = typography.headerDisplay, - fontSize = 32.sp, - color = PrimaryWhite, - textAlign = TextAlign.Center, - modifier = Modifier.padding(bottom = 24.dp) - ) - - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - faqList.forEachIndexed { index, (q, a) -> - CloneflixFaqItem( - question = q, - answer = a, - isOpen = openIndex == index, - onToggle = { - openIndex = if (openIndex == index) -1 else index - } - ) - } - } - } -} - -@Composable -fun CloneflixTvPreviewCard( - modifier: Modifier = Modifier -) { - Surface( - color = Color(0xFF141414), - shape = RoundedCornerShape(12.dp), - border = BorderStroke(1.dp, TransparentWhite20), - modifier = modifier - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(16.dp) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(16f / 9f) - .clip(RoundedCornerShape(8.dp)) - .background(Color(0xFF0F0F0F)) - .border(2.dp, Grey700, RoundedCornerShape(8.dp)), - contentAlignment = Alignment.Center - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf(Color(0xFF2A1010), Color(0xFF141414), Color(0xFF0A0A0A)) - ) - ) - ) { - Column( - modifier = Modifier - .align(Alignment.Center) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "CLONEFLIX", - style = CloneflixTheme.typography.logoBebas, - fontSize = 36.sp, - color = PrimaryRed - ) - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = "Ultra HD 4K Streaming", - style = CloneflixTheme.typography.regularCaption1, - color = Grey10 - ) - } - } - } - - Box( - modifier = Modifier - .width(60.dp) - .height(6.dp) - .background(Grey600, RoundedCornerShape(bottomStart = 3.dp, bottomEnd = 3.dp)) - ) - Box( - modifier = Modifier - .width(100.dp) - .height(3.dp) - .background(Grey700, RoundedCornerShape(2.dp)) - ) - } - } -} - -@Composable -fun CloneflixMacPreviewCard( - modifier: Modifier = Modifier -) { - Surface( - color = Color(0xFF141414), - shape = RoundedCornerShape(12.dp), - border = BorderStroke(1.dp, TransparentWhite20), - modifier = modifier - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(16.dp) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(16f / 10f) - .clip(RoundedCornerShape(6.dp)) - .background(Color(0xFF1F1F1F)) - .border(1.5.dp, Grey700, RoundedCornerShape(6.dp)) - ) { - Column(modifier = Modifier.fillMaxSize()) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(16.dp) - .background(Color(0xFF2B2B2B)) - .padding(horizontal = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - Box(modifier = Modifier.size(6.dp).background(Color(0xFFFF5F56), CircleShape)) - Box(modifier = Modifier.size(6.dp).background(Color(0xFFFFBD2E), CircleShape)) - Box(modifier = Modifier.size(6.dp).background(Color(0xFF27C93F), CircleShape)) - } - - Box( - modifier = Modifier - .weight(1f) - .fillMaxWidth() - .background( - Brush.linearGradient( - colors = listOf(Color(0xFF1E1B4B), Color(0xFF111827), Color(0xFF0F172A)) - ) - ), - contentAlignment = Alignment.Center - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = "Stream on Web & Desktop", - style = CloneflixTheme.typography.boldTitle2, - fontSize = 18.sp, - color = PrimaryWhite - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "Watch anywhere seamlessly", - style = CloneflixTheme.typography.regularCaption1, - color = Grey100 - ) - } - } - } - } - - Box( - modifier = Modifier - .fillMaxWidth(0.95f) - .height(6.dp) - .background(Grey600, RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp)) - ) - } - } -} - -@Composable -fun CloneflixDownloadProgressCard( - title: String = "Stranger Things", - statusText: String = "Downloading...", - modifier: Modifier = Modifier -) { - Surface( - color = PrimaryBlack, - shape = RoundedCornerShape(12.dp), - border = BorderStroke(2.dp, Color(0xFF404040)), - shadowElevation = 8.dp, - modifier = modifier - ) { - Row( - modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(14.dp) - ) { - Box( - modifier = Modifier - .width(36.dp) - .height(50.dp) - .clip(RoundedCornerShape(4.dp)) - .background( - Brush.verticalGradient( - listOf(Color(0xFFE50914), Color(0xFF1F1F1F)) - ) - ), - contentAlignment = Alignment.Center - ) { - Text( - text = "ST", - fontWeight = FontWeight.Bold, - fontSize = 12.sp, - color = PrimaryWhite - ) - } - - Column(modifier = Modifier.weight(1f)) { - Text( - text = title, - style = CloneflixTheme.typography.mediumBody, - fontSize = 14.sp, - color = PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = statusText, - style = CloneflixTheme.typography.regularCaption1, - fontSize = 12.sp, - color = Blue100 - ) - } - - Box( - modifier = Modifier.size(24.dp), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator( - modifier = Modifier.size(20.dp), - color = Blue100, - strokeWidth = 2.5.dp - ) - } - } - } -} - -@Composable -fun CloneflixMobilePreviewCard( - modifier: Modifier = Modifier -) { - Surface( - color = Color(0xFF141414), - shape = RoundedCornerShape(12.dp), - border = BorderStroke(1.dp, TransparentWhite20), - modifier = modifier - ) { - Box( - modifier = Modifier - .padding(16.dp) - .widthIn(max = 280.dp) - .aspectRatio(9f / 16f) - .clip(RoundedCornerShape(24.dp)) - .background(Color(0xFF1E1E1E)) - .border(2.dp, Grey700, RoundedCornerShape(24.dp)) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf(Color(0xFF1A1A2E), Color(0xFF16213E), Color(0xFF0F3460)) - ) - ) - .padding(16.dp) - ) { - Box( - modifier = Modifier - .align(Alignment.TopCenter) - .width(70.dp) - .height(14.dp) - .background(Color.Black, RoundedCornerShape(bottomStart = 8.dp, bottomEnd = 8.dp)) - ) - - CloneflixDownloadProgressCard( - modifier = Modifier - .align(Alignment.BottomCenter) - .fillMaxWidth() - .padding(bottom = 12.dp) - ) - } - } - } -} - -@Composable -fun CloneflixKidsPreviewCard( - modifier: Modifier = Modifier -) { - Surface( - color = Color(0xFF141414), - shape = RoundedCornerShape(12.dp), - border = BorderStroke(1.dp, TransparentWhite20), - modifier = modifier - ) { - Box( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth() - .aspectRatio(16f / 9f) - .clip(RoundedCornerShape(8.dp)) - .background( - Brush.linearGradient( - listOf(Color(0xFF3B82F6), Color(0xFF8B5CF6), Color(0xFFEC4899)) - ) - ), - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(16.dp) - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(44.dp) - .background(Color(0xFFFFB703), CircleShape), - contentAlignment = Alignment.Center - ) { - Text(text = "🐯", fontSize = 22.sp) - } - Box( - modifier = Modifier - .size(52.dp) - .background(Color(0xFFFB8500), CircleShape) - .border(2.dp, PrimaryWhite, CircleShape), - contentAlignment = Alignment.Center - ) { - Text(text = "🚀", fontSize = 26.sp) - } - Box( - modifier = Modifier - .size(44.dp) - .background(Color(0xFF219EBC), CircleShape), - contentAlignment = Alignment.Center - ) { - Text(text = "🦄", fontSize = 22.sp) - } - } - - Spacer(modifier = Modifier.height(12.dp)) - - Text( - text = "Kids Profile Experience", - style = CloneflixTheme.typography.boldTitle2, - fontSize = 18.sp, - color = PrimaryWhite - ) - - Text( - text = "Family-friendly entertainment & parental controls", - style = CloneflixTheme.typography.regularCaption1, - color = PrimaryWhite.copy(alpha = 0.9f) - ) - } - } - } -} - -enum class CloneflixBlockLayout { - HEADLINE_IMAGE, - IMAGE_HEADLINE -} - -@Composable -fun CloneflixFeatureBlock( - title: String, - description: String, - layout: CloneflixBlockLayout = CloneflixBlockLayout.HEADLINE_IMAGE, - previewContent: @Composable () -> Unit, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - Surface( - color = PrimaryBlack, - modifier = modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = dimens.spacing2Xl) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacingL), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - val textContent: @Composable (Modifier) -> Unit = { mod -> - Column( - modifier = mod.padding(dimens.spacingL), - verticalArrangement = Arrangement.Center - ) { - Text( - text = title, - style = typography.headerDisplay, - fontSize = 32.sp, - lineHeight = 38.sp, - color = PrimaryWhite - ) - Spacer(modifier = Modifier.height(dimens.spacingM)) - Text( - text = description, - style = typography.regularBody, - fontSize = 18.sp, - lineHeight = 24.sp, - color = Grey10 - ) - } - } - - val previewSlot: @Composable (Modifier) -> Unit = { mod -> - Box( - modifier = mod.padding(dimens.spacingL), - contentAlignment = Alignment.Center - ) { - previewContent() - } - } - - when (layout) { - CloneflixBlockLayout.HEADLINE_IMAGE -> { - textContent(Modifier.weight(1f)) - previewSlot(Modifier.weight(1f)) - } - CloneflixBlockLayout.IMAGE_HEADLINE -> { - previewSlot(Modifier.weight(1f)) - textContent(Modifier.weight(1f)) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - CloneflixDivider() - } - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun CloneflixLandingFooter( - onPhoneClick: () -> Unit = {}, - onLinkClick: (String) -> Unit = {}, - selectedLanguage: String = "English", - onLanguageSelected: (String) -> Unit = {}, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val links = listOf( - "FAQ", "Help Center", "Account", "Media Center", - "Investor Relations", "Jobs", "Ways to Watch", "Terms of Use", - "Privacy", "Cookie Preferences", "Corporate Information", "Contact Us", - "Speed Test", "Legal Notices", "Only on Netflix", "Ad Choices" - ) - - Column( - modifier = modifier - .fillMaxWidth() - .background(PrimaryBlack) - .padding(horizontal = dimens.spacing2Xl, vertical = 48.dp) - ) { - Text( - text = "Questions? Call 1-844-505-2993", - style = typography.regularBody, - color = Grey200, - modifier = Modifier - .clickable { onPhoneClick() } - .padding(bottom = dimens.spacingXl) - ) - - FlowRow( - modifier = Modifier.fillMaxWidth(), - maxItemsInEachRow = 4, - horizontalArrangement = Arrangement.SpaceBetween, - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - links.forEach { link -> - Text( - text = link, - style = typography.regularCaption1, - fontSize = 13.sp, - color = Grey200, - modifier = Modifier - .widthIn(min = 140.dp, max = 220.dp) - .clickable { onLinkClick(link) } - .padding(vertical = 4.dp) - ) - } - } - - Spacer(modifier = Modifier.height(28.dp)) - - CloneflixDropdown( - options = listOf("English", "Español", "Français", "Deutsch"), - selectedOption = selectedLanguage, - onOptionSelected = onLanguageSelected, - variant = CloneflixDropdownVariant.COMPACT, - width = 130.dp - ) - - Spacer(modifier = Modifier.height(24.dp)) - - Text( - text = "Netflix United States", - style = typography.regularCaption2, - color = Grey400 - ) - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun CloneflixAuthFooter( - onPhoneClick: () -> Unit = {}, - onLinkClick: (String) -> Unit = {}, - selectedLanguage: String = "English", - onLanguageSelected: (String) -> Unit = {}, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val links = listOf( - "FAQ", "Help Center", "Terms of Use", - "Privacy", "Cookie Preferences", "Corporate Information" - ) - - Column( - modifier = modifier - .fillMaxWidth() - .background(PrimaryBlack.copy(alpha = 0.9f)) - .padding(horizontal = dimens.spacing2Xl, vertical = 32.dp) - ) { - Text( - text = "Questions? Call 1-844-505-2993", - style = typography.regularBody, - color = Grey200, - modifier = Modifier - .clickable { onPhoneClick() } - .padding(bottom = dimens.spacingL) - ) - - FlowRow( - modifier = Modifier.fillMaxWidth(), - maxItemsInEachRow = 4, - horizontalArrangement = Arrangement.SpaceBetween, - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - links.forEach { link -> - Text( - text = link, - style = typography.regularCaption1, - fontSize = 13.sp, - color = Grey200, - modifier = Modifier - .widthIn(min = 140.dp, max = 220.dp) - .clickable { onLinkClick(link) } - .padding(vertical = 4.dp) - ) - } - } - - Spacer(modifier = Modifier.height(20.dp)) - - CloneflixDropdown( - options = listOf("English", "Español", "Français"), - selectedOption = selectedLanguage, - onOptionSelected = onLanguageSelected, - variant = CloneflixDropdownVariant.COMPACT, - width = 120.dp - ) - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun CloneflixHomeFooter( - onSocialClick: (String) -> Unit = {}, - onLinkClick: (String) -> Unit = {}, - onServiceCodeClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val links = listOf( - "Audio Description", "Help Center", "Gift Cards", "Media Center", - "Investor Relations", "Jobs", "Netflix Shop", "Terms of Use", - "Privacy", "Legal Notices", "Cookie Preferences", "Corporate Information", - "Contact Us", "Do Not Sell or Share My Personal Information", "Ad Choices" - ) - - Column( - modifier = modifier - .fillMaxWidth() - .background(PrimaryBlack) - .padding(horizontal = dimens.spacing2Xl, vertical = 40.dp) - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(20.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(bottom = 20.dp) - ) { - val socialIcons = listOf( - "Facebook" to R.drawable.cloneflix_ic_social_fb, - "Instagram" to R.drawable.cloneflix_ic_social_ig, - "Twitter" to R.drawable.cloneflix_ic_social_x, - "YouTube" to R.drawable.cloneflix_ic_social_yt - ) - - socialIcons.forEach { (platform, iconRes) -> - IconButton( - onClick = { onSocialClick(platform) }, - modifier = Modifier.size(32.dp) - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = platform, - tint = PrimaryWhite, - modifier = Modifier.size(24.dp) - ) - } - } - } - - FlowRow( - modifier = Modifier.fillMaxWidth(), - maxItemsInEachRow = 4, - horizontalArrangement = Arrangement.SpaceBetween, - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - links.forEach { link -> - Text( - text = link, - style = typography.regularCaption1, - fontSize = 13.sp, - color = Grey200, - modifier = Modifier - .widthIn(min = 140.dp, max = 220.dp) - .clickable { onLinkClick(link) } - .padding(vertical = 4.dp) - ) - } - } - - Spacer(modifier = Modifier.height(24.dp)) - - Surface( - onClick = onServiceCodeClick, - shape = RoundedCornerShape(2.dp), - border = BorderStroke(1.dp, Grey200), - color = Color.Transparent, - modifier = Modifier.padding(bottom = 20.dp) - ) { - Text( - text = "Service Code", - style = typography.regularCaption1, - fontSize = 13.sp, - color = Grey200, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp) - ) - } - - Text( - text = "© 1997 - 2024 Netflix, Inc.", - style = typography.regularCaption2, - fontSize = 11.sp, - color = Grey400 - ) - } -} - -@Preview(name = "Landing Header Preview", showBackground = true, backgroundColor = 0xFF000000) -@Composable -private fun LandingHeaderPreview() { - CloneflixTheme { - CloneflixLandingHeader() - } -} - -@Preview(name = "Home Header Preview", showBackground = true, backgroundColor = 0xFF000000) -@Composable -private fun HomeHeaderPreview() { - CloneflixTheme { - CloneflixHomeHeader() - } -} - -@Preview(name = "FAQ Section Preview", showBackground = true, backgroundColor = 0xFF000000) -@Composable -private fun FaqSectionPreview() { - CloneflixTheme { - CloneflixFaqSection(modifier = Modifier.padding(16.dp)) - } -} - -@Preview(name = "Feature Block Preview", showBackground = true, backgroundColor = 0xFF000000) -@Composable -private fun FeatureBlockPreview() { - CloneflixTheme { - CloneflixFeatureBlock( - title = "Enjoy on your TV", - description = "Watch on Smart TVs, Playstation, Xbox, Chromecast, Apple TV, Blu-ray players, and more.", - layout = CloneflixBlockLayout.HEADLINE_IMAGE, - previewContent = { - CloneflixTvPreviewCard(modifier = Modifier.fillMaxWidth(0.9f)) - } - ) - } -} - -@Preview(name = "Home Footer Preview", showBackground = true, backgroundColor = 0xFF000000) -@Composable -private fun HomeFooterPreview() { - CloneflixTheme { - CloneflixHomeFooter() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt deleted file mode 100644 index 468ca196499..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDefaults.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.TweenSpec -import androidx.compose.animation.core.tween -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp - -object CloneflixTokens { - const val FOCUS_SCALE_FACTOR = 1.05f - const val FOCUS_SCALE_FACTOR_LARGE = 1.08f - const val FOCUS_SCALE_FACTOR_SUBTLE = 1.02f - - const val ALPHA_MUTED = 0.3f - const val ALPHA_SUBTLE = 0.4f - const val ALPHA_MEDIUM = 0.6f - const val ALPHA_HIGH = 0.7f - const val ALPHA_NEAR_OPAQUE = 0.9f - - val RadiusPill: Dp = 999.dp - val RadiusCard: Dp = 4.dp - val RadiusCardMedium: Dp = 8.dp - val RadiusCardLarge: Dp = 12.dp - - val ShapeCardSmall = RoundedCornerShape(4.dp) - val ShapeCardMedium = RoundedCornerShape(8.dp) - val ShapeCardLarge = RoundedCornerShape(12.dp) - val ShapePill = RoundedCornerShape(999.dp) - - val FastFocusAnimationSpec: TweenSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing) -} - -object CloneflixMovieCardDefaults { - val WidthMedium: Dp = 218.dp - val WidthSmall: Dp = 128.dp - val WidthTop10Medium: Dp = 215.dp - val WidthTop10Small: Dp = 160.dp - val WidthEpisodeMedium: Dp = 160.dp - val WidthEpisodeSmall: Dp = 128.dp - val WidthMoreLikeThis: Dp = 236.dp - val WidthTrailer: Dp = 236.dp - val WidthPlayerPreview: Dp = 245.dp - val WidthContinueWatchingMedium: Dp = 218.dp - val WidthContinueWatchingSmall: Dp = 160.dp - val WidthPoster: Dp = 150.dp - - val HeightMedium: Dp = 123.dp - val HeightSmall: Dp = 72.dp - val HeightTop10Medium: Dp = 154.dp - val HeightTop10Small: Dp = 115.dp - val HeightEpisodeMedium: Dp = 90.dp - val HeightEpisodeSmall: Dp = 72.dp - val HeightMoreLikeThis: Dp = 132.dp - val HeightTrailer: Dp = 204.dp - val HeightTrailerThumbnail: Dp = 132.dp - val HeightPlayerPreview: Dp = 152.dp - val HeightContinueWatchingMedium: Dp = 133.dp - val HeightContinueWatchingSmall: Dp = 98.dp - val HeightPoster: Dp = 225.dp - - val RankBoxWidth: Dp = 90.dp - val RankFontSize: TextUnit = 110.sp - val RankLineHeight: TextUnit = 110.sp - val RankShadowOffsetX: Dp = 10.dp - val RankForegroundOffsetX: Dp = 8.dp - - val PlayButtonOverlaySize: Dp = 36.dp - val PlayIconOverlaySize: Dp = 16.dp - val EpisodeItemPlayButtonSize: Dp = 32.dp - val EpisodeItemPlayIconSize: Dp = 14.dp - - val EpisodeThumbnailWidth: Dp = 128.dp - val EpisodeThumbnailHeight: Dp = 72.dp - - val ExpandedPreviewHeaderHeight: Dp = 260.dp - val ActionButtonSize: Dp = 40.dp - - fun cardWidth(type: CloneflixMovieCardType, size: CloneflixMovieCardSize): Dp = when (type) { - CloneflixMovieCardType.POSTER -> if (size == CloneflixMovieCardSize.MEDIUM) WidthPoster else 110.dp - CloneflixMovieCardType.TOP10 -> if (size == CloneflixMovieCardSize.MEDIUM) WidthTop10Medium else WidthTop10Small - CloneflixMovieCardType.EPISODE -> if (size == CloneflixMovieCardSize.SMALL) WidthEpisodeSmall else WidthEpisodeMedium - CloneflixMovieCardType.MORE_LIKE_THIS, - CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY -> WidthMoreLikeThis - CloneflixMovieCardType.TRAILER -> WidthTrailer - CloneflixMovieCardType.PLAYER_PREVIEW -> WidthPlayerPreview - CloneflixMovieCardType.CONTINUE_WATCHING -> if (size == CloneflixMovieCardSize.MEDIUM) WidthContinueWatchingMedium else WidthContinueWatchingSmall - CloneflixMovieCardType.DEFAULT -> if (size == CloneflixMovieCardSize.MEDIUM) WidthMedium else WidthSmall - } - - fun cardHeight(type: CloneflixMovieCardType, size: CloneflixMovieCardSize): Dp = when (type) { - CloneflixMovieCardType.POSTER -> if (size == CloneflixMovieCardSize.MEDIUM) HeightPoster else 165.dp - CloneflixMovieCardType.TOP10 -> if (size == CloneflixMovieCardSize.MEDIUM) HeightTop10Medium else HeightTop10Small - CloneflixMovieCardType.EPISODE -> if (size == CloneflixMovieCardSize.SMALL) HeightEpisodeSmall else HeightEpisodeMedium - CloneflixMovieCardType.MORE_LIKE_THIS, - CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY -> HeightMoreLikeThis - CloneflixMovieCardType.TRAILER -> HeightTrailer - CloneflixMovieCardType.PLAYER_PREVIEW -> HeightPlayerPreview - CloneflixMovieCardType.CONTINUE_WATCHING -> if (size == CloneflixMovieCardSize.MEDIUM) HeightContinueWatchingMedium else HeightContinueWatchingSmall - CloneflixMovieCardType.DEFAULT -> if (size == CloneflixMovieCardSize.MEDIUM) HeightMedium else HeightSmall - } -} - -object CloneflixPlayerDefaults { - val ControlButtonSize: Dp = 48.dp - val ControlButtonSizeLarge: Dp = 64.dp - val ControlIconSize: Dp = 24.dp - val ControlIconSizeLarge: Dp = 32.dp - val BottomBarHeight: Dp = 80.dp - val TopBarHeight: Dp = 64.dp - val ProgressThumbSize: Dp = 12.dp - val ProgressTrackHeight: Dp = 4.dp - val VolumeSliderWidth: Dp = 140.dp - val SpeedItemWidth: Dp = 72.dp -} - -object CloneflixDrawerDefaults { - val DrawerWidth: Dp = 380.dp - val EpisodeItemCornerRadius: Dp = 6.dp - val EpisodeNumberWidth: Dp = 32.dp -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt deleted file mode 100644 index 380f631d4d5..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixDropdown.kt +++ /dev/null @@ -1,291 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -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.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.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.MenuDefaults -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.draw.rotate -import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite - -enum class CloneflixDropdownVariant { - DEFAULT, - OUTLINE, - COMPACT -} - -@Composable -fun CloneflixDropdown( - options: List, - selectedOption: String, - onOptionSelected: (String) -> Unit, - modifier: Modifier = Modifier, - label: String? = null, - variant: CloneflixDropdownVariant = CloneflixDropdownVariant.DEFAULT, - width: Dp? = null, - enabled: Boolean = true -) { - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - val shapes = CloneflixTheme.shapes - - var expanded by remember { mutableStateOf(false) } - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.05f else 1f, - label = "DropdownScale" - ) - - val rotationAngle by animateFloatAsState( - targetValue = if (expanded) 180f else 0f, - label = "DropdownChevronRotation" - ) - - val borderStroke = when { - isFocused || expanded -> BorderStroke(2.dp, PrimaryWhite) - variant == CloneflixDropdownVariant.OUTLINE -> BorderStroke(1.dp, PrimaryWhite) - else -> BorderStroke(1.dp, Color(0xFF666666)) - } - - val backgroundColor = when { - isFocused || expanded -> Color(0xFF333333) - else -> Color(0xFF1E1E1E) - } - - val height = when (variant) { - CloneflixDropdownVariant.COMPACT -> 32.dp - else -> 40.dp - } - - val widthModifier = if (width != null) Modifier.width(width) else Modifier.fillMaxWidth() - - Column(modifier = modifier) { - if (!label.isNullOrBlank()) { - Text( - text = label, - style = typography.regularCaption1, - color = colors.textSecondary, - modifier = Modifier.padding(bottom = dimens.spacingXs) - ) - } - - Box(modifier = widthModifier) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(height) - .scale(scale) - .clip(shapes.extraSmall) - .background(backgroundColor) - .border(borderStroke, shapes.extraSmall) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.DropdownList, - onClick = { expanded = !expanded } - ) - .padding(horizontal = dimens.spacingM, vertical = dimens.spacingS) - .semantics { - role = Role.DropdownList - contentDescription = "$label: $selectedOption, ${if (expanded) "Expanded" else "Collapsed"}" - } - ) { - Text( - text = selectedOption, - style = typography.regularBody, - fontSize = 14.sp, - fontWeight = if (isFocused || expanded) FontWeight.Bold else FontWeight.Normal, - color = PrimaryWhite, - modifier = Modifier.weight(1f), - maxLines = 1 - ) - - Spacer(modifier = Modifier.width(dimens.spacingS)) - - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_dropdown), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(16.dp) - .rotate(rotationAngle) - ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - modifier = Modifier - .wrapContentHeight() - .heightIn(max = 300.dp) - .background(Color(0xF0181818)) - .border(BorderStroke(1.dp, PrimaryWhite), shapes.extraSmall) - ) { - options.forEach { option -> - val isSelected = option == selectedOption - val itemInteractionSource = remember { MutableInteractionSource() } - val isItemFocused by itemInteractionSource.collectIsFocusedAsState() - - DropdownMenuItem( - text = { - Text( - text = option, - style = typography.regularBody, - fontSize = 14.sp, - fontWeight = if (isSelected || isItemFocused) FontWeight.Bold else FontWeight.Normal, - color = if (isSelected) colors.primary else if (isItemFocused) PrimaryWhite else colors.textPrimary - ) - }, - trailingIcon = if (isSelected) { - { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_check), - contentDescription = "Selected", - tint = colors.primary, - modifier = Modifier.size(16.dp) - ) - } - } else null, - onClick = { - onOptionSelected(option) - expanded = false - }, - colors = MenuDefaults.itemColors( - textColor = colors.textPrimary, - trailingIconColor = colors.primary - ), - modifier = Modifier - .fillMaxWidth() - .height(40.dp) - .background(if (isItemFocused) Color(0xFF333333) else Color.Transparent), - interactionSource = itemInteractionSource - ) - } - } - } - } -} - -@Composable -fun CloneflixDropdownStaticShowcase( - title: String, - selectedOption: String, - modifier: Modifier = Modifier, - options: List = emptyList(), - variant: CloneflixDropdownVariant = CloneflixDropdownVariant.DEFAULT, - isHoverState: Boolean = false, - isExpandedState: Boolean = false, - width: Dp? = null -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - val shapes = CloneflixTheme.shapes - - val borderStroke = when { - isHoverState || isExpandedState -> BorderStroke(2.dp, PrimaryWhite) - variant == CloneflixDropdownVariant.OUTLINE -> BorderStroke(1.dp, PrimaryWhite) - else -> BorderStroke(1.dp, Color(0xFF666666)) - } - - val backgroundColor = when { - isHoverState || isExpandedState -> Color(0xFF333333) - else -> Color(0xFF1E1E1E) - } - - val height = when (variant) { - CloneflixDropdownVariant.COMPACT -> 32.dp - else -> 40.dp - } - - val widthModifier = if (width != null) Modifier.width(width) else Modifier.fillMaxWidth() - - Column(modifier = modifier) { - Text( - text = title, - style = typography.boldTitle2, - fontSize = 16.sp, - color = PrimaryWhite, - modifier = Modifier.padding(bottom = dimens.spacingXs) - ) - - Box(modifier = widthModifier) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(height) - .clip(shapes.extraSmall) - .background(backgroundColor) - .border(borderStroke, shapes.extraSmall) - .padding(horizontal = dimens.spacingM, vertical = dimens.spacingS) - ) { - Text( - text = selectedOption, - style = typography.regularBody, - fontSize = 14.sp, - fontWeight = if (isHoverState || isExpandedState) FontWeight.Bold else FontWeight.Normal, - color = PrimaryWhite, - modifier = Modifier.weight(1f), - maxLines = 1 - ) - - Spacer(modifier = Modifier.width(dimens.spacingS)) - - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_dropdown), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(16.dp) - .rotate(if (isExpandedState) 180f else 0f) - ) - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt deleted file mode 100644 index e2623db1d34..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixEpisodesDrawer.kt +++ /dev/null @@ -1,330 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -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.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.draw.scale -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.res.stringResource -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -data class CloneflixEpisodeItemData( - val episodeNumber: Int, - val title: String, - val durationText: String, - val synopsis: String, - val progress: Float = 0f, - val isCurrent: Boolean = false -) - -@Composable -fun CloneflixEpisodeCard( - item: CloneflixEpisodeItemData, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = CloneflixTheme.dimens - val colors = CloneflixTheme.colors - - val scale by animateFloatAsState( - targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, - label = "episodeCardScale" - ) - - val borderStroke = when { - isFocused -> BorderStroke(dimens.borderFocus, PrimaryWhite) - item.isCurrent -> BorderStroke(dimens.borderDefault, PrimaryRed) - else -> BorderStroke(dimens.borderSubtle, colors.border) - } - - Surface( - modifier = modifier - .width(CloneflixMovieCardDefaults.WidthTop10Medium) - .scale(scale) - .border(borderStroke, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .semantics { - role = Role.Button - contentDescription = item.title - }, - shape = RoundedCornerShape(CloneflixTokens.RadiusCardMedium), - color = if (isFocused) Color(0xFF282828) else Color(0xEB1C1C1C) - ) { - Column(modifier = Modifier.padding(dimens.spacingS)) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(CloneflixMovieCardDefaults.HeightTop10Small) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background( - Brush.verticalGradient( - colors = listOf(Color(0xFF333333), Color(0xFF141414)) - ) - ), - contentAlignment = Alignment.Center - ) { - if (item.isCurrent) { - Box( - modifier = Modifier - .size(CloneflixMovieCardDefaults.PlayButtonOverlaySize) - .clip(CircleShape) - .background(PrimaryRed), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = stringResource(R.string.cloneflix_cd_play), - tint = PrimaryWhite, - modifier = Modifier.size(CloneflixMovieCardDefaults.PlayIconOverlaySize) - ) - } - } else { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = stringResource(R.string.cloneflix_cd_play), - tint = Grey200.copy(alpha = CloneflixTokens.ALPHA_MEDIUM), - modifier = Modifier.size(dimens.iconM) - ) - } - - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(dimens.spacingXs) - .background(TransparentBlack90, RoundedCornerShape(CloneflixTokens.RadiusCard)) - .padding(horizontal = dimens.spacingXs, vertical = dimens.spacingXxs) - ) { - Text( - text = item.durationText, - style = CloneflixTheme.typography.regularCaption2, - color = PrimaryWhite - ) - } - - if (item.progress > 0f) { - Box( - modifier = Modifier - .align(Alignment.BottomStart) - .fillMaxWidth() - .height(dimens.progressHeight) - .background(Color(0x66FFFFFF)) - ) { - Box( - modifier = Modifier - .fillMaxWidth(item.progress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(PrimaryRed) - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "${item.episodeNumber}. ${item.title}", - style = CloneflixTheme.typography.mediumBody, - color = if (item.isCurrent) PrimaryRed else PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f) - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - Text( - text = item.synopsis, - style = CloneflixTheme.typography.regularCaption1, - color = Grey200, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } -} - -@Composable -fun CloneflixEpisodesDrawer( - seriesTitle: String, - seasonName: String, - episodes: List, - onEpisodeSelected: (CloneflixEpisodeItemData) -> Unit, - onCloseClick: () -> Unit, - modifier: Modifier = Modifier -) { - val listState = rememberLazyListState() - val dimens = CloneflixTheme.dimens - - Surface( - modifier = modifier - .fillMaxWidth() - .border(BorderStroke(dimens.borderDefault, dimens.dividerThickness.let { CloneflixTheme.colors.border }), RoundedCornerShape(topStart = dimens.radiusL, topEnd = dimens.radiusL)), - shape = RoundedCornerShape(topStart = dimens.radiusL, topEnd = dimens.radiusL), - color = TransparentBlack90 - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = dimens.spacingL) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text( - text = seriesTitle, - style = CloneflixTheme.typography.mediumCaption1, - color = PrimaryRed - ) - Text( - text = seasonName, - style = CloneflixTheme.typography.mediumTitle4, - color = PrimaryWhite - ) - } - - Box( - modifier = Modifier - .size(dimens.iconL) - .clip(CircleShape) - .background(Color(0x33FFFFFF)) - .clickable(onClick = onCloseClick), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_close), - contentDescription = stringResource(R.string.cloneflix_cd_close), - tint = PrimaryWhite, - modifier = Modifier.size(dimens.iconS) - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - LazyRow( - state = listState, - contentPadding = PaddingValues(horizontal = dimens.spacing2Xl), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - modifier = Modifier.fillMaxWidth() - ) { - itemsIndexed(episodes) { _, episode -> - CloneflixEpisodeCard( - item = episode, - onClick = { onEpisodeSelected(episode) } - ) - } - } - } - } -} - -@Preview(name = "Episodes Drawer Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun EpisodesDrawerPreview() { - CloneflixTheme { - val sampleEpisodes = listOf( - CloneflixEpisodeItemData( - episodeNumber = 1, - title = "Sanctuary", - durationText = "54m", - synopsis = "A troubled teenager with sumo ancestry enters the sacred world of sumo wrestling.", - progress = 1.0f, - isCurrent = false - ), - CloneflixEpisodeItemData( - episodeNumber = 2, - title = "The Rebel", - durationText = "48m", - synopsis = "Kiyoshi refuses to follow tradition and sparks outrage among seasoned stable masters.", - progress = 0.45f, - isCurrent = true - ), - CloneflixEpisodeItemData( - episodeNumber = 3, - title = "The Crucible", - durationText = "51m", - synopsis = "An intensive training camp tests the physical limits of every rookie wrestler.", - progress = 0f, - isCurrent = false - ) - ) - - CloneflixEpisodesDrawer( - seriesTitle = "SANCTUARY", - seasonName = "Season 1 Episodes", - episodes = sampleEpisodes, - onEpisodeSelected = {}, - onCloseClick = {} - ) - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt deleted file mode 100644 index d78d416d0ee..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixHeroBannerComponents.kt +++ /dev/null @@ -1,1119 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -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.draw.drawBehind -import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.zIndex -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey10 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T40 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T70 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600T60 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Red300 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite70 - -enum class CloneflixTitlePreviewSize { - LARGE, - MEDIUM, - SMALL1, - SMALL2, - SMALL3 -} - -enum class CloneflixHeroBannerType { - LANDING_PAGE, - AUTHENTICATION_PAGE, - HOME_PAGE, - MOVIE_PREVIEW -} - -@Composable -fun CloneflixHeroPlayButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, - text: String = "Play", - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true -) { - val isFocused by interactionSource.collectIsFocusedAsState() - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) 1.05f else 1f, - animationSpec = CloneflixTokens.FastFocusAnimationSpec, - label = "heroPlayBtnScale" - ) - - val background = if (isFocused) PrimaryWhite else PrimaryWhite.copy(alpha = 0.95f) - val border = if (isFocused) BorderStroke(2.dp, PrimaryRed) else null - - Box( - modifier = modifier - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .semantics { - contentDescription = "$text Video" - }, - contentAlignment = Alignment.Center - ) { - Row( - modifier = Modifier - .height(42.dp) - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(CloneflixTokens.ShapeCardSmall) - .background(background) - .then(if (border != null) Modifier.border(border, CloneflixTokens.ShapeCardSmall) else Modifier) - .padding(horizontal = 24.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryBlack, - modifier = Modifier.size(20.dp) - ) - Text( - text = text, - color = PrimaryBlack, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) - } - } -} - -@Composable -fun CloneflixHeroMoreInfoButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, - text: String = "More Info" -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val background = if (isFocused) Grey300T70 else Grey600T60 - val border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(1.dp, TransparentWhite20) - - Row( - modifier = modifier - .height(42.dp) - .clip(RoundedCornerShape(4.dp)) - .background(background) - .border(border, RoundedCornerShape(4.dp)) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .padding(horizontal = 22.dp, vertical = 8.dp) - .semantics { - contentDescription = text - }, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_info), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier.size(22.dp) - ) - Text( - text = text, - color = PrimaryWhite, - fontSize = 16.sp, - fontWeight = FontWeight.Medium - ) - } -} - -@Composable -fun CloneflixHeroTrailerButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, - text: String = "Play Trailer", - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true -) { - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = CloneflixTheme.dimens - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, - animationSpec = CloneflixTokens.FastFocusAnimationSpec, - label = "heroTrailerBtnScale" - ) - - val background = if (isFocused) TransparentWhite20 else TransparentBlack60 - val border = if (isFocused) { - BorderStroke(dimens.borderFocus, PrimaryWhite) - } else { - BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_HIGH)) - } - - Box( - modifier = modifier - .height(40.dp) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(CloneflixTokens.ShapeCardSmall) - .background(background) - .border(border, CloneflixTokens.ShapeCardSmall) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .padding(horizontal = 16.dp, vertical = 8.dp) - .semantics { - role = Role.Button - contentDescription = text - }, - contentAlignment = Alignment.Center - ) { - Text( - text = text, - color = PrimaryWhite, - fontSize = 15.sp, - fontWeight = FontWeight.Bold, - style = CloneflixTheme.typography.mediumBody - ) - } -} - -@Composable -fun CloneflixHeroCircleButton( - iconRes: Int, - contentDescription: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - size: Dp = 40.dp, - iconSize: Dp = 18.dp, - backgroundColor: Color = Grey700.copy(alpha = 0.8f) -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val activeBorder = if (isFocused) { - BorderStroke(2.dp, PrimaryWhite) - } else { - BorderStroke(1.dp, TransparentWhite20) - } - - Box( - modifier = modifier - .size(size) - .clip(CircleShape) - .background(if (isFocused) Grey600 else backgroundColor) - .border(activeBorder, CircleShape) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .semantics { - this.contentDescription = contentDescription - }, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier.size(iconSize) - ) - } -} - -@Composable -fun CloneflixHeroMaturityRating( - rating: String = "16+", - modifier: Modifier = Modifier -) { - Row( - modifier = modifier - .height(32.dp) - .clip(RoundedCornerShape(topStart = 2.dp, bottomStart = 2.dp)) - .background(Grey600T60) - .border( - width = 1.dp, - color = TransparentWhite20, - shape = RoundedCornerShape(topStart = 2.dp, bottomStart = 2.dp) - ) - .drawBehind { - drawRect( - color = PrimaryRed, - size = androidx.compose.ui.geometry.Size(3.dp.toPx(), size.height) - ) - } - .padding(start = 12.dp, end = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = rating, - color = PrimaryWhite, - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - lineHeight = 16.sp - ) - } -} - -@Composable -fun CloneflixTitlePreview( - size: CloneflixTitlePreviewSize = CloneflixTitlePreviewSize.LARGE, - title: String = "HOUSE OF NINJAS", - subtitle: String = "忍びの家", - synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - top10RankText: String? = "#2 in TV Shows Today", - onPlayClick: () -> Unit = {}, - onMoreInfoClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - - when (size) { - CloneflixTitlePreviewSize.LARGE -> { - Column( - modifier = modifier.widthIn(max = 540.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - CloneflixTitleLogoView(title = title, subtitle = subtitle) - - if (!top10RankText.isNullOrBlank()) { - CloneflixTop10RankBanner( - rankText = top10RankText, - size = CloneflixTop10Size.LARGE - ) - } - - Text( - text = synopsis, - style = typography.regularBody, - color = colors.textPrimary, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - lineHeight = 22.sp - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixHeroPlayButton(onClick = onPlayClick) - CloneflixHeroMoreInfoButton(onClick = onMoreInfoClick) - } - } - } - - CloneflixTitlePreviewSize.MEDIUM -> { - Column( - modifier = modifier.widthIn(max = 518.dp), - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - CloneflixTitleLogoView(title = title, subtitle = subtitle) - - Text( - text = synopsis, - style = typography.regularBody, - color = colors.textPrimary, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - lineHeight = 22.sp - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixHeroPlayButton(onClick = onPlayClick) - CloneflixHeroMoreInfoButton(onClick = onMoreInfoClick) - } - } - } - - CloneflixTitlePreviewSize.SMALL1 -> { - Column( - modifier = modifier.widthIn(max = 480.dp), - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - CloneflixTitleLogoView(title = title, subtitle = subtitle) - - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixHeroPlayButton(onClick = onPlayClick) - CloneflixHeroMoreInfoButton(onClick = onMoreInfoClick) - } - } - } - - CloneflixTitlePreviewSize.SMALL2 -> { - Box( - modifier = modifier - .width(518.dp) - .height(207.dp) - .clip(RoundedCornerShape(8.dp)) - .background( - Brush.linearGradient( - listOf(Color(0xFF1E2229), Color(0xFF0F1115)) - ) - ) - .border(BorderStroke(1.dp, TransparentWhite20), RoundedCornerShape(8.dp)), - contentAlignment = Alignment.Center - ) { - CloneflixTitleLogoView(title = title, subtitle = subtitle, isLarge = true) - } - } - - CloneflixTitlePreviewSize.SMALL3 -> { - Box( - modifier = modifier - .width(340.dp) - .height(136.dp) - .clip(RoundedCornerShape(6.dp)) - .background( - Brush.linearGradient( - listOf(Color(0xFF1E2229), Color(0xFF0F1115)) - ) - ) - .border(BorderStroke(1.dp, TransparentWhite20), RoundedCornerShape(6.dp)), - contentAlignment = Alignment.Center - ) { - CloneflixTitleLogoView(title = title, subtitle = subtitle, isLarge = false) - } - } - } -} - -@Composable -fun CloneflixTitleLogoView( - title: String, - subtitle: String? = null, - isLarge: Boolean = false, - modifier: Modifier = Modifier -) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.Start - ) { - Text( - text = title, - fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, - fontWeight = FontWeight.Bold, - fontSize = if (isLarge) 40.sp else 32.sp, - color = PrimaryWhite, - letterSpacing = 2.sp, - lineHeight = if (isLarge) 44.sp else 36.sp - ) - if (!subtitle.isNullOrBlank()) { - Text( - text = subtitle, - fontSize = if (isLarge) 16.sp else 13.sp, - fontWeight = FontWeight.Normal, - color = Grey200, - letterSpacing = 4.sp - ) - } - } -} - -@Composable -fun CloneflixHeroBanner( - type: CloneflixHeroBannerType = CloneflixHeroBannerType.HOME_PAGE, - title: String = "HOUSE OF NINJAS", - synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - top10RankText: String? = "#2 in TV Shows Today", - maturityRating: String = "16+", - onPlayClick: () -> Unit = {}, - onMoreInfoClick: () -> Unit = {}, - onAudioToggle: (() -> Unit)? = null, - onCloseClick: (() -> Unit)? = null, - onAddToListClick: (() -> Unit)? = null, - onThumbsUpClick: (() -> Unit)? = null, - isMuted: Boolean = false, - modifier: Modifier = Modifier -) { - when (type) { - CloneflixHeroBannerType.HOME_PAGE -> { - Box( - modifier = modifier - .fillMaxWidth() - .height(480.dp) - .clip(RoundedCornerShape(8.dp)) - .background(Color(0xFF181C24)) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.radialGradient( - colors = listOf(Color(0xFF2C3E50), Color(0xFF0F141D), Color(0xFF0B0E14)), - radius = 1200f - ) - ) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .height(140.dp) - .background( - Brush.verticalGradient( - listOf(TransparentBlack90, Color.Transparent) - ) - ) - ) - - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.horizontalGradient( - listOf( - Color(0xFF0B0E14).copy(alpha = 0.95f), - Color(0xFF0B0E14).copy(alpha = 0.6f), - Color.Transparent - ) - ) - ) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .height(180.dp) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - listOf(Color.Transparent, Color(0xFF141414)) - ) - ) - ) - - Box( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(start = 48.dp, bottom = 48.dp) - ) { - CloneflixTitlePreview( - size = CloneflixTitlePreviewSize.LARGE, - title = title, - synopsis = synopsis, - top10RankText = top10RankText, - onPlayClick = onPlayClick, - onMoreInfoClick = onMoreInfoClick - ) - } - - Row( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(bottom = 60.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - if (onAudioToggle != null) { - CloneflixHeroCircleButton( - iconRes = if (isMuted) R.drawable.cloneflix_ic_mute else R.drawable.cloneflix_ic_volume, - contentDescription = if (isMuted) "Unmute Audio" else "Mute Audio", - onClick = onAudioToggle, - size = 40.dp - ) - } - - CloneflixHeroMaturityRating(rating = maturityRating) - } - } - } - - CloneflixHeroBannerType.MOVIE_PREVIEW -> { - Box( - modifier = modifier - .fillMaxWidth() - .height(380.dp) - .clip(RoundedCornerShape(8.dp)) - .background(Color(0xFF181C24)) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.radialGradient( - colors = listOf(Color(0xFF34495E), Color(0xFF1A252F), Color(0xFF11171E)), - radius = 900f - ) - ) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .height(140.dp) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - listOf(Color.Transparent, Color(0xFF181818)) - ) - ) - ) - - if (onCloseClick != null) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(16.dp) - ) { - CloneflixHeroCircleButton( - iconRes = R.drawable.cloneflix_ic_close, - contentDescription = "Close Preview", - onClick = onCloseClick, - size = 36.dp, - iconSize = 16.dp, - backgroundColor = Grey850 - ) - } - } - - Column( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(start = 32.dp, bottom = 28.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - CloneflixTitleLogoView(title = title, subtitle = "忍びの家") - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - CloneflixHeroPlayButton(onClick = onPlayClick) - - if (onAddToListClick != null) { - CloneflixHeroCircleButton( - iconRes = R.drawable.cloneflix_ic_plus, - contentDescription = "Add to My List", - onClick = onAddToListClick, - size = 42.dp, - iconSize = 18.dp, - backgroundColor = Grey700 - ) - } - - if (onThumbsUpClick != null) { - CloneflixHeroCircleButton( - iconRes = R.drawable.cloneflix_ic_thumb_up, - contentDescription = "I like this", - onClick = onThumbsUpClick, - size = 42.dp, - iconSize = 18.dp, - backgroundColor = Grey700 - ) - } - } - } - - if (onAudioToggle != null) { - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(end = 32.dp, bottom = 28.dp) - ) { - CloneflixHeroCircleButton( - iconRes = if (isMuted) R.drawable.cloneflix_ic_mute else R.drawable.cloneflix_ic_volume, - contentDescription = if (isMuted) "Unmute Audio" else "Mute Audio", - onClick = onAudioToggle, - size = 40.dp - ) - } - } - } - } - - CloneflixHeroBannerType.LANDING_PAGE -> { - Box( - modifier = modifier - .fillMaxWidth() - .height(440.dp) - .clip(RoundedCornerShape(8.dp)) - .background(Color(0xFF0F1115)) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.radialGradient( - colors = listOf(Color(0xFF2C3E50).copy(alpha = 0.7f), Color(0xFF0F1115), Color(0xFF000000)), - radius = 1000f - ) - ) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp, vertical = 20.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "CLONEFLIX", - fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, - fontWeight = FontWeight.Bold, - color = PrimaryRed, - fontSize = 28.sp, - letterSpacing = 2.sp - ) - - CloneflixButton( - text = "Sign In", - onClick = onPlayClick, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.SMALL - ) - } - - Column( - modifier = Modifier - .align(Alignment.Center) - .padding(horizontal = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - Text( - text = "Unlimited movies, TV shows, and more", - fontSize = 32.sp, - fontWeight = FontWeight.Bold, - color = PrimaryWhite, - textAlign = TextAlign.Center - ) - - Text( - text = "Watch anywhere. Cancel anytime.", - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - color = Grey10, - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.height(6.dp)) - - Text( - text = "Ready to watch? Enter your email to create or restart your membership.", - fontSize = 15.sp, - color = Grey200, - textAlign = TextAlign.Center - ) - - Row( - modifier = Modifier.padding(top = 8.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixButton( - text = "Get Started >", - onClick = onPlayClick, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.LARGE - ) - } - } - } - } - - CloneflixHeroBannerType.AUTHENTICATION_PAGE -> { - Box( - modifier = modifier - .fillMaxWidth() - .height(440.dp) - .clip(RoundedCornerShape(8.dp)) - .background(Color(0xFF0F1115)) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.radialGradient( - colors = listOf(Color(0xFF1E272E), Color(0xFF0F1115), Color(0xFF000000)), - radius = 900f - ) - ) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp, vertical = 20.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "CLONEFLIX", - fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, - fontWeight = FontWeight.Bold, - color = PrimaryRed, - fontSize = 28.sp, - letterSpacing = 2.sp - ) - } - - Box( - modifier = Modifier - .align(Alignment.Center) - .width(360.dp) - .clip(RoundedCornerShape(8.dp)) - .background(TransparentBlack90) - .border(BorderStroke(1.dp, TransparentWhite20), RoundedCornerShape(8.dp)) - .padding(28.dp) - ) { - Column( - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Sign In", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - - CloneflixButton( - text = "Sign In with Account", - onClick = onPlayClick, - modifier = Modifier.fillMaxWidth(), - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.MEDIUM - ) - - Text( - text = "New to Cloneflix? Sign up now.", - fontSize = 13.sp, - color = Grey200 - ) - } - } - } - } - } -} - -@Composable -fun CloneflixHomePageHeroPattern( - onPlayClick: () -> Unit = {}, - onMoreInfoClick: () -> Unit = {}, - onSearchClick: () -> Unit = {}, - onNotificationClick: () -> Unit = {}, - onProfileClick: () -> Unit = {}, - onCardClick: ((String) -> Unit)? = null, - modifier: Modifier = Modifier -) { - val navItems = listOf("Home", "TV Shows", "Movies", "New & Popular", "My List", "Browse by Languages") - var selectedNavIndex by remember { mutableStateOf(0) } - var isMuted by remember { mutableStateOf(false) } - - Column( - modifier = modifier - .fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .background(Color(0xFF141414)) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .background( - Brush.verticalGradient( - listOf(Color(0xFF0F1115), Color(0xCC0F1115)) - ) - ) - .padding(horizontal = 32.dp, vertical = 14.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(20.dp) - ) { - Text( - text = "CLONEFLIX", - fontFamily = com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixFontFamily, - fontWeight = FontWeight.Bold, - color = PrimaryRed, - fontSize = 24.sp, - letterSpacing = 2.sp - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - navItems.forEachIndexed { index, item -> - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val isSelected = selectedNavIndex == index - - Text( - text = item, - color = when { - isFocused -> PrimaryWhite - isSelected -> PrimaryWhite - else -> Grey200 - }, - fontWeight = if (isSelected || isFocused) FontWeight.Bold else FontWeight.Normal, - fontSize = 13.sp, - modifier = Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { selectedNavIndex = index } - ) - .focusable(interactionSource = interactionSource) - .then( - if (isFocused) Modifier.border( - BorderStroke(1.dp, PrimaryWhite), - RoundedCornerShape(2.dp) - ).padding(horizontal = 4.dp, vertical = 2.dp) else Modifier - ) - ) - } - } - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_search), - contentDescription = "Search", - tint = PrimaryWhite, - modifier = Modifier - .size(20.dp) - .clickable(onClick = onSearchClick) - ) - - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_notification), - contentDescription = "Notifications", - tint = PrimaryWhite, - modifier = Modifier - .size(20.dp) - .clickable(onClick = onNotificationClick) - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp), - modifier = Modifier.clickable(onClick = onProfileClick) - ) { - Box( - modifier = Modifier - .size(28.dp) - .clip(RoundedCornerShape(4.dp)) - .background(Blue100), - contentAlignment = Alignment.Center - ) { - Text( - text = "C", - color = PrimaryWhite, - fontWeight = FontWeight.Bold, - fontSize = 14.sp - ) - } - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_arrow_down), - contentDescription = "Profile Menu", - tint = PrimaryWhite, - modifier = Modifier.size(12.dp) - ) - } - } - } - - CloneflixHeroBanner( - type = CloneflixHeroBannerType.HOME_PAGE, - title = "HOUSE OF NINJAS", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - top10RankText = "#2 in TV Shows Today", - maturityRating = "16+", - onPlayClick = onPlayClick, - onMoreInfoClick = onMoreInfoClick, - onAudioToggle = { isMuted = !isMuted }, - isMuted = isMuted - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .offset(y = (-40).dp) - .padding(horizontal = 32.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Trending Now", - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - listOf( - Triple("House of Ninjas", "Recently Added", "TOP 10"), - Triple("Avatar: The Last Airbender", "New Season", "TOP 10"), - Triple("One Piece", "Recently Added", "TOP 10"), - Triple("Stranger Things", "Leaving Soon", "TOP 10") - ).forEach { (movieTitle, tag, badge) -> - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - Box( - modifier = Modifier - .weight(1f) - .height(110.dp) - .clip(RoundedCornerShape(4.dp)) - .background( - Brush.linearGradient( - listOf(Color(0xFF2C3E50), Color(0xFF1A252F)) - ) - ) - .border( - BorderStroke( - if (isFocused) 2.dp else 1.dp, - if (isFocused) PrimaryWhite else TransparentWhite20 - ), - RoundedCornerShape(4.dp) - ) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { onCardClick?.invoke(movieTitle) } - ) - .focusable(interactionSource = interactionSource) - .padding(8.dp) - ) { - Box( - modifier = Modifier - .align(Alignment.TopStart) - .clip(RoundedCornerShape(2.dp)) - .background(Red300) - .padding(horizontal = 6.dp, vertical = 2.dp) - ) { - Text( - text = tag, - color = PrimaryWhite, - fontSize = 10.sp, - fontWeight = FontWeight.Bold - ) - } - - Text( - text = movieTitle, - color = PrimaryWhite, - fontSize = 12.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.align(Alignment.BottomStart) - ) - } - } - } - } - } -} - -val Blue100 = Color(0xFF0071EB) - -@Preview(name = "Hero Banners Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun HeroBannersPreview() { - CloneflixTheme { - Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) { - CloneflixTitlePreview(size = CloneflixTitlePreviewSize.LARGE) - CloneflixHeroBanner(type = CloneflixHeroBannerType.HOME_PAGE) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt deleted file mode 100644 index e0dd3691107..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMediaControlButtons.kt +++ /dev/null @@ -1,371 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -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.Icon -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -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.draw.scale -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.res.stringResource -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -enum class CloneflixPlayerControlAction { - PLAY, - PAUSE, - REPLAY_10, - FORWARD_10, - VOLUME_UP, - VOLUME_OFF, - NEXT_EPISODE, - SUBTITLES, - SPEED, - EPISODES, - FULLSCREEN -} - -@Composable -fun CloneflixPlayerControlButton( - action: CloneflixPlayerControlAction, - onClick: () -> Unit, - modifier: Modifier = Modifier, - size: Dp = CloneflixPlayerDefaults.ControlButtonSize, - iconSize: Dp = CloneflixPlayerDefaults.ControlIconSize, - enabled: Boolean = true, - isSelected: Boolean = false -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = CloneflixTheme.dimens - - val scale by animateFloatAsState( - targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, - label = "playerBtnScale" - ) - - val iconRes = when (action) { - CloneflixPlayerControlAction.PLAY -> R.drawable.cloneflix_ic_play - CloneflixPlayerControlAction.PAUSE -> R.drawable.cloneflix_ic_play - CloneflixPlayerControlAction.REPLAY_10 -> R.drawable.netflix_skip_back - CloneflixPlayerControlAction.FORWARD_10 -> R.drawable.netflix_skip_forward - CloneflixPlayerControlAction.VOLUME_UP -> R.drawable.ic_baseline_volume_up_24 - CloneflixPlayerControlAction.VOLUME_OFF -> R.drawable.ic_baseline_volume_mute_24 - CloneflixPlayerControlAction.NEXT_EPISODE -> R.drawable.ic_baseline_skip_next_24 - CloneflixPlayerControlAction.SUBTITLES -> R.drawable.ic_baseline_subtitles_24 - CloneflixPlayerControlAction.SPEED -> R.drawable.ic_baseline_speed_24 - CloneflixPlayerControlAction.EPISODES -> R.drawable.baseline_list_alt_24 - CloneflixPlayerControlAction.FULLSCREEN -> R.drawable.baseline_fullscreen_24 - } - - val contentDesc = when (action) { - CloneflixPlayerControlAction.PLAY -> stringResource(R.string.cloneflix_cd_play) - CloneflixPlayerControlAction.PAUSE -> stringResource(R.string.cloneflix_cd_pause) - CloneflixPlayerControlAction.REPLAY_10 -> stringResource(R.string.cloneflix_cd_replay_10) - CloneflixPlayerControlAction.FORWARD_10 -> stringResource(R.string.cloneflix_cd_forward_10) - CloneflixPlayerControlAction.VOLUME_UP -> stringResource(R.string.cloneflix_cd_unmute) - CloneflixPlayerControlAction.VOLUME_OFF -> stringResource(R.string.cloneflix_cd_mute) - CloneflixPlayerControlAction.NEXT_EPISODE -> stringResource(R.string.cloneflix_cd_next_episode) - CloneflixPlayerControlAction.SUBTITLES -> stringResource(R.string.cloneflix_cd_audio_subtitles) - CloneflixPlayerControlAction.SPEED -> stringResource(R.string.cloneflix_cd_playback_speed) - CloneflixPlayerControlAction.EPISODES -> stringResource(R.string.cloneflix_cd_episodes) - CloneflixPlayerControlAction.FULLSCREEN -> stringResource(R.string.cloneflix_cd_unlock_screen) - } - - val backgroundColor = when { - isFocused -> TransparentWhite20 - isSelected -> TransparentWhite20 - else -> Color.Transparent - } - - val borderStroke = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null - - Box( - modifier = modifier - .size(size) - .scale(scale) - .clip(CircleShape) - .background(backgroundColor) - .then(if (borderStroke != null) Modifier.border(borderStroke, CircleShape) else Modifier) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .semantics { - role = Role.Button - contentDescription = contentDesc - }, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = contentDesc, - tint = if (enabled) PrimaryWhite else Grey200.copy(alpha = CloneflixTokens.ALPHA_MEDIUM), - modifier = Modifier.size(iconSize) - ) - } -} - -enum class CloneflixCircleActionType { - PLAY, - ADD, - ADDED, - THUMB_UP, - THUMB_DOWN, - DOUBLE_THUMB_UP, - MUTE, - UNMUTE, - ARROW_DOWN -} - -@Composable -fun CloneflixCircleActionButton( - type: CloneflixCircleActionType, - onClick: () -> Unit, - modifier: Modifier = Modifier, - size: Dp = CloneflixMovieCardDefaults.ActionButtonSize, - iconSize: Dp = CloneflixTheme.dimens.iconM, - enabled: Boolean = true -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = CloneflixTheme.dimens - - val scale by animateFloatAsState( - targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, - label = "circleBtnScale" - ) - - val isPlay = type == CloneflixCircleActionType.PLAY - - val containerColor = when { - isPlay -> PrimaryWhite - isFocused -> TransparentWhite20 - else -> TransparentBlack60 - } - - val contentColor = when { - isPlay -> PrimaryBlack - else -> PrimaryWhite - } - - val borderStroke = when { - isPlay -> if (isFocused) BorderStroke(dimens.borderFocus, PrimaryBlack) else null - isFocused -> BorderStroke(dimens.borderFocus, PrimaryWhite) - else -> BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_HIGH)) - } - - val iconRes = when (type) { - CloneflixCircleActionType.PLAY -> R.drawable.cloneflix_ic_play - CloneflixCircleActionType.ADD -> R.drawable.cloneflix_ic_plus - CloneflixCircleActionType.ADDED -> R.drawable.cloneflix_ic_check - CloneflixCircleActionType.THUMB_UP -> R.drawable.cloneflix_ic_thumb_up - CloneflixCircleActionType.THUMB_DOWN -> R.drawable.cloneflix_ic_thumb_down - CloneflixCircleActionType.DOUBLE_THUMB_UP -> R.drawable.cloneflix_ic_double_thumb_up - CloneflixCircleActionType.MUTE -> R.drawable.ic_baseline_volume_mute_24 - CloneflixCircleActionType.UNMUTE -> R.drawable.ic_baseline_volume_up_24 - CloneflixCircleActionType.ARROW_DOWN -> R.drawable.cloneflix_ic_arrow_down - } - - val desc = when (type) { - CloneflixCircleActionType.PLAY -> stringResource(R.string.cloneflix_cd_play) - CloneflixCircleActionType.ADD -> stringResource(R.string.cloneflix_cd_add_to_list) - CloneflixCircleActionType.ADDED -> stringResource(R.string.cloneflix_cd_remove_from_list) - CloneflixCircleActionType.THUMB_UP -> stringResource(R.string.cloneflix_cd_like) - CloneflixCircleActionType.THUMB_DOWN -> stringResource(R.string.cloneflix_cd_dislike) - CloneflixCircleActionType.DOUBLE_THUMB_UP -> stringResource(R.string.cloneflix_cd_double_like) - CloneflixCircleActionType.MUTE -> stringResource(R.string.cloneflix_cd_mute) - CloneflixCircleActionType.UNMUTE -> stringResource(R.string.cloneflix_cd_unmute) - CloneflixCircleActionType.ARROW_DOWN -> stringResource(R.string.cloneflix_cd_episodes) - } - - Box( - modifier = modifier - .size(size) - .scale(scale) - .clip(CircleShape) - .background(containerColor) - .then(if (borderStroke != null) Modifier.border(borderStroke, CircleShape) else Modifier) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .semantics { - role = Role.Button - contentDescription = desc - }, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = desc, - tint = contentColor, - modifier = Modifier.size(iconSize) - ) - } -} - -@Composable -fun CloneflixThumbsReactionPill( - onDislikeClick: () -> Unit, - onLikeClick: () -> Unit, - onLoveClick: () -> Unit, - modifier: Modifier = Modifier -) { - val dimens = CloneflixTheme.dimens - Surface( - modifier = modifier, - shape = RoundedCornerShape(CloneflixTokens.RadiusPill), - color = Color(0xEB232323), - border = BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_MEDIUM)) - ) { - Row( - modifier = Modifier.padding(horizontal = dimens.spacingM, vertical = dimens.spacingS), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixCircleActionButton( - type = CloneflixCircleActionType.THUMB_DOWN, - onClick = onDislikeClick, - size = CloneflixMovieCardDefaults.PlayButtonOverlaySize, - iconSize = dimens.iconS - ) - CloneflixCircleActionButton( - type = CloneflixCircleActionType.THUMB_UP, - onClick = onLikeClick, - size = CloneflixMovieCardDefaults.PlayButtonOverlaySize, - iconSize = dimens.iconS - ) - CloneflixCircleActionButton( - type = CloneflixCircleActionType.DOUBLE_THUMB_UP, - onClick = onLoveClick, - size = CloneflixMovieCardDefaults.PlayButtonOverlaySize, - iconSize = dimens.iconS - ) - } - } -} - -@Composable -fun CloneflixHeroSoundButton( - isMuted: Boolean, - onToggleMute: () -> Unit, - modifier: Modifier = Modifier, - size: Dp = CloneflixMovieCardDefaults.PlayButtonOverlaySize -) { - val dimens = CloneflixTheme.dimens - CloneflixCircleActionButton( - type = if (isMuted) CloneflixCircleActionType.MUTE else CloneflixCircleActionType.UNMUTE, - onClick = onToggleMute, - size = size, - iconSize = dimens.iconS, - modifier = modifier - ) -} - -@Composable -fun CloneflixHeroReplayButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, - size: Dp = CloneflixMovieCardDefaults.PlayButtonOverlaySize -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = CloneflixTheme.dimens - - val scale by animateFloatAsState( - targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR_LARGE else 1f, - label = "replayBtnScale" - ) - - val border = if (isFocused) { - BorderStroke(dimens.borderFocus, PrimaryWhite) - } else { - BorderStroke(dimens.borderDefault, Grey200.copy(alpha = CloneflixTokens.ALPHA_HIGH)) - } - - val replayDesc = stringResource(R.string.cloneflix_cd_replay_10) - - Box( - modifier = modifier - .size(size) - .scale(scale) - .clip(CircleShape) - .background(TransparentBlack60) - .border(border, CircleShape) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .semantics { - role = Role.Button - contentDescription = replayDesc - }, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_replay_arrow), - contentDescription = replayDesc, - tint = PrimaryWhite, - modifier = Modifier.size(dimens.iconS) - ) - } -} - -@Preview(name = "Media Control Buttons Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun MediaControlButtonsPreview() { - CloneflixTheme { - Row( - modifier = Modifier.padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixCircleActionButton(type = CloneflixCircleActionType.PLAY, onClick = {}) - CloneflixCircleActionButton(type = CloneflixCircleActionType.ADD, onClick = {}) - CloneflixCircleActionButton(type = CloneflixCircleActionType.THUMB_UP, onClick = {}) - CloneflixThumbsReactionPill(onDislikeClick = {}, onLikeClick = {}, onLoveClick = {}) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt deleted file mode 100644 index 1bb809fcf3b..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieCard.kt +++ /dev/null @@ -1,857 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -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.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -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.draw.scale -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.zIndex -import coil3.compose.AsyncImage -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.res.stringResource -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent -import com.lagradost.cloudstream3.ui.revamp.compose.theme.getRatingScoreColor -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey500 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 - -enum class CloneflixMovieCardSize { - MEDIUM, - SMALL -} - -enum class CloneflixMovieCardType { - DEFAULT, - POSTER, - MORE_LIKE_THIS, - MORE_LIKE_THIS_WITH_PLAY, - EPISODE, - TRAILER, - PLAYER_PREVIEW, - TOP10, - CONTINUE_WATCHING -} - -@Composable -fun CloneflixMovieCard( - title: String, - modifier: Modifier = Modifier, - type: CloneflixMovieCardType = CloneflixMovieCardType.DEFAULT, - size: CloneflixMovieCardSize = CloneflixMovieCardSize.MEDIUM, - badge: CloneflixBadgeType? = null, - showLogo: Boolean = false, - showBottomTitle: Boolean = false, - top10Rank: Int? = null, - progress: Float? = null, - runtime: String? = null, - timestamp: String? = null, - subtitle: String? = null, - posterUrl: String? = null, - onClick: (() -> Unit)? = null -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, - animationSpec = CloneflixTokens.FastFocusAnimationSpec, - label = "cardScale" - ) - - val cardWidth: Dp = CloneflixMovieCardDefaults.cardWidth(type, size) - val cardHeight: Dp = CloneflixMovieCardDefaults.cardHeight(type, size) - - val border = if (isFocused) { - BorderStroke(dimens.borderFocus, PrimaryWhite) - } else { - BorderStroke(dimens.borderSubtle, colors.border) - } - - Box( - modifier = modifier - .width(cardWidth) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - } else { - Modifier.focusable(interactionSource = interactionSource) - } - ) - .semantics { contentDescription = title }, - contentAlignment = Alignment.Center - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - ) { - if (type == CloneflixMovieCardType.TOP10 && top10Rank != null) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(cardHeight), - verticalAlignment = Alignment.Bottom - ) { - Box( - modifier = Modifier - .width(CloneflixMovieCardDefaults.RankBoxWidth) - .fillMaxHeight(), - contentAlignment = Alignment.BottomCenter - ) { - Text( - text = top10Rank.toString(), - fontSize = CloneflixMovieCardDefaults.RankFontSize, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - color = Grey800, - lineHeight = CloneflixMovieCardDefaults.RankLineHeight, - modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankShadowOffsetX) - ) - Text( - text = top10Rank.toString(), - fontSize = CloneflixMovieCardDefaults.RankFontSize, - fontWeight = FontWeight.Black, - fontFamily = FontFamily.SansSerif, - color = PrimaryBlack, - lineHeight = CloneflixMovieCardDefaults.RankLineHeight, - modifier = Modifier.offset(x = CloneflixMovieCardDefaults.RankForegroundOffsetX) - ) - } - - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(Grey800) - .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) - ) { - MovieCardSurface( - title = title, - showLogo = showLogo, - showBottomTitle = showBottomTitle, - badge = badge, - runtime = runtime, - timestamp = timestamp, - progress = progress, - showCenterPlay = false, - posterUrl = posterUrl - ) - } - } - } else { - Box( - modifier = Modifier - .fillMaxWidth() - .height(if (type == CloneflixMovieCardType.TRAILER) CloneflixMovieCardDefaults.HeightTrailerThumbnail else cardHeight) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(Grey800) - .border(border, RoundedCornerShape(CloneflixTokens.RadiusCard)) - ) { - MovieCardSurface( - title = title, - showLogo = showLogo, - showBottomTitle = showBottomTitle, - badge = badge, - runtime = runtime, - timestamp = timestamp, - progress = progress, - showCenterPlay = type == CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY || - type == CloneflixMovieCardType.EPISODE, - posterUrl = posterUrl - ) - } - - if (type == CloneflixMovieCardType.TRAILER && !subtitle.isNullOrBlank()) { - Spacer(modifier = Modifier.height(dimens.spacingS)) - Text( - text = subtitle, - style = typography.mediumSmallBody, - color = if (isFocused) PrimaryWhite else Grey100, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } - } - } -} - -@Composable -private fun MovieCardSurface( - title: String, - showLogo: Boolean, - badge: CloneflixBadgeType?, - runtime: String?, - timestamp: String?, - progress: Float?, - showCenterPlay: Boolean, - showBottomTitle: Boolean = false, - posterUrl: String? = null -) { - val typography = CloneflixTheme.typography - - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf( - Grey700, - Grey850, - PrimaryBlack - ) - ) - ) - ) { - if (!posterUrl.isNullOrBlank()) { - AsyncImage( - model = posterUrl, - contentDescription = title, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } else { - Text( - text = title, - style = typography.regularCaption1, - fontWeight = FontWeight.SemiBold, - color = Grey50, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .align(Alignment.Center) - .padding(horizontal = 8.dp) - ) - } - - if (showLogo) { - Box( - modifier = Modifier - .align(Alignment.TopStart) - .padding(6.dp) - ) { - CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_SMALL) - } - } - - if (badge != null) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(6.dp) - ) { - CloneflixContentBadge(type = badge) - } - } - - if (showCenterPlay) { - Box( - modifier = Modifier - .size(CloneflixMovieCardDefaults.PlayButtonOverlaySize) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(CloneflixTheme.dimens.borderDefault, PrimaryWhite), CircleShape), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = stringResource(R.string.cloneflix_cd_play), - tint = PrimaryWhite, - modifier = Modifier.size(CloneflixMovieCardDefaults.PlayIconOverlaySize) - ) - } - } - - if (!runtime.isNullOrBlank()) { - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(CloneflixTheme.dimens.spacingS) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(TransparentBlack90) - .padding(horizontal = CloneflixTheme.dimens.spacingXs, vertical = CloneflixTheme.dimens.spacingXxs) - ) { - Text( - text = runtime, - style = typography.regularCaption2, - fontWeight = FontWeight.Medium, - color = PrimaryWhite - ) - } - } - - if (!timestamp.isNullOrBlank()) { - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(CloneflixTheme.dimens.spacingS) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(TransparentBlack90) - .padding(horizontal = CloneflixTheme.dimens.spacingXs, vertical = CloneflixTheme.dimens.spacingXxs) - ) { - Text( - text = timestamp, - style = typography.regularCaption2, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - } - } - - if (progress != null) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(CloneflixTheme.dimens.progressHeight) - .align(Alignment.BottomCenter) - .background(Grey500) - ) { - Box( - modifier = Modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(PrimaryRed) - ) - } - } - - if (showBottomTitle && !title.isNullOrBlank()) { - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.30f) - .align(Alignment.BottomStart) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color(0x99000000), - Color(0xCC000000), - Color(0xEE000000), - Color(0xFF000000) - ) - ) - ) - .padding(horizontal = 8.dp, vertical = 6.dp), - contentAlignment = Alignment.BottomStart - ) { - Text( - text = title, - style = typography.regularCaption1, - fontWeight = FontWeight.Bold, - color = PrimaryWhite, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - fontSize = 12.sp, - lineHeight = 15.sp - ) - } - } - } -} - -@Composable -fun CloneflixEpisodeItem( - episodeNumber: Int, - title: String, - duration: String, - synopsis: String, - modifier: Modifier = Modifier, - isCurrent: Boolean = false, - onClick: (() -> Unit)? = null -) { - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val bgColor = when { - isFocused -> Grey700 - isCurrent -> Grey800 - else -> colors.surface - } - - val border = if (isFocused) { - BorderStroke(dimens.borderFocus, PrimaryWhite) - } else if (isCurrent) { - BorderStroke(dimens.borderDefault, PrimaryWhite.copy(alpha = CloneflixTokens.ALPHA_SUBTLE)) - } else { - BorderStroke(dimens.borderSubtle, colors.border) - } - - Row( - modifier = modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixDrawerDefaults.EpisodeItemCornerRadius)) - .background(bgColor) - .border(border, RoundedCornerShape(CloneflixDrawerDefaults.EpisodeItemCornerRadius)) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - } else { - Modifier.focusable(interactionSource = interactionSource) - } - ) - .padding(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = episodeNumber.toString(), - style = typography.boldTitle2, - color = if (isFocused || isCurrent) PrimaryWhite else Grey200, - modifier = Modifier.width(CloneflixDrawerDefaults.EpisodeNumberWidth) - ) - - Spacer(modifier = Modifier.width(dimens.spacingM)) - - Box( - modifier = Modifier - .width(CloneflixMovieCardDefaults.EpisodeThumbnailWidth) - .height(CloneflixMovieCardDefaults.EpisodeThumbnailHeight) - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(Grey850) - .border(BorderStroke(dimens.borderSubtle, colors.border), RoundedCornerShape(CloneflixTokens.RadiusCard)), - contentAlignment = Alignment.Center - ) { - Text( - text = stringResource(R.string.cloneflix_label_episode_prefix, episodeNumber), - style = typography.regularCaption2, - color = Grey50 - ) - - Box( - modifier = Modifier - .size(CloneflixMovieCardDefaults.EpisodeItemPlayButtonSize) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(dimens.borderDefault, PrimaryWhite), CircleShape), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = stringResource(R.string.cloneflix_cd_play_episode, episodeNumber), - tint = PrimaryWhite, - modifier = Modifier.size(CloneflixMovieCardDefaults.EpisodeItemPlayIconSize) - ) - } - } - - Spacer(modifier = Modifier.width(dimens.spacingL)) - - Column(modifier = Modifier.weight(1f)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = title, - style = typography.mediumHeadline2, - color = if (isFocused || isCurrent) PrimaryWhite else colors.textPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f) - ) - - Spacer(modifier = Modifier.width(dimens.spacingS)) - - Text( - text = duration, - style = typography.regularCaption1, - color = Grey100 - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - Text( - text = synopsis, - style = typography.regularSmallBody, - color = if (isFocused) PrimaryWhite else Grey200, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } -} - -@Composable -fun CloneflixMovieBlockRow( - title: String, - items: List, - modifier: Modifier = Modifier, - onItemClick: ((CloneflixMovieCardItem) -> Unit)? = null -) { - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - - Column(modifier = modifier.fillMaxWidth()) { - Text( - text = title, - style = typography.mediumTitle3, - fontSize = 18.sp, - color = colors.textPrimary, - modifier = Modifier.padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingS) - ) - - LazyRow( - modifier = Modifier.fillMaxWidth(), - contentPadding = PaddingValues(horizontal = dimens.spacing2Xl, vertical = dimens.spacingS), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - itemsIndexed(items) { index, item -> - CloneflixMovieCard( - title = item.title, - type = item.type, - size = item.size, - badge = item.badge, - showLogo = item.showLogo, - showBottomTitle = item.showBottomTitle, - top10Rank = item.top10Rank, - progress = item.progress, - runtime = item.runtime, - timestamp = item.timestamp, - subtitle = item.subtitle, - posterUrl = item.posterUrl, - onClick = { onItemClick?.invoke(item) } - ) - } - } - } -} - -@Immutable -data class CloneflixMovieCardItem( - val title: String, - val type: CloneflixMovieCardType = CloneflixMovieCardType.DEFAULT, - val size: CloneflixMovieCardSize = CloneflixMovieCardSize.MEDIUM, - val badge: CloneflixBadgeType? = null, - val showLogo: Boolean = false, - val showBottomTitle: Boolean = false, - val top10Rank: Int? = null, - val progress: Float? = null, - val runtime: String? = null, - val timestamp: String? = null, - val subtitle: String? = null, - val posterUrl: String? = null -) - -@Composable -fun CloneflixExpandedMoviePreview( - title: String = CloneflixSampleData.SAMPLE_TITLE_HOUSE_OF_NINJAS, - matchScore: String = CloneflixSampleData.SAMPLE_MATCH_NEW, - maturityRating: String = CloneflixSampleData.SAMPLE_MATURITY_RATING, - duration: String = CloneflixSampleData.SAMPLE_DURATION_SEASONS, - quality: String = CloneflixSampleData.SAMPLE_QUALITY_HD, - genres: List = CloneflixSampleData.SAMPLE_GENRES, - synopsis: String = CloneflixSampleData.SAMPLE_SYNOPSIS, - onPlayClick: () -> Unit = {}, - onAddClick: () -> Unit = {}, - onThumbUpClick: () -> Unit = {}, - onMuteClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - Box( - modifier = modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(Grey850) - .border(BorderStroke(dimens.borderDefault, colors.border), RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - ) { - Column(modifier = Modifier.fillMaxWidth()) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(CloneflixMovieCardDefaults.ExpandedPreviewHeaderHeight) - .background( - Brush.verticalGradient( - colors = listOf( - Grey700, - Grey850, - PrimaryBlack - ) - ) - ) - ) { - Column( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(dimens.spacing2Xl) - ) { - CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = title, - style = typography.boldTitle1, - fontSize = 28.sp, - color = PrimaryWhite - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = stringResource(R.string.cloneflix_cd_play), - isPrimary = true, - onClick = onPlayClick - ) - - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_plus), - contentDescription = stringResource(R.string.cloneflix_cd_add_to_list), - onClick = onAddClick - ) - - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), - contentDescription = stringResource(R.string.cloneflix_cd_like), - onClick = onThumbUpClick - ) - - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_mute), - contentDescription = stringResource(R.string.cloneflix_cd_mute), - onClick = onMuteClick - ) - } - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacing2Xl) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = matchScore, - color = getRatingScoreColor(matchScore), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - - CloneflixMaturityRating(rating = maturityRating) - - Text( - text = duration, - color = PrimaryWhite, - style = typography.regularBody - ) - - CloneflixVideoQualityBadge(quality = quality) - } - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - genres.forEachIndexed { index, genre -> - Text( - text = genre, - style = typography.regularCaption1, - color = PrimaryWhite - ) - if (index < genres.size - 1) { - Box( - modifier = Modifier - .size(dimens.spacingXs) - .clip(CircleShape) - .background(Grey200) - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Text( - text = synopsis, - style = typography.regularBody, - color = Grey100, - lineHeight = 22.sp - ) - } - } - } -} - -@Composable -fun CloneflixCircleActionButton( - icon: Painter, - contentDescription: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isPrimary: Boolean = false, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true -) { - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = CloneflixTheme.dimens - - val bgColor = when { - isPrimary -> PrimaryWhite - isFocused -> PrimaryWhite.copy(alpha = CloneflixTokens.ALPHA_MUTED) - else -> TransparentBlack60 - } - - val iconTint = if (isPrimary) PrimaryBlack else PrimaryWhite - val border = if (isFocused) { - BorderStroke(dimens.borderFocus, PrimaryWhite) - } else { - BorderStroke(dimens.borderDefault, PrimaryWhite.copy(alpha = CloneflixTokens.ALPHA_HIGH)) - } - - Box( - modifier = modifier - .size(CloneflixMovieCardDefaults.ActionButtonSize) - .clip(CircleShape) - .background(bgColor) - .border(border, CircleShape) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource), - contentAlignment = Alignment.Center - ) { - Icon( - painter = icon, - contentDescription = contentDescription, - tint = iconTint, - modifier = Modifier.size(dimens.iconM) - ) - } -} - -@Preview(name = "Movie Cards Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun MovieCardsPreview() { - CloneflixTheme { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - CloneflixMovieCard( - title = "House of Ninjas", - type = CloneflixMovieCardType.DEFAULT, - showLogo = true, - badge = CloneflixBadgeType.NEW_SEASON - ) - - CloneflixMovieCard( - title = "The Witcher", - type = CloneflixMovieCardType.CONTINUE_WATCHING, - progress = 0.65f, - showLogo = true - ) - } - - CloneflixEpisodeItem( - episodeNumber = 1, - title = "The Offer", - duration = "55m", - synopsis = "While Haru Tawara develops a crush on a mysterious young woman at work...", - isCurrent = true - ) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt deleted file mode 100644 index e6c6f4c6a50..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixMovieInfoComponents.kt +++ /dev/null @@ -1,295 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.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.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.getRatingScoreColor - -@Composable -fun CloneflixMovieInfoRow( - matchText: String = "New", - maturityRating: String = "TV-MA", - duration: String = "3 Seasons", - quality: String = "HD", - hasAudioDescription: Boolean = false, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - Text( - text = matchText, - color = getRatingScoreColor(matchText), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - - CloneflixMaturityRating(rating = maturityRating) - - Text( - text = duration, - color = PrimaryWhite, - style = typography.regularBody - ) - - CloneflixVideoQualityBadge(quality = quality) - - if (hasAudioDescription) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_ad), - contentDescription = "Audio Description", - tint = PrimaryWhite, - modifier = Modifier.size(20.dp) - ) - } - } -} - -@Composable -fun CloneflixMovieInfoBlock( - matchScore: String = "98% Match", - releaseYear: String = "2024", - duration: String = "3 Seasons", - maturityRating: String = "TV-MA", - advisories: String = "smoking, violence", - quality: String = "HD", - top10RankText: String? = "#2 in TV Shows Today", - hasAudioDescription: Boolean = true, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - - Column( - modifier = modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = matchScore, - color = getRatingScoreColor(matchScore), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - - Text( - text = releaseYear, - color = PrimaryWhite, - style = typography.regularBody - ) - - Text( - text = duration, - color = PrimaryWhite, - style = typography.regularBody - ) - - CloneflixVideoQualityBadge(quality = quality) - - if (hasAudioDescription) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_ad), - contentDescription = "Audio Description Available", - tint = PrimaryWhite, - modifier = Modifier.size(20.dp) - ) - } - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - CloneflixMaturityRating(rating = maturityRating) - - if (advisories.isNotBlank()) { - Text( - text = advisories, - color = Grey200, - style = typography.regularSmallBody - ) - } - } - - if (!top10RankText.isNullOrBlank()) { - CloneflixTop10RankBanner( - rankText = top10RankText, - size = CloneflixTop10Size.MEDIUM - ) - } - } -} - -@Composable -fun CloneflixMovieInfoOverview( - title: String = "House of Ninjas", - synopsis: String = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - cast: String = "Kento Kaku, Yosuke Eguchi, Tae Kimura, more", - genres: String = "TV Dramas, Japanese, TV Thrillers", - moodTags: String = "Dark, Suspenseful, Exciting", - matchScore: String = "New", - releaseYear: String = "2024", - duration: String = "3 Seasons", - maturityRating: String = "TV-MA", - advisories: String = "smoking, violence", - quality: String = "HD", - top10RankText: String? = "#2 in TV Shows Today", - onClick: (() -> Unit)? = null, - modifier: Modifier = Modifier -) { - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val activeBorder = if (isFocused) { - BorderStroke(2.dp, PrimaryWhite) - } else { - BorderStroke(1.dp, colors.border) - } - - Box( - modifier = modifier - .fillMaxWidth() - .clip(CloneflixTheme.shapes.large) - .background(colors.surface) - .border(activeBorder, CloneflixTheme.shapes.large) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - } else { - Modifier.focusable(interactionSource = interactionSource) - } - ) - .padding(dimens.spacing2Xl) - ) { - Column(modifier = Modifier.fillMaxWidth()) { - CloneflixMovieInfoBlock( - matchScore = matchScore, - releaseYear = releaseYear, - duration = duration, - maturityRating = maturityRating, - advisories = advisories, - quality = quality, - top10RankText = top10RankText, - hasAudioDescription = true - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = synopsis, - style = typography.regularBody, - color = colors.textPrimary, - lineHeight = 22.sp - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text( - text = buildAnnotatedString { - withStyle(SpanStyle(color = Grey100, fontWeight = FontWeight.Normal)) { - append("Cast: ") - } - withStyle(SpanStyle(color = PrimaryWhite, fontWeight = FontWeight.Medium)) { - append(cast) - } - }, - style = typography.regularSmallBody - ) - - Text( - text = buildAnnotatedString { - withStyle(SpanStyle(color = Grey100, fontWeight = FontWeight.Normal)) { - append("Genres: ") - } - withStyle(SpanStyle(color = PrimaryWhite, fontWeight = FontWeight.Medium)) { - append(genres) - } - }, - style = typography.regularSmallBody - ) - - Text( - text = buildAnnotatedString { - withStyle(SpanStyle(color = Grey100, fontWeight = FontWeight.Normal)) { - append("This show is: ") - } - withStyle(SpanStyle(color = PrimaryWhite, fontWeight = FontWeight.Medium)) { - append(moodTags) - } - }, - style = typography.regularSmallBody - ) - } - } - } -} - -@Preview(name = "Movie Info Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun MovieInfoComponentsPreview() { - CloneflixTheme { - Column(modifier = Modifier.padding(16.dp)) { - CloneflixMovieInfoRow() - Spacer(modifier = Modifier.height(24.dp)) - CloneflixMovieInfoOverview() - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt deleted file mode 100644 index 41d41bb2aa6..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixNextEpisodeComponents.kt +++ /dev/null @@ -1,290 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.draw.scale -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -@Composable -fun CloneflixNextEpisodeCard( - episodeNumber: String, - episodeTitle: String, - durationText: String, - synopsis: String, - onPlayNextClick: () -> Unit, - modifier: Modifier = Modifier, - countdownSeconds: Int = 10, - totalCountdownSeconds: Int = 10, - onCancelClick: (() -> Unit)? = null -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.03f else 1f, - label = "nextEpisodeScale" - ) - - val borderStroke = if (isFocused) { - BorderStroke(2.dp, PrimaryWhite) - } else { - BorderStroke(1.dp, Color(0x33FFFFFF)) - } - - val countdownProgress = (countdownSeconds.toFloat() / totalCountdownSeconds.toFloat()).coerceIn(0f, 1f) - - Surface( - modifier = modifier - .width(380.dp) - .scale(scale) - .border(borderStroke, RoundedCornerShape(12.dp)), - shape = RoundedCornerShape(12.dp), - color = TransparentBlack90 - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Box(contentAlignment = Alignment.Center) { - CircularProgressIndicator( - progress = { countdownProgress }, - modifier = Modifier.size(26.dp), - color = PrimaryRed, - trackColor = Color(0x33FFFFFF), - strokeWidth = 2.5.dp - ) - Text( - text = "$countdownSeconds", - style = CloneflixTheme.typography.regularCaption2, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - } - - Text( - text = "Next Episode in ${countdownSeconds}s", - style = CloneflixTheme.typography.mediumHeadline2, - fontSize = 16.sp, - color = PrimaryWhite - ) - } - - if (onCancelClick != null) { - Box( - modifier = Modifier - .size(28.dp) - .clip(CircleShape) - .clickable(onClick = onCancelClick), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_close), - contentDescription = "Dismiss", - tint = Grey200, - modifier = Modifier.size(16.dp) - ) - } - } - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.Top - ) { - Box( - modifier = Modifier - .width(130.dp) - .height(78.dp) - .clip(RoundedCornerShape(8.dp)) - .background( - Brush.linearGradient( - colors = listOf(Color(0xFF2C2C2C), Color(0xFF161616)) - ) - ), - contentAlignment = Alignment.Center - ) { - Box( - modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(Color(0x99000000)) - .border(1.dp, Color(0x66FFFFFF), CircleShape), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = "Play", - tint = PrimaryWhite, - modifier = Modifier.size(16.dp) - ) - } - - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(4.dp) - .background(Color(0xB2000000), RoundedCornerShape(4.dp)) - .padding(horizontal = 4.dp, vertical = 2.dp) - ) { - Text( - text = durationText, - style = CloneflixTheme.typography.regularCaption2, - fontSize = 10.sp, - color = PrimaryWhite - ) - } - } - - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = episodeNumber, - style = CloneflixTheme.typography.mediumCaption1, - color = PrimaryRed - ) - Text( - text = episodeTitle, - style = CloneflixTheme.typography.mediumBody, - color = PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = synopsis, - style = CloneflixTheme.typography.regularCaption1, - color = Grey200, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } - - val playNextInteraction = remember { MutableInteractionSource() } - val isPlayNextFocused by playNextInteraction.collectIsFocusedAsState() - - Row( - modifier = Modifier - .fillMaxWidth() - .height(40.dp) - .clip(RoundedCornerShape(8.dp)) - .background(if (isPlayNextFocused) Color(0xFFD00812) else PrimaryRed) - .then( - if (isPlayNextFocused) Modifier.border(2.dp, PrimaryWhite, RoundedCornerShape(8.dp)) - else Modifier - ) - .clickable( - interactionSource = playNextInteraction, - indication = null, - onClick = onPlayNextClick - ) - .focusable(interactionSource = playNextInteraction) - .semantics { - role = Role.Button - contentDescription = "Play Next Episode" - }, - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = "Play Next Episode", - style = CloneflixTheme.typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - } - } - } -} - -@Preview(name = "Next Episode Card Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun NextEpisodeCardPreview() { - CloneflixTheme { - Box( - modifier = Modifier - .background(Color(0xFF141414)) - .padding(24.dp) - ) { - CloneflixNextEpisodeCard( - episodeNumber = "Episode 2", - episodeTitle = "The Way of Shadows", - durationText = "48m", - synopsis = "A cryptic message from the past sends Haru on a clandestine reconnaissance mission.", - countdownSeconds = 8, - totalCountdownSeconds = 10, - onPlayNextClick = {}, - onCancelClick = {} - ) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt deleted file mode 100644 index 4474782238d..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixPlaybackSpeedSelector.kt +++ /dev/null @@ -1,261 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.draw.scale -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onKeyEvent -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 - -enum class CloneflixPlaybackSpeed(val speedMultiplier: Float, val label: String) { - SPEED_0_5X(0.5f, "0.5x"), - SPEED_0_75X(0.75f, "0.75x"), - SPEED_1_0X(1.0f, "1x (Normal)"), - SPEED_1_25X(1.25f, "1.25x"), - SPEED_1_5X(1.5f, "1.5x") -} - -@Composable -fun CloneflixPlaybackSpeedSelector( - currentSpeed: CloneflixPlaybackSpeed, - onSpeedSelected: (CloneflixPlaybackSpeed) -> Unit, - modifier: Modifier = Modifier, - onCloseClick: (() -> Unit)? = null -) { - val speeds = CloneflixPlaybackSpeed.entries - val currentIndex = speeds.indexOf(currentSpeed).coerceAtLeast(0) - - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - Surface( - modifier = modifier - .width(520.dp) - .border( - border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(1.dp, Color(0x33FFFFFF)), - shape = RoundedCornerShape(16.dp) - ) - .onKeyEvent { keyEvent -> - when (keyEvent.key) { - Key.DirectionLeft -> { - if (currentIndex > 0) { - onSpeedSelected(speeds[currentIndex - 1]) - true - } else false - } - Key.DirectionRight -> { - if (currentIndex < speeds.size - 1) { - onSpeedSelected(speeds[currentIndex + 1]) - true - } else false - } - else -> false - } - } - .focusable(interactionSource = interactionSource) - .semantics { - role = Role.Button - contentDescription = "Playback Speed: ${currentSpeed.label}" - }, - shape = RoundedCornerShape(16.dp), - color = TransparentBlack90 - ) { - Column( - modifier = Modifier.padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Playback Speed", - style = CloneflixTheme.typography.mediumHeadline2, - fontSize = 18.sp, - color = PrimaryWhite - ) - - if (onCloseClick != null) { - Box( - modifier = Modifier - .size(30.dp) - .clip(CircleShape) - .background(Color(0x33FFFFFF)) - .clickable(onClick = onCloseClick), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_close), - contentDescription = "Close", - tint = PrimaryWhite, - modifier = Modifier.size(14.dp) - ) - } - } - } - - Spacer(modifier = Modifier.height(28.dp)) - - Box( - modifier = Modifier - .fillMaxWidth() - .height(48.dp), - contentAlignment = Alignment.Center - ) { - Canvas( - modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .padding(horizontal = 24.dp) - ) { - val y = size.height / 2f - drawLine( - color = Grey450, - start = Offset(0f, y), - end = Offset(size.width, y), - strokeWidth = 3.dp.toPx() - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - speeds.forEachIndexed { index, speed -> - val isSelected = speed == currentSpeed - val scale by animateFloatAsState( - targetValue = if (isSelected) 1.25f else 1f, - label = "speedPointScale" - ) - - Box( - modifier = Modifier - .size(40.dp) - .scale(scale) - .clip(CircleShape) - .clickable { onSpeedSelected(speed) }, - contentAlignment = Alignment.Center - ) { - if (isSelected) { - Box( - modifier = Modifier - .size(28.dp) - .clip(CircleShape) - .background(PrimaryRed.copy(alpha = 0.3f)), - contentAlignment = Alignment.Center - ) { - Box( - modifier = Modifier - .size(16.dp) - .clip(CircleShape) - .background(PrimaryRed) - ) - } - } else { - Box( - modifier = Modifier - .size(12.dp) - .clip(CircleShape) - .background(Grey200) - ) - } - } - } - } - } - - Spacer(modifier = Modifier.height(8.dp)) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 4.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - speeds.forEach { speed -> - val isSelected = speed == currentSpeed - Text( - text = speed.label, - style = if (isSelected) CloneflixTheme.typography.mediumBody else CloneflixTheme.typography.regularSmallBody, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - color = if (isSelected) PrimaryWhite else Grey200, - textAlign = TextAlign.Center, - modifier = Modifier - .clickable { onSpeedSelected(speed) } - .padding(horizontal = 4.dp, vertical = 2.dp) - ) - } - } - } - } -} - -@Preview(name = "Playback Speed Selector Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun PlaybackSpeedSelectorPreview() { - CloneflixTheme { - Box( - modifier = Modifier - .background(Color(0xFF141414)) - .padding(24.dp) - ) { - CloneflixPlaybackSpeedSelector( - currentSpeed = CloneflixPlaybackSpeed.SPEED_1_0X, - onSpeedSelected = {}, - onCloseClick = {} - ) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt deleted file mode 100644 index 637ce260254..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixSampleData.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -object CloneflixSampleData { - const val SAMPLE_TITLE_HOUSE_OF_NINJAS = "House of Ninjas" - const val SAMPLE_MATCH_SCORE = "98% Match" - const val SAMPLE_MATCH_NEW = "New" - const val SAMPLE_MATURITY_RATING = "TV-MA" - const val SAMPLE_DURATION_SEASONS = "3 Seasons" - const val SAMPLE_QUALITY_HD = "HD" - const val SAMPLE_QUALITY_4K = "4K Ultra HD" - - val SAMPLE_GENRES = listOf("Violent", "Dark", "Action") - const val SAMPLE_SYNOPSIS = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats." - - val SAMPLE_MOVIE_ITEMS = listOf( - CloneflixMovieCardItem( - title = "House of Ninjas", - badge = CloneflixBadgeType.RECENTLY_ADDED, - showLogo = true, - progress = 0.65f, - runtime = "2h 18m" - ), - CloneflixMovieCardItem( - title = "Stranger Things", - badge = CloneflixBadgeType.TOP_10, - showLogo = true, - top10Rank = 1, - runtime = "55m" - ), - CloneflixMovieCardItem( - title = "The Witcher", - badge = CloneflixBadgeType.NEW_SEASON, - showLogo = true, - progress = 0.20f, - runtime = "1h 02m" - ), - CloneflixMovieCardItem( - title = "Cyberpunk: Edgerunners", - showLogo = true, - progress = 0.85f, - runtime = "24m" - ), - CloneflixMovieCardItem( - title = "Squid Game", - badge = CloneflixBadgeType.TOP_10, - showLogo = true, - top10Rank = 2, - runtime = "1h 10m" - ) - ) -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt deleted file mode 100644 index 88fbc34aca6..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoPlayerPattern.kt +++ /dev/null @@ -1,559 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.aspectRatio -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.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf -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.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -enum class CloneflixPlayerOverlayState { - DEFAULT, - SOUND_CHANGE, - SCROLL_PREVIEW, - NEXT_PREVIEW, - LIST_PREVIEW, - SPEED_CHANGE, - SUBTITLES_CHANGE -} - -@Composable -fun CloneflixVideoPlayerPattern( - title: String = "Sanctuary", - episodeLabel: String = "E1 Episode 1", - overlayState: CloneflixPlayerOverlayState = CloneflixPlayerOverlayState.DEFAULT, - onOverlayStateChange: ((CloneflixPlayerOverlayState) -> Unit)? = null, - onBackClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - var isPlaying by remember { mutableStateOf(true) } - var currentProgress by remember { mutableFloatStateOf(0.35f) } - var volume by remember { mutableFloatStateOf(0.75f) } - var isMuted by remember { mutableStateOf(false) } - var selectedSpeed by remember { mutableStateOf(CloneflixPlaybackSpeed.SPEED_1_0X) } - var selectedAudio by remember { mutableStateOf("Japanese [Original]") } - var selectedSubtitle by remember { mutableStateOf("English (CC)") } - var countdownSec by remember { mutableIntStateOf(9) } - - val sampleEpisodes = remember { - listOf( - CloneflixEpisodeItemData(1, "Sanctuary", "54m", "A troubled teenager with sumo ancestry enters the sacred world of sumo wrestling.", 1.0f, false), - CloneflixEpisodeItemData(2, "The Rebel", "48m", "Kiyoshi refuses to follow tradition and sparks outrage among seasoned stable masters.", 0.35f, true), - CloneflixEpisodeItemData(3, "The Crucible", "51m", "An intensive training camp tests the physical limits of every rookie wrestler.", 0f, false), - CloneflixEpisodeItemData(4, "Blood on the Clay", "50m", "A tense tournament match pushes Kiyoshi to the brink against a fierce veteran.", 0f, false), - CloneflixEpisodeItemData(5, "Honor Bound", "56m", "Family secrets emerge that threaten the future of the entire Ensho stable.", 0f, false) - ) - } - - val audioTracks = remember { - listOf("Japanese [Original]", "English", "Spanish (Latin America)", "French", "German") - } - - val subtitleTracks = remember { - listOf("Off", "English (CC)", "Japanese (CC)", "Spanish", "Simplified Chinese", "Traditional Chinese", "Arabic", "German", "French") - } - - Box( - modifier = modifier - .fillMaxWidth() - .aspectRatio(16f / 9f) - .clip(RoundedCornerShape(12.dp)) - .background(Color(0xFF0F0F0F)) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.radialGradient( - colors = listOf(Color(0xFF2A1115), Color(0xFF0D0D0E)), - radius = 900f - ) - ) - ) { - Text( - text = title.uppercase(), - style = CloneflixTheme.typography.logoBebas, - fontSize = 48.sp, - color = Color(0x15FFFFFF), - modifier = Modifier.align(Alignment.Center) - ) - } - - Box( - modifier = Modifier - .fillMaxWidth() - .height(100.dp) - .align(Alignment.TopCenter) - .background( - Brush.verticalGradient( - colors = listOf(Color(0xD9000000), Color.Transparent) - ) - ) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .height(140.dp) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf(Color.Transparent, Color(0xE6000000)) - ) - ) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.TopCenter) - .padding(horizontal = 20.dp, vertical = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(14.dp) - ) { - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape) - .background(TransparentWhite20) - .clickable(onClick = onBackClick), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_arrow_left), - contentDescription = "Back", - tint = PrimaryWhite, - modifier = Modifier.size(20.dp) - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = title, - style = CloneflixTheme.typography.mediumHeadline2, - fontSize = 18.sp, - color = PrimaryWhite - ) - Text( - text = "•", - style = CloneflixTheme.typography.mediumBody, - color = Grey200 - ) - Text( - text = episodeLabel, - style = CloneflixTheme.typography.regularBody, - color = Grey50 - ) - } - } - - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape) - .background(Color(0x33FFFFFF)) - .clickable {}, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_flag), - contentDescription = "Report / Flag", - tint = PrimaryWhite, - modifier = Modifier.size(18.dp) - ) - } - } - - Row( - modifier = Modifier.align(Alignment.Center), - horizontalArrangement = Arrangement.spacedBy(28.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.REPLAY_10, - onClick = { currentProgress = (currentProgress - 0.05f).coerceAtLeast(0f) }, - size = 48.dp, - iconSize = 24.dp - ) - - Box( - modifier = Modifier - .size(64.dp) - .clip(CircleShape) - .background(PrimaryWhite) - .clickable { isPlaying = !isPlaying }, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = if (isPlaying) "Pause" else "Play", - tint = PrimaryBlack, - modifier = Modifier.size(28.dp) - ) - } - - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.FORWARD_10, - onClick = { currentProgress = (currentProgress + 0.05f).coerceAtMost(1f) }, - size = 48.dp, - iconSize = 24.dp - ) - } - - if (overlayState == CloneflixPlayerOverlayState.SCROLL_PREVIEW) { - Column( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(start = 140.dp, bottom = 80.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Surface( - shape = RoundedCornerShape(8.dp), - color = Color(0xEB1A1A1A), - border = BorderStroke(1.dp, Color(0x40FFFFFF)) - ) { - Column( - modifier = Modifier.padding(6.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Box( - modifier = Modifier - .size(width = 130.dp, height = 75.dp) - .clip(RoundedCornerShape(4.dp)) - .background( - Brush.linearGradient( - colors = listOf(Color(0xFF442222), Color(0xFF1E1E1E)) - ) - ), - contentAlignment = Alignment.Center - ) { - Text( - text = "Preview Frame", - style = CloneflixTheme.typography.regularCaption2, - color = Grey200 - ) - } - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "00:32:15", - style = CloneflixTheme.typography.mediumCaption1, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - } - } - } - } - - if (overlayState == CloneflixPlayerOverlayState.SOUND_CHANGE) { - Box( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(start = 110.dp, bottom = 68.dp) - ) { - CloneflixVolumeControlPopup( - volume = volume, - onVolumeChange = { volume = it }, - isMuted = isMuted, - onToggleMute = { isMuted = !isMuted } - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .padding(horizontal = 20.dp, vertical = 12.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - CloneflixVideoProgressIndicator( - progress = currentProgress, - bufferedProgress = 0.65f, - durationMs = 54 * 60 * 1000L, - currentPositionMs = (currentProgress * 54 * 60 * 1000L).toLong(), - onProgressChange = { currentProgress = it }, - size = CloneflixProgressSize.LARGE, - showTimestamps = false - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp) - ) { - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.PLAY, - onClick = { isPlaying = !isPlaying }, - size = 38.dp, - iconSize = 20.dp - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.REPLAY_10, - onClick = { currentProgress = (currentProgress - 0.05f).coerceAtLeast(0f) }, - size = 38.dp, - iconSize = 20.dp - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.FORWARD_10, - onClick = { currentProgress = (currentProgress + 0.05f).coerceAtMost(1f) }, - size = 38.dp, - iconSize = 20.dp - ) - CloneflixPlayerControlButton( - action = if (isMuted) CloneflixPlayerControlAction.VOLUME_OFF else CloneflixPlayerControlAction.VOLUME_UP, - onClick = { - onOverlayStateChange?.invoke( - if (overlayState == CloneflixPlayerOverlayState.SOUND_CHANGE) CloneflixPlayerOverlayState.DEFAULT - else CloneflixPlayerOverlayState.SOUND_CHANGE - ) - }, - isSelected = overlayState == CloneflixPlayerOverlayState.SOUND_CHANGE, - size = 38.dp, - iconSize = 20.dp - ) - } - - Text( - text = "$title • $episodeLabel", - style = CloneflixTheme.typography.mediumBody, - fontSize = 13.sp, - color = Grey200, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp) - ) { - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.NEXT_EPISODE, - onClick = { - onOverlayStateChange?.invoke( - if (overlayState == CloneflixPlayerOverlayState.NEXT_PREVIEW) CloneflixPlayerOverlayState.DEFAULT - else CloneflixPlayerOverlayState.NEXT_PREVIEW - ) - }, - isSelected = overlayState == CloneflixPlayerOverlayState.NEXT_PREVIEW, - size = 38.dp, - iconSize = 20.dp - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.EPISODES, - onClick = { - onOverlayStateChange?.invoke( - if (overlayState == CloneflixPlayerOverlayState.LIST_PREVIEW) CloneflixPlayerOverlayState.DEFAULT - else CloneflixPlayerOverlayState.LIST_PREVIEW - ) - }, - isSelected = overlayState == CloneflixPlayerOverlayState.LIST_PREVIEW, - size = 38.dp, - iconSize = 20.dp - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.SUBTITLES, - onClick = { - onOverlayStateChange?.invoke( - if (overlayState == CloneflixPlayerOverlayState.SUBTITLES_CHANGE) CloneflixPlayerOverlayState.DEFAULT - else CloneflixPlayerOverlayState.SUBTITLES_CHANGE - ) - }, - isSelected = overlayState == CloneflixPlayerOverlayState.SUBTITLES_CHANGE, - size = 38.dp, - iconSize = 20.dp - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.SPEED, - onClick = { - onOverlayStateChange?.invoke( - if (overlayState == CloneflixPlayerOverlayState.SPEED_CHANGE) CloneflixPlayerOverlayState.DEFAULT - else CloneflixPlayerOverlayState.SPEED_CHANGE - ) - }, - isSelected = overlayState == CloneflixPlayerOverlayState.SPEED_CHANGE, - size = 38.dp, - iconSize = 20.dp - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.FULLSCREEN, - onClick = {}, - size = 38.dp, - iconSize = 20.dp - ) - } - } - } - - if (overlayState == CloneflixPlayerOverlayState.NEXT_PREVIEW) { - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(end = 24.dp, bottom = 80.dp) - ) { - CloneflixNextEpisodeCard( - episodeNumber = "Episode 2", - episodeTitle = "The Way of Shadows", - durationText = "48m", - synopsis = "A cryptic message from the past sends Haru on a clandestine reconnaissance mission.", - countdownSeconds = countdownSec, - totalCountdownSeconds = 10, - onPlayNextClick = { - onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) - }, - onCancelClick = { - onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) - } - ) - } - } - - if (overlayState == CloneflixPlayerOverlayState.LIST_PREVIEW) { - Box( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - ) { - CloneflixEpisodesDrawer( - seriesTitle = title.uppercase(), - seasonName = "Season 1 Episodes", - episodes = sampleEpisodes, - onEpisodeSelected = { - onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) - }, - onCloseClick = { - onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) - } - ) - } - } - - if (overlayState == CloneflixPlayerOverlayState.SPEED_CHANGE) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color(0x80000000)) - .clickable { onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) }, - contentAlignment = Alignment.Center - ) { - CloneflixPlaybackSpeedSelector( - currentSpeed = selectedSpeed, - onSpeedSelected = { - selectedSpeed = it - onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) - }, - onCloseClick = { - onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) - } - ) - } - } - - if (overlayState == CloneflixPlayerOverlayState.SUBTITLES_CHANGE) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color(0x80000000)) - .clickable { onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) }, - contentAlignment = Alignment.Center - ) { - CloneflixAudioSubtitlesSelector( - audioTracks = audioTracks, - selectedAudio = selectedAudio, - onAudioSelected = { selectedAudio = it }, - subtitleTracks = subtitleTracks, - selectedSubtitle = selectedSubtitle, - onSubtitleSelected = { selectedSubtitle = it }, - onCloseClick = { - onOverlayStateChange?.invoke(CloneflixPlayerOverlayState.DEFAULT) - } - ) - } - } - } -} - -@Preview(name = "Video Player Pattern Preview - Default", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun VideoPlayerPatternDefaultPreview() { - CloneflixTheme { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color(0xFF141414)) - .padding(24.dp) - ) { - CloneflixVideoPlayerPattern( - overlayState = CloneflixPlayerOverlayState.DEFAULT - ) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt deleted file mode 100644 index 4117cd2992c..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVideoProgressIndicator.kt +++ /dev/null @@ -1,316 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.focusable -import androidx.compose.foundation.gestures.detectHorizontalDragGestures -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.interaction.collectIsHoveredAsState -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.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -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.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onKeyEvent -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.semantics.ProgressBarRangeInfo -import androidx.compose.ui.semantics.progressBarRangeInfo -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite30 - -enum class CloneflixProgressSize { - REGULAR, - LARGE -} - -@Composable -fun CloneflixVideoProgressIndicator( - progress: Float, - onProgressChange: (Float) -> Unit, - modifier: Modifier = Modifier, - bufferedProgress: Float = 0f, - durationMs: Long = 0L, - currentPositionMs: Long = 0L, - size: CloneflixProgressSize = CloneflixProgressSize.LARGE, - showTimestamps: Boolean = true, - showRemainingTime: Boolean = false, - enabled: Boolean = true, - isHoveredOrActive: Boolean? = null -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val isHoveredInternal by interactionSource.collectIsHoveredAsState() - val isHovered = isHoveredOrActive ?: (isFocused || isHoveredInternal) - - val trackHeight by animateDpAsState( - targetValue = when { - isHovered && size == CloneflixProgressSize.LARGE -> 8.dp - isHovered -> 6.dp - size == CloneflixProgressSize.LARGE -> 5.dp - else -> 3.5.dp - }, - label = "trackHeight" - ) - - val thumbRadius by animateDpAsState( - targetValue = when { - isHovered && size == CloneflixProgressSize.LARGE -> 9.dp - isHovered -> 7.dp - size == CloneflixProgressSize.LARGE -> 7.dp - else -> 5.dp - }, - label = "thumbRadius" - ) - - val haloRadius by animateDpAsState( - targetValue = if (isHovered) (thumbRadius + 8.dp) else 0.dp, - label = "haloRadius" - ) - - var componentWidth by remember { mutableFloatStateOf(1f) } - var isDragging by remember { mutableStateOf(false) } - var dragProgress by remember { mutableFloatStateOf(progress) } - - val activeProgress = if (isDragging) dragProgress else progress.coerceIn(0f, 1f) - val animatedProgress by animateFloatAsState( - targetValue = activeProgress, - label = "animatedProgress" - ) - - val effectiveCurrentMs = if (isDragging && durationMs > 0L) { - (dragProgress * durationMs).toLong() - } else { - currentPositionMs - } - - val elapsedText = remember(effectiveCurrentMs) { - formatTimeMs(effectiveCurrentMs) - } - - val endText = remember(effectiveCurrentMs, durationMs, showRemainingTime) { - if (showRemainingTime && durationMs > effectiveCurrentMs) { - "-${formatTimeMs(durationMs - effectiveCurrentMs)}" - } else { - formatTimeMs(durationMs) - } - } - - Column( - modifier = modifier - .fillMaxWidth() - .semantics { - progressBarRangeInfo = ProgressBarRangeInfo( - current = activeProgress, - range = 0f..1f - ) - } - .onKeyEvent { keyEvent -> - if (!enabled) return@onKeyEvent false - when (keyEvent.key) { - Key.DirectionLeft -> { - val step = if (durationMs > 0L) (10_000f / durationMs).coerceAtLeast(0.01f) else 0.05f - val newProgress = (activeProgress - step).coerceIn(0f, 1f) - onProgressChange(newProgress) - true - } - Key.DirectionRight -> { - val step = if (durationMs > 0L) (10_000f / durationMs).coerceAtLeast(0.01f) else 0.05f - val newProgress = (activeProgress + step).coerceIn(0f, 1f) - onProgressChange(newProgress) - true - } - else -> false - } - } - .focusable(enabled = enabled, interactionSource = interactionSource) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(32.dp) - .onSizeChanged { componentWidth = it.width.toFloat().coerceAtLeast(1f) } - .pointerInput(enabled) { - if (!enabled) return@pointerInput - detectTapGestures { offset -> - val tapProgress = (offset.x / componentWidth).coerceIn(0f, 1f) - onProgressChange(tapProgress) - } - } - .pointerInput(enabled) { - if (!enabled) return@pointerInput - detectHorizontalDragGestures( - onDragStart = { offset -> - isDragging = true - dragProgress = (offset.x / componentWidth).coerceIn(0f, 1f) - }, - onDragEnd = { - isDragging = false - onProgressChange(dragProgress) - }, - onDragCancel = { - isDragging = false - }, - onHorizontalDrag = { _, dragAmount -> - val deltaProgress = dragAmount / componentWidth - dragProgress = (dragProgress + deltaProgress).coerceIn(0f, 1f) - onProgressChange(dragProgress) - } - ) - }, - contentAlignment = Alignment.CenterStart - ) { - Canvas( - modifier = Modifier - .fillMaxWidth() - .height(32.dp) - ) { - val centerY = this.size.height / 2f - val totalWidth = this.size.width - val trackHeightPx = trackHeight.toPx() - val cornerRadius = CornerRadius(trackHeightPx / 2f, trackHeightPx / 2f) - - drawRoundRect( - color = Grey450, - topLeft = Offset(0f, centerY - trackHeightPx / 2f), - size = Size(totalWidth, trackHeightPx), - cornerRadius = cornerRadius - ) - - val bufferedWidth = (bufferedProgress.coerceIn(0f, 1f) * totalWidth) - if (bufferedWidth > 0f) { - drawRoundRect( - color = Grey50.copy(alpha = 0.65f), - topLeft = Offset(0f, centerY - trackHeightPx / 2f), - size = Size(bufferedWidth, trackHeightPx), - cornerRadius = cornerRadius - ) - } - - val playedWidth = (animatedProgress * totalWidth) - if (playedWidth > 0f) { - drawRoundRect( - color = PrimaryRed, - topLeft = Offset(0f, centerY - trackHeightPx / 2f), - size = Size(playedWidth, trackHeightPx), - cornerRadius = cornerRadius - ) - } - - if (haloRadius.value > 0f) { - drawCircle( - color = PrimaryRed.copy(alpha = 0.25f), - radius = haloRadius.toPx(), - center = Offset(playedWidth, centerY) - ) - } - - drawCircle( - color = PrimaryRed, - radius = thumbRadius.toPx(), - center = Offset(playedWidth, centerY) - ) - - if (isHovered) { - drawCircle( - color = PrimaryWhite, - radius = (thumbRadius * 0.4f).toPx(), - center = Offset(playedWidth, centerY) - ) - } - } - } - - if (showTimestamps) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 2.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = elapsedText, - style = CloneflixTheme.typography.regularSmallBody, - color = PrimaryWhite - ) - Text( - text = endText, - style = CloneflixTheme.typography.regularSmallBody, - color = Grey200 - ) - } - } - } -} - -private fun formatTimeMs(ms: Long): String { - val totalSeconds = (ms / 1000).coerceAtLeast(0) - val hours = totalSeconds / 3600 - val minutes = (totalSeconds % 3600) / 60 - val seconds = totalSeconds % 60 - return if (hours > 0) { - "%d:%02d:%02d".format(hours, minutes, seconds) - } else { - "%02d:%02d".format(minutes, seconds) - } -} - -@Preview(name = "Progress Indicator Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun VideoProgressIndicatorPreview() { - CloneflixTheme { - Column( - modifier = Modifier - .background(Color(0xFF141414)) - .padding(24.dp), - verticalArrangement = Arrangement.spacedBy(20.dp) - ) { - CloneflixVideoProgressIndicator( - progress = 0.05f, - bufferedProgress = 0.25f, - durationMs = 3600_000L, - currentPositionMs = 180_000L, - onProgressChange = {} - ) - CloneflixVideoProgressIndicator( - progress = 0.60f, - bufferedProgress = 0.85f, - durationMs = 5400_000L, - currentPositionMs = 3240_000L, - isHoveredOrActive = true, - onProgressChange = {} - ) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt deleted file mode 100644 index f533e633d87..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/components/CloneflixVolumeSlider.kt +++ /dev/null @@ -1,258 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.components - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.gestures.detectVerticalDragGestures -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.interaction.collectIsHoveredAsState -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -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.draw.scale -import androidx.compose.ui.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onKeyEvent -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.ProgressBarRangeInfo -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.progressBarRangeInfo -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey450 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.SurfaceDark -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack90 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -enum class CloneflixVolumeLevel { - HIGH, - MIDDLE, - MUTE -} - -@Composable -fun CloneflixVerticalVolumeBar( - volume: Float, - onVolumeChange: (Float) -> Unit, - modifier: Modifier = Modifier, - height: Float = 100f, - width: Float = 6f, - enabled: Boolean = true -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - var trackHeightPx by remember { mutableFloatStateOf(1f) } - val animatedVolume by animateFloatAsState( - targetValue = volume.coerceIn(0f, 1f), - label = "animatedVolume" - ) - - Box( - modifier = modifier - .width(36.dp) - .height(110.dp) - .onSizeChanged { trackHeightPx = it.height.toFloat().coerceAtLeast(1f) } - .semantics { - progressBarRangeInfo = ProgressBarRangeInfo( - current = volume, - range = 0f..1f - ) - } - .onKeyEvent { keyEvent -> - if (!enabled) return@onKeyEvent false - when (keyEvent.key) { - Key.DirectionUp -> { - onVolumeChange((volume + 0.1f).coerceIn(0f, 1f)) - true - } - Key.DirectionDown -> { - onVolumeChange((volume - 0.1f).coerceIn(0f, 1f)) - true - } - else -> false - } - } - .pointerInput(enabled) { - if (!enabled) return@pointerInput - detectVerticalDragGestures { change, _ -> - val y = change.position.y - val newVol = (1f - (y / trackHeightPx)).coerceIn(0f, 1f) - onVolumeChange(newVol) - } - } - .focusable(enabled = enabled, interactionSource = interactionSource), - contentAlignment = Alignment.Center - ) { - Canvas(modifier = Modifier.size(width = 36.dp, height = 100.dp)) { - val totalH = size.height - val totalW = size.width - val barW = 6.dp.toPx() - val barX = (totalW - barW) / 2f - val corner = CornerRadius(barW / 2f, barW / 2f) - - drawRoundRect( - color = Grey450, - topLeft = Offset(barX, 0f), - size = Size(barW, totalH), - cornerRadius = corner - ) - - val fillH = animatedVolume * totalH - if (fillH > 0f) { - drawRoundRect( - color = PrimaryRed, - topLeft = Offset(barX, totalH - fillH), - size = Size(barW, fillH), - cornerRadius = corner - ) - } - - val thumbY = totalH - fillH - val thumbRadius = 7.dp.toPx() - drawCircle( - color = PrimaryWhite, - radius = thumbRadius, - center = Offset(totalW / 2f, thumbY.coerceIn(thumbRadius, totalH - thumbRadius)) - ) - } - } -} - -@Composable -fun CloneflixVolumeControlPopup( - volume: Float, - onVolumeChange: (Float) -> Unit, - isMuted: Boolean, - onToggleMute: () -> Unit, - modifier: Modifier = Modifier, - isExpanded: Boolean = true -) { - val volumeLevel = when { - isMuted || volume <= 0.01f -> CloneflixVolumeLevel.MUTE - volume < 0.5f -> CloneflixVolumeLevel.MIDDLE - else -> CloneflixVolumeLevel.HIGH - } - - Surface( - modifier = modifier, - shape = RoundedCornerShape(24.dp), - color = TransparentBlack90, - border = BorderStroke(1.dp, Color(0x33FFFFFF)) - ) { - Column( - modifier = Modifier.padding(horizontal = 10.dp, vertical = 14.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - CloneflixVerticalVolumeBar( - volume = if (isMuted) 0f else volume, - onVolumeChange = { - if (isMuted) onToggleMute() - onVolumeChange(it) - } - ) - - val iconRes = when (volumeLevel) { - CloneflixVolumeLevel.HIGH -> R.drawable.ic_baseline_volume_up_24 - CloneflixVolumeLevel.MIDDLE -> R.drawable.ic_baseline_volume_up_24 - CloneflixVolumeLevel.MUTE -> R.drawable.ic_baseline_volume_mute_24 - } - - val iconColor = if (volumeLevel == CloneflixVolumeLevel.MUTE) Grey200 else PrimaryWhite - - Box( - modifier = Modifier - .size(36.dp) - .clip(CircleShape) - .clickable(onClick = onToggleMute) - .padding(4.dp), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = "Toggle Mute", - tint = iconColor, - modifier = Modifier.size(22.dp) - ) - } - } - } -} - -@Preview(name = "Volume Slider Preview", showBackground = true, backgroundColor = 0xFF141414) -@Composable -private fun VolumeSliderPreview() { - CloneflixTheme { - Row( - modifier = Modifier - .background(Color(0xFF141414)) - .padding(24.dp), - horizontalArrangement = Arrangement.spacedBy(24.dp) - ) { - CloneflixVolumeControlPopup( - volume = 0.85f, - onVolumeChange = {}, - isMuted = false, - onToggleMute = {} - ) - CloneflixVolumeControlPopup( - volume = 0.40f, - onVolumeChange = {}, - isMuted = false, - onToggleMute = {} - ) - CloneflixVolumeControlPopup( - volume = 0f, - onVolumeChange = {}, - isMuted = true, - onToggleMute = {} - ) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt deleted file mode 100644 index 04e474a52d6..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAboutComposeScreen.kt +++ /dev/null @@ -1,149 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import androidx.compose.foundation.background -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.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonSize -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonVariant -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -@Composable -fun CloneflixAboutComposeScreen( - onFollowClick: () -> Unit = {}, - onFeedbackClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val dimens = CloneflixTheme.dimens - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val scrollState = rememberScrollState() - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "About", - subtitle = "Cloneflix Design System 2024 (Website Version)", - iconRes = R.drawable.cloneflix_ic_about - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacing2Xl)) { - Text( - text = "Hello! I'm Ivanna Heraskina, a Product Designer from San Francisco 🌉.", - style = typography.regularHeadline1, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "The Cloneflix Design System 2024 (Website Version) was created with love and dedication. Whether you're a designer, developer, or just interested in UI/UX, feel free to use this toolkit. Use it to bring your creative ideas to life.", - style = typography.regularBody, - color = colors.textPrimary, - lineHeight = 22.sp - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "If you have any thoughts or feedback, or simply want to say hi, add me on X.com (@ivannaheraskina).", - style = typography.regularSmallBody, - color = colors.textDimmed, - lineHeight = 20.sp - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Text( - text = "If it was useful, your support would mean a lot!", - style = typography.regularSmallBody, - color = colors.textMuted - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Row(modifier = Modifier.fillMaxWidth()) { - CloneflixButton( - text = "Follow @ivannaheraskina", - onClick = onFollowClick, - modifier = Modifier.weight(1f), - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.MEDIUM, - icon = painterResource(id = R.drawable.cloneflix_ic_open_in_new) - ) - - Spacer(modifier = Modifier.width(dimens.spacingM)) - - CloneflixButton( - text = "Feedback", - onClick = onFeedbackClick, - variant = CloneflixButtonVariant.OUTLINE, - size = CloneflixButtonSize.MEDIUM, - icon = painterResource(id = R.drawable.cloneflix_ic_feedback) - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.ELEVATED - ) { - Column(modifier = Modifier.padding(dimens.spacing2Xl)) { - Text( - text = "Design System Architecture", - style = typography.mediumHeadline2, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Text( - text = "• Pure Jetpack Compose UI with reactive theme\n• 33+ Figma color tokens\n• Netflix Sans & Bebas Neue typography hierarchy\n• Reusable modular components with D-pad & touch compatibility", - style = typography.regularSmallBody, - color = colors.textMuted, - lineHeight = 22.sp - ) - } - } - } -} - -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixAboutScreenPreview() { - CloneflixTheme { - CloneflixAboutComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt deleted file mode 100644 index c518dfe85b5..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixAvatarsComposeScreen.kt +++ /dev/null @@ -1,479 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -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.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -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.draw.scale -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAvatarGraphic -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAvatarModel -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLargeAvatar -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixOfficialAvatars -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixProfileMenuDropdown -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixSmallAvatar -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixUserProfilesRow -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun CloneflixAvatarsComposeScreen( - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - var selectedProfileName by remember { mutableStateOf("Jennifer") } - var menuExpanded by remember { mutableStateOf(false) } - var activeOfficialAvatar by remember { mutableStateOf(CloneflixOfficialAvatars.Red) } - - val sampleProfiles = remember { - listOf( - "Jennifer" to CloneflixOfficialAvatars.Red, - "Bill" to CloneflixOfficialAvatars.FluffyBlue, - "Alise" to CloneflixOfficialAvatars.Green, - "James" to CloneflixOfficialAvatars.Purple - ) - } - - val dropdownProfiles = remember { - listOf( - "Jennifer" to CloneflixOfficialAvatars.Red, - "Max" to CloneflixOfficialAvatars.Blue, - "Beyoncé" to CloneflixOfficialAvatars.Pink - ) - } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "User Profile Avatar", - subtitle = "Small & Large Variants • Who's Watching Sample • Profile Menu • Official Avatars", - iconRes = R.drawable.cloneflix_ic_account - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "SMALL AVATAR (32DP)", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Header & Compact Navigation Avatars", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Type=UserProfilePicture (Standalone) and Type=UserProfileMenu (With Dropdown Arrow).", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - horizontalArrangement = Arrangement.spacedBy(32.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixSmallAvatar( - avatar = CloneflixOfficialAvatars.Purple, - showMenuArrow = false, - onClick = { - Toast.makeText(context, "Small Avatar Clicked", Toast.LENGTH_SHORT).show() - } - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "UserProfilePicture", - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixSmallAvatar( - avatar = CloneflixOfficialAvatars.Red, - showMenuArrow = true, - isExpanded = menuExpanded, - onClick = { menuExpanded = !menuExpanded } - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "UserProfileMenu", - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "LARGE AVATAR & FOCUS STATES (144DP)", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Profile Avatar & Add Profile (Default vs Hover/Focused)", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Google TV / Android TV D-Pad focus traversal with white border and scale effect.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - FlowRow( - horizontalArrangement = Arrangement.spacedBy(24.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixLargeAvatar( - name = "James", - avatar = CloneflixOfficialAvatars.Purple, - isHoverState = false, - onClick = { Toast.makeText(context, "Selected James (Default)", Toast.LENGTH_SHORT).show() } - ) - Text(text = "Default", style = typography.regularCaption1, color = colors.textSecondary) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixLargeAvatar( - name = "James", - avatar = CloneflixOfficialAvatars.Purple, - isHoverState = true, - onClick = { Toast.makeText(context, "Selected James (Hover/Focus)", Toast.LENGTH_SHORT).show() } - ) - Text(text = "Hover / Focus", style = typography.regularCaption1, color = colors.primary) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixLargeAvatar( - name = "Add Profile", - avatar = null, - isAddProfile = true, - isHoverState = false, - onClick = { Toast.makeText(context, "Add Profile (Default)", Toast.LENGTH_SHORT).show() } - ) - Text(text = "Default", style = typography.regularCaption1, color = colors.textSecondary) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixLargeAvatar( - name = "Add Profile", - avatar = null, - isAddProfile = true, - isHoverState = true, - onClick = { Toast.makeText(context, "Add Profile (Hover/Focus)", Toast.LENGTH_SHORT).show() } - ) - Text(text = "Hover / Focus", style = typography.regularCaption1, color = colors.primary) - } - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "SAMPLE: WHO'S WATCHING? (PROFILE SELECTOR)", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = dimens.spacing2Xl, horizontal = dimens.spacingL) - ) { - Text( - text = "Who's Watching?", - style = typography.boldTitle1, - color = PrimaryWhite, - textAlign = TextAlign.Center - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Active Profile: $selectedProfileName", - style = typography.mediumSmallBody, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - CloneflixUserProfilesRow( - profiles = sampleProfiles, - selectedProfileName = selectedProfileName, - onProfileClick = { name, _ -> - selectedProfileName = name - Toast.makeText(context, "Switched to profile $name", Toast.LENGTH_SHORT).show() - }, - onAddProfileClick = { - Toast.makeText(context, "Add new profile clicked", Toast.LENGTH_SHORT).show() - } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "SAMPLE: PROFILE MENU (HEADER POPOVER)", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Interactive Profile Dropdown Menu", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Top-right navigation popover with profile switching, management actions, and sign out.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacingL), - contentAlignment = Alignment.CenterStart - ) { - CloneflixProfileMenuDropdown( - currentProfiles = dropdownProfiles, - onProfileSelected = { name -> - Toast.makeText(context, "Selected $name", Toast.LENGTH_SHORT).show() - }, - onManageProfilesClick = { - Toast.makeText(context, "Manage Profiles", Toast.LENGTH_SHORT).show() - }, - onTransferProfilesClick = { - Toast.makeText(context, "Transfer Profiles", Toast.LENGTH_SHORT).show() - }, - onAccountClick = { - Toast.makeText(context, "Account Settings", Toast.LENGTH_SHORT).show() - }, - onHelpCenterClick = { - Toast.makeText(context, "Help Center", Toast.LENGTH_SHORT).show() - }, - onSignOutClick = { - Toast.makeText(context, "Sign out of Cloneflix", Toast.LENGTH_SHORT).show() - } - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "OFFICIAL AVATARS CATALOG", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Main / Popular (16 Official Avatars)", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Selected Avatar: ${activeOfficialAvatar.name}", - style = typography.regularCaption1, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - FlowRow( - horizontalArrangement = Arrangement.spacedBy(14.dp), - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - CloneflixOfficialAvatars.Popular.forEach { avatar -> - AvatarGridItem( - avatar = avatar, - isSelected = avatar.id == activeOfficialAvatar.id, - onClick = { activeOfficialAvatar = avatar } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "All Others (Extended Catalog 01..36)", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Algorithmic avatar variations for user profile customization.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - FlowRow( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - CloneflixOfficialAvatars.getAllOthers(36).forEach { avatar -> - AvatarGridItem( - avatar = avatar, - isSelected = avatar.id == activeOfficialAvatar.id, - size = 64.dp, - onClick = { activeOfficialAvatar = avatar } - ) - } - } - } - } - } -} - -@Composable -private fun AvatarGridItem( - avatar: CloneflixAvatarModel, - isSelected: Boolean, - modifier: Modifier = Modifier, - size: androidx.compose.ui.unit.Dp = 80.dp, - onClick: () -> Unit -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val scale by androidx.compose.animation.core.animateFloatAsState( - targetValue = if (isFocused || isSelected) 1.08f else 1.0f, - label = "GridAvatarScale" - ) - - val borderStroke = when { - isFocused || isSelected -> androidx.compose.foundation.BorderStroke(2.5.dp, PrimaryWhite) - else -> androidx.compose.foundation.BorderStroke(1.dp, Color(0x33FFFFFF)) - } - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .scale(scale) - .clip(RoundedCornerShape(6.dp)) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .padding(2.dp) - .semantics { - role = Role.Button - contentDescription = "Avatar ${avatar.name}${if (isSelected) ", selected" else ""}" - } - ) { - Box( - modifier = Modifier - .size(size) - .clip(RoundedCornerShape(6.dp)) - .border(borderStroke, RoundedCornerShape(6.dp)) - ) { - CloneflixAvatarGraphic(avatar = avatar) - } - - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = avatar.name, - style = CloneflixTheme.typography.regularCaption2, - color = if (isFocused || isSelected) PrimaryWhite else Grey200, - fontSize = 11.sp, - maxLines = 1 - ) - } -} - -@Preview(name = "Avatars Screen Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "Avatars Screen TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixAvatarsScreenPreview() { - CloneflixTheme { - CloneflixAvatarsComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt deleted file mode 100644 index f97f21540cf..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixButtonsComposeScreen.kt +++ /dev/null @@ -1,470 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.foundation.background -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -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.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonSize -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonVariant -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCheckbox -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCircleActionButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCircleActionType -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDivider -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroActionPattern -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroRatingPattern -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroReplayButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroSoundButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlayerControlAction -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlayerControlButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPreviewActionsPattern -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixThumbsReactionPill -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoPlayerControlBar -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite - -@Composable -fun CloneflixButtonsComposeScreen( - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - var rememberMeChecked by remember { mutableStateOf(true) } - var isAddedToList by remember { mutableStateOf(false) } - var isMutedHero by remember { mutableStateOf(true) } - var isPlayerPlaying by remember { mutableStateOf(true) } - var isPlayerMuted by remember { mutableStateOf(false) } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Buttons", - subtitle = "Action Buttons • Media Control Buttons • Compositions & Patterns", - iconRes = R.drawable.cloneflix_ic_play - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "ACTION BUTTONS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Sign In (Primary Red • Large & Small)", - style = typography.mediumBody, - color = PrimaryWhite - ) - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixButton( - text = "Sign In", - onClick = { Toast.makeText(context, "Sign In Large Clicked", Toast.LENGTH_SHORT).show() }, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.LARGE - ) - CloneflixButton( - text = "Sign In", - onClick = { Toast.makeText(context, "Sign In Small Clicked", Toast.LENGTH_SHORT).show() }, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.SMALL - ) - } - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Secondary / Dark Action", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixButton( - text = "Use a Sign-In Code", - onClick = { Toast.makeText(context, "Use a Sign-In Code Clicked", Toast.LENGTH_SHORT).show() }, - variant = CloneflixButtonVariant.DARK_SECONDARY, - size = CloneflixButtonSize.MEDIUM - ) - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Hero CTA Actions (Play & More Info)", - style = typography.mediumBody, - color = PrimaryWhite - ) - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixButton( - text = "Play", - onClick = { Toast.makeText(context, "Play Clicked", Toast.LENGTH_SHORT).show() }, - variant = CloneflixButtonVariant.SECONDARY, - size = CloneflixButtonSize.MEDIUM, - icon = painterResource(id = R.drawable.cloneflix_ic_play) - ) - CloneflixButton( - text = "More Info", - onClick = { Toast.makeText(context, "More Info Clicked", Toast.LENGTH_SHORT).show() }, - variant = CloneflixButtonVariant.MORE_INFO, - size = CloneflixButtonSize.MEDIUM, - icon = painterResource(id = R.drawable.cloneflix_ic_info) - ) - } - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Get Started Action (Onboarding)", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixButton( - text = "Get Started", - onClick = { Toast.makeText(context, "Get Started Clicked", Toast.LENGTH_SHORT).show() }, - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.LARGE, - icon = painterResource(id = R.drawable.cloneflix_ic_chevron_right), - modifier = Modifier.height(56.dp) - ) - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Manage Profiles (Outlined Border)", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixButton( - text = "Manage Profiles", - onClick = { Toast.makeText(context, "Manage Profiles Clicked", Toast.LENGTH_SHORT).show() }, - variant = CloneflixButtonVariant.OUTLINE, - size = CloneflixButtonSize.MEDIUM - ) - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Checkbox Button (Remember Me)", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixCheckbox( - checked = rememberMeChecked, - onCheckedChange = { rememberMeChecked = it }, - label = "Remember me" - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "MEDIA CONTROL BUTTONS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text( - text = "Video Player Controls (48dp / D-pad focusable)", - style = typography.mediumBody, - color = PrimaryWhite - ) - Text( - text = "Play, 10s Rewind, 10s Forward, Sound, Mute, Next Ep, Subtitles, Speed, Episodes, Fullscreen", - style = typography.regularCaption1, - color = Grey200 - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.PLAY, - onClick = { Toast.makeText(context, "Play", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.REPLAY_10, - onClick = { Toast.makeText(context, "10s Back", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.FORWARD_10, - onClick = { Toast.makeText(context, "10s Forward", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.VOLUME_UP, - onClick = { Toast.makeText(context, "Volume Up", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.VOLUME_OFF, - onClick = { Toast.makeText(context, "Mute", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.NEXT_EPISODE, - onClick = { Toast.makeText(context, "Next Episode", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.SUBTITLES, - onClick = { Toast.makeText(context, "Subtitles", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.SPEED, - onClick = { Toast.makeText(context, "Speed", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.EPISODES, - onClick = { Toast.makeText(context, "Episodes", Toast.LENGTH_SHORT).show() } - ) - CloneflixPlayerControlButton( - action = CloneflixPlayerControlAction.FULLSCREEN, - onClick = { Toast.makeText(context, "Fullscreen", Toast.LENGTH_SHORT).show() } - ) - } - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text( - text = "Movie Preview Action Buttons (40dp)", - style = typography.mediumBody, - color = PrimaryWhite - ) - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixCircleActionButton( - type = CloneflixCircleActionType.PLAY, - onClick = { Toast.makeText(context, "Play Circle", Toast.LENGTH_SHORT).show() } - ) - CloneflixCircleActionButton( - type = if (isAddedToList) CloneflixCircleActionType.ADDED else CloneflixCircleActionType.ADD, - onClick = { - isAddedToList = !isAddedToList - Toast.makeText(context, if (isAddedToList) "Added to list" else "Removed from list", Toast.LENGTH_SHORT).show() - } - ) - CloneflixCircleActionButton( - type = CloneflixCircleActionType.THUMB_UP, - onClick = { Toast.makeText(context, "Liked", Toast.LENGTH_SHORT).show() } - ) - CloneflixCircleActionButton( - type = CloneflixCircleActionType.ARROW_DOWN, - onClick = { Toast.makeText(context, "Details", Toast.LENGTH_SHORT).show() } - ) - } - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text( - text = "Thumbs Multi-Reaction Pill (Dislike • Like • Love)", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixThumbsReactionPill( - onDislikeClick = { Toast.makeText(context, "Dislike Clicked", Toast.LENGTH_SHORT).show() }, - onLikeClick = { Toast.makeText(context, "Like Clicked", Toast.LENGTH_SHORT).show() }, - onLoveClick = { Toast.makeText(context, "Love Clicked", Toast.LENGTH_SHORT).show() } - ) - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text( - text = "Hero Banner Video Controls (35dp Sound Toggle & Replay)", - style = typography.mediumBody, - color = PrimaryWhite - ) - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixHeroSoundButton( - isMuted = isMutedHero, - onToggleMute = { - isMutedHero = !isMutedHero - Toast.makeText(context, if (isMutedHero) "Audio Muted" else "Audio Playing", Toast.LENGTH_SHORT).show() - } - ) - CloneflixHeroReplayButton( - onClick = { Toast.makeText(context, "Replay Video", Toast.LENGTH_SHORT).show() } - ) - } - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "PATTERNS & COMPOSITIONS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Pattern: Hero CTAs (Play + More Info)", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixHeroActionPattern( - onPlayClick = { Toast.makeText(context, "Hero Play", Toast.LENGTH_SHORT).show() }, - onMoreInfoClick = { Toast.makeText(context, "Hero More Info", Toast.LENGTH_SHORT).show() } - ) - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Pattern: Movie Card Quick Actions", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixPreviewActionsPattern( - isAdded = isAddedToList, - onPlayClick = { Toast.makeText(context, "Play Preview", Toast.LENGTH_SHORT).show() }, - onAddClick = { isAddedToList = !isAddedToList }, - onThumbUpClick = { Toast.makeText(context, "Thumb Up", Toast.LENGTH_SHORT).show() }, - onExpandClick = { Toast.makeText(context, "Expand Details", Toast.LENGTH_SHORT).show() } - ) - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Pattern: Hero Banner Rating + Sound", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixHeroRatingPattern( - isMuted = isMutedHero, - rating = "TV-MA", - onToggleMute = { isMutedHero = !isMutedHero } - ) - } - - CloneflixDivider() - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Pattern: Video Player Bottom Control Bar", - style = typography.mediumBody, - color = PrimaryWhite - ) - CloneflixVideoPlayerControlBar( - isPlaying = isPlayerPlaying, - isMuted = isPlayerMuted, - onPlayPauseToggle = { isPlayerPlaying = !isPlayerPlaying }, - onToggleMute = { isPlayerMuted = !isPlayerMuted }, - onSeekBack = { Toast.makeText(context, "-10s", Toast.LENGTH_SHORT).show() }, - onSeekForward = { Toast.makeText(context, "+10s", Toast.LENGTH_SHORT).show() }, - onNextEpisode = { Toast.makeText(context, "Next Episode", Toast.LENGTH_SHORT).show() }, - onEpisodesClick = { Toast.makeText(context, "Episode List Drawer", Toast.LENGTH_SHORT).show() }, - onSubtitlesClick = { Toast.makeText(context, "Subtitles Dialog", Toast.LENGTH_SHORT).show() }, - onSpeedClick = { Toast.makeText(context, "Speed Selector", Toast.LENGTH_SHORT).show() }, - onFullscreenToggle = { Toast.makeText(context, "Fullscreen Toggle", Toast.LENGTH_SHORT).show() } - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } -} - -@Preview(name = "Buttons Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "Buttons TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixButtonsScreenPreview() { - CloneflixTheme { - CloneflixButtonsComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt deleted file mode 100644 index 0cc13dc5932..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixColorsComposeScreen.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import androidx.compose.foundation.background -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.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixColorSwatch -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors - -@Composable -fun CloneflixColorsComposeScreen( - onColorClick: (CloneflixColors.ColorToken) -> Unit = {}, - modifier: Modifier = Modifier -) { - val tokens = CloneflixColors.getAllTokens() - val sections = tokens.groupBy { it.category } - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - - LazyColumn( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .padding(horizontal = dimens.spacing2Xl) - ) { - item { - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - CloneflixHeader( - title = "Colors", - subtitle = "33+ Tokens • Primary, Secondary, Greys, Transparent", - iconRes = R.drawable.cloneflix_ic_colors - ) - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - } - - sections.forEach { (categoryName, categoryTokens) -> - item { - Text( - text = categoryName.uppercase(), - style = typography.sectionHeader, - color = colors.primary, - modifier = Modifier.padding(top = dimens.spacingM, bottom = dimens.spacingL) - ) - } - - items(categoryTokens) { token -> - CloneflixColorSwatch( - token = token, - onClick = { onColorClick(token) } - ) - Spacer(modifier = Modifier.height(dimens.spacingM)) - } - } - - item { - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } - } -} - -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixColorsScreenPreview() { - CloneflixTheme { - CloneflixColorsComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt deleted file mode 100644 index 2f9d667f8ff..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixComponentsComposeScreen.kt +++ /dev/null @@ -1,195 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import androidx.compose.foundation.background -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.rememberScrollState -import androidx.compose.foundation.verticalScroll -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.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonSize -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixButtonVariant -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixInputField -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTextField -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -@Composable -fun CloneflixComponentsComposeScreen( - onPrimaryClick: () -> Unit = {}, - onSecondaryClick: () -> Unit = {}, - onOutlineClick: () -> Unit = {}, - onGhostClick: () -> Unit = {}, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - var emailText by remember { mutableStateOf("") } - var passwordText by remember { mutableStateOf("") } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Components", - subtitle = "Buttons • Cards • Inputs • Custom Views", - iconRes = R.drawable.cloneflix_ic_about - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "BUTTON VARIANTS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixButton( - text = "Primary Action (Red)", - onClick = onPrimaryClick, - modifier = Modifier.fillMaxWidth(), - variant = CloneflixButtonVariant.PRIMARY, - size = CloneflixButtonSize.LARGE, - icon = painterResource(id = R.drawable.cloneflix_ic_open_in_new) - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixButton( - text = "Secondary Action (White)", - onClick = onSecondaryClick, - modifier = Modifier.fillMaxWidth(), - variant = CloneflixButtonVariant.SECONDARY, - size = CloneflixButtonSize.MEDIUM - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixButton( - text = "Outlined Action (Border)", - onClick = onOutlineClick, - modifier = Modifier.fillMaxWidth(), - variant = CloneflixButtonVariant.OUTLINE, - size = CloneflixButtonSize.MEDIUM, - icon = painterResource(id = R.drawable.cloneflix_ic_copy) - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixButton( - text = "Ghost Action (Transparent)", - onClick = onGhostClick, - variant = CloneflixButtonVariant.GHOST, - size = CloneflixButtonSize.SMALL - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "INPUT FIELDS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixInputField( - value = emailText, - onValueChange = { emailText = it }, - label = "Email address or phone number" - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixInputField( - value = passwordText, - onValueChange = { passwordText = it }, - label = "Password", - isPassword = true - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "CARD CONTAINERS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Surface Container (#232323)", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Standard content card with 16dp rounded corners and subtle border.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.ELEVATED - ) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Elevated Container (#181818)", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Elevated surface card for modals, tooltips, and bottom sheets.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } -} - -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixComponentsScreenPreview() { - CloneflixTheme { - CloneflixComponentsComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt deleted file mode 100644 index 2590a4522cd..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixContentBlocksComposeScreen.kt +++ /dev/null @@ -1,323 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.foundation.background -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAuthFooter -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBlockLayout -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDivider -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDownloadProgressCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixFaqSection -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixFeatureBlock -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHomeFooter -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHomeHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixKidsPreviewCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLandingFooter -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLandingHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMacPreviewCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMobilePreviewCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTvPreviewCard -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -@Composable -fun CloneflixContentBlocksComposeScreen( - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - var selectedLandingLang by remember { mutableStateOf("English") } - var selectedHomeNavIndex by remember { mutableIntStateOf(0) } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Content Blocks", - subtitle = "Headers • FAQ Accordion • Screen Previews • Landing Blocks • Footers", - iconRes = R.drawable.cloneflix_ic_about - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "HEADERS", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Text( - text = "Landing Page Header", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - CloneflixLandingHeader( - selectedLanguage = selectedLandingLang, - onLanguageSelected = { selectedLandingLang = it }, - onSignInClick = { - Toast.makeText(context, "Sign In clicked", Toast.LENGTH_SHORT).show() - } - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Home Page Header", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - CloneflixHomeHeader( - selectedNavIndex = selectedHomeNavIndex, - onNavItemSelected = { selectedHomeNavIndex = it }, - onSearchClick = { - Toast.makeText(context, "Search action", Toast.LENGTH_SHORT).show() - }, - onNotificationsClick = { - Toast.makeText(context, "Notifications action", Toast.LENGTH_SHORT).show() - }, - onProfileClick = { - Toast.makeText(context, "Profile menu action", Toast.LENGTH_SHORT).show() - } - ) - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - CloneflixDivider() - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "LANDING PAGE BLOCKS", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - CloneflixFeatureBlock( - title = "Enjoy on your TV", - description = "Watch on Smart TVs, Playstation, Xbox, Chromecast, Apple TV, Blu-ray players, and more.", - layout = CloneflixBlockLayout.HEADLINE_IMAGE, - previewContent = { - CloneflixTvPreviewCard(modifier = Modifier.fillMaxWidth(0.95f)) - } - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixFeatureBlock( - title = "Watch everywhere", - description = "Stream unlimited movies and TV shows on your phone, tablet, laptop, and TV.", - layout = CloneflixBlockLayout.IMAGE_HEADLINE, - previewContent = { - CloneflixMacPreviewCard(modifier = Modifier.fillMaxWidth(0.95f)) - } - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixFeatureBlock( - title = "Create profiles for kids", - description = "Send kids on adventures with their favorite characters in a space made just for them — free with your membership.", - layout = CloneflixBlockLayout.HEADLINE_IMAGE, - previewContent = { - CloneflixKidsPreviewCard(modifier = Modifier.fillMaxWidth(0.95f)) - } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - CloneflixDivider() - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "SCREENS PREVIEWS", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Column(modifier = Modifier.weight(1f)) { - Text( - text = "TV Screen Preview", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixTvPreviewCard(modifier = Modifier.fillMaxWidth()) - } - - Column(modifier = Modifier.weight(1f)) { - Text( - text = "Mac Computer Screen Preview", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixMacPreviewCard(modifier = Modifier.fillMaxWidth()) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Column(modifier = Modifier.weight(1f)) { - Text( - text = "Kids Profile Preview", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixKidsPreviewCard(modifier = Modifier.fillMaxWidth()) - } - - Column(modifier = Modifier.weight(1f)) { - Text( - text = "Download Status Preview", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixDownloadProgressCard( - title = "Stranger Things", - statusText = "Downloading...", - modifier = Modifier.fillMaxWidth() - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "iPhone Screen Preview", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixMobilePreviewCard() - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - CloneflixDivider() - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "FAQ ACCORDION", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - CloneflixFaqSection(modifier = Modifier.padding(dimens.spacingL)) - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - CloneflixDivider() - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "FOOTERS", - style = typography.sectionHeader, - color = colors.primary - ) - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Text( - text = "Landing Page Footer", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - CloneflixLandingFooter( - onPhoneClick = { Toast.makeText(context, "Call Support clicked", Toast.LENGTH_SHORT).show() }, - onLinkClick = { link -> Toast.makeText(context, "Link clicked: $link", Toast.LENGTH_SHORT).show() } - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Authentication Page Footer", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - CloneflixAuthFooter( - onPhoneClick = { Toast.makeText(context, "Call Support clicked", Toast.LENGTH_SHORT).show() }, - onLinkClick = { link -> Toast.makeText(context, "Link clicked: $link", Toast.LENGTH_SHORT).show() } - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Home Page Footer", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - CloneflixHomeFooter( - onSocialClick = { platform -> Toast.makeText(context, "$platform clicked", Toast.LENGTH_SHORT).show() }, - onLinkClick = { link -> Toast.makeText(context, "Link clicked: $link", Toast.LENGTH_SHORT).show() }, - onServiceCodeClick = { Toast.makeText(context, "Service Code: 839-204", Toast.LENGTH_SHORT).show() } - ) - } - } -} - -@Preview(name = "Content Blocks Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "Content Blocks TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixContentBlocksScreenPreview() { - CloneflixTheme { - CloneflixContentBlocksComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt deleted file mode 100644 index 6f32bae97da..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixDropdownComposeScreen.kt +++ /dev/null @@ -1,295 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import androidx.compose.foundation.background -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -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.Modifier -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdown -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdownStaticShowcase -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdownVariant -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -@Composable -fun CloneflixDropdownComposeScreen( - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - val viewingPreferencesOptions = listOf("Original Language", "Dubbing", "Subtitles") - var selectedViewingPref by remember { mutableStateOf("Original Language") } - - val languageOptions = listOf( - "Arabic", "Cantonese", "Danish", "Dutch", "English", - "Filipino", "Flemish", "French", "French Canadian", - "German", "Greek", "Hebrew", "Hindi", "Hungarian", - "Indonesian", "Italian", "Japanese", "Korean", - "Malay", "Mandarin", "Polish", "Portuguese", - "Romanian", "Russian", "Spanish", "Swedish", - "Thai", "Turkish", "Ukrainian", "Vietnamese" - ) - var selectedLanguage by remember { mutableStateOf("English") } - - val suggestionOptions = listOf( - "Suggestions For you", - "Year Released", - "A-Z", - "Z-A" - ) - var selectedSuggestion by remember { mutableStateOf("Suggestions For you") } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Dropdowns", - subtitle = "Viewing Preferences • Language Options • Suggestions For You", - iconRes = R.drawable.cloneflix_ic_dropdown - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "VIEWING PREFERENCES", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Interactive Dropdown", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Select audio/subtitle preference mode.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingM)) - CloneflixDropdown( - options = viewingPreferencesOptions, - selectedOption = selectedViewingPref, - onOptionSelected = { selectedViewingPref = it }, - variant = CloneflixDropdownVariant.OUTLINE, - width = 220.dp - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Figma Design States (Default • Hover • Click/Expanded)", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CloneflixDropdownStaticShowcase( - title = "Default", - selectedOption = "Original Language", - options = viewingPreferencesOptions, - modifier = Modifier.weight(1f) - ) - CloneflixDropdownStaticShowcase( - title = "Hover", - selectedOption = "Original Language", - options = viewingPreferencesOptions, - isHoverState = true, - modifier = Modifier.weight(1f) - ) - CloneflixDropdownStaticShowcase( - title = "Click / Expanded", - selectedOption = "Original Language", - options = viewingPreferencesOptions, - isExpandedState = true, - modifier = Modifier.weight(1f) - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "LANGUAGE OPTIONS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Interactive Language Selector", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Comprehensive language picker with scrollable option menu.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingM)) - CloneflixDropdown( - options = languageOptions, - selectedOption = selectedLanguage, - onOptionSelected = { selectedLanguage = it }, - variant = CloneflixDropdownVariant.OUTLINE, - width = 220.dp - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Figma Design States (Default • Hover • Click/Expanded)", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CloneflixDropdownStaticShowcase( - title = "Default", - selectedOption = "English", - options = languageOptions.take(5), - modifier = Modifier.weight(1f) - ) - CloneflixDropdownStaticShowcase( - title = "Hover", - selectedOption = "English", - options = languageOptions.take(5), - isHoverState = true, - modifier = Modifier.weight(1f) - ) - CloneflixDropdownStaticShowcase( - title = "Click / Expanded", - selectedOption = "English", - options = languageOptions.take(6), - isExpandedState = true, - modifier = Modifier.weight(1f) - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "SUGGESTIONS FOR YOU", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Text( - text = "Interactive Sort & Filter Filter", - style = typography.mediumBody, - color = colors.textPrimary - ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) - Text( - text = "Browse feed filter dropdown with sorting options.", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingM)) - CloneflixDropdown( - options = suggestionOptions, - selectedOption = selectedSuggestion, - onOptionSelected = { selectedSuggestion = it }, - variant = CloneflixDropdownVariant.OUTLINE, - width = 240.dp - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Figma Design States (Default • Hover • Click/Expanded)", - style = typography.regularCaption1, - color = colors.textSecondary - ) - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CloneflixDropdownStaticShowcase( - title = "Default", - selectedOption = "Suggestions For you", - options = suggestionOptions, - modifier = Modifier.weight(1f) - ) - CloneflixDropdownStaticShowcase( - title = "Hover", - selectedOption = "Suggestions For you", - options = suggestionOptions, - isHoverState = true, - modifier = Modifier.weight(1f) - ) - CloneflixDropdownStaticShowcase( - title = "Click / Expanded", - selectedOption = "Suggestions For you", - options = suggestionOptions, - isExpandedState = true, - modifier = Modifier.weight(1f) - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } -} - -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixDropdownScreenPreview() { - CloneflixTheme { - CloneflixDropdownComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt deleted file mode 100644 index 509517f5c61..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixHeroBannersComposeScreen.kt +++ /dev/null @@ -1,422 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.foundation.background -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -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.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroBanner -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroBannerType -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHomePageHeroPattern -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoOverview -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTitlePreview -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTitlePreviewSize -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -@Composable -fun CloneflixHeroBannersComposeScreen( - onActionClick: ((String) -> Unit)? = null, - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - val notifyAction: (String) -> Unit = onActionClick ?: { actionName -> - Toast.makeText(context, actionName, Toast.LENGTH_SHORT).show() - } - - var isHomeAudioMuted by remember { mutableStateOf(false) } - var isModalAudioMuted by remember { mutableStateOf(false) } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Hero Banners", - subtitle = "Title Preview • Hero Banners • Page Patterns", - iconRes = R.drawable.cloneflix_ic_play - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "TITLE PREVIEW SIZES", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Size = Large (518 × 406)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - CloneflixTitlePreview( - size = CloneflixTitlePreviewSize.LARGE, - title = "HOUSE OF NINJAS", - top10RankText = "#2 in TV Shows Today", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - onPlayClick = { notifyAction("Play: House of Ninjas") }, - onMoreInfoClick = { notifyAction("More Info: House of Ninjas") } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Size = Medium (518 × 360)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - CloneflixTitlePreview( - size = CloneflixTitlePreviewSize.MEDIUM, - title = "HOUSE OF NINJAS", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - onPlayClick = { notifyAction("Play: House of Ninjas") }, - onMoreInfoClick = { notifyAction("More Info: House of Ninjas") } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Size = Small Variants (Small1, Small2, Small3)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - Text( - text = "Small 1: Logo + Action Buttons", - style = typography.mediumBody, - color = colors.textPrimary - ) - CloneflixTitlePreview( - size = CloneflixTitlePreviewSize.SMALL1, - title = "HOUSE OF NINJAS", - onPlayClick = { notifyAction("Play: House of Ninjas") }, - onMoreInfoClick = { notifyAction("More Info: House of Ninjas") } - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = "Small 2: Title Banner Box (518 × 207)", - style = typography.mediumBody, - color = colors.textPrimary - ) - CloneflixTitlePreview( - size = CloneflixTitlePreviewSize.SMALL2, - title = "HOUSE OF NINJAS" - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = "Small 3: Compact Title Box (340 × 136)", - style = typography.mediumBody, - color = colors.textPrimary - ) - CloneflixTitlePreview( - size = CloneflixTitlePreviewSize.SMALL3, - title = "HOUSE OF NINJAS" - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "HERO BANNER VARIANTS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Type = HomePage (Full Backdrop Scrims, Title Preview, Rating, Audio Toggle)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - CloneflixHeroBanner( - type = CloneflixHeroBannerType.HOME_PAGE, - title = "HOUSE OF NINJAS", - top10RankText = "#2 in TV Shows Today", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - maturityRating = "16+", - onPlayClick = { notifyAction("Playing House of Ninjas") }, - onMoreInfoClick = { notifyAction("Opening House of Ninjas details") }, - onAudioToggle = { - isHomeAudioMuted = !isHomeAudioMuted - notifyAction(if (isHomeAudioMuted) "Audio Muted" else "Audio Unmuted") - }, - isMuted = isHomeAudioMuted - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Type = MoviePreview (Modal Header with Close, Title Logo, Play, +, Like, Volume)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - CloneflixHeroBanner( - type = CloneflixHeroBannerType.MOVIE_PREVIEW, - title = "HOUSE OF NINJAS", - onPlayClick = { notifyAction("Playing Movie Preview") }, - onCloseClick = { notifyAction("Closed Movie Preview Modal") }, - onAddToListClick = { notifyAction("Added House of Ninjas to My List") }, - onThumbsUpClick = { notifyAction("Liked House of Ninjas") }, - onAudioToggle = { - isModalAudioMuted = !isModalAudioMuted - notifyAction(if (isModalAudioMuted) "Preview Audio Muted" else "Preview Audio Unmuted") - }, - isMuted = isModalAudioMuted - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Type = LandingPage (Vignette, Header, Central Headline, CTA Button)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - CloneflixHeroBanner( - type = CloneflixHeroBannerType.LANDING_PAGE, - onPlayClick = { notifyAction("Get Started / Sign In clicked") } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Type = AuthenticationPage (Vignette, Center Sign-In Box)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - CloneflixHeroBanner( - type = CloneflixHeroBannerType.AUTHENTICATION_PAGE, - onPlayClick = { notifyAction("Sign In clicked") } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "COMPOSED PATTERNS (HERO BANNER+)", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Pattern = HomePage (Top Navigation Bar + Hero Banner + Trending Shelf Row)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - CloneflixHomePageHeroPattern( - onPlayClick = { notifyAction("Home Pattern: Play Video") }, - onMoreInfoClick = { notifyAction("Home Pattern: More Info") }, - onSearchClick = { notifyAction("Home Pattern: Open Search") }, - onNotificationClick = { notifyAction("Home Pattern: Open Notifications") }, - onProfileClick = { notifyAction("Home Pattern: Open Profile Menu") }, - onCardClick = { movie -> notifyAction("Selected Trending: $movie") } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Pattern = MoviePreview (Header Hero + Integrated Details Overview)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .background(colors.surfaceElevated) - ) { - CloneflixHeroBanner( - type = CloneflixHeroBannerType.MOVIE_PREVIEW, - title = "HOUSE OF NINJAS", - onPlayClick = { notifyAction("Modal: Play Video") }, - onCloseClick = { notifyAction("Modal: Closed") }, - onAddToListClick = { notifyAction("Modal: Added to My List") }, - onThumbsUpClick = { notifyAction("Modal: Liked") }, - onAudioToggle = { - isModalAudioMuted = !isModalAudioMuted - notifyAction(if (isModalAudioMuted) "Audio Muted" else "Audio Unmuted") - }, - isMuted = isModalAudioMuted - ) - - CloneflixMovieInfoOverview( - title = "House of Ninjas", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - cast = "Kento Kaku, Yosuke Eguchi, Tae Kimura, more", - genres = "TV Dramas, Japanese, TV Thrillers", - moodTags = "Dark, Suspenseful, Exciting", - matchScore = "98% Match", - releaseYear = "2024", - duration = "1 Season", - maturityRating = "TV-MA", - advisories = "smoking, violence", - quality = "HD", - top10RankText = "#2 in TV Shows Today", - onClick = { notifyAction("Overview clicked") }, - modifier = Modifier.padding(16.dp) - ) - } - } - } - } -} - -@Preview( - name = "Hero Banners Screen Preview", - showBackground = true, - backgroundColor = 0xFF141414, - device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape" -) -@Composable -private fun HeroBannersScreenPreview() { - CloneflixTheme { - CloneflixHeroBannersComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt deleted file mode 100644 index a6cc806bdf1..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixIconsLabelsComposeScreen.kt +++ /dev/null @@ -1,495 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.widget.Toast -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -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.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixContentBadge -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoVariant -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoView -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMaturityRating -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoBlock -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoOverview -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieInfoRow -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTop10Badge -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTop10RankBanner -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTop10Size -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoQualityBadge -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey300T40 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite - -data class CloneflixIconItem( - val name: String, - val resId: Int -) - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun CloneflixIconsLabelsComposeScreen( - onItemClick: ((String) -> Unit)? = null, - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - val copyAction: (String) -> Unit = onItemClick ?: { textToCopy -> - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Cloneflix Asset", textToCopy) - clipboard.setPrimaryClip(clip) - Toast.makeText(context, "Copied: $textToCopy", Toast.LENGTH_SHORT).show() - } - - val iconList = remember { - listOf( - CloneflixIconItem("Info", R.drawable.cloneflix_ic_info), - CloneflixIconItem("Notification", R.drawable.cloneflix_ic_notification), - CloneflixIconItem("Search", R.drawable.cloneflix_ic_search), - CloneflixIconItem("Play", R.drawable.cloneflix_ic_play), - CloneflixIconItem("ArrowRight", R.drawable.cloneflix_ic_arrow_right), - CloneflixIconItem("ArrowLeft", R.drawable.cloneflix_ic_arrow_left), - CloneflixIconItem("ArrowUp", R.drawable.cloneflix_ic_arrow_up), - CloneflixIconItem("ArrowDown", R.drawable.cloneflix_ic_arrow_down), - CloneflixIconItem("ThumbUp", R.drawable.cloneflix_ic_thumb_up), - CloneflixIconItem("ThumbDown", R.drawable.cloneflix_ic_thumb_down), - CloneflixIconItem("PlusWide", R.drawable.cloneflix_ic_plus), - CloneflixIconItem("Cross", R.drawable.cloneflix_ic_close), - CloneflixIconItem("Flag", R.drawable.cloneflix_ic_flag), - CloneflixIconItem("ArrowRightNarrow", R.drawable.cloneflix_ic_arrow_right_narrow), - CloneflixIconItem("CircleError", R.drawable.cloneflix_ic_error_circle), - CloneflixIconItem("Question", R.drawable.cloneflix_ic_help), - CloneflixIconItem("Pensil", R.drawable.cloneflix_ic_edit), - CloneflixIconItem("Person", R.drawable.cloneflix_ic_person), - CloneflixIconItem("Account", R.drawable.cloneflix_ic_account), - CloneflixIconItem("AudioDesc", R.drawable.cloneflix_ic_ad), - CloneflixIconItem("Facebook", R.drawable.cloneflix_ic_social_fb), - CloneflixIconItem("Twitter/X", R.drawable.cloneflix_ic_social_x), - CloneflixIconItem("Instagram", R.drawable.cloneflix_ic_social_ig), - CloneflixIconItem("YouTube", R.drawable.cloneflix_ic_social_yt) - ) - } - - val maturityRatings = remember { - listOf("TV-Y", "TV-Y7", "G", "TV-G", "PG", "TV-PG", "PG-13", "TV-14", "R", "TV-MA", "NC-17") - } - - val videoQualities = remember { - listOf("HD", "4K", "HDR", "UltraHD 4K", "Dolby Vision") - } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Icons & Labels", - subtitle = "Design System 2024 • Website & TV Reference", - iconRes = R.drawable.cloneflix_ic_labels - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "ICONS (24)", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - FlowRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - iconList.forEach { iconItem -> - CloneflixIconCard( - iconItem = iconItem, - onClick = { copyAction(iconItem.name) } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "MATURITY RATINGS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - FlowRow( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - maturityRatings.forEach { rating -> - CloneflixMaturityRating( - rating = rating, - onClick = { copyAction(rating) } - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "VIDEO QUALITY", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - FlowRow( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - videoQualities.forEach { quality -> - CloneflixVideoQualityBadge( - quality = quality, - onClick = { copyAction(quality) } - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "LABELS & BADGES", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Text( - text = "Top 10 Badges", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixTop10Badge(size = CloneflixTop10Size.LARGE) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "Large (32dp)", style = typography.regularCaption2, color = colors.textSecondary) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixTop10Badge(size = CloneflixTop10Size.MEDIUM) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "Medium (28dp)", style = typography.regularCaption2, color = colors.textSecondary) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CloneflixTop10Badge(size = CloneflixTop10Size.SMALL) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "Small (24dp)", style = typography.regularCaption2, color = colors.textSecondary) - } - } - - CloneflixTop10RankBanner( - rankText = "#2 in TV Shows Today", - size = CloneflixTop10Size.LARGE, - onClick = { copyAction("#2 in TV Shows Today") } - ) - - CloneflixTop10RankBanner( - rankText = "#1 in Movies Today", - size = CloneflixTop10Size.MEDIUM, - onClick = { copyAction("#1 in Movies Today") } - ) - - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - Text( - text = "Content Status Pills", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { - CloneflixContentBadge(type = CloneflixBadgeType.RECENTLY_ADDED) - CloneflixContentBadge(type = CloneflixBadgeType.NEW_SEASON) - CloneflixContentBadge(type = CloneflixBadgeType.LEAVING_SOON) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "LOGO VARIANTS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Text( - text = "Wordmark", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(24.dp) - ) { - CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_MEDIUM) - CloneflixLogoView(variant = CloneflixLogoVariant.WORDMARK_SMALL) - } - - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - Text( - text = "Lettermark (Icon / Emblem)", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_LARGE) - CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_MEDIUM) - CloneflixLogoView(variant = CloneflixLogoVariant.LETTERMARK_SMALL) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "PATTERNS & PREVIEWS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Text( - text = "Inline Metadata Pattern (Figma Section 12)", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Box(modifier = Modifier.padding(dimens.spacingL)) { - CloneflixMovieInfoRow( - matchText = "New", - maturityRating = "TV-MA", - duration = "3 Seasons", - quality = "HD", - hasAudioDescription = true - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Movie Info Block Pattern (Figma Section 13)", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.ELEVATED - ) { - Box(modifier = Modifier.padding(dimens.spacingL)) { - CloneflixMovieInfoBlock( - matchScore = "98% Match", - releaseYear = "2024", - duration = "3 Seasons", - maturityRating = "TV-MA", - advisories = "smoking, violence", - quality = "HD", - top10RankText = "#2 in TV Shows Today", - hasAudioDescription = true - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "Full Movie Info Overview (Figma Section 14)", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - CloneflixMovieInfoOverview( - title = "House of Ninjas", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - cast = "Kento Kaku, Yosuke Eguchi, Tae Kimura, more", - genres = "TV Dramas, Japanese, TV Thrillers", - moodTags = "Dark, Suspenseful, Exciting", - matchScore = "New", - releaseYear = "2024", - duration = "3 Seasons", - maturityRating = "TV-MA", - advisories = "smoking, violence", - quality = "HD", - top10RankText = "#2 in TV Shows Today", - onClick = { copyAction("House of Ninjas Overview") } - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } -} - -@Composable -private fun CloneflixIconCard( - iconItem: CloneflixIconItem, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val activeBorder = if (isFocused) { - BorderStroke(2.dp, PrimaryWhite) - } else { - BorderStroke(1.dp, colors.border) - } - - Column( - modifier = modifier - .width(100.dp) - .clip(RoundedCornerShape(8.dp)) - .background(if (isFocused) Grey300T40 else colors.surface) - .border(activeBorder, RoundedCornerShape(8.dp)) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .padding(vertical = dimens.spacingM, horizontal = dimens.spacingS), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Icon( - painter = painterResource(id = iconItem.resId), - contentDescription = iconItem.name, - tint = if (isFocused) colors.primary else PrimaryWhite, - modifier = Modifier.size(28.dp) - ) - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Text( - text = iconItem.name, - style = typography.regularCaption2, - color = if (isFocused) PrimaryWhite else Grey200, - textAlign = TextAlign.Center, - maxLines = 1 - ) - } -} - -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixIconsLabelsPreview() { - CloneflixTheme { - CloneflixIconsLabelsComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt deleted file mode 100644 index f2fe79bbd6a..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixInputFieldsComposeScreen.kt +++ /dev/null @@ -1,198 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.foundation.background -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -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.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixGetStartedRow -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixInputField -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixInputFieldSize -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixSignInCard -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -@Composable -fun CloneflixInputFieldsComposeScreen( - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - var emailLarge by remember { mutableStateOf("") } - var emailOrPhoneMedium by remember { mutableStateOf("user@example.com") } - var passwordMedium by remember { mutableStateOf("") } - - var defaultField by remember { mutableStateOf("") } - var focusedField by remember { mutableStateOf("active.typing@netflix.com") } - var errorField by remember { mutableStateOf("invalid-email") } - - var getStartedEmail by remember { mutableStateOf("") } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Input Fields", - subtitle = "Netflix Design System 2024 • Floating labels, sizes & form components", - iconRes = R.drawable.cloneflix_ic_input_field - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "FIELD SIZES & TYPES", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixInputField( - value = emailLarge, - onValueChange = { emailLarge = it }, - label = "Email address (Large - 56dp)", - size = CloneflixInputFieldSize.LARGE - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixInputField( - value = emailOrPhoneMedium, - onValueChange = { emailOrPhoneMedium = it }, - label = "Email or phone number (Medium - 48dp)", - size = CloneflixInputFieldSize.MEDIUM - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixInputField( - value = passwordMedium, - onValueChange = { passwordMedium = it }, - label = "Password (Password Type with Reveal)", - isPassword = true, - size = CloneflixInputFieldSize.MEDIUM - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "FIELD STATES (DEFAULT, FOCUSED, ERROR)", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixInputField( - value = defaultField, - onValueChange = { defaultField = it }, - label = "Default State (Empty / Centered Placeholder)", - size = CloneflixInputFieldSize.LARGE - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixInputField( - value = focusedField, - onValueChange = { focusedField = it }, - label = "Focused / Active Typing State (Floating Top Label)", - size = CloneflixInputFieldSize.LARGE - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixInputField( - value = errorField, - onValueChange = { errorField = it }, - label = "Error State with Validation Message", - size = CloneflixInputFieldSize.LARGE, - isError = true, - errorMessage = "Please enter a valid email address or phone number." - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "GET STARTED CALLOUT ROW", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixGetStartedRow( - emailValue = getStartedEmail, - onEmailChange = { getStartedEmail = it }, - onGetStartedClick = { - if (getStartedEmail.isBlank() || !getStartedEmail.contains("@")) { - Toast.makeText(context, "Please enter a valid email address", Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(context, "Get Started: $getStartedEmail", Toast.LENGTH_SHORT).show() - } - } - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "SIGN-IN COMPOUND FORM", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixSignInCard( - onSignInClick = { email, _ -> - Toast.makeText(context, "Signing in with: $email", Toast.LENGTH_SHORT).show() - }, - onUseCodeClick = { - Toast.makeText(context, "Sign-in Code requested", Toast.LENGTH_SHORT).show() - }, - onForgotPasswordClick = { - Toast.makeText(context, "Forgot Password clicked", Toast.LENGTH_SHORT).show() - }, - onSignUpClick = { - Toast.makeText(context, "Sign Up clicked", Toast.LENGTH_SHORT).show() - } - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } -} - -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixInputFieldsScreenPreview() { - CloneflixTheme { - CloneflixInputFieldsComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt deleted file mode 100644 index be208064be9..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMovieDetailsComposeScreen.kt +++ /dev/null @@ -1,1176 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.gestures.BringIntoViewSpec -import androidx.compose.foundation.gestures.LocalBringIntoViewSpec -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.LaunchedEffect -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.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.draw.scale -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.zIndex -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import coil3.compose.AsyncImage -import com.lagradost.cloudstream3.ActorData -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.SearchResponse -import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.getWatchProgress -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCircleActionButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDropdown -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroPlayButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeroTrailerButton -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoVariant -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixLogoView -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMaturityRating -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardItem -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixSampleData -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTokens -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoQualityBadge -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.GreenAccent -import com.lagradost.cloudstream3.ui.revamp.compose.theme.getRatingScoreColor -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey50 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey600 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey700 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey750 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentBlack60 -import kotlinx.coroutines.launch - -@Immutable -data class CloneflixTrailerData( - val title: String, - val runtime: String, - val rawTrailer: Any? = null -) - -@Immutable -data class CloneflixRecommendationRow( - val rowIndex: Int, - val items: List -) - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun CloneflixMovieDetailsComposeScreen( - modifier: Modifier = Modifier, - title: String = CloneflixSampleData.SAMPLE_TITLE_HOUSE_OF_NINJAS, - providerName: String? = null, - backdropUrl: String? = null, - posterUrl: String? = null, - logoUrl: String? = null, - matchScore: String? = null, - releaseYear: String? = null, - seasonsCount: String? = null, - quality: String? = null, - maturityRating: String? = null, - advisories: String? = null, - top10RankText: String? = null, - synopsis: String = "", - cast: List = emptyList(), - genres: List = emptyList(), - moodTags: List = emptyList(), - creator: String? = null, - writers: List = emptyList(), - dynamicEpisodes: List? = null, - dynamicRecommendations: List? = null, - dynamicActors: List? = null, - dynamicSeasons: List? = null, - dynamicTrailers: List? = null, - selectedSeasonIndex: Int = 0, - isInWatchList: Boolean = false, - hasTrailers: Boolean = false, - onPlayClick: () -> Unit = {}, - onEpisodeClick: ((ResultEpisode) -> Unit)? = null, - onEpisodeDownloadClick: ((ResultEpisode) -> Unit)? = null, - onSeasonSelect: ((Int) -> Unit)? = null, - onAddToListClick: () -> Unit = {}, - onLikeClick: () -> Unit = {}, - onTrailerClick: () -> Unit = {}, - onActorClick: ((String) -> Unit)? = null, - onRecommendationClick: ((SearchResponse) -> Unit)? = null, - onCloseClick: () -> Unit = {} -) { - val context = LocalContext.current - val configuration = LocalConfiguration.current - val screenHeight = configuration.screenHeightDp.dp - val heroHeight = (screenHeight * 0.65f).coerceAtLeast(380.dp) - - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - val lazyListState = rememberLazyListState() - val coroutineScope = rememberCoroutineScope() - - val playInteractionSource = remember { MutableInteractionSource() } - val inMyListInteractionSource = remember { MutableInteractionSource() } - val likeInteractionSource = remember { MutableInteractionSource() } - val trailerInteractionSource = remember { MutableInteractionSource() } - - val playButtonFocusRequester = remember { FocusRequester() } - - LaunchedEffect(Unit) { - lazyListState.scrollToItem(0) - playButtonFocusRequester.requestFocus() - } - - var inMyListState by remember(isInWatchList) { mutableStateOf(isInWatchList) } - - val seasonOptions = remember(dynamicSeasons) { - dynamicSeasons ?: emptyList() - } - - var selectedSeasonText by remember(selectedSeasonIndex, seasonOptions) { - mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "" }) - } - - LaunchedEffect(selectedSeasonIndex, seasonOptions) { - if (seasonOptions.isNotEmpty()) { - selectedSeasonText = seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.first() } - } - } - - fun showToast(message: String) { - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } - - val episodesToDisplay = dynamicEpisodes ?: emptyList() - - val castList = remember(dynamicActors, cast) { - dynamicActors?.map { it.actor.name }?.filter { it.isNotBlank() }?.ifEmpty { cast } ?: cast - } - - val recommendationCards = remember(dynamicRecommendations) { - dynamicRecommendations?.map { rec -> - CloneflixMovieCardItem( - title = rec.name, - type = CloneflixMovieCardType.POSTER, - posterUrl = rec.posterUrl, - showLogo = false, - showBottomTitle = true - ) - } ?: emptyList() - } - - val chunkedRecommendations = remember(recommendationCards) { - recommendationCards.chunked(6).mapIndexed { idx, list -> - CloneflixRecommendationRow(idx, list) - } - } - - val trailersList = dynamicTrailers ?: emptyList() - - val tvBringIntoViewSpec = remember { - object : BringIntoViewSpec { - override fun calculateScrollDistance( - offset: Float, - size: Float, - containerSize: Float - ): Float { - val parentFraction = 0.30f - val targetOffset = (parentFraction * containerSize) - return offset - targetOffset - } - } - } - - CompositionLocalProvider(LocalBringIntoViewSpec provides tvBringIntoViewSpec) { - LazyColumn( - state = lazyListState, - modifier = modifier - .fillMaxSize() - .background(colors.background) - ) { - item(key = "hero_banner", contentType = "hero_banner") { - Box( - modifier = Modifier - .fillMaxWidth() - .height(heroHeight) - .clipToBounds() - .background(colors.background) - ) { - if (!backdropUrl.isNullOrBlank()) { - AsyncImage( - model = backdropUrl, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .clipToBounds() - ) - } - - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.85f) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - colors.background.copy(alpha = 0.5f), - colors.background.copy(alpha = 0.92f), - colors.background - ) - ) - ) - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.TopStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) - .zIndex(50f), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Box( - modifier = Modifier - .height(42.dp) - .width(110.dp) - .focusRequester(playButtonFocusRequester) - .focusable(interactionSource = playInteractionSource) - .clickable( - interactionSource = playInteractionSource, - indication = null, - role = Role.Button, - onClick = onPlayClick - ) - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Box( - modifier = Modifier - .size(40.dp) - .focusable(interactionSource = inMyListInteractionSource) - .clickable( - interactionSource = inMyListInteractionSource, - indication = null, - role = Role.Button - ) { - inMyListState = !inMyListState - onAddToListClick() - } - ) - - Box( - modifier = Modifier - .size(40.dp) - .focusable(interactionSource = likeInteractionSource) - .clickable( - interactionSource = likeInteractionSource, - indication = null, - role = Role.Button, - onClick = onLikeClick - ) - ) - } - - if (hasTrailers) { - Box( - modifier = Modifier - .height(40.dp) - .width(120.dp) - .focusable(interactionSource = trailerInteractionSource) - .clickable( - interactionSource = trailerInteractionSource, - indication = null, - role = Role.Button, - onClick = onTrailerClick - ) - ) - } - } - } - - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(dimens.spacing2Xl) - ) { - CloneflixLogoView( - variant = CloneflixLogoVariant.WORDMARK_MEDIUM, - text = providerName - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom - ) { - Column { - if (!logoUrl.isNullOrBlank()) { - Box( - modifier = Modifier - .height(68.dp) - .width(240.dp) - ) { - AsyncImage( - model = logoUrl, - contentDescription = title, - contentScale = ContentScale.Fit, - modifier = Modifier.fillMaxSize() - ) - } - } else { - Text( - text = title, - style = typography.boldTitle1, - fontSize = 32.sp, - color = PrimaryWhite, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixHeroPlayButton( - onClick = onPlayClick, - text = "Play", - interactionSource = playInteractionSource, - enabled = false - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CloneflixCircleActionButton( - icon = painterResource( - id = if (inMyListState) R.drawable.cloneflix_ic_check else R.drawable.cloneflix_ic_plus - ), - contentDescription = if (inMyListState) "In My List" else "Add to My List", - onClick = { - inMyListState = !inMyListState - onAddToListClick() - }, - interactionSource = inMyListInteractionSource, - enabled = false - ) - - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.cloneflix_ic_thumb_up), - contentDescription = "Like", - onClick = onLikeClick, - interactionSource = likeInteractionSource, - enabled = false - ) - } - } - - if (hasTrailers) { - CloneflixHeroTrailerButton( - text = "Play Trailer", - onClick = onTrailerClick, - interactionSource = trailerInteractionSource, - enabled = false - ) - } - } - } - } - - item(key = "movie_info_synopsis", contentType = "movie_info") { - val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) - ) { - Column( - modifier = Modifier - .weight(if (hasRightColumn) 0.65f else 1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || - !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() - - if (hasLine1) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (!matchScore.isNullOrBlank()) { - Text( - text = matchScore, - color = getRatingScoreColor(matchScore), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - } - - if (!releaseYear.isNullOrBlank()) { - Text( - text = releaseYear, - color = PrimaryWhite, - style = typography.regularBody - ) - } - - if (!seasonsCount.isNullOrBlank()) { - Text( - text = seasonsCount, - color = PrimaryWhite, - style = typography.regularBody - ) - } - - if (!quality.isNullOrBlank()) { - CloneflixVideoQualityBadge(quality = quality) - } - } - } - - if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - if (!maturityRating.isNullOrBlank()) { - CloneflixMaturityRating(rating = maturityRating) - } - if (!advisories.isNullOrBlank()) { - Text( - text = advisories, - color = Grey100, - style = typography.regularCaption1 - ) - } - } - } - - if (!top10RankText.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - modifier = Modifier.padding(vertical = dimens.spacingXs) - ) { - Box( - modifier = Modifier - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(PrimaryRed) - .padding(horizontal = 6.dp, vertical = 2.dp) - ) { - Text( - text = "TOP 10", - style = typography.regularCaption2, - color = PrimaryWhite, - fontWeight = FontWeight.Black - ) - } - Text( - text = top10RankText, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite - ) - } - } - - if (synopsis.isNotBlank()) { - val synopsisInteractionSource = remember { MutableInteractionSource() } - val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() - - Box( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCard)) - .background(if (isSynopsisFocused) Grey800.copy(alpha = 0.5f) else Color.Transparent) - .then( - if (isSynopsisFocused) { - Modifier.border( - BorderStroke(dimens.borderFocus, PrimaryWhite), - RoundedCornerShape(CloneflixTokens.RadiusCard) - ) - } else Modifier - ) - .focusable(interactionSource = synopsisInteractionSource) - .padding(dimens.spacingS) - ) { - Text( - text = synopsis, - style = typography.regularBody, - color = if (isSynopsisFocused) PrimaryWhite else Grey50, - lineHeight = 22.sp - ) - } - } - } - - if (hasRightColumn) { - Column( - modifier = Modifier - .weight(0.35f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (castList.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Cast", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = castList.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) - } - } - - if (genres.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "Genres", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = genres.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite - ) - } - } - - if (moodTags.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = "This show is", - style = typography.regularCaption2, - color = Grey200 - ) - Text( - text = moodTags.joinToString(", "), - style = typography.regularCaption1, - color = PrimaryWhite - ) - } - } - } - } - } - } - - if (episodesToDisplay.isNotEmpty()) { - item(key = "episodes_header", contentType = "episodes_header") { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Episodes", - style = typography.boldTitle2, - fontSize = 24.sp, - color = PrimaryWhite - ) - - if (seasonOptions.size > 1) { - CloneflixDropdown( - options = seasonOptions, - selectedOption = selectedSeasonText, - onOptionSelected = { seasonStr -> - selectedSeasonText = seasonStr - val idx = seasonOptions.indexOf(seasonStr) - if (idx >= 0) onSeasonSelect?.invoke(idx) - }, - width = 180.dp - ) - } - } - } - - items( - items = episodesToDisplay, - key = { ep -> "episode_${ep.id}" }, - contentType = { "episode_row" } - ) { ep -> - val onEpClick = remember(ep) { - { - if (onEpisodeClick != null) onEpisodeClick(ep) - else showToast("Playing ${ep.headerName}: ${ep.name}") - } - } - val onEpDownload = remember(ep) { - { - if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) - else showToast("Downloading Episode ${ep.episode}") - } - } - CloneflixDynamicEpisodeRow( - episode = ep, - onClick = onEpClick, - onDownloadClick = onEpDownload, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp) - ) - } - } - - if (chunkedRecommendations.isNotEmpty()) { - item(key = "recommendations_header", contentType = "recommendations_header") { - Text( - text = "More Like This", - style = typography.boldTitle2, - fontSize = 22.sp, - color = PrimaryWhite, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) - ) - } - - items( - items = chunkedRecommendations, - key = { row -> "rec_row_${row.rowIndex}" }, - contentType = { "recommendation_row" } - ) { row -> - CloneflixRecommendationRowView( - row = row, - onCardClick = { cardItem -> - val rec = dynamicRecommendations?.find { it.name == cardItem.title } - if (rec != null && onRecommendationClick != null) { - onRecommendationClick(rec) - } else { - showToast("Opened recommendation: ${cardItem.title}") - } - } - ) - } - } - - if (trailersList.isNotEmpty()) { - item(key = "trailers_section", contentType = "trailers_section") { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) - ) { - Text( - text = "Trailers & More", - style = typography.boldTitle2, - fontSize = 22.sp, - color = PrimaryWhite - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - trailersList.forEach { trailer -> - Box(modifier = Modifier.weight(1f)) { - CloneflixTrailerItemCard( - trailer = trailer, - onClick = { - if (hasTrailers) onTrailerClick() - else showToast("Playing ${trailer.title}") - }, - modifier = Modifier.fillMaxWidth() - ) - } - } - } - } - } - } - - val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || - writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || - !maturityRating.isNullOrBlank() - - if (hasAboutContent) { - item(key = "about_section", contentType = "about_section") { - val aboutInteractionSource = remember { MutableInteractionSource() } - val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() - val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(1.dp, Grey600) - - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing3Xl, bottom = dimens.spacing3Xl) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .background(if (isAboutFocused) Grey750 else Grey850) - .border(aboutBorder, RoundedCornerShape(CloneflixTokens.RadiusCardMedium)) - .focusable(interactionSource = aboutInteractionSource) - .padding(dimens.spacing2Xl), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = "About $title", - style = typography.boldTitle2, - fontSize = 20.sp, - color = PrimaryWhite - ) - - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - if (!creator.isNullOrBlank()) { - CloneflixAboutMetadataRow(label = "Creator", value = creator) - } - if (castList.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Cast", value = castList.joinToString(", ")) - } - if (writers.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Writers", value = writers.joinToString(", ")) - } - if (genres.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "Genres", value = genres.joinToString(", ")) - } - if (moodTags.isNotEmpty()) { - CloneflixAboutMetadataRow(label = "This show is", value = moodTags.joinToString(", ")) - } - if (!maturityRating.isNullOrBlank()) { - val ratingDescription = if (!advisories.isNullOrBlank()) { - "$maturityRating Recommended for ages 16 and up. Contains $advisories." - } else { - maturityRating - } - CloneflixAboutMetadataRow( - label = "Maturity Rating", - value = ratingDescription - ) - } - } - } - } - } - } - } -} - -@Composable -fun CloneflixDynamicEpisodeRow( - episode: ResultEpisode, - onClick: () -> Unit, - onDownloadClick: () -> Unit, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) 1.02f else 1f, - animationSpec = CloneflixTokens.FastFocusAnimationSpec, - label = "episodeScale" - ) - - val background = if (isFocused) Grey800 else Grey850 - val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null - val progress = remember(episode) { episode.getWatchProgress() } - - Row( - modifier = modifier - .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(CloneflixTokens.ShapeCardMedium) - .background(background) - .then(if (border != null) Modifier.border(border, CloneflixTokens.ShapeCardMedium) else Modifier) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - .padding(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Text( - text = episode.episode.toString(), - style = typography.boldTitle1, - fontSize = 24.sp, - color = Grey200, - modifier = Modifier.width(32.dp) - ) - - Box( - modifier = Modifier - .width(160.dp) - .height(90.dp) - .clip(CloneflixTokens.ShapeCardSmall) - .background(Grey700) - ) { - if (!episode.poster.isNullOrBlank()) { - AsyncImage( - model = episode.poster, - contentDescription = episode.name, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } - - Box( - modifier = Modifier - .size(36.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) - .align(Alignment.Center) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(16.dp) - .align(Alignment.Center) - ) - } - - if (progress > 0.05f) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .align(Alignment.BottomStart) - .background(Grey800) - ) { - Box( - modifier = Modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(PrimaryRed) - ) - } - } - } - - Column( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "${episode.episode}. ${episode.name ?: episode.headerName}", - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = if (episode.runTime != null) "${episode.runTime}m" else "", - style = typography.regularCaption1, - color = Grey200 - ) - } - - if (!episode.description.isNullOrBlank()) { - Text( - text = episode.description, - style = typography.regularCaption1, - color = Grey100, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - lineHeight = 18.sp - ) - } - } - - CloneflixCircleActionButton( - icon = painterResource(id = R.drawable.netflix_download), - contentDescription = "Download Episode ${episode.episode}", - onClick = onDownloadClick, - modifier = Modifier.size(36.dp) - ) - } -} - -@Composable -fun CloneflixTrailerItemCard( - trailer: CloneflixTrailerData, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) CloneflixTokens.FOCUS_SCALE_FACTOR else 1f, - animationSpec = CloneflixTokens.FastFocusAnimationSpec, - label = "trailerScale" - ) - - val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else BorderStroke(dimens.borderSubtle, Grey700) - - Column( - modifier = modifier - .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(CloneflixTokens.ShapeCardMedium) - .background(Grey850) - .border(border, CloneflixTokens.ShapeCardMedium) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(130.dp) - .background(Grey700) - ) { - Box( - modifier = Modifier - .size(42.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) - .align(Alignment.Center) - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_play), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(20.dp) - .align(Alignment.Center) - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingM), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = trailer.title, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = PrimaryWhite, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = trailer.runtime, - style = typography.regularCaption2, - color = Grey200 - ) - } - } -} - -@Composable -fun CloneflixAboutMetadataRow( - label: String, - value: String, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = "$label:", - style = typography.regularCaption1, - color = Grey200, - modifier = Modifier.width(130.dp) - ) - Text( - text = value, - style = typography.regularCaption1, - color = PrimaryWhite, - modifier = Modifier.weight(1f) - ) - } -} - -@Composable -fun CloneflixRecommendationRowView( - row: CloneflixRecommendationRow, - onCardClick: (CloneflixMovieCardItem) -> Unit, - modifier: Modifier = Modifier -) { - val dimens = CloneflixTheme.dimens - Row( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - row.items.forEach { cardItem -> - Box(modifier = Modifier.weight(1f)) { - CloneflixMovieCard( - title = cardItem.title, - type = CloneflixMovieCardType.POSTER, - size = CloneflixMovieCardSize.MEDIUM, - badge = cardItem.badge, - runtime = cardItem.runtime, - posterUrl = cardItem.posterUrl, - showLogo = false, - showBottomTitle = true, - onClick = { onCardClick(cardItem) }, - modifier = Modifier.fillMaxWidth() - ) - } - } - if (row.items.size < 6) { - repeat(6 - row.items.size) { - Spacer(modifier = Modifier.weight(1f)) - } - } - } -} - -@Preview( - name = "TV 1080p Landscape", - device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape", - showBackground = true, - backgroundColor = 0xFF141414 -) -@Composable -private fun CloneflixMovieDetailsComposeScreenPreview() { - CloneflixTheme { - CloneflixMovieDetailsComposeScreen( - title = "HOUSE OF NINJAS", - providerName = "SUPERSTREAM", - matchScore = "98% Match", - releaseYear = "2024", - seasonsCount = "1 Season", - quality = "4K Ultra HD", - maturityRating = "TV-MA", - advisories = "smoking, violence, language", - top10RankText = "#2 in TV Shows Today", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - cast = listOf("Kento Kaku", "Yosuke Eguchi", "Tae Kimura", "Kengo Kora"), - genres = listOf("TV Dramas", "Action", "Japanese", "Suspenseful"), - moodTags = listOf("Dark", "Suspenseful", "Exciting"), - creator = "Dave Boyle", - writers = listOf("Dave Boyle", "Masahiro Yamaura", "Kota Oishi"), - isInWatchList = false, - hasTrailers = true - ) - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt deleted file mode 100644 index f91dff3d716..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixMoviePreviewComposeScreen.kt +++ /dev/null @@ -1,548 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon -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.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixEpisodeItem -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixExpandedMoviePreview -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMaturityRating -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieBlockRow -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardItem -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoQualityBadge -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey850 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite - -@Composable -fun CloneflixMoviePreviewComposeScreen( - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - var currentEpisode by remember { mutableIntStateOf(1) } - - fun showToast(msg: String) { - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() - } - - val trendingMovies = remember { - listOf( - CloneflixMovieCardItem("House of Ninjas", showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), - CloneflixMovieCardItem("Rurouni Kenshin", showLogo = true, badge = CloneflixBadgeType.RECENTLY_ADDED), - CloneflixMovieCardItem("Avatar: The Last Airbender", showLogo = true), - CloneflixMovieCardItem("One Piece", showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), - CloneflixMovieCardItem("Stranger Things", showLogo = true), - CloneflixMovieCardItem("Squid Game", showLogo = true, badge = CloneflixBadgeType.LEAVING_SOON) - ) - } - - val continueWatchingMovies = remember { - listOf( - CloneflixMovieCardItem("House of Ninjas", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.75f, showLogo = true), - CloneflixMovieCardItem("The Witcher", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.40f, showLogo = true), - CloneflixMovieCardItem("Cyberpunk: Edgerunners", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.90f, showLogo = true), - CloneflixMovieCardItem("Arcane", type = CloneflixMovieCardType.CONTINUE_WATCHING, progress = 0.55f, showLogo = true) - ) - } - - val top10Movies = remember { - listOf( - CloneflixMovieCardItem("House of Ninjas", type = CloneflixMovieCardType.TOP10, top10Rank = 1, showLogo = true, badge = CloneflixBadgeType.NEW_SEASON), - CloneflixMovieCardItem("Avatar", type = CloneflixMovieCardType.TOP10, top10Rank = 2, showLogo = true), - CloneflixMovieCardItem("Damsel", type = CloneflixMovieCardType.TOP10, top10Rank = 3, showLogo = true, badge = CloneflixBadgeType.RECENTLY_ADDED), - CloneflixMovieCardItem("Spaceman", type = CloneflixMovieCardType.TOP10, top10Rank = 4, showLogo = true), - CloneflixMovieCardItem("The Gentlemen", type = CloneflixMovieCardType.TOP10, top10Rank = 5, showLogo = true), - CloneflixMovieCardItem("Code 8 Part II", type = CloneflixMovieCardType.TOP10, top10Rank = 6, showLogo = true), - CloneflixMovieCardItem("Shogun", type = CloneflixMovieCardType.TOP10, top10Rank = 7, showLogo = true) - ) - } - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Movie Preview", - subtitle = "Movie Cards • Samples • Carousels • Episode Lists • Detail Previews", - iconRes = R.drawable.cloneflix_ic_play - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "MOVIE CARDS (VARIANTS)", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Text( - text = "Standard & More Like This", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column { - CloneflixMovieCard( - title = "Default Medium", - type = CloneflixMovieCardType.DEFAULT, - size = CloneflixMovieCardSize.MEDIUM, - onClick = { showToast("Default Medium Clicked") } - ) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "Default (218x123dp)", style = typography.regularCaption2, color = Grey200) - } - - Column { - CloneflixMovieCard( - title = "More Like This", - type = CloneflixMovieCardType.MORE_LIKE_THIS, - runtime = "2h 18m", - showLogo = true, - onClick = { showToast("More Like This Clicked") } - ) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "More Like This (236x132dp)", style = typography.regularCaption2, color = Grey200) - } - - Column { - CloneflixMovieCard( - title = "With Play Icon", - type = CloneflixMovieCardType.MORE_LIKE_THIS_WITH_PLAY, - runtime = "2h 18m", - showLogo = true, - onClick = { showToast("Play Clicked") } - ) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "With Play Button", style = typography.regularCaption2, color = Grey200) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Text( - text = "Episode, Player & Trailer Previews", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column { - CloneflixMovieCard( - title = "Episode Thumbnail", - type = CloneflixMovieCardType.EPISODE, - size = CloneflixMovieCardSize.SMALL, - onClick = { showToast("Episode Clicked") } - ) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "Episode (128x72dp)", style = typography.regularCaption2, color = Grey200) - } - - Column { - CloneflixMovieCard( - title = "Player Timestamp", - type = CloneflixMovieCardType.PLAYER_PREVIEW, - timestamp = "51:29", - onClick = { showToast("Player Preview Clicked") } - ) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "Player Preview (51:29)", style = typography.regularCaption2, color = Grey200) - } - - Column { - CloneflixMovieCard( - title = "Trailer", - subtitle = "Season 1 Trailer 1: House of Ninjas", - type = CloneflixMovieCardType.TRAILER, - onClick = { showToast("Trailer Clicked") } - ) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = "Trailer Preview", style = typography.regularCaption2, color = Grey200) - } - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "SAMPLES (HOME PAGE CARDS)", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Text( - text = "Badges & Branding Variations", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CloneflixMovieCard( - title = "Default", - type = CloneflixMovieCardType.DEFAULT, - onClick = { showToast("Default Card") } - ) - - CloneflixMovieCard( - title = "With Logo", - type = CloneflixMovieCardType.DEFAULT, - showLogo = true, - onClick = { showToast("Card With Logo") } - ) - - CloneflixMovieCard( - title = "Recently Added", - type = CloneflixMovieCardType.DEFAULT, - showLogo = true, - badge = CloneflixBadgeType.RECENTLY_ADDED, - onClick = { showToast("Recently Added Card") } - ) - - CloneflixMovieCard( - title = "New Season", - type = CloneflixMovieCardType.DEFAULT, - showLogo = true, - badge = CloneflixBadgeType.NEW_SEASON, - onClick = { showToast("New Season Card") } - ) - - CloneflixMovieCard( - title = "Leaving Soon", - type = CloneflixMovieCardType.DEFAULT, - showLogo = true, - badge = CloneflixBadgeType.LEAVING_SOON, - onClick = { showToast("Leaving Soon Card") } - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "PATTERNS: MOVIE BLOCKS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.ELEVATED - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - CloneflixMovieBlockRow( - title = "Trending Now", - items = trendingMovies, - onItemClick = { showToast("Selected: ${it.title}") } - ) - - CloneflixMovieBlockRow( - title = "Continue Watching", - items = continueWatchingMovies, - onItemClick = { showToast("Resuming: ${it.title}") } - ) - - CloneflixMovieBlockRow( - title = "Top 10 TV Shows Today", - items = top10Movies, - onItemClick = { showToast("Top 10: #${it.top10Rank} ${it.title}") } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "PATTERNS: LIST OF EPISODES", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixCard(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingL), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - CloneflixEpisodeItem( - episodeNumber = 1, - title = "The Offer", - duration = "55m", - synopsis = "While Haru Tawara develops a crush on a mysterious young woman at work, an unusual opportunity arises at his father's financially struggling brewery.", - isCurrent = currentEpisode == 1, - onClick = { - currentEpisode = 1 - showToast("Playing Episode 1") - } - ) - - CloneflixEpisodeItem( - episodeNumber = 2, - title = "The Trail", - duration = "52m", - synopsis = "Haru accompanies Karen to investigate a whistleblower's apartment. Meanwhile, several other Tawaras are tempted to step out of their ordinary lives.", - isCurrent = currentEpisode == 2, - onClick = { - currentEpisode = 2 - showToast("Playing Episode 2") - } - ) - - CloneflixEpisodeItem( - episodeNumber = 3, - title = "The Flower", - duration = "50m", - synopsis = "As Yoko investigates the Gentenkai group, Sohei receives an unexpected visitor. Haru uncovers a hidden truth about the recent incident.", - isCurrent = currentEpisode == 3, - onClick = { - currentEpisode = 3 - showToast("Playing Episode 3") - } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - - Text( - text = "PATTERNS: MOVIE PREVIEW (EXPANDED & DETAIL)", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - CloneflixExpandedMoviePreview( - title = "House of Ninjas", - matchScore = "New", - maturityRating = "TV-MA", - duration = "3 Seasons", - quality = "HD", - genres = listOf("Violent", "Dark", "Action"), - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - onPlayClick = { showToast("Play Clicked") }, - onAddClick = { showToast("Added to My List") }, - onThumbUpClick = { showToast("Liked") }, - onMuteClick = { showToast("Mute Toggled") } - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Text( - text = "More Like This Detail Card (Figma Type=Movie Preview Other)", - style = typography.mediumBody, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - CloneflixMoreLikeThisDetailCard( - title = "Rurouni Kenshin: The Beginning", - runtime = "2h 18m", - maturityRating = "TV-MA", - quality = "HD", - year = "2021", - synopsis = "Before he was a protector, Kenshin was a fearsome assassin known as Battosai. But when he meets the gentle Tomoe Yukishiro, his story begins to change.", - onAddClick = { showToast("Added to List") } - ) - - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } -} - -@Composable -fun CloneflixMoreLikeThisDetailCard( - title: String, - runtime: String, - maturityRating: String, - quality: String, - year: String, - synopsis: String, - onAddClick: () -> Unit, - modifier: Modifier = Modifier -) { - val colors = CloneflixTheme.colors - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - - Box( - modifier = modifier - .width(260.dp) - .clip(RoundedCornerShape(6.dp)) - .background(Grey850) - .border(BorderStroke(0.5.dp, colors.border), RoundedCornerShape(6.dp)) - ) { - Column(modifier = Modifier.fillMaxWidth()) { - CloneflixMovieCard( - title = title, - type = CloneflixMovieCardType.MORE_LIKE_THIS, - runtime = runtime, - showLogo = true, - modifier = Modifier.fillMaxWidth() - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingM) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - CloneflixMaturityRating(rating = maturityRating) - CloneflixVideoQualityBadge(quality = quality) - Text( - text = year, - style = typography.regularCaption1, - color = Grey100 - ) - } - - Box( - modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(Grey800) - .border(BorderStroke(1.dp, PrimaryWhite.copy(alpha = 0.6f)), CircleShape) - .clickable(onClick = onAddClick), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.cloneflix_ic_plus), - contentDescription = "Add", - tint = PrimaryWhite, - modifier = Modifier.size(16.dp) - ) - } - } - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = synopsis, - style = typography.regularSmallBody, - fontSize = 12.sp, - lineHeight = 16.sp, - color = Grey200, - maxLines = 3 - ) - } - } - } -} - -@Preview(name = "Phone Movie Preview Screen", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview( - name = "TV Movie Preview Screen", - showBackground = true, - backgroundColor = 0xFF141414, - device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape" -) -@Composable -private fun CloneflixMoviePreviewScreenPreview() { - CloneflixTheme { - CloneflixMoviePreviewComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt deleted file mode 100644 index 1d6076ac7a8..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixShowcaseComposeScreen.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.content.Intent -import android.net.Uri -import android.widget.Toast -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.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.ScrollableTabRow -import androidx.compose.material3.Tab -import androidx.compose.material3.TabRowDefaults -import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset -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.platform.LocalContext -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixDivider -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTheme as LegacyCloneflixTheme - -@Composable -fun CloneflixShowcaseComposeScreen() { - val context = LocalContext.current - val tabs = listOf("About", "Typography", "Colors", "Buttons", "Components", "Content Blocks", "Icons & Labels", "Dropdowns", "Input Fields", "Hero Banners", "Avatars", "Video Player", "Movie Preview", "Movie Details") - var selectedTabIndex by remember { mutableIntStateOf(0) } - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - - Column( - modifier = Modifier - .fillMaxSize() - .background(colors.background) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .background(colors.surfaceElevated) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "CLONEFLIX", - style = typography.logoBebas, - fontSize = 24.sp - ) - Spacer(modifier = Modifier.width(dimens.spacingM)) - Text( - text = "Design System 2024", - style = typography.mediumBody, - color = colors.textPrimary - ) - } - - ScrollableTabRow( - selectedTabIndex = selectedTabIndex, - containerColor = colors.surfaceElevated, - contentColor = colors.textPrimary, - edgePadding = dimens.spacingL, - indicator = { tabPositions -> - if (selectedTabIndex < tabPositions.size) { - TabRowDefaults.SecondaryIndicator( - modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]), - color = colors.primary, - height = 3.dp - ) - } - } - ) { - tabs.forEachIndexed { index, title -> - Tab( - selected = selectedTabIndex == index, - onClick = { selectedTabIndex = index }, - text = { - Text( - text = title, - style = if (selectedTabIndex == index) typography.mediumBody else typography.regularBody, - color = if (selectedTabIndex == index) colors.textPrimary else colors.textSecondary - ) - } - ) - } - } - - CloneflixDivider() - - Box(modifier = Modifier.weight(1f)) { - when (selectedTabIndex) { - 0 -> CloneflixAboutComposeScreen( - onFollowClick = { - try { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://x.com/ivannaheraskina")) - context.startActivity(intent) - } catch (_: Exception) { - Toast.makeText(context, "Could not open link", Toast.LENGTH_SHORT).show() - } - }, - onFeedbackClick = { - Toast.makeText(context, "Feedback submitted! Thank you!", Toast.LENGTH_SHORT).show() - } - ) - 1 -> CloneflixTypographyComposeScreen( - onStyleClick = { styleLevel -> - LegacyCloneflixTheme.copyToClipboard( - context, - styleLevel.label, - "fontSize: ${styleLevel.sizeSp}sp, lineHeight: ${styleLevel.lineHeightSp}sp" - ) - } - ) - 2 -> CloneflixColorsComposeScreen( - onColorClick = { token -> - LegacyCloneflixTheme.copyToClipboard( - context, - token.name, - token.hex - ) - } - ) - 3 -> CloneflixButtonsComposeScreen() - 4 -> CloneflixComponentsComposeScreen( - onPrimaryClick = { Toast.makeText(context, "Primary Action Clicked", Toast.LENGTH_SHORT).show() }, - onSecondaryClick = { Toast.makeText(context, "Secondary Action Clicked", Toast.LENGTH_SHORT).show() }, - onOutlineClick = { Toast.makeText(context, "Outline Action Clicked", Toast.LENGTH_SHORT).show() }, - onGhostClick = { Toast.makeText(context, "Ghost Action Clicked", Toast.LENGTH_SHORT).show() } - ) - 5 -> CloneflixContentBlocksComposeScreen() - 6 -> CloneflixIconsLabelsComposeScreen() - 7 -> CloneflixDropdownComposeScreen() - 8 -> CloneflixInputFieldsComposeScreen() - 9 -> CloneflixHeroBannersComposeScreen() - 10 -> CloneflixAvatarsComposeScreen() - 11 -> CloneflixVideoPlayerComposeScreen() - 12 -> CloneflixMoviePreviewComposeScreen() - 13 -> CloneflixMovieDetailsComposeScreen() - } - } - } -} - -@Preview(name = "Phone Showcase Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Showcase Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixShowcasePreview() { - CloneflixTheme { - CloneflixShowcaseComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt deleted file mode 100644 index 54c632ad304..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixTypographyComposeScreen.kt +++ /dev/null @@ -1,222 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -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.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography - -@Composable -fun CloneflixTypographyComposeScreen( - onStyleClick: (CloneflixTypography.StyleLevel) -> Unit = {}, - modifier: Modifier = Modifier -) { - val typography = CloneflixTheme.typography - val dimens = CloneflixTheme.dimens - val colors = CloneflixTheme.colors - - val regularStyles = listOf( - CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE to typography.regularLargeTitle, - CloneflixTypography.StyleLevel.REGULAR_TITLE1 to typography.regularTitle1, - CloneflixTypography.StyleLevel.REGULAR_TITLE2 to typography.regularTitle2, - CloneflixTypography.StyleLevel.REGULAR_TITLE3 to typography.regularTitle3, - CloneflixTypography.StyleLevel.REGULAR_TITLE4 to typography.regularTitle4, - CloneflixTypography.StyleLevel.REGULAR_HEADLINE1 to typography.regularHeadline1, - CloneflixTypography.StyleLevel.REGULAR_HEADLINE2 to typography.regularHeadline2, - CloneflixTypography.StyleLevel.REGULAR_BODY to typography.regularBody, - CloneflixTypography.StyleLevel.REGULAR_SMALL_BODY to typography.regularSmallBody, - CloneflixTypography.StyleLevel.REGULAR_CAPTION1 to typography.regularCaption1, - CloneflixTypography.StyleLevel.REGULAR_CAPTION2 to typography.regularCaption2 - ) - - val mediumStyles = listOf( - CloneflixTypography.StyleLevel.MEDIUM_LARGE_TITLE to typography.mediumLargeTitle, - CloneflixTypography.StyleLevel.MEDIUM_TITLE1 to typography.mediumTitle1, - CloneflixTypography.StyleLevel.MEDIUM_TITLE2 to typography.mediumTitle2, - CloneflixTypography.StyleLevel.MEDIUM_TITLE3 to typography.mediumTitle3, - CloneflixTypography.StyleLevel.MEDIUM_TITLE4 to typography.mediumTitle4, - CloneflixTypography.StyleLevel.MEDIUM_HEADLINE1 to typography.mediumHeadline1, - CloneflixTypography.StyleLevel.MEDIUM_HEADLINE2 to typography.mediumHeadline2, - CloneflixTypography.StyleLevel.MEDIUM_BODY to typography.mediumBody, - CloneflixTypography.StyleLevel.MEDIUM_SMALL_BODY to typography.mediumSmallBody, - CloneflixTypography.StyleLevel.MEDIUM_CAPTION1 to typography.mediumCaption1, - CloneflixTypography.StyleLevel.MEDIUM_CAPTION2 to typography.mediumCaption2 - ) - - val boldStyles = listOf( - CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE to typography.boldLargeTitle, - CloneflixTypography.StyleLevel.BOLD_TITLE1 to typography.boldTitle1, - CloneflixTypography.StyleLevel.BOLD_TITLE2 to typography.boldTitle2 - ) - - val displayStyles = listOf( - CloneflixTypography.StyleLevel.LOGO_BEBAS to typography.logoBebas, - CloneflixTypography.StyleLevel.HEADER_DISPLAY to typography.headerDisplay - ) - - LazyColumn( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .padding(horizontal = dimens.spacing2Xl) - ) { - item { - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - CloneflixHeader( - title = "Typography", - subtitle = "Netflix Sans • Bebas Neue", - iconRes = R.drawable.cloneflix_ic_typography - ) - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - } - - item { - Text( - text = "REGULAR STYLES", - style = typography.sectionHeader, - color = colors.primary, - modifier = Modifier.padding(bottom = dimens.spacingL) - ) - } - items(regularStyles) { (level, style) -> - TypographyStyleCard(level, style, onStyleClick) - Spacer(modifier = Modifier.height(dimens.spacingM)) - } - - item { - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - Text( - text = "MEDIUM STYLES", - style = typography.sectionHeader, - color = colors.primary, - modifier = Modifier.padding(bottom = dimens.spacingL) - ) - } - items(mediumStyles) { (level, style) -> - TypographyStyleCard(level, style, onStyleClick) - Spacer(modifier = Modifier.height(dimens.spacingM)) - } - - item { - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - Text( - text = "BOLD STYLES", - style = typography.sectionHeader, - color = colors.primary, - modifier = Modifier.padding(bottom = dimens.spacingL) - ) - } - items(boldStyles) { (level, style) -> - TypographyStyleCard(level, style, onStyleClick) - Spacer(modifier = Modifier.height(dimens.spacingM)) - } - - item { - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - Text( - text = "LOGO & DISPLAY", - style = typography.sectionHeader, - color = colors.primary, - modifier = Modifier.padding(bottom = dimens.spacingL) - ) - } - items(displayStyles) { (level, style) -> - TypographyStyleCard(level, style, onStyleClick) - Spacer(modifier = Modifier.height(dimens.spacingM)) - } - - item { - Spacer(modifier = Modifier.height(dimens.spacing3Xl)) - } - } -} - -@Composable -private fun TypographyStyleCard( - level: CloneflixTypography.StyleLevel, - style: TextStyle, - onStyleClick: (CloneflixTypography.StyleLevel) -> Unit -) { - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val typography = CloneflixTheme.typography - - val sample = when (level) { - CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE, - CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE -> "Unlimited movies, TV shows, and more" - CloneflixTypography.StyleLevel.LOGO_BEBAS -> "CLONEFLIX" - else -> "Watch anywhere. Cancel anytime." - } - - CloneflixCard( - modifier = Modifier - .fillMaxWidth() - .clickable( - onClick = { onStyleClick(level) }, - role = Role.Button - ) - .focusable() - .semantics { - contentDescription = "${level.label}, Size: ${level.sizeSp}sp, Sample: $sample" - } - ) { - Column(modifier = Modifier.padding(dimens.spacingL)) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = level.label, - style = typography.regularCaption1, - color = colors.textMuted, - modifier = Modifier.weight(1f) - ) - Text( - text = "Size: ${level.sizeSp.toInt()}sp • LH: ${level.lineHeightSp.toInt()}sp", - style = typography.regularCaption2, - color = colors.textSecondary - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingS)) - - Text( - text = sample, - style = style, - color = colors.textPrimary - ) - } - } -} - -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Composable -private fun CloneflixTypographyScreenPreview() { - CloneflixTheme { - CloneflixTypographyComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt deleted file mode 100644 index 80f11ecc252..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/screens/CloneflixVideoPlayerComposeScreen.kt +++ /dev/null @@ -1,432 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.screens - -import android.widget.Toast -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ScrollableTabRow -import androidx.compose.material3.Tab -import androidx.compose.material3.TabRowDefaults -import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -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 -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixAudioSubtitlesSelector -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixCardElevation -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixHeader -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixNextEpisodeCard -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlaybackSpeed -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlaybackSpeedSelector -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixPlayerOverlayState -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixProgressSize -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoPlayerPattern -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVideoProgressIndicator -import com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixVolumeControlPopup -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryRed -import com.lagradost.cloudstream3.ui.revamp.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.revamp.compose.theme.TransparentWhite20 - -@Composable -fun CloneflixVideoPlayerComposeScreen( - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val typography = CloneflixTheme.typography - val colors = CloneflixTheme.colors - val dimens = CloneflixTheme.dimens - val scrollState = rememberScrollState() - - var interactiveProgress by remember { mutableFloatStateOf(0.42f) } - var interactiveVolume by remember { mutableFloatStateOf(0.70f) } - var isMuted by remember { mutableStateOf(false) } - var demoSpeed by remember { mutableStateOf(CloneflixPlaybackSpeed.SPEED_1_0X) } - var demoAudio by remember { mutableStateOf("Japanese [Original]") } - var demoSubtitle by remember { mutableStateOf("English (CC)") } - - var selectedPatternState by remember { mutableStateOf(CloneflixPlayerOverlayState.DEFAULT) } - - val patternTabs = listOf( - "Default" to CloneflixPlayerOverlayState.DEFAULT, - "Sound Change" to CloneflixPlayerOverlayState.SOUND_CHANGE, - "Scroll Preview" to CloneflixPlayerOverlayState.SCROLL_PREVIEW, - "Next Preview" to CloneflixPlayerOverlayState.NEXT_PREVIEW, - "List Preview" to CloneflixPlayerOverlayState.LIST_PREVIEW, - "Speed Change" to CloneflixPlayerOverlayState.SPEED_CHANGE, - "Subtitles Change" to CloneflixPlayerOverlayState.SUBTITLES_CHANGE - ) - - Column( - modifier = modifier - .fillMaxSize() - .background(colors.background) - .verticalScroll(scrollState) - .padding(dimens.spacing2Xl) - ) { - CloneflixHeader( - title = "Video Player", - subtitle = "Video Progress Indicator • Volume Slider • Speed • Subtitles • Patterns", - iconRes = R.drawable.cloneflix_ic_videoplayer - ) - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "COMPONENTS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Video Progress Indicator (Beginning, Default, Hover)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - Text( - text = "Size = Large, Progress = Beginning (00:12 / 1:45:00)", - style = typography.mediumBody, - color = colors.textPrimary - ) - CloneflixVideoProgressIndicator( - progress = 0.02f, - bufferedProgress = 0.15f, - durationMs = 105 * 60 * 1000L, - currentPositionMs = 12 * 1000L, - size = CloneflixProgressSize.LARGE, - onProgressChange = {} - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = "Size = Large, Progress = Middle Default (1:00:41 / 1:45:00)", - style = typography.mediumBody, - color = colors.textPrimary - ) - CloneflixVideoProgressIndicator( - progress = 0.58f, - bufferedProgress = 0.78f, - durationMs = 105 * 60 * 1000L, - currentPositionMs = 3641 * 1000L, - size = CloneflixProgressSize.LARGE, - onProgressChange = {} - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = "Size = Large, Progress = Middle Hover (Glow Ring & Scrubber)", - style = typography.mediumBody, - color = colors.textPrimary - ) - CloneflixVideoProgressIndicator( - progress = 0.58f, - bufferedProgress = 0.78f, - durationMs = 105 * 60 * 1000L, - currentPositionMs = 3641 * 1000L, - size = CloneflixProgressSize.LARGE, - isHoveredOrActive = true, - onProgressChange = {} - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = "Interactive Live Scrubber (Touch / D-Pad Seekable)", - style = typography.mediumBody, - color = colors.primary - ) - CloneflixVideoProgressIndicator( - progress = interactiveProgress, - bufferedProgress = 0.85f, - durationMs = 60 * 60 * 1000L, - currentPositionMs = (interactiveProgress * 60 * 60 * 1000L).toLong(), - size = CloneflixProgressSize.LARGE, - onProgressChange = { interactiveProgress = it } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Volume Slider (High / Middle / Mute)", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(32.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(text = "High (85%)", style = typography.mediumCaption1, color = colors.textSecondary) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixVolumeControlPopup( - volume = 0.85f, - onVolumeChange = {}, - isMuted = false, - onToggleMute = {} - ) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(text = "Middle (40%)", style = typography.mediumCaption1, color = colors.textSecondary) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixVolumeControlPopup( - volume = 0.40f, - onVolumeChange = {}, - isMuted = false, - onToggleMute = {} - ) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(text = "Mute (0%)", style = typography.mediumCaption1, color = colors.textSecondary) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixVolumeControlPopup( - volume = 0f, - onVolumeChange = {}, - isMuted = true, - onToggleMute = {} - ) - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(text = "Interactive", style = typography.mediumCaption1, color = colors.primary) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixVolumeControlPopup( - volume = interactiveVolume, - onVolumeChange = { interactiveVolume = it }, - isMuted = isMuted, - onToggleMute = { isMuted = !isMuted } - ) - } - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Next Episode Preview & Speed Selector", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(24.dp), - verticalAlignment = Alignment.Top - ) { - Column(modifier = Modifier.weight(1f)) { - Text(text = "Next Episode Floating Card", style = typography.mediumCaption1, color = colors.textSecondary) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixNextEpisodeCard( - episodeNumber = "Episode 2", - episodeTitle = "The Way of Shadows", - durationText = "48m", - synopsis = "A cryptic message from the past sends Haru on a clandestine reconnaissance mission.", - countdownSeconds = 8, - totalCountdownSeconds = 10, - onPlayNextClick = { Toast.makeText(context, "Playing Next Episode", Toast.LENGTH_SHORT).show() }, - onCancelClick = { Toast.makeText(context, "Dismissed Prompt", Toast.LENGTH_SHORT).show() } - ) - } - - Column(modifier = Modifier.weight(1.2f)) { - Text(text = "Playback Speed Indicator & Stepper", style = typography.mediumCaption1, color = colors.textSecondary) - Spacer(modifier = Modifier.height(8.dp)) - CloneflixPlaybackSpeedSelector( - currentSpeed = demoSpeed, - onSpeedSelected = { demoSpeed = it } - ) - } - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Subtitles & Audio Dialog", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - val audioTracks = listOf("Japanese [Original]", "English", "Spanish (Latin America)", "French", "German") - val subtitleTracks = listOf("Off", "English (CC)", "Japanese (CC)", "Spanish", "Simplified Chinese", "Traditional Chinese", "Arabic", "German", "French") - - CloneflixAudioSubtitlesSelector( - audioTracks = audioTracks, - selectedAudio = demoAudio, - onAudioSelected = { demoAudio = it }, - subtitleTracks = subtitleTracks, - selectedSubtitle = demoSubtitle, - onSubtitleSelected = { demoSubtitle = it }, - onCloseClick = { Toast.makeText(context, "Closed Dialog", Toast.LENGTH_SHORT).show() } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacing2Xl)) - - Text( - text = "PATTERNS", - style = typography.sectionHeader, - color = colors.primary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - ScrollableTabRow( - selectedTabIndex = patternTabs.indexOfFirst { it.second == selectedPatternState }.coerceAtLeast(0), - containerColor = colors.surfaceElevated, - contentColor = colors.textPrimary, - edgePadding = 0.dp, - indicator = { tabPositions -> - val curIndex = patternTabs.indexOfFirst { it.second == selectedPatternState }.coerceAtLeast(0) - if (curIndex < tabPositions.size) { - TabRowDefaults.SecondaryIndicator( - modifier = Modifier.tabIndicatorOffset(tabPositions[curIndex]), - color = colors.primary, - height = 3.dp - ) - } - } - ) { - patternTabs.forEach { (label, state) -> - val isSelected = selectedPatternState == state - Tab( - selected = isSelected, - onClick = { selectedPatternState = state }, - text = { - Text( - text = label, - style = if (isSelected) typography.mediumBody else typography.regularBody, - color = if (isSelected) colors.textPrimary else colors.textSecondary - ) - } - ) - } - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - CloneflixCard( - modifier = Modifier.fillMaxWidth(), - elevation = CloneflixCardElevation.SURFACE - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingXl), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Type = ${patternTabs.find { it.second == selectedPatternState }?.first ?: "Default"}", - style = typography.sectionHeader, - color = colors.textSecondary - ) - - CloneflixVideoPlayerPattern( - title = "Sanctuary", - episodeLabel = "E1 Episode 1", - overlayState = selectedPatternState, - onOverlayStateChange = { selectedPatternState = it }, - onBackClick = { Toast.makeText(context, "Back Pressed", Toast.LENGTH_SHORT).show() } - ) - } - } - } -} - -@Preview(name = "Video Player Screen Preview - TV", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) -@Preview(name = "Video Player Screen Preview - Phone", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Composable -private fun VideoPlayerScreenPreview() { - CloneflixTheme { - CloneflixVideoPlayerComposeScreen() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt deleted file mode 100644 index 921fe0205eb..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/compose/theme/CloneflixComposeTheme.kt +++ /dev/null @@ -1,282 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.compose.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Shapes -import androidx.compose.material3.darkColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.Color -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.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.R - -val CloneflixFontFamily = FontFamily( - Font(R.font.netflix_sans_thin, FontWeight.Thin), - Font(R.font.netflix_sans_thin_italic, FontWeight.Thin, FontStyle.Italic), - Font(R.font.netflix_sans_light, FontWeight.Light), - Font(R.font.netflix_sans_light_italic, FontWeight.Light, FontStyle.Italic), - Font(R.font.netflix_sans_regular, FontWeight.Normal), - Font(R.font.netflix_sans_italic, FontWeight.Normal, FontStyle.Italic), - Font(R.font.netflix_sans_medium, FontWeight.Medium), - Font(R.font.netflix_sans_medium_italic, FontWeight.Medium, FontStyle.Italic), - Font(R.font.netflix_sans_bold, FontWeight.Bold), - Font(R.font.netflix_sans_bold_italic, FontWeight.Bold, FontStyle.Italic), - Font(R.font.netflix_sans_black, FontWeight.Black), - Font(R.font.netflix_sans_black_italic, FontWeight.Black, FontStyle.Italic) -) - -val PrimaryRed = Color(0xFFE50914) -val PrimaryBlack = Color(0xFF000000) -val PrimaryWhite = Color(0xFFFFFFFF) - -val Red100 = Color(0xFFEB3942) -val Red200 = Color(0xFFC11119) -val Red300 = Color(0xFFF50723) - -val Blue100 = Color(0xFF0071EB) -val Blue200 = Color(0xFF448EF4) -val Blue300 = Color(0xFF54B9C5) - -val GreenAccent = Color(0xFF46D369) -val YellowAccent = Color(0xFFFFB800) -val OrangeAccent = Color(0xFFFF8C00) - -val Grey10 = Color(0xFFE5E5E5) -val Grey20 = Color(0xFFDCDCDC) -val Grey25 = Color(0xFFD2D2D2) -val Grey50 = Color(0xFFBCBCBC) -val Grey100 = Color(0xFFB3B3B3) -val Grey150 = Color(0xFF979797) -val Grey200 = Color(0xFF808080) -val Grey250 = Color(0xFF777777) -val Grey300T40 = Color(0x666D6D6E) -val Grey300T70 = Color(0xB26D6D6E) -val Grey350 = Color(0xFF545454) -val Grey400 = Color(0xFF414141) -val Grey450 = Color(0xFF404040) -val Grey500 = Color(0xFF3A3A3A) -val Grey550 = Color(0xFF363636) -val Grey600T60 = Color(0x99333333) -val Grey600 = Color(0xFF333333) -val Grey650 = Color(0xFF2F2F2F) -val Grey700 = Color(0xFF2A2A2A) -val Grey750 = Color(0xFF262626) -val Grey800 = Color(0xFF232323) -val Grey850 = Color(0xFF181818) -val Grey900 = Color(0xFF141414) - -val TransparentWhite15 = Color(0x26FFFFFF) -val TransparentWhite20 = Color(0x33FFFFFF) -val TransparentWhite30 = Color(0x4CFFFFFF) -val TransparentWhite35 = Color(0x59FFFFFF) -val TransparentWhite50 = Color(0x7FFFFFFF) -val TransparentWhite70 = Color(0xB2FFFFFF) - -val TransparentBlack30 = Color(0x4C000000) -val TransparentBlack60 = Color(0x99000000) -val TransparentBlack90 = Color(0xE5000000) - -val SurfaceDark = Color(0xFF232323) -val SurfaceElevated = Color(0xFF181818) -val CanvasBackground = Color(0xFF141414) -val BorderSubtle = Color(0x33FFFFFF) -val DividerColor = Color(0x26FFFFFF) - -@Immutable -data class CloneflixColors( - val primary: Color = PrimaryRed, - val onPrimary: Color = PrimaryWhite, - val background: Color = CanvasBackground, - val onBackground: Color = PrimaryWhite, - val surface: Color = SurfaceDark, - val onSurface: Color = PrimaryWhite, - val surfaceElevated: Color = SurfaceElevated, - val border: Color = BorderSubtle, - val divider: Color = DividerColor, - val textPrimary: Color = PrimaryWhite, - val textSecondary: Color = Grey200, - val textMuted: Color = Grey100, - val textDimmed: Color = Grey50, - val redAccent100: Color = Red100, - val redAccent200: Color = Red200, - val redAccent300: Color = Red300, - val blueAccent100: Color = Blue100, - val blueAccent200: Color = Blue200, - val blueAccent300: Color = Blue300, - val greenAccent: Color = GreenAccent, - val yellowAccent: Color = YellowAccent, - val orangeAccent: Color = OrangeAccent -) - -fun getRatingScoreColor(scoreText: String?): Color { - if (scoreText.isNullOrBlank()) return GreenAccent - val text = scoreText.trim() - if (text.equals("New", ignoreCase = true)) return GreenAccent - - val match = Regex("""(\d+(\.\d+)?)""").find(text) - if (match != null) { - val num = match.value.toDoubleOrNull() ?: return GreenAccent - val isPercentage = text.contains("%") || num > 10.0 - val percentage = if (isPercentage) num else (num * 10.0) - - return when { - percentage >= 70.0 -> GreenAccent - percentage >= 50.0 -> YellowAccent - else -> Red100 - } - } - - return GreenAccent -} - -@Immutable -data class CloneflixTypography( - val regularCaption2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 11.sp, lineHeight = 14.sp), - val regularCaption1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 16.sp), - val regularSmallBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 18.sp), - val regularBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 20.sp), - val regularHeadline2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 17.sp, lineHeight = 22.sp), - val regularHeadline1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 18.sp, lineHeight = 22.sp), - val regularTitle4: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 20.sp, lineHeight = 30.sp), - val regularTitle3: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 21.sp, lineHeight = 25.sp), - val regularTitle2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 24.sp, lineHeight = 30.sp), - val regularTitle1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 27.sp, lineHeight = 36.sp), - val regularLargeTitle: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Normal, fontSize = 50.sp, lineHeight = 64.sp), - - val mediumCaption2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 15.sp), - val mediumCaption1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 13.sp, lineHeight = 17.sp), - val mediumSmallBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 18.sp), - val mediumBody: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 16.sp, lineHeight = 24.sp), - val mediumHeadline2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 20.sp, lineHeight = 30.sp), - val mediumHeadline1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 21.sp, lineHeight = 25.sp), - val mediumTitle4: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 22.sp, lineHeight = 22.sp), - val mediumTitle3: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 24.sp, lineHeight = 31.sp), - val mediumTitle2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 28.sp, lineHeight = 45.sp), - val mediumTitle1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 30.sp, lineHeight = 45.sp), - val mediumLargeTitle: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Medium, fontSize = 33.sp, lineHeight = 42.sp), - - val boldTitle2: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 20.sp, lineHeight = 30.sp), - val boldTitle1: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 48.sp, lineHeight = 62.sp), - val boldLargeTitle: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 55.sp, lineHeight = 70.sp), - - val headerDisplay: TextStyle = TextStyle(fontFamily = CloneflixFontFamily, fontWeight = FontWeight.Bold, fontSize = 36.sp, lineHeight = 44.sp), - val sectionHeader: TextStyle = TextStyle(fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Bold, fontSize = 24.sp, lineHeight = 30.sp, color = PrimaryRed), - val logoBebas: TextStyle = TextStyle(fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Bold, fontSize = 32.sp, lineHeight = 38.sp, color = PrimaryRed) -) - -@Immutable -data class CloneflixDimens( - val spacingXxs: Dp = 2.dp, - val spacingXs: Dp = 4.dp, - val spacingS: Dp = 8.dp, - val spacingM: Dp = 12.dp, - val spacingL: Dp = 16.dp, - val spacingXl: Dp = 20.dp, - val spacing2Xl: Dp = 24.dp, - val spacing3Xl: Dp = 32.dp, - val spacing4Xl: Dp = 40.dp, - val spacing5Xl: Dp = 48.dp, - val spacing6Xl: Dp = 64.dp, - val spacing7Xl: Dp = 80.dp, - - val radiusXs: Dp = 4.dp, - val radiusS: Dp = 8.dp, - val radiusM: Dp = 12.dp, - val radiusL: Dp = 16.dp, - val radiusXl: Dp = 24.dp, - val radiusFull: Dp = 999.dp, - - val headerIconSize: Dp = 80.dp, - val headerIconStroke: Dp = 1.4.dp, - val buttonHeightLarge: Dp = 48.dp, - val buttonHeightMedium: Dp = 40.dp, - val buttonHeightSmall: Dp = 32.dp, - val inputHeight: Dp = 52.dp, - val swatchSize: Dp = 56.dp, - val dividerThickness: Dp = 1.dp, - - val iconXs: Dp = 14.dp, - val iconS: Dp = 16.dp, - val iconM: Dp = 24.dp, - val iconL: Dp = 32.dp, - val iconXl: Dp = 40.dp, - - val borderSubtle: Dp = 0.5.dp, - val borderDefault: Dp = 1.dp, - val borderFocus: Dp = 2.dp, - - val progressHeight: Dp = 3.5.dp -) - -val LocalCloneflixColors = staticCompositionLocalOf { CloneflixColors() } -val LocalCloneflixTypography = staticCompositionLocalOf { CloneflixTypography() } -val LocalCloneflixDimens = staticCompositionLocalOf { CloneflixDimens() } - -val CloneflixShapes = Shapes( - extraSmall = RoundedCornerShape(4.dp), - small = RoundedCornerShape(8.dp), - medium = RoundedCornerShape(12.dp), - large = RoundedCornerShape(16.dp), - extraLarge = RoundedCornerShape(24.dp) -) - -object CloneflixTheme { - val colors: CloneflixColors - @Composable - @ReadOnlyComposable - get() = LocalCloneflixColors.current - - val typography: CloneflixTypography - @Composable - @ReadOnlyComposable - get() = LocalCloneflixTypography.current - - val dimens: CloneflixDimens - @Composable - @ReadOnlyComposable - get() = LocalCloneflixDimens.current - - val shapes: Shapes - @Composable - @ReadOnlyComposable - get() = MaterialTheme.shapes -} - -@Composable -fun CloneflixTheme( - colors: CloneflixColors = CloneflixColors(), - typography: CloneflixTypography = CloneflixTypography(), - dimens: CloneflixDimens = CloneflixDimens(), - content: @Composable () -> Unit -) { - val materialColors = darkColorScheme( - primary = colors.primary, - onPrimary = colors.onPrimary, - background = colors.background, - onBackground = colors.onBackground, - surface = colors.surface, - onSurface = colors.onSurface - ) - - CompositionLocalProvider( - LocalCloneflixColors provides colors, - LocalCloneflixTypography provides typography, - LocalCloneflixDimens provides dimens - ) { - MaterialTheme( - colorScheme = materialColors, - shapes = CloneflixShapes, - content = content - ) - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt deleted file mode 100644 index e5f4d1742da..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/icons/CloneflixIconsLabelsFragment.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.icons - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView -import androidx.fragment.app.Fragment -import com.lagradost.cloudstream3.ui.revamp.compose.screens.CloneflixIconsLabelsComposeScreen -import com.lagradost.cloudstream3.ui.revamp.compose.theme.CloneflixTheme - -class CloneflixIconsLabelsFragment : Fragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return ComposeView(requireContext()).apply { - setContent { - CloneflixTheme { - CloneflixIconsLabelsComposeScreen() - } - } - } - } - - companion object { - fun newInstance() = CloneflixIconsLabelsFragment() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt deleted file mode 100644 index 24facb00218..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixColors.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.theme - -import android.graphics.Color -import androidx.annotation.ColorInt - -object CloneflixColors { - - const val PRIMARY_RED_HEX = "#E50914" - const val PRIMARY_BLACK_HEX = "#000000" - const val PRIMARY_WHITE_HEX = "#FFFFFF" - - @ColorInt val PrimaryRed = Color.parseColor(PRIMARY_RED_HEX) - @ColorInt val PrimaryBlack = Color.parseColor(PRIMARY_BLACK_HEX) - @ColorInt val PrimaryWhite = Color.parseColor(PRIMARY_WHITE_HEX) - - const val RED_100_HEX = "#EB3942" - const val RED_200_HEX = "#C11119" - const val RED_300_HEX = "#F50723" - - @ColorInt val Red100 = Color.parseColor(RED_100_HEX) - @ColorInt val Red200 = Color.parseColor(RED_200_HEX) - @ColorInt val Red300 = Color.parseColor(RED_300_HEX) - - const val BLUE_100_HEX = "#0071EB" - const val BLUE_200_HEX = "#448EF4" - const val BLUE_300_HEX = "#54B9C5" - - @ColorInt val Blue100 = Color.parseColor(BLUE_100_HEX) - @ColorInt val Blue200 = Color.parseColor(BLUE_200_HEX) - @ColorInt val Blue300 = Color.parseColor(BLUE_300_HEX) - - const val GREEN_HEX = "#46D369" - @ColorInt val Green = Color.parseColor(GREEN_HEX) - - const val GREY_10_HEX = "#E5E5E5" - const val GREY_20_HEX = "#DCDCDC" - const val GREY_25_HEX = "#D2D2D2" - const val GREY_50_HEX = "#BCBCBC" - const val GREY_100_HEX = "#B3B3B3" - const val GREY_150_HEX = "#979797" - const val GREY_200_HEX = "#808080" - const val GREY_250_HEX = "#777777" - const val GREY_300_T40_HEX = "#666D6D6E" - const val GREY_300_T70_HEX = "#B26D6D6E" - const val GREY_350_HEX = "#545454" - const val GREY_400_HEX = "#414141" - const val GREY_450_HEX = "#404040" - const val GREY_500_HEX = "#3A3A3A" - const val GREY_550_HEX = "#363636" - const val GREY_600_T60_HEX = "#99333333" - const val GREY_600_HEX = "#333333" - const val GREY_650_HEX = "#2F2F2F" - const val GREY_700_HEX = "#2A2A2A" - const val GREY_750_HEX = "#262626" - const val GREY_800_HEX = "#232323" - const val GREY_850_HEX = "#181818" - const val GREY_900_HEX = "#141414" - - @ColorInt val Grey10 = Color.parseColor(GREY_10_HEX) - @ColorInt val Grey20 = Color.parseColor(GREY_20_HEX) - @ColorInt val Grey25 = Color.parseColor(GREY_25_HEX) - @ColorInt val Grey50 = Color.parseColor(GREY_50_HEX) - @ColorInt val Grey100 = Color.parseColor(GREY_100_HEX) - @ColorInt val Grey150 = Color.parseColor(GREY_150_HEX) - @ColorInt val Grey200 = Color.parseColor(GREY_200_HEX) - @ColorInt val Grey250 = Color.parseColor(GREY_250_HEX) - @ColorInt val Grey300T40 = Color.parseColor(GREY_300_T40_HEX) - @ColorInt val Grey300T70 = Color.parseColor(GREY_300_T70_HEX) - @ColorInt val Grey350 = Color.parseColor(GREY_350_HEX) - @ColorInt val Grey400 = Color.parseColor(GREY_400_HEX) - @ColorInt val Grey450 = Color.parseColor(GREY_450_HEX) - @ColorInt val Grey500 = Color.parseColor(GREY_500_HEX) - @ColorInt val Grey550 = Color.parseColor(GREY_550_HEX) - @ColorInt val Grey600T60 = Color.parseColor(GREY_600_T60_HEX) - @ColorInt val Grey600 = Color.parseColor(GREY_600_HEX) - @ColorInt val Grey650 = Color.parseColor(GREY_650_HEX) - @ColorInt val Grey700 = Color.parseColor(GREY_700_HEX) - @ColorInt val Grey750 = Color.parseColor(GREY_750_HEX) - @ColorInt val Grey800 = Color.parseColor(GREY_800_HEX) - @ColorInt val Grey850 = Color.parseColor(GREY_850_HEX) - @ColorInt val Grey900 = Color.parseColor(GREY_900_HEX) - - const val TRANSPARENT_WHITE_15_HEX = "#26FFFFFF" - const val TRANSPARENT_WHITE_20_HEX = "#33FFFFFF" - const val TRANSPARENT_WHITE_30_HEX = "#4CFFFFFF" - const val TRANSPARENT_WHITE_35_HEX = "#59FFFFFF" - const val TRANSPARENT_WHITE_50_HEX = "#7FFFFFFF" - const val TRANSPARENT_WHITE_70_HEX = "#B2FFFFFF" - - @ColorInt val TransparentWhite15 = Color.parseColor(TRANSPARENT_WHITE_15_HEX) - @ColorInt val TransparentWhite20 = Color.parseColor(TRANSPARENT_WHITE_20_HEX) - @ColorInt val TransparentWhite30 = Color.parseColor(TRANSPARENT_WHITE_30_HEX) - @ColorInt val TransparentWhite35 = Color.parseColor(TRANSPARENT_WHITE_35_HEX) - @ColorInt val TransparentWhite50 = Color.parseColor(TRANSPARENT_WHITE_50_HEX) - @ColorInt val TransparentWhite70 = Color.parseColor(TRANSPARENT_WHITE_70_HEX) - - const val TRANSPARENT_BLACK_30_HEX = "#4C000000" - const val TRANSPARENT_BLACK_60_HEX = "#99000000" - const val TRANSPARENT_BLACK_90_HEX = "#E5000000" - - @ColorInt val TransparentBlack30 = Color.parseColor(TRANSPARENT_BLACK_30_HEX) - @ColorInt val TransparentBlack60 = Color.parseColor(TRANSPARENT_BLACK_60_HEX) - @ColorInt val TransparentBlack90 = Color.parseColor(TRANSPARENT_BLACK_90_HEX) - - data class ColorToken( - val category: String, - val name: String, - val hex: String, - val rgbDescription: String, - @ColorInt val colorInt: Int, - val isDark: Boolean = true - ) - - fun getAllTokens(): List = listOf( - ColorToken("Primary", "Primary / Red", PRIMARY_RED_HEX, "RGB (229, 9, 20)", PrimaryRed), - ColorToken("Primary", "Primary / Black", PRIMARY_BLACK_HEX, "RGB (0, 0, 0)", PrimaryBlack), - ColorToken("Primary", "Primary / White", PRIMARY_WHITE_HEX, "RGB (255, 255, 255)", PrimaryWhite, isDark = false), - - ColorToken("Secondary", "Secondary / Red-100", RED_100_HEX, "RGB (235, 57, 66)", Red100), - ColorToken("Secondary", "Secondary / Red-200", RED_200_HEX, "RGB (193, 17, 25)", Red200), - ColorToken("Secondary", "Secondary / Red-300", RED_300_HEX, "RGB (245, 7, 35)", Red300), - ColorToken("Secondary", "Secondary / Blue-100", BLUE_100_HEX, "RGB (0, 113, 235)", Blue100), - ColorToken("Secondary", "Secondary / Blue-200", BLUE_200_HEX, "RGB (68, 142, 244)", Blue200), - ColorToken("Secondary", "Secondary / Blue-300", BLUE_300_HEX, "RGB (84, 185, 197)", Blue300), - ColorToken("Secondary", "Secondary / Green", GREEN_HEX, "RGB (70, 211, 105)", Green), - - ColorToken("Neutral Greys", "Grey-10", GREY_10_HEX, "RGB (229, 229, 229)", Grey10, isDark = false), - ColorToken("Neutral Greys", "Grey-20", GREY_20_HEX, "RGB (220, 220, 220)", Grey20, isDark = false), - ColorToken("Neutral Greys", "Grey-25", GREY_25_HEX, "RGB (210, 210, 210)", Grey25, isDark = false), - ColorToken("Neutral Greys", "Grey-50", GREY_50_HEX, "RGB (188, 188, 188)", Grey50, isDark = false), - ColorToken("Neutral Greys", "Grey-100", GREY_100_HEX, "RGB (179, 179, 179)", Grey100), - ColorToken("Neutral Greys", "Grey-150", GREY_150_HEX, "RGB (151, 151, 151)", Grey150), - ColorToken("Neutral Greys", "Grey-200", GREY_200_HEX, "RGB (128, 128, 128)", Grey200), - ColorToken("Neutral Greys", "Grey-250", GREY_250_HEX, "RGB (119, 119, 119)", Grey250), - ColorToken("Neutral Greys", "Grey-300T40", GREY_300_T40_HEX, "RGB (109, 109, 110, 40%)", Grey300T40), - ColorToken("Neutral Greys", "Grey-300T70", GREY_300_T70_HEX, "RGB (109, 109, 110, 70%)", Grey300T70), - ColorToken("Neutral Greys", "Grey-350", GREY_350_HEX, "RGB (84, 84, 84)", Grey350), - ColorToken("Neutral Greys", "Grey-400", GREY_400_HEX, "RGB (65, 65, 65)", Grey400), - ColorToken("Neutral Greys", "Grey-450", GREY_450_HEX, "RGB (64, 64, 64)", Grey450), - ColorToken("Neutral Greys", "Grey-500", GREY_500_HEX, "RGB (58, 58, 58)", Grey500), - ColorToken("Neutral Greys", "Grey-550", GREY_550_HEX, "RGB (54, 54, 54)", Grey550), - ColorToken("Neutral Greys", "Grey-600T60", GREY_600_T60_HEX, "RGB (51, 51, 51, 60%)", Grey600T60), - ColorToken("Neutral Greys", "Grey-600", GREY_600_HEX, "RGB (51, 51, 51)", Grey600), - ColorToken("Neutral Greys", "Grey-650", GREY_650_HEX, "RGB (47, 47, 47)", Grey650), - ColorToken("Neutral Greys", "Grey-700", GREY_700_HEX, "RGB (42, 42, 42)", Grey700), - ColorToken("Neutral Greys", "Grey-750", GREY_750_HEX, "RGB (38, 38, 38)", Grey750), - ColorToken("Neutral Greys", "Grey-800", GREY_800_HEX, "RGB (35, 35, 35)", Grey800), - ColorToken("Neutral Greys", "Grey-850", GREY_850_HEX, "RGB (24, 24, 24)", Grey850), - ColorToken("Neutral Greys", "Grey-900", GREY_900_HEX, "RGB (20, 20, 20)", Grey900), - - ColorToken("Transparent White", "TransparentWhite-15", TRANSPARENT_WHITE_15_HEX, "RGB (255, 255, 255, 15%)", TransparentWhite15), - ColorToken("Transparent White", "TransparentWhite-20", TRANSPARENT_WHITE_20_HEX, "RGB (255, 255, 255, 20%)", TransparentWhite20), - ColorToken("Transparent White", "TransparentWhite-30", TRANSPARENT_WHITE_30_HEX, "RGB (255, 255, 255, 30%)", TransparentWhite30), - ColorToken("Transparent White", "TransparentWhite-35", TRANSPARENT_WHITE_35_HEX, "RGB (255, 255, 255, 35%)", TransparentWhite35), - ColorToken("Transparent White", "TransparentWhite-50", TRANSPARENT_WHITE_50_HEX, "RGB (255, 255, 255, 50%)", TransparentWhite50), - ColorToken("Transparent White", "TransparentWhite-70", TRANSPARENT_WHITE_70_HEX, "RGB (255, 255, 255, 70%)", TransparentWhite70), - - ColorToken("Transparent Black", "TransparentBlack-30", TRANSPARENT_BLACK_30_HEX, "RGB (0, 0, 0, 30%)", TransparentBlack30), - ColorToken("Transparent Black", "TransparentBlack-60", TRANSPARENT_BLACK_60_HEX, "RGB (0, 0, 0, 60%)", TransparentBlack60), - ColorToken("Transparent Black", "TransparentBlack-90", TRANSPARENT_BLACK_90_HEX, "RGB (0, 0, 0, 90%)", TransparentBlack90) - ) -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt deleted file mode 100644 index 5f457f45c13..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixDimens.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.theme - -import android.content.Context -import android.util.TypedValue - -object CloneflixDimens { - const val SPACING_XXS = 2 - const val SPACING_XS = 4 - const val SPACING_S = 8 - const val SPACING_M = 12 - const val SPACING_L = 16 - const val SPACING_XL = 20 - const val SPACING_2XL = 24 - const val SPACING_3XL = 32 - const val SPACING_4XL = 40 - const val SPACING_5XL = 48 - const val SPACING_6XL = 64 - const val SPACING_7XL = 80 - - const val RADIUS_XS = 4f - const val RADIUS_S = 8f - const val RADIUS_M = 12f - const val RADIUS_L = 16f - const val RADIUS_XL = 24f - const val RADIUS_FULL = 999f - - const val HEADER_ICON_SIZE = 80 - const val HEADER_ICON_SIZE_COMPACT = 56 - const val HEADER_ICON_STROKE_WIDTH = 1.4f - - const val BUTTON_HEIGHT_LARGE = 48 - const val BUTTON_HEIGHT_MEDIUM = 40 - const val BUTTON_HEIGHT_SMALL = 32 - const val INPUT_HEIGHT = 52 - - const val ICON_XS = 14 - const val ICON_S = 16 - const val ICON_M = 24 - const val ICON_L = 32 - const val ICON_XL = 40 - - const val BORDER_SUBTLE = 0.5f - const val BORDER_DEFAULT = 1f - const val BORDER_FOCUS = 2f - - fun dpToPx(context: Context, dp: Float): Float { - return TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - dp, - context.resources.displayMetrics - ) - } - - fun spToPx(context: Context, sp: Float): Float { - return TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_SP, - sp, - context.resources.displayMetrics - ) - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt deleted file mode 100644 index 742ffd9000a..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTheme.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.theme - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.widget.Toast - -object CloneflixTheme { - - fun copyToClipboard(context: Context, label: String, text: String) { - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager - val clip = ClipData.newPlainText(label, text) - clipboard?.setPrimaryClip(clip) - Toast.makeText(context, "Copied $label: $text", Toast.LENGTH_SHORT).show() - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt deleted file mode 100644 index 7c6830707d9..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/theme/CloneflixTypography.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.theme - -import android.content.Context -import android.graphics.Typeface -import androidx.annotation.StyleRes -import androidx.core.content.res.ResourcesCompat -import com.lagradost.cloudstream3.R - -object CloneflixTypography { - - const val FONT_FAMILY_NETFLIX_SANS = "Netflix Sans" - const val FONT_FAMILY_BEBAS_NEUE = "Bebas Neue" - - enum class Weight { - REGULAR, - MEDIUM, - BOLD - } - - enum class StyleLevel( - val label: String, - val sizeSp: Float, - val lineHeightSp: Float, - val weight: Weight, - @StyleRes val styleResId: Int - ) { - REGULAR_CAPTION2("Regular / Caption2 / 11px", 11f, 14f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Caption2), - REGULAR_CAPTION1("Regular / Caption1 / 13px", 13f, 16f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Caption1), - REGULAR_SMALL_BODY("Regular / SmallBody / 14px", 14f, 18f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_SmallBody), - REGULAR_BODY("Regular / Body / 16px", 16f, 20f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Body), - REGULAR_HEADLINE2("Regular / Headline2 / 17px", 17f, 22f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Headline2), - REGULAR_HEADLINE1("Regular / Headline1 / 18px", 18f, 22f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Headline1), - REGULAR_TITLE4("Regular / Title4 / 20px", 20f, 30f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title4), - REGULAR_TITLE3("Regular / Title3 / 21px", 21f, 25f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title3), - REGULAR_TITLE2("Regular / Title2 / 24px", 24f, 30f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title2), - REGULAR_TITLE1("Regular / Title1 / 27px", 27f, 36f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_Title1), - REGULAR_LARGE_TITLE("Regular / LargeTitle / 50px", 50f, 64f, Weight.REGULAR, R.style.TextAppearance_Cloneflix_Regular_LargeTitle), - - MEDIUM_CAPTION2("Medium / Caption2 / 12px", 12f, 15f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Caption2), - MEDIUM_CAPTION1("Medium / Caption1 / 13px", 13f, 17f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Caption1), - MEDIUM_SMALL_BODY("Medium / SmallBody / 14px", 14f, 18f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_SmallBody), - MEDIUM_BODY("Medium / Body / 16px", 16f, 24f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Body), - MEDIUM_HEADLINE2("Medium / Headline2 / 20px", 20f, 30f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Headline2), - MEDIUM_HEADLINE1("Medium / Headline1 / 21px", 21f, 25f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Headline1), - MEDIUM_TITLE4("Medium / Title4 / 22px", 22f, 22f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title4), - MEDIUM_TITLE3("Medium / Title3 / 24px", 24f, 31f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title3), - MEDIUM_TITLE2("Medium / Title2 / 28px", 28f, 45f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title2), - MEDIUM_TITLE1("Medium / Title1 / 30px", 30f, 45f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_Title1), - MEDIUM_LARGE_TITLE("Medium / LargeTitle / 33px", 33f, 42f, Weight.MEDIUM, R.style.TextAppearance_Cloneflix_Medium_LargeTitle), - - BOLD_TITLE2("Bold / Title2 / 20px", 20f, 30f, Weight.BOLD, R.style.TextAppearance_Cloneflix_Bold_Title2), - BOLD_TITLE1("Bold / Title1 / 48px", 48f, 62f, Weight.BOLD, R.style.TextAppearance_Cloneflix_Bold_Title1), - BOLD_LARGE_TITLE("Bold / LargeTitle / 55px", 55f, 70f, Weight.BOLD, R.style.TextAppearance_Cloneflix_Bold_LargeTitle), - - LOGO_BEBAS("Logo – Bebas Neue / 32px", 32f, 38f, Weight.BOLD, R.style.TextAppearance_Cloneflix_LogoBebas), - HEADER_DISPLAY("Display Header / 40px", 40f, 48f, Weight.BOLD, R.style.TextAppearance_Cloneflix_HeaderDisplay); - - fun getCategoryName(): String = when (weight) { - Weight.REGULAR -> "Regular Styles" - Weight.MEDIUM -> "Medium Styles" - Weight.BOLD -> if (this == LOGO_BEBAS) "Logo & Display" else "Bold Styles" - } - } - - fun getFontTypeface(context: Context, weight: Weight): Typeface { - val base = try { - ResourcesCompat.getFont(context, R.font.netflix_sans) - } catch (_: Exception) { - null - } ?: Typeface.SANS_SERIF - - return when (weight) { - Weight.REGULAR -> Typeface.create(base, Typeface.NORMAL) - Weight.MEDIUM -> Typeface.create(base, Typeface.BOLD) - Weight.BOLD -> Typeface.create(base, Typeface.BOLD) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt deleted file mode 100644 index e8522d0e9b1..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyAdapter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.typography - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.core.widget.TextViewCompat -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.databinding.RevampItemTypographyStyleBinding -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTheme -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography - -class CloneflixTypographyAdapter( - private val styles: List, - private val sampleTextProvider: ((CloneflixTypography.StyleLevel) -> String)? = null -) : RecyclerView.Adapter() { - - class ViewHolder(val binding: RevampItemTypographyStyleBinding) : - RecyclerView.ViewHolder(binding.root) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = RevampItemTypographyStyleBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val style = styles[position] - holder.binding.styleLabel.text = style.label - holder.binding.styleMetrics.text = "Size: ${style.sizeSp.toInt()}sp • LH: ${style.lineHeightSp.toInt()}sp" - - val sample = sampleTextProvider?.invoke(style) ?: when (style) { - CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE, - CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE -> "Unlimited movies, TV shows, and more" - CloneflixTypography.StyleLevel.LOGO_BEBAS -> "CLONEFLIX" - else -> "Watch anywhere. Cancel anytime." - } - - holder.binding.styleSampleText.text = sample - TextViewCompat.setTextAppearance(holder.binding.styleSampleText, style.styleResId) - holder.binding.styleSampleText.typeface = - CloneflixTypography.getFontTypeface(holder.itemView.context, style.weight) - - holder.itemView.setOnClickListener { - CloneflixTheme.copyToClipboard( - holder.itemView.context, - style.label, - "fontSize: ${style.sizeSp}sp, lineHeight: ${style.lineHeightSp}sp" - ) - } - } - - override fun getItemCount(): Int = styles.size -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt deleted file mode 100644 index 00165a6bd25..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/revamp/typography/CloneflixTypographyFragment.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp.typography - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import com.lagradost.cloudstream3.databinding.RevampFragmentTypographyBinding -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography - -class CloneflixTypographyFragment : Fragment() { - - private var _binding: RevampFragmentTypographyBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = RevampFragmentTypographyBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val regularStyles = listOf( - CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE, - CloneflixTypography.StyleLevel.REGULAR_TITLE1, - CloneflixTypography.StyleLevel.REGULAR_TITLE2, - CloneflixTypography.StyleLevel.REGULAR_TITLE3, - CloneflixTypography.StyleLevel.REGULAR_TITLE4, - CloneflixTypography.StyleLevel.REGULAR_HEADLINE1, - CloneflixTypography.StyleLevel.REGULAR_HEADLINE2, - CloneflixTypography.StyleLevel.REGULAR_BODY, - CloneflixTypography.StyleLevel.REGULAR_SMALL_BODY, - CloneflixTypography.StyleLevel.REGULAR_CAPTION1, - CloneflixTypography.StyleLevel.REGULAR_CAPTION2 - ) - binding.rvRegularStyles.layoutManager = LinearLayoutManager(requireContext()) - binding.rvRegularStyles.adapter = CloneflixTypographyAdapter(regularStyles) - - val mediumStyles = listOf( - CloneflixTypography.StyleLevel.MEDIUM_LARGE_TITLE, - CloneflixTypography.StyleLevel.MEDIUM_TITLE1, - CloneflixTypography.StyleLevel.MEDIUM_TITLE2, - CloneflixTypography.StyleLevel.MEDIUM_TITLE3, - CloneflixTypography.StyleLevel.MEDIUM_TITLE4, - CloneflixTypography.StyleLevel.MEDIUM_HEADLINE1, - CloneflixTypography.StyleLevel.MEDIUM_HEADLINE2, - CloneflixTypography.StyleLevel.MEDIUM_BODY, - CloneflixTypography.StyleLevel.MEDIUM_SMALL_BODY, - CloneflixTypography.StyleLevel.MEDIUM_CAPTION1, - CloneflixTypography.StyleLevel.MEDIUM_CAPTION2 - ) - binding.rvMediumStyles.layoutManager = LinearLayoutManager(requireContext()) - binding.rvMediumStyles.adapter = CloneflixTypographyAdapter(mediumStyles) - - val boldStyles = listOf( - CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE, - CloneflixTypography.StyleLevel.BOLD_TITLE1, - CloneflixTypography.StyleLevel.BOLD_TITLE2 - ) - binding.rvBoldStyles.layoutManager = LinearLayoutManager(requireContext()) - binding.rvBoldStyles.adapter = CloneflixTypographyAdapter(boldStyles) - - val displayStyles = listOf( - CloneflixTypography.StyleLevel.LOGO_BEBAS, - CloneflixTypography.StyleLevel.HEADER_DISPLAY - ) - binding.rvDisplayStyles.layoutManager = LinearLayoutManager(requireContext()) - binding.rvDisplayStyles.adapter = CloneflixTypographyAdapter(displayStyles) - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - companion object { - fun newInstance() = CloneflixTypographyFragment() - } -} diff --git a/app/src/main/res/drawable/cloneflix_bg_card.xml b/app/src/main/res/drawable/cloneflix_bg_card.xml deleted file mode 100644 index 7171b555c1d..00000000000 --- a/app/src/main/res/drawable/cloneflix_bg_card.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/cloneflix_bg_card_elevated.xml b/app/src/main/res/drawable/cloneflix_bg_card_elevated.xml deleted file mode 100644 index 3fc4058710f..00000000000 --- a/app/src/main/res/drawable/cloneflix_bg_card_elevated.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/cloneflix_bg_header_icon.xml b/app/src/main/res/drawable/cloneflix_bg_header_icon.xml deleted file mode 100644 index 7211a98c88f..00000000000 --- a/app/src/main/res/drawable/cloneflix_bg_header_icon.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/cloneflix_bg_input_field.xml b/app/src/main/res/drawable/cloneflix_bg_input_field.xml deleted file mode 100644 index 63b61482418..00000000000 --- a/app/src/main/res/drawable/cloneflix_bg_input_field.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml b/app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml deleted file mode 100644 index a95dbb28122..00000000000 --- a/app/src/main/res/drawable/cloneflix_bg_swatch_preview.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/cloneflix_btn_ghost.xml b/app/src/main/res/drawable/cloneflix_btn_ghost.xml deleted file mode 100644 index 372d0556001..00000000000 --- a/app/src/main/res/drawable/cloneflix_btn_ghost.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/cloneflix_btn_outline.xml b/app/src/main/res/drawable/cloneflix_btn_outline.xml deleted file mode 100644 index f5063b7f9de..00000000000 --- a/app/src/main/res/drawable/cloneflix_btn_outline.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/cloneflix_btn_primary.xml b/app/src/main/res/drawable/cloneflix_btn_primary.xml deleted file mode 100644 index 8ffe51a8bf0..00000000000 --- a/app/src/main/res/drawable/cloneflix_btn_primary.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/cloneflix_btn_secondary.xml b/app/src/main/res/drawable/cloneflix_btn_secondary.xml deleted file mode 100644 index 20525949d09..00000000000 --- a/app/src/main/res/drawable/cloneflix_btn_secondary.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_about.xml b/app/src/main/res/drawable/cloneflix_ic_about.xml deleted file mode 100644 index b6dec8b6fbf..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_about.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_account.xml b/app/src/main/res/drawable/cloneflix_ic_account.xml deleted file mode 100644 index 03572e7e9c1..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_account.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_ad.xml b/app/src/main/res/drawable/cloneflix_ic_ad.xml deleted file mode 100644 index ec135ce2fef..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_ad.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_down.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_down.xml deleted file mode 100644 index 748ed226427..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_arrow_down.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_left.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_left.xml deleted file mode 100644 index d37be42d2ad..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_arrow_left.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_right.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_right.xml deleted file mode 100644 index e8d20a05af1..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_arrow_right.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml deleted file mode 100644 index e0636536296..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_arrow_right_narrow.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_arrow_up.xml b/app/src/main/res/drawable/cloneflix_ic_arrow_up.xml deleted file mode 100644 index 9c911645664..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_arrow_up.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_check.xml b/app/src/main/res/drawable/cloneflix_ic_check.xml deleted file mode 100644 index 09f9186706c..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_check.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_chevron_right.xml b/app/src/main/res/drawable/cloneflix_ic_chevron_right.xml deleted file mode 100644 index 5124bcac0d7..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_chevron_right.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_circle_error.xml b/app/src/main/res/drawable/cloneflix_ic_circle_error.xml deleted file mode 100644 index 5fa180d5082..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_circle_error.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_close.xml b/app/src/main/res/drawable/cloneflix_ic_close.xml deleted file mode 100644 index 5de778d0621..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_close.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_colors.xml b/app/src/main/res/drawable/cloneflix_ic_colors.xml deleted file mode 100644 index 13066f556be..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_colors.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_copy.xml b/app/src/main/res/drawable/cloneflix_ic_copy.xml deleted file mode 100644 index 048265f78e2..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_copy.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml b/app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml deleted file mode 100644 index 3193dee30da..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_double_thumb_up.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_dropdown.xml b/app/src/main/res/drawable/cloneflix_ic_dropdown.xml deleted file mode 100644 index f9419db01c6..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_dropdown.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_edit.xml b/app/src/main/res/drawable/cloneflix_ic_edit.xml deleted file mode 100644 index d123eb107ac..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_edit.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_error_circle.xml b/app/src/main/res/drawable/cloneflix_ic_error_circle.xml deleted file mode 100644 index ad4bdca4bbf..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_error_circle.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_feedback.xml b/app/src/main/res/drawable/cloneflix_ic_feedback.xml deleted file mode 100644 index 39631e3ec8b..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_feedback.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_flag.xml b/app/src/main/res/drawable/cloneflix_ic_flag.xml deleted file mode 100644 index cd43e572858..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_flag.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_help.xml b/app/src/main/res/drawable/cloneflix_ic_help.xml deleted file mode 100644 index 47f5e55eab6..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_help.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_info.xml b/app/src/main/res/drawable/cloneflix_ic_info.xml deleted file mode 100644 index d522173abf7..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_info.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_input_field.xml b/app/src/main/res/drawable/cloneflix_ic_input_field.xml deleted file mode 100644 index cd21cf23975..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_input_field.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_labels.xml b/app/src/main/res/drawable/cloneflix_ic_labels.xml deleted file mode 100644 index f985fb53c4d..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_labels.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_mute.xml b/app/src/main/res/drawable/cloneflix_ic_mute.xml deleted file mode 100644 index f782a2ec788..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_mute.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_notification.xml b/app/src/main/res/drawable/cloneflix_ic_notification.xml deleted file mode 100644 index f906b77cf5a..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_notification.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_open_in_new.xml b/app/src/main/res/drawable/cloneflix_ic_open_in_new.xml deleted file mode 100644 index 673ed03eac6..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_open_in_new.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_person.xml b/app/src/main/res/drawable/cloneflix_ic_person.xml deleted file mode 100644 index d81d7dc1aea..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_person.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_play.xml b/app/src/main/res/drawable/cloneflix_ic_play.xml deleted file mode 100644 index 5489ac7f16c..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_play.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_plus.xml b/app/src/main/res/drawable/cloneflix_ic_plus.xml deleted file mode 100644 index 346be53277d..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_plus.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_replay.xml b/app/src/main/res/drawable/cloneflix_ic_replay.xml deleted file mode 100644 index 2f8e48c887e..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_replay.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml b/app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml deleted file mode 100644 index 2c2a810f4b4..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_replay_arrow.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_search.xml b/app/src/main/res/drawable/cloneflix_ic_search.xml deleted file mode 100644 index 6790961fce5..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_search.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_social_fb.xml b/app/src/main/res/drawable/cloneflix_ic_social_fb.xml deleted file mode 100644 index 3eca65fa879..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_social_fb.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_social_ig.xml b/app/src/main/res/drawable/cloneflix_ic_social_ig.xml deleted file mode 100644 index 7dd0bdae8a5..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_social_ig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_social_x.xml b/app/src/main/res/drawable/cloneflix_ic_social_x.xml deleted file mode 100644 index 67e638e3a5e..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_social_x.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_social_yt.xml b/app/src/main/res/drawable/cloneflix_ic_social_yt.xml deleted file mode 100644 index f99c797c5ac..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_social_yt.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_thumb_down.xml b/app/src/main/res/drawable/cloneflix_ic_thumb_down.xml deleted file mode 100644 index 7295507eab1..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_thumb_down.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_thumb_up.xml b/app/src/main/res/drawable/cloneflix_ic_thumb_up.xml deleted file mode 100644 index 8d2fb9eea72..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_thumb_up.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_typography.xml b/app/src/main/res/drawable/cloneflix_ic_typography.xml deleted file mode 100644 index 6f7c3fdc36c..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_typography.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_videoplayer.xml b/app/src/main/res/drawable/cloneflix_ic_videoplayer.xml deleted file mode 100644 index b56a1cf043b..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_videoplayer.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_visibility.xml b/app/src/main/res/drawable/cloneflix_ic_visibility.xml deleted file mode 100644 index 6e68a22b58c..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_visibility.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_visibility_off.xml b/app/src/main/res/drawable/cloneflix_ic_visibility_off.xml deleted file mode 100644 index 7769749c1bf..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_visibility_off.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/cloneflix_ic_volume.xml b/app/src/main/res/drawable/cloneflix_ic_volume.xml deleted file mode 100644 index 72e2231319c..00000000000 --- a/app/src/main/res/drawable/cloneflix_ic_volume.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/font/netflix_sans_black.ttf b/app/src/main/res/font/netflix_sans_black.ttf deleted file mode 100644 index 3ec41a44b9bdac5834f5f574e8f7dfdbbe2001d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147116 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 z z`=m+AI+9xp#`q_J*DT}~eN-LeYng@1i+aP=XyHS?^km!MrOR&ZW^a$}sGd^rSlsMu zyO-bA^91ap+8W&B*z_fL_D(y#&h2h2t65k*>qie)H8co5qkqsnRMoBy^b6gNZw~S@ zmg_9Ud?XY-LUD07pPE>qrtOjVT`ntddUa`x?xJv^2ld1ZTIa;Td=lmiCg{>OOu2ju zn-<-kkQNM?SqO>Al~QiKw&TiH5|D_niPM!?(`>k_2mLAD$YyP97}p25y_c<%?`tG4A8;s;h5k>sC5RnmScF`^cnSlU_W| zVv2WRV=rTj{|(McKEd8JLud}G^UT7`1X~X5aK-5;wjFjO?*G7y6RiS6Dmp*5%k|wj zCBcbL`#Ae8zOPd13kfNXo)sz1?!wd;%C0K8zK2dsl4^b5*l+5Z%UE4-xw5}}Zf$d! z@;LEP+T9a@|j2Bi*Im6qWc+hmOM)8o3wU)eSF%0~AkjX6!p(ItDD zOUFHxk#eBJxkDXO*p*#Ne>h>>hPKW%wMi-N>f8mfIXN+L`3vI4DJP1HYijT!{tG&U z4XJAB;M1D$;RW-ZXkavkf$LC&>udgpFudgrXZH^Ti?1sH|t2*00(a(H3)Cn~;r#EiY=}}7w+fve{ zzQmGv?|Pr=!3>)Ih}${KI4M^RRaM;)oxTcNR&00J*ctfaTBM2igHA~rGgz`Aj1)=f z8%nrdiEswvtqpXlYfyeS+6Ai;v!BARkBx3yDmE9PZ-()XKcx%84%xIBQ5KkJYq)GQe7iPY!%mpj&u( zeQAZR=DLOTFl8;6BiH0Q8|J0Jnh4eO97MirM#*RIODm|jSU!vok0@WWqsV8(4e(4t z1zdM39~l;SUcs8oFmkh2pFR*BAD)!y$Sx@VA}8J!A7o3;C@itt{{Q5=LmF?GFE>zm z(umDkg>Ar;8j+M)o>NdRoAMG1lGFkbbJ(*^`PMKWJrMa~#Nxwc<|aCJ-kft07eo5N$+HV$_yAHi=n zrT?)CU+snC>>u!-sPJ)KIL_?>CnH27ViH{Ah2v})aIsH{VM{qY0{HcG#xRY}kNoJ2 zLF^%_SBUa0+0p>dQI0=O((r#vwjqGurNS9cAB(nv-2~wG3OLaVJQcu$vtGb|#^Ldh zeu5`~KRD|H{O24V!Lm6#5&Z+Yb-;h4!eco+8t|p?K%E8exK!T=!(Rm+I@^*P0Dp|b zV;My(^BA0{FZPvy|BAz-sU-m~MPK`p!=DmxafZaxV-17!'KR1oRud;suY`=w7* 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

PtaufQ4K!%*fIH0?%xvB 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 W{Hs@E6+v diff --git a/app/src/main/res/font/netflix_sans_bold.ttf b/app/src/main/res/font/netflix_sans_bold.ttf deleted file mode 100644 index 6a8eb27c9ed127dc21e23196988492aa2ec135e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146248 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 Drq@u( diff --git a/app/src/main/res/font/netflix_sans_italic.ttf b/app/src/main/res/font/netflix_sans_italic.ttf deleted file mode 100644 index 681b8e4f346bf50a5e8e83d7d13dcccdce4ea607..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150520 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

2w&l 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

65&qNuE-=MtnsrheD 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 Wn$3g@{NO8UJx^k9pk2Lzm;FC%fL9d& diff --git a/app/src/main/res/font/netflix_sans_light_italic.ttf b/app/src/main/res/font/netflix_sans_light_italic.ttf deleted file mode 100644 index b836c37816b15aef505c0d62894d0c5a71c298b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149336 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 diff --git a/app/src/main/res/font/netflix_sans_medium.ttf b/app/src/main/res/font/netflix_sans_medium.ttf deleted file mode 100644 index 8a8776344a1c591550782fb48a826ab21e5b6863..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146636 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 diff --git a/app/src/main/res/font/netflix_sans_medium_italic.ttf b/app/src/main/res/font/netflix_sans_medium_italic.ttf deleted file mode 100644 index 536aebd6584c4b3bc1d8161396264e065807e79e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147660 zcmc${cVJXi`agWmxp!vL+oY0YG9@!9lRA@R(tB^D6M6zk2#|yTA%q@!6%mz177@_3 zt;+^%$RZ-L=(32Y=&}}6R8({kSqr)_^M1~qNeF)3UwPj@-r#fZx#ynxJkNRBId>$K z5aNrANOBA7%S!M>39Ze=-KnIosCa(5#aH$cS~o<KWhO$5A%rl} z(L1Ai=CM~65~ANih^$~n>-=7-AO`f8fcjoDx)yb~t`2>h5Q{IN0lA&+t!=@Nu2P}> zO1w|$M22gWMMJ2&GNjLFl-nRG)+e=c(WFkXNFOo~Fn2C(I zfp(OLY!3Y%ZwNcefN|1hzte4GERArIF;|7a7;|RgUczjIqzc3B7v@#vQM7Km#SYu_ zbe8Zw)wU4&&`1A3TQYVS<GPx&Gl}PQFgC)mF>kK|1F8T~fCxYXAcuZ$ zJH%pv>yso4&oWv{BIzs0e+BJ*M6~o<(t){6qo0v-l)WW<2^>|E7+Pg}lRjd5nZ8P< z(gnnwF0g$>A0!(3fbB!t3wk!9Oe1k&NhAi~B9@W_I);?97i^cg>vaHS(5Yzpw1NP=lAiDj)AQweA+aIo!QQTDXZjIzjQ72w$^w(A&!hAzUp zZ^0LxsF#m<=t!C%0#D(#4_G)c(*M|g5q6MvdXnVQZ^&3d559IIh5RmT0M7+t{REOi z8VH9c|D+!93sA0yJJSibdprCt?0AP4#El&D-0t2TX`~! z`2nI6V{I42EhLe>O~&x?3%$fyID)zKV~t^s>>3GYpO92(j!EE&%Os40kWBI=JkBQ` zz_FS&fPXqsubc#7-tLeKH(4cd#rOs}KsM~geZ2!t;ywXT3*hgU;{7Ixw!`${bal{@ z(@~=R@H@^6pg%R(j!AR`tw!+8=_JtxxaRa)1pXSP1*a{ir(k=E1E&r8J_Yz2`sZWn z2FyabRpK|!Yf?YxpW_yMHNsQFyaYayc*wy&9G@H?65nwi<9%{olKKTcfp<=?VV)W0 z6+RX_k2v@Pbl~`t_{u)cyD+wm7~6*ujNrh*+oQ)ZGEelw`Epd-XamyMR*E(r!Mz1A z4(T=0d>wNg9{b3=(3iBnhS$^ZyzF!G{dFIF&ZFn(!1G-6)sDXI#~s?s_6h*{-!=z% zzXzPYV{JQdeFxq7`uvS~-?3I5xE;nTUxR$TNpu*dfdeP#8`{S<6@72OeIB3(=^ek- z$7ozgvIqRhaWagn-)euDp2P3B43Y41yZ^tU&%dJ2tGMp~{BDHbIPdW_z~{{QpW|a9 z0CSM$%iH4fl4KI}+4IDo*9Y8<_Fj_a!0E*C#rXj|!0&k94wwmm-D2Z3Sp}8L^|Mhb z%gg_t0{BJ3#ecbDeOe_PK^Aj7{Z~-o-EV-jwmDzBlKkHSNoMnP$AQoB{{}c7{}%xH z@HMBEB;Pr`{u^+a_gld6&7tkT0Q3OY1sp&F&WA?k_)Qri>&UrT2JH2^NVIslUa1%T-Q-Y%zg6JQQto70d4&RC_$aoB!{(- z0O%nj(~E9JUD8VZ(ZC9B_=aFjD8TH2)EuBCU_tiFssPPZ1y3 z(&dt#-ASh4ZW2tW!`DP4>_d_}V#jbYTk42Mx^>JOUnJYU;-N!*!Ax|yz3dUk4D;X zv^Bh*Zb2U}g8jY7_6TIQjC=_@oy+V_*w&rU%X|#^#2q}B#tK1Chb_fr-!QNlWVYg* z?I!G_V~TUc3_i~XWIOJexWn(W1p&MOTuv$gd~J*eEatWrE2R+tZ#oWkP$>=IX}tR! zz}Fi5P}@0_)6obLNU!6*jU<3iBbg`WlLwprdol@YJq7nRKmcs3c-U}R+O0LJDK3+@KV zHf|#Wtd}f@u2~9r5%4Ntk>mbn+&=&;0KnEIB47wKb-{fKU@u@JpboIw-Ushz7^s>x z+ujA71{^`3%K!@i~0s8)c14_sJgzg}0{@)H8i!3dtW$*|GF1%5 z`-g0og$&XHouEeSB>Wul*Q^uu)n*V%lmd|Z4S$P@Z77jA=bUnrCKLFRE=X6lIU z=6rC*_OnEPhn>vnD&hZOygOvOG)!-_?Q{ptTWwZJuYwN4^cDh1CCWHS?Jt2Z@RO|v zHf)dJY1_lL+w%EcYMaxJ+d|dAUnFE~E_`L!CiG2=e;RnF81e2BJ#1%ypGN_w0G+Iix*&EkUW(C`k{9^g5Mu}fF1Va80))~U^o?ikJ>XAp zKnS9W_;c??4o`&;(H(XueFP5*?l~f|mqv2Q1kyqlljU>;T}ju`O>{fGk3K*jrZ3P} z=!f)cdX4@-Z7i6nSpcpwyz2fi0--{26Pm9lse-V$1Z;0=R{}ewJzY@Qd-7RIikVx=`J#UZh^3-l*QHenkCe^&$1M>Lcox zG=fH^an>j`0UE8wpo!BoXr^c$&^)erLGza8yygon)5^7;S|6>Sc8pf7jnW#mrP@|) zyN>C+bQ(QGf!O=J5`WStZK2zVNAJnhWZ_sbi->cuJe@g$f{(1co{fqjS z^(Xaz*T1R%r~b13TZ7!-Z16OM7*vKVL%yNa&~2C#(-xQh*p|oc`HyHVw%TmBY>4yX zPfZd?I>{pwNh^Gay%Lo+(ygG<4*DSdGd)gUr=QWQ^m}@P1u_*gusCJ{mGVHPQdY@o zSUqbRp;G4vl{Su0=`ZXUJH;*tM39eA$sojzP^k}8S_3L=748up7M>Ii3a3RP28-e1 zHgSizOWZFW5T6r|NK`s6UJx&dUy4^9R63;+Rc@+4m0A_4GO5y4`Kk%3DXM9zE>*v3 zscM62JE*i*P1IiM1c^$0>c#3o^(OT`^`q*8>Sxr4)kjCDeb(RXM2CqHU9?r2a2dsu`iuHvL}xllnvY=Rl>S`s1L|Dg9af1^p#ZNd_u;NK{HU zoV}a0p{O-xG|K)Fq^$^}X1eoq9Q9_=; zD$c``fh3U!2}7!lbdtGb&{0CVS($Z(Ez9~Gf3V#`Z1oA-Wx9YO_Cb>XO6r3vqKfzn zkglUHwuiVaJp6a-i(AvLn}(n6yniEdS>-?@11;+q%GTzviF$%{{4{PRWZxm=96sJW=R7;-V_VhAsd91rO#3#nPi!=Vp+2cye69*!qh5&1Fsanw34KY{BB`59bC z@!ROKJS_)3M*RthZMl;TWEL4uwh%Y6o!m?Ag3ObXyRk=hA3RG1R>E?!k~m@A>_8Oq zchFpG>2$J_Y$OkmRm79HlG%v7EF#^alB}bL$p!K*R?=!=8u=%EfNUkpNG8dmX~I2- z!|a8Bm_hzQ_K+!%u2bO+x6>@Thz`=D^k3vBI)--89J-jkNN3Pox`ZC10$oKrX(3%o zSJRis8tO!6(h|B1GPsQVNN&+CI+m`bTj<~DHS!-CNxNw+bn0o^L+j{z`giIKxjlzA z(GB!&Sb!_22koWf=|)KKiR2gh2DI)$Xx(!(oSJAhtzms^4Rr8lbQgV`{+>QTMOXum z&_nbjeUrXVKc#wVrn~8r(7WsCU+G))BK@3(QX}hU8>o!BLRZ{H|3IInr|8?%K$BpN ze+duu2R6VqQx%ONR+>*6Xc&!!Cix@1M8Bd6>P`!2BURHlx`#f+wh$Yo^ilc@eU+Z4 zRfvt;AUA0d9Y-}Zo<2nXM2l%NTf*+9!8D5Qr3YvTji&pcm*muquA$rLWAs`28hr;E z>N{FZTWADLq03=ste~6e30h7k(U0h5x}OfQZLk*}WDilo_OX}Ae0B!5!ynkA>@mTI zJr3Pg!rm7GV26xlpR+I6lkAV|8zF+d2pi*ukPmwzLnvVL*-EyMZDfntR<@Mg!v@*? zut~PF2#;iLEGqG=mq)>4Wa`yleSU|olpCr!C#>7(&yNCdWELbdxTu-Lp`aV&<@=j zBVHB16OW4DiZ6=C#FxaE#S`Mk;w$1w@v`^@H0(uKLmxrcekOh*elA9d(PFKr7t6#_ zu|ljAtHfflM64DQ#dtA8%ojC~N;+ul2r*JLiZNoW7zf#uDQ1Z|Vm9PZo>(Xri3MW0 zI999?Tg7SObg@mGA$E$h#V)a1>=Ap#dE$JrPh22ADE5mB#Q|}VxI|nkE*F=HE5t!@ zwYWxHC2kbAig$}M#aXZo+QkjxdU2DuS==J771xP(iF3s{kajCYgV-U~iT8+8#64oY zxLs_3z1Jw-CpL-qi{oHZG{gEEFYXju#J%DK@d0rntc*#p2PccWg=E3Zo?};GUw#J* z^BOzCuCv4JTlN?BZ+4vh%>K%(?6e@zkLefeZ`d?{RUqsQ!Ihm8+~_CtOZKMV&fXF{ zm>0`oZwsF6yx_&&5xm(y1SM=BU-p3z2#Ym{eJq5qPlQl*Nl>xR1U36o(6P&chR&jM zX(^pRU#4f-S3)?uBIw!IfzG0GCKU!TYX*kXjm(_dH=y~&L97K@=) zhW!i{&0JYJb7L9IonQ1RD=Kb}WQduuxcgV_=!9V4H`rYNmnhr(<<292TG+wz`(p(%Ez#EW}3G z@=Yv~jT5E{EkY~VhA6-kp;)LUACM2pMPWMmhw5!%QmvYG4>W(u=}Dnwpq!_(@5 zg&r)1h@qlN3=_J^@5yeVTqqGrg)(8HFiDs!j29*dMM9G>4iwTK683d}FWfKuPIy3g5c+wy zumgH}7yN|1!dAqd28FwYjly=}K4Gn}N?0$fgN3?UcvE;sxG20YoD)72-V@#sz7@U^ z&I?zCuZ6dTAE4VW2;U3e3I7m25dJBg75*h$5#AF1EnE}ch4z0*_=|8HHo(imUxgFG zDd832RpEKzu<(L#NH`)K5S|wP1P|q)@OR;i@VanXc!qr>1mo0#3)f)Wz!;^;?o6^f zAOFkK|4#dbkWiu`VMI+dL`!reoale05%-b($g4Qn@i%gs z{GFU3uamRn4RVgWN!}uFlk+2*mvEg4jhO=t`4aTuSm?sF(1TmxKSn|ec1wD&4w{hb z!L87NbD#y!Ko5?G4ul2-r)`yV_d2o*y7?CQCv^8aG>sk>)~b{SJrLpTRXg2;3>@`kCZQ$n^pE$aabD4S60! z?#P#uV#hD zPF)&q%={!8dz$gCG*1h=OYBX1iG=+I!YYQ;)1LMs>{qC>XN$?{;04EL zk*=s^XmImTwk|(5<`AXC+s99bxvPg3Mr*Y8Y5xB+GO$Kt^APdE_;riZhs*`uLrDcb z>a;+FG}SX&+tovHpi*vY`}kmuX2>?4^G3lCt7+Cqe?ugAC`p$;WCoaY`9(w71+7CV z_C(82vMxW@!$MMX(hyt7$bn5v^6h^IDOG~8KzyBg=<{e0UZXkaVJR3IZyZX~(Fg*ZS%n_OYp(0^zjAxb9YT*29!cA0nHFa<@!x=|p9(Ax~X~E>qVU87sCJ7RmIs@(3f)=gSDJKr3{%LDj8y8b*wb~mGKAe_A^P)B52{t({kX7KOU@oVqGNgHy= zc95(ZOSa9nGMveJ43YU+L|`&larx{VSw-(=e}V__Fj10ZGKprvj+;Q4 zEf{;mq3m~vyZu0~Fbh>dQ+$d&{%f``peNkfHR!QG_73|Lw%IlECfyB7E)O>TT*PBe z&`s=9l46^Py{rAqMDlELq?qi_)M<^>Iz08%`Yzx_8dKXSPyv#b;D%)WajTq+*oGkmm z_889Wd`upK=k^A3fiE~7=O*0X?envLC8QN&Pp}=M7FfMrpyhS4-FDEn577c*Gr^nO zXX|DPoSNB2-bK8BqisBm=4Xfz5x4(KfY(0>k+cH@JHYU?%3*(W{4f6Ppij~d;A!T< zlRg4Z(IE5){fJa=hXuMHu_A5}o`4Pb7Oc!mup+Muzrg%=L4;@we5^Pz6&_b5yqOmG zL4EL$9!GTaOPN7dDC?6A$xh1s|)i2Q*3u>3VewBi}XDa8fF z6~#>_4=1&g$*It($*I$6vC|f(Jx)VTC!7PFo1C{fA9i6bCYKhMr(B6^oa;Q-Bd(|1 z#<*3w?Qnb2?WEgzw~yVvaaX&ixL3L_b>Hg#r27#Mtw*89fX4=p{T`=0)tbc}($`onx*A z7Y4TluL<57yes(8;Aeur2?-CG5VAdFcgUk5&xM=~`7xA*`h==NO`-Xr)uB^DXN4{g z-5R<-^mOQTRiZvf7u$HjBVMoHgQHQG=5KBI$KBfL%G z8SNFFlg>w%psUt(={D&0>rUuC)?Ly47|y~y!pDRg!cF1X;pO2?;nTuO|DpsQ02S zN8N~aiS~`wMkhpPM^{ESMYlwEMGr);iQX2yJNohHXQGcspNYN@{aN(2=$l3-qpwkI zj4`Gf3yn3#NyaYYfN_m+n{l`CapN<_i!n|yJ~7cT`7xC-%`t5;y)jE;HpJW)vp43c zn8PtAW6s5V5c6fs_pv0_EjBPVJT@UVGqyCgA+{y9D|R4uP3-R2XJU`Vz8iZr_Ln$$ zoKKu8E;=qHEYwkeg7G(2~%Vury&y!tR8p5{@RENw|>kS;F^;Vxn(icw$mwZenF(b7EU! zZ{pI#Es47mpGrKMcqZ{e;%89SnWj=xgQ>;TWg0N8F+FEGVLEGi&ve;z-DEYpnElNL zbBejxTxXtUo@ZWV-frG+K4d;-K5c%_e8v1rl1tK2ox|yxYuFIZ~ zJuQ1{_H)_CvQKBfnih}$9r^R} zhw_i+zm|Wqz^x##AiN-~aLLJ%b0t5PSW8_>{Y$l_&82Ony`{TLA1{5T^myr+ z(yL`|Wg%rTWtn9SWj$p}%QltmEPK4{aM^2R7s|dYyHPGL_b3l6*OnKSx0SCc-(CJp z`HAv#J8Nw$3~7VAA8@} z!!=^fs+t2emun5RL$%-6nd++Rmew7p`@TNAzO=rseqQ~i`u+8%>ffrrSbwD!mm8xSO^t<(O^scRYZ~`7KG%4*@yjOC6xfu|RM*tsw7cnS)75bvcOKq!ZYia9k zTi3Ru?Wwk-ZD-m(X#1v}wFkDxwCA?hw70bPv@dVp(!RU>So?eJKX$lvBy?1EbapK7 zc&g)U$F&(QGqf|(XEe>|nXzuh?imMW9G`J+#%D8r>~!i3>@;lQK<7RK0{YzJQ*WRv6-P-PH-H&vi>j~^> z=-JbAs^^0_&2z5xdh}-Z&g*@u_xxP>+|Ien=N_IXpVu(2WnS03b@O)2+duEnyc6@z z&bv6@cYetH;`xK~@0-7O{&Vv`oBz%H8+~G5LSJ9s*1lbRkM^DHJJ)x$@5est0{McF z1>p-~79=fbSkSUy?}En{oL+FbpY@0L=k|B@-`9V#|Cfbv3sV;MEPQ0)Qwt9-JihSC zK+Hh%z?Ok02hI*$8@RD3a8dfAX^Yk^+PY}hq6>>YTkOABwb-yYZt=Xu2NoY+d}i^b zB`!;RmZ+9QFR5E{-;x7Mjx0H~OFfoGFD+bJxwK*Fq^0wgE?&BB>1#{RE&YC( z*Rq&p)yq1UZCiF^*_Gwm<#o#^EZ?&H*z(iM-(7xbh0lu06@4oXtT?^m%Am`jb})Ca zWpLBr_Q73)j}Cq?_~oE=W%NqZ%Jh}XSMFSSXyuWWCsv+bd3oj4l|QbuuF73ix~gVX z$Eu!H{i}AY+Oz7>RR>m`UiH?h_f}n6b!Bzn>gLsLt9w^3UAg&S)&PTJVDaq-3t8+U9xwej4>_cmVMcx~g2O{trD zH$AoKm(8lpQ#S8~)yMdGTlyKhijxltd`YJ9)+oI--VJn{wbw*r*wtG;?C7n2={qY* z{A|||<=Tnpk$7-ZgE7UDP0c2Mg$LDX{p2#U)(|a|C28~#dTq4XpyelhZM+V3#*Tym z`%?Tgg$Rp#kWky}wmQViw!>-_55Cl3^jGMzsU?m&nv^d|?@P?_2vW&pu^LmPlcGD% z(xi5Z3=4@Yh&7^JUfzi71l`vzkUwujKFCB>lA%Fzomn^Y8I zR(LQ^Dz}f`z`K1_rby2-#mE%qR8zkD3b)dp;;L?wt4l?SKFV7rYK?q~dZP^23z4QO zRX{{gR7z|_`h=v+j_i!wU>}vrHz1c!JQ)b!xb?@}!*E_z7@nolEa>D?XLhtbux?Vn zo75slomtBcTh*6&Z9DpS8=@_T{=|HNbH{wB1LM3IAB)+8>9i8sEZHoFnx%FJigc&0RMrP#t;&LZ`x+rY0sXP z#ujY*4rgm#1-66Ha%-B=2S~A#8(1>v?JU4?lM?LjGk&mg;-WY|>Ask5^i40znd&WF zscT>Ry)_+AFKD~3rsLm>wk??jz__2lTKS&+6|rRT;M6puMyFI*^mO6sX@BygDJ<5! z5Q_;#&_7`lFbq}Z9-P|hY=>M4`9c;18*%03FAhd~~+Sr}g~4=T&T@>Rh4T^BSTqXC?x7Q{dwuCQN>(&~ zCRT?Z#@mSTjsdSHqwL}|qf8sexHNM}O1mVJu1p#!rXdHd!SxPy;Ip71mg!~T&MplF z3CXS=8)p^urn@P>ZHPZ?0D;!t)GC6;29`XJ}eON@}Ce zm~k0ZQ&L*7(({NvVlZ+JqqM-(uq;lg1>*Mwo%$XpFZR;|;@?X5zsI z_-Jplo`tIkK5V`s`4aHqIF}Y9P)z-&EcIFEmK0Gk$%`@dgyiBGac*uEy>=9Pj~fh* zNUU&i$;vX1RVaczCyxydn={434N>WNMFp)K%T?|7RI;`#Q+i#V*^nj4iwuls6UHMa zA!@#6a88G={U7VsY1Om`{HTV-#tl@1K9DoeC^)gl>#2U?&p~LXT6zwG>wg+n-CM=dba;iAJG(sZ?LY`$p-x zOnPs{bE{OpTzaoV`B~EY3hDh-yq_YyuausD#`AdTxk@Tujh(4uI9sV8g@k`2%;$jb zi0MKh9C<|}dHAlFhfa}KJd)Rly7s)1kvx2NO#dW=qFgB{a?gXA-Vfax0Dc|kc85hXvYenU z6iSb&Wlgn8_o?NL`<+}ubzw#)7nN4s-&xzSd}eLOHdAJ4swt}kJcjdMI19l~;6O*X z8O_iUIrOWxq$!@Ks6J?XJYL&w;xZHc2BFLcZpP8p3BIglxHl^DP)mhVPSGbLqTzaQ>$CD$ypW}ocOKI zFT}??HA|fqp^k8JPYF**%}zJy146l6V>n^QcmD1g)&*|RN(#F!(B)8t`KKw-Ssqy) z6FvUw{@%jr^sawZ*VR=FEZ}u1Y^9&*4Ahl$CTeP+LmYYlasy%nL$#Acp=+ni*{r-6}c@bG*POeij^D55C)M3GTQBc1cW$;Wg1l`C-*p2Ool2ga!izqdun)mI`#Al3JUb` z8?)UfIKqgyYq;eG_d8sJ(xKyrkB(DH@4@-Gf_B=wZ{{cTd4P7AFzT8^ExvBI;w_aYv z4&N$;)R+Jp_i5}Xc*3#@!@2Ve@WQqRqtCGBlq@=4Yqa_#cf1|_=B;|Yvr}?IN@ktj z#VNTyg?_T^)-%hVjMAIriWq*C)5Q94UL+Ysq@p=ewlT_(C|A&ij44TR<*^x)lVdAl zY1^$*)*h$Tr^jjyw_d&Dl~;^T%I5Yu<{+CkY7RN10y~fW!^zMcK+t=?3CKMU1l@-TJx*EprtvlkF--PmErB!*933pkzbYJ<5~Ty zcI;y=8)=7+XEk_y^my)(+PPh(c%;njzW8Za1-=9to_n%X=5@4FDyN_oD=U!8*IG&D}52cH*=YQ1^q zD=ZH20qB4t+cli2dkr4879)K)%@|E6~qxfCYXC9)>E zD6Q3(mb-DsiQ>#N!8yHGu}>-<+@EHQlHxi#i#eI2hliquJvsCYy93D4P^;C|C#6Wy z?o+9di5|-*PwMts>TJ>*EedQx{>|s0A}NYaoSrb%+)fRJVRfxjt3tyPqJq=Z?a34M z=#x9S`yj9V(PuOFvx6NDuEVggm@OE=)OD?A@A~&Vj|bfz^nB27rQaUUJ#KqE7Qop( zMboWM)ECq(T2xn1kM=xa>o3I|;?drYG$Y{(b9mTj5*`sZne6VmBe)ZR4pMR`zf#N0 ztV!w41MVpXb7S!NkaG-2DPInG*f}RLD$8rJXJK1zNk^trxkoxvP>V-a1ba2H&a5`% z=hwNqhK#ApDb$4~)Wm0Zm(+#pg*u1rrNJD_F-J$d!lKAw_|L4ccryHF0(W5~M%v%# z)#wHY9}Iff>tT05SoYLhw?XC99W%CG(NENu873LdZY!NsT3$M-rXSn~wiV5He{Jsd*PB+#LzAi%yA>1A%<8+c13lE3!rGn98Il&e; zTl}$nQY?t_Ia1rs`F{FD_w~EXtV=2K3TVkrua4;MX5~Xe*$K{1xa7a^^2-HjPA`evhs2tW!nn2ykR0{4{&r-7mz*m0QGNZ)`>NaLIC-sqHrisXbxw+{PH63N z_FDUN8a<=1rUtsuH{|nM4W(*2pO2H;j@!)iQOG$dV%A^`@SvaxH?@`EH>|1*AWkdi z?uhQFM{8rMGRP3AsHpR;b@FIyPF99$jS8{Nw@p_&ezrc6{;4~rreSt?R3LtyK_R@yVoQrysj6Q7GKAo{YFpe^;=6zOAm45QAE##t&#Xo4OtxvF5H=B zG)ohdMghTr{(bo%`1J9^Y`BXe|Zbbi(YUmU#cav|CG73W3+Z7a4EZLi5M3JESQu0F@ILzDaE0lt=&F)3U}!T%RG zU_1t#GUZzEb;iFdQE>T&Q}c&m_i=1RlQ@z{k}%q5Fj_7IBmlmC3vU=a$C(3nGM%4Z z7tkYXaj&IC?iSfx*4j8rK7DLYQ!=&+YuqPK>2~Uvddx?8CLySFT^WnpWb!Yw{>z^h z1~OZ5LDAP=fA!VZUl$b=Bica!6Stv&|8309&c`?zWV`Q{Qc}!-}o7^?y>osXUwAB9fey8a$M_YH7eK4{0}?a-(~&bW=YnqXX(GJ zgDJW69cyq;JM>H-Y=a(rPaifC;j{$6NyE|!;450L2v9`uh*E?if(OG=EdC?0B%QWw zY*|v4Han;_f4ZlO!c|)}wk#+;tT4GXZ<+_5HH8W%OL}~P!YMT^o+V{BhbX<9O2&oe z>n$;pvYS0T^-U$sA^fQ@HmN*2)?AKhLypzc%{agGZ@VwYZFi*(tCrTxU%jyL@fGaf z)}1`4N%6aS%{b3qWZx%2If=>aEKcb`oy!wJV9Bn#YWivYPRj(VoL=#a2*nP}wKb3XKKrS+xg5cO~lOVP!sgLScC zR_pB9J&v?A?kd=n<5=SeP89A`p|`Mce%8BbmeN|MWR3khcFaE41-wL|Y|`kmGD|ED zDkUkcPX4sl`ic^BeY5}m9lUG=`Hr>Vbk}esU!px%i}pUtp$syD5O+n%?9hDZ&eBe8<+`^_s2aBIw*0ktQ(KGZ~-md%We)mxR z@9(dLDNb#NY+3ZM?FlkGAXl z6hl0|C*mO?Y^z9{7sJ+p+&_%+z$sO5BKpKr*a~6N-onkM`Lg-4#7J$FC|9eSa|>LZ zEOGJfPALsZ$;~Njt{7|3M2n&(EYFy)c5$*qC3(2ciU@DYaB?b~G)5;X#x|D+=|Y0S zG%g{&&FK-j-sW;+K}19;0@-1Kp&A!o_r&73hzzfUvAK=(@2+Efffr_Tyum2bDRlxs3STg@hUvSo3EGt}HuR(8+U3=>Xbb!J7F1A9e{J4Ggv5Xo ze5Z)-k)8#;!_Z34N#iIj!6lxwd?YOac?|8Y3(lzZnwC1zt1Dw>##TVB_q5bW-rX59 zGPY$*zB~2qC#OC<^-28MZ`Ao=`&c7w8)-6of0#CW@9D*X`v=(jHxHlyXl^@B|AF5P zkoGuF8YLwmbuQ>mu2rrJ9SB{oTsv_-eYR`gzAme)RL2>0K0qBMM*hm^I`%csRo&m$ zE7yk%gsfApiyBGU+xVh;-#%zdzGk(s4KJ|M*q?~D$NPax35mao_jB_hKnL-sv~MeM z&d7$91F>jlBI;S#o96C1z9cxz$-Q@6+ElZv%Y>Tt3HLnJd7m-1Wtl80LK`EK1uF$p z`T+xi0Ao_LnzS%a%gplJrVv9-Y++MuQ+8%W;Kr6`x1@!Kvrt34Av8ECSoDm3T$!ZR zrE2_mNC^1q!1sMtf#C}R@!%&7upNe1NCZ}knKrM{{;pV)ceXs4%d0o|wJtG9<>f4JMDp@Ut^diKD{EgieIK_6C*-3i4lBk<>YI$K5m58Scj8oxzhR^?tJe> z|LtRSyZv7H=~wTCFMMtW+TW8HTMN#veq`VIvhRn(QkGmYogv20zoT?*UAwQ3f9%4P zm-+|(IllXox!srM&gFQsy@|Tz@_99#8 z8MGIU_V%H@+g2kcrNU==a(~H;G~`ge11Xu8N4#e=75YXQVx9e5)8pgATzwUxF$QJo z8(#L}jgbZJd}1`zm*~v>hByg_AEMv&xBDIL2G;7Z z6)SE2$Kg{ON5B3Gnj-q5b$@k&v#-~TaTr2GjI*y>T0*?q)z2}6YH#|W*4(d;ADB{# z?tAl-UC@1>)IG;v2DwJ3;WsQDJK%TjGG{2{F?r$9xnT-*g0E z<+^BUWAYoqT;0lMW|_;BfpexixreE<5?qu;znj_p{1P8!;RCb6o8lV!&5_v#CYYm> z%UxYAm9BlZt9@TzR>s_iJL{JhMd>2+W%F~2y0Q}uW!5txhS<8anfF#T?dhKXWJPuzUA1z4a=W$53xNHBY28ea*1pD2f z+e3pYXT-Vs^rl&y-8U|`{`G6s)~q7A=c1Ri4;Gimoig$JW#);QZQ=ft^Xghy=^vx? zldWI8WQ6;wz5+M(bY?9(oZ7Lmw(ah!1*WO_m2jNq}$>3-{MqsJ58z3XIVVR>dPv(j!-&ep!soj!_A@djFQ1OxtyKe+ zZkr?Kr?=#~xoamBr&J{<#KZnH%g=M-rg8Nv>uF5$lm9%}VKaQHCS>x9bhM&%k@hhB|4~I~JnDDpZ=8uiw_LqF($=ppa`$DsZV^cd>sUV@5$@ zW^M+3$Bq+@|98=bgUiteH9J$CD@9i(nsUIpg8J|KCYcZI3mRnoqxC@=Ykm86-5}JJ zWRf&K&~DGV4m#1-s=_E=bi|6#hX8AD#6B`B;qK{__$ouUXU9F%`OzgSCtA-wGGP#% z?4i@JVIW$2>DF6T2kw4A-C;UQu{3FTD{nQgT(a`kxK*g-MXv#C-&s8|Fea@B8|bXG zNBty9I|P=%Elm9#qA8o2JlzW0mloBoY}AaTSb%%Pq{58WJXbfvq=L-WJZfwx85p0K zg|%S6A1ECt&1lc%*A9*k`})`;&C?fLd6ILbeP@yI3XOe9?%umMY>r$< zQ)`->-Ac+AM6<&MeHCr9zo6q&6S9lHvCrcrd`#_hzJdjh>Z3H)CX0+ZxIi$84$BW_8b~&6+qeHFs`raUS9E zOfJ(9(Y!q`1)N#LV5ogV7&CKjoM(1(n_RKw{v{iY>uACKKCXF{OCs6f+}_fXR)4GY z`gPhqJ-y&#;ENI;@b5y*FA}Xj$ocoqb4PZk#^KLMJFeVl^=Adn#q+BAcPZC-)#O?V z74qbq;)zq;oeTTMj-6NNygq1QY95ZMnJWK&z|xf1H6tbo+fs3!UjCgEEKTO*3F%F- z+Nd};Wui2OshHc#n44s;K+5yA4^M;^e(uMr{4RB@V+9Kq+W^+n{|}81D~p9!Z`%)+?~7u}DXiUZP!ox4!bFN31*jn2)lrua}qQ;>*1zpLe1YYQ zSaJk<+S34RSRAOkhg)ZE&$m9w??l0|F8mlUcvecQpT-{1%=A>5pc`T-db#6|+mRM7b ztCKP%8YdHNDs5t8cV=l%c5-a}w2Gp{+QiC2nuz~@10^Bo(-U(J#f(=-et`XC0bj{* z((OK(6wEh-c(_;0FP%I`J}8TiFju)Myv@U9UEl&&lbm)kcmjj;~43 zg(uY~R0F4>XdkB|!K>0KxjS$w^`taQ5^1DA`t2f@uu5ZLYov#J>8$LON`KQb>ywy_ zvu}#gRPTzZrK%%pHa1lBWSOFA=&dakMW)&WX;$cupQH50d;$=45)aN9_ClrA>q#}k z^8x=!-Wm0*pPS(2UOuWHi43@>mVQ{k5FuMl=!xxB|Rt`3Y6AC3*2<5!Aq$77R>vdv1^|h+@Hf#Im!ywSsQPlK8xFnH8a~Da(^xqD=95 zuF1>5Z8u6XI&!RMXmna$VXE~4RwCM>q4=iIfZwHot&d-2`ZKrnZ95RZx95L#JKxcF zA^MhV0cjOj%rYrnxno(``wv;Kow)b0DBe};8#k>#UAf{IFIA$B!>{Bv%8q4&D0B;F zx$SKVDZsI`^T*r7#v{k3bR9jbk?GA{#Pk(o6w<>prFOOt}t!2tmp7cZZtoV?nG zlAsWkzyBClFF%YS7Qei_0ACvq^A?vrDHbVkHypLFtZYy=gyiLhO;=8jl=;=bX?Y;KQu`3s!xQ1Cj{mys3QvPxO{irh^vetz%s(vWe>63}X2QJ&W z_LAzKUh%76N+#t9gXatO5@Z>Edq@DGD$pMsl0&+&N~kqFR&A7fU~>{3&Z$KEKE;C)#ovGg889bLJ$Qa^r*3 z(v_C2Z8eL_TF2X~7vJQY=bqn+UkhF=%Z^F5^tHA9aWD-*2+5W_$>VhFG`yg3_h5by z*T!g`?QGg1&Wjm?ulrmpscDMX2P*~+Ey#$njoq|j4)`2pU7ZsaS+RFv--UzW5f%Fu z_U%``SlW4gRcDGNWJOD&itCj zUtgGQEagc)i({AmzbOhj&wETBYq5mam6g@9V>BlJON>vNzed>iSv!2P0ydYwn#UXmv$ss3KElY>WT_bz6Is;b>*ksCwp5fZ!F9x$hTP79|GlTiwm%idL7Z0 zpF-Rtv*C6Bx9E(BJ=X|4Kb?4^Asey2{7Ct|;rGbt$NNOzmrSTD8+l3Z`nutFd@lwZ z>}KF_sW>P_oHr%*2mh1ucMTt)eO$8#nVCl6^c!%}{^HH~-Uztz8mY+^^KHdoK}2Gb z_C2`V-JZ-jP+MHMNdJZUeG5(`Wr#@~sL#Mn6O$axp7xzxqp>Kc$e9lo-Bz?E&lj%M zXST?ao@I;5O9QnRpuUR{$MCe=GFcz2m|UBjU0%j*x7KOj+3E6@B%>=a6*F4l;+832 zq{}n1%!wHVS=l9-1oxxI_jg*P6KzF=q@ zsSA%>9&`BK9Rq{GVI5s`VXb)^)P>vLt0=B($GPEEEHpL17a7n_1@!39zzDkq!XcK# zDA#k1`ghaVh8QI>$#2;E9cjg1A68ZCFl00(;)~qS_5t4z})G=xJ}<2}S?=t7_YJcGj=1Y1<+6-3ega^Jt@HSHbX2 zj}-n5s)dj^CbnR*b8XCZ=d;S_AC=F_2#GqrX?ygt+E#N&tFO`a<8L+04)3{`J-=() z_zx|$ODh%)Zz<04dY7MW?NX>J^&CcL;eh@G`*E5sW;}uHdRAH9HGZhkctjSc8-sSY1U!!iA012!83n;|&Vz&(JvOWXrN7 z*NW!uP439>{;kb5!>QRTnl^5>2Prp8)t$?Qnu)4B}a^KY#r_{FPLv8eUV`b$}TKFluXO#LDchb?pi_y1S+gL@mXLaO>&4W&s z)L}?_dfB-y`sy`}YrJ8Wx{`|W@Q6dp$87UT>dnEGRi)AS#WkkjDtB3D#K2zm;nr>C zD_Tlgqa15mW-nV`R^Vw|pP%KfXzoz3s~0*RI->>jEM~5fg_?28zU;SdRz7(0>f3X0 z?0w}Gd^L!q=)8>^&Oe}@w}l8*=c7NuFD^+RaAXg1=fxiP)EGb8Ipz@ z>dzwhX{Q(QbB%fv-hh7c!t?2A^OEaV_H6G?3Nx|K+2X;f{@7!&i!Ye9^stQRK zgKoxE@qJQBUjzwd9G0my2 z;slPIj#$uD&&Vey))$Fq!1!pex_Ins?=#?(R9a_tzG773O!9G<0)$fqNI% zU2EexP@|ZuFf#>|yn9)itoMy=Jst zd6PM+NS*xw%@^US-i_ZmfIbK^I7R7(H4km1AFq$upm~d0VO?$sXx~Bxt%Fg6Hd4z; z#4JXG~r;vO!ST6WXk@hsAa9SMSl8s^U0zHl#`_loi}22E9YxxQJc8UMa) zPfm8e+1ydr8(!`)84FR?T&b1Cob+vf5UiRR*!@Q`ey0kwT&&>~r)1(Pz?*qVx$#D4e0+LQHj4j;Webus5j5nSvQJ43hC^}|)hkUqt(uA! zr(|0b5{y9wDVaGYqx5%odNJNviHlD!&PqhR3;ia{r|~p*#bS2B*2))4^Gh{LDroNu z3t7-ryeiC`k(T;php)IhKE9+P3Cv^u*(g_oa^ZM$K`Vh95ZS)tHx{o8HKwMeM&`Bk zg*t2EJ>^LmgB?DU*^e^2vCp$=bxFS12#HtLP~6!OVss6*<-056Wg}vFe%V=>R9=~u zm{RDprlj%y+Jo{taPtd&Eoi0k@3g! z1b7Bt9Ki1c+{7Fk@CS4@B0qcr0RJ)1AIC1{@FegD_FcfC7ow42_>{vVFjio{0Q^@f z{1px_1ROR6z+X_|XgBb48zo}kXCr1f;faQCDZw3R7tSYuKh5Db1GMF6*YB0j$sPyz zGk}|zZ3utV{3*QN=R|(mKOp~`$PYUV^8ZrgpD*%90IsLghKS*Wrx0*Goi@V{IXoEr zfejyc9_9FB@K!nHPXvEp7Y6(%93IQCgF(IDNB&9Mq;^g=v6wl$eo8032g;G{G609Y z#6bULe5I)$&w`{*|z=RW4DCUWNKDIGpW1ZuN>S;tH328?#PjrZRg3@*zs@@iuCtk;# za5EpnBV5x-G<^e1_ziRU6XP0p6*!$VuJq$~xo-UC@cd%@E?39zbh%tUgIq43Vf-w| zo6tn#^zpMCu{cydW4`KQat$P98a!oFEf zMo3Q3Cq3w|r@(g;&9E@q;pXq5rQ19Md(dk`w6-Bn2m0+-=(ou{&SN9zR}Sq%?{#-0 z3;1vw=HNu$fAGejVkoyvnq_!ZdIM)B19x*sfgWh`su(uvbW_YbHzz(nE+RTO$s{i{ z2Uq3dTVf=AF$GI+SfheN(ycjV<<=-$nk}b{7od@JAAa7z^?3knz@tFuN`4I9#klJEp8dlppgYII&|2odg%_%8nW4>+u=nd|Z|DXb8qiot*!D207RAPnYxY z8RUF?200%W^C8W9c$w%8&@aa0c9l+==XXf20WQYfr`kFJ{5ExR?*OtMlO&jIaL_7++8jIFD_`=sI~wdVgq0ofk(jzWy8-UzF#D?Uh3ZP9Bp_ zc6VRH<O(s-!Q%>%O7u8CjIt(5?tp0IU?@Qg)AeDc23FT zWy8`NH;c3#(gwp%q!)NO*e>o-p10!4=Y|m~w+XKTJuUCm(te~oW@W8%;&K|}H%J@U zA5qSXm%r5UVfcl#0WUcT*Z`qNzUh0c6S_UtFZtNG4H?aNtMyi%-#KmWJ~26#@In;r z{ly;YYPQV(N8qRY`aRSMRdxxk-z%M%VXt&T-75_v|Iau*_p9_=&&T}qz0V0I_C8`R z*b92Z7`t|IjA88|xY#>hJ4rwLE{BV;ckPt9fbxqmcI}igMsP94u2shv?R^UPm=SxQ zAzY!%K1rahKuJHd!pm4fx}7GUksdG%y@%U%zJ<%J}a$NLe`I~ zvQGKm<8ZNeIX<~}!C0dFV()UCf%E7W^4t0gA2Znd;Vr(C>{`$jbb3QJd=1TTS6 zIk6`>PN!p(6MMcF02gEPnkjP#!9_c+(Y0eOH0kP?g+(2F1Rk+g5?qW;Ex#C>1Q%oT z8a_59|4OOL@B-eH2!&?JAnrJ7cd=n`M+N#xRCz`r^r9Sh5SD_Mg9pcFC2TwP0r%n}|UvuA|Nq6Rt#5&RJ~{{=#p{RI;4$N_!_ho4pbMVx@^*Vwb_Tn^dC z3yTyFg|EE^nClq5cO%X(6P6>S}`LC6{h8tx!bjGln z*x|v0*zND_RvK#4QeE(S;-+cUWxEOzbR_71eGsntrtNC4^47wOlSizPWdN5W- zvA6wkU~fxj$ApWz*5$VDz}d0CpBlmZYou9*=cL#8SHXQW`Mz4hi%?yNDDrRCcLaIIDlFFDH%;_8i?7boj?|>A!n7n>i}_#Co(r+={tD}2 zIA3?UU0W=q``AjBoRr`SUKzErrB?b@Uq+70*@rE$44>?a?BRec&}6EZO0N z2cax4E8@0)59bJ+D|~k%Q^L~o-8Mq$UhCFuUF|>&Ym}{2+*bJbX|;}#i!Brt8XYjqLoKyM9Tj>2md0Z zA5{5r^c&JwV|;yAtDn!IVmwae$FqUp;ynP1~}ZWU~bmiZqAOOnIb>i$?@`MK){$VfVK3x{J( zllypkXpjjdH~2NFOD2Btbnt7PRvzgjKU2$(Fs=N@auMgp)bvG2Cw|eI z>i>rH)$EU)U%Y;9d!zC+9`yARzc?KHBD(lJX?d2M3#&DcEU4WtcEmbjJq}!D@Tfrx z{?(=F?8bzII4QTt9J)Osc7rP=BCL6IFI}aUV&T^}+j{4s2kepDz#`Q=)yRX;&?kM*wZTi3W(kS{4Cq1@pVf6<$?Sk1CDkJJZQIu zhv1MC75@$u-iZFw+w#t`K8|0kXT(3go&gW7Pt4E+c{nP2g#Qw}rQj8%KBoZ(ChiXs zxWpr^T$G+|4%r?SKBuEXyCZzKLEI6R9!k$DZ|`h3DbG*2Doipe>L6aEsl33xiNg!1 z?g0EO>s8@I(`5eTOv_Jj;swWpHt6BR3xR)!3SS6b;O;NlEY2=x;m=IPbe!hI8vdH$ok01o zzoGn!KzZC9mXE3B&C(Ke&p>;F3`De@UhgQIZ8$CHk{*`l1=2UGbS-0x3>yVqI{ml{ zalp@^OB(!!^234hf0Ith+s?ti80i7jkEOWMpD@YjrZXlr2Hliyo>H$vruJ~Sw1DVR@x^gnjmW=U!2Pt({wBMze!tzt z@$s{rv_L)EIk2{RdHbK`_50YzfqDero(KG!`8H`cBRK$lM8B$Qn&jKm^5^nxYPz0p zWt?wQ(?{8g0N-%$6#Sv|sr7nzJ2_nN4Rbotb4)r9a}tjkF?q8bhY59Z(p;Fo>CS3W zajGr4CNy+YL}*n>ba*h`V-4J2?GK~-tM1wS{%T05xuL4t*A|ksq^xsuJ-^7>+k%U% z-2uKHBYsQmoNgbhRpA9hUjVM>qdxygAV1ne?G$*>P7M#W6MAtK|EDUv0CKMP4|q-W zyAWpxlCu|y9^fIm#Qn?1r9C2!t^SsLVCsewr^-0?^3ITR?qPOUhlOkm4V_h6nUSsD z$E>KM`=EvRrmJ9C#Wh;e5=A{M!kRGtx?Smti4z1UEByUcI9uny_tZ!EtP@$D?ZJ>}4=rr;7=evkCAucWXb!crF&92uUB@{)fw z&R;*5kD>i3XaR4y2wx^hF_Gsqzb^^@(5Q@5JCnj~bHnE0`mnj7JTt!`IX7fQ_-J#- z0^xzp;d{=%}uQhsAB{=q37f;QAg@ITta0g?XHigXMSmsF{rpK zFFZF2wS|PwUu|3_=cT0;hm1t7Y3-E1EXqvGi3+a{H-=eEp*MP?;vM#?O5)`p&anSP zf5^nkSm)rjk}=IzZrBZb_);y+?EKUDJ7IE%X<3Y=AsHkG6$DxXQ(YCcIm( zT48EPDKIY&A8VT>9dD{hNoj8*x>reW8J>|BkPo8``V3s4Q^P@d9$nW8Z(TNmS6UK^ zf|tvQ)ydxM7>Awq*v!8KZ8{^*Hki=YPJWTQ2EqMNDD6#-PtQq-PtR5Vih9qJMhs6#k7#t?tNc8Q)kq@?Fb8v8!*PDb6le7l z>Y1F*c)|vG3P{X2Lkj;dp-;uWUe%{=owNZ+|KND;Q1ip)g*n~{=KO-g@8s|khPODJ zVupRn;dhDrl1s}!#b&vDioKocy;tC&vm@}^fCnyafd5F~nZdv7glc0DXQ;1{pUzM& zSZZm%PrAR?40i&bNGF}l)bw>z($6iwYD)Uw&ndr=y=u4(_~|?l&-Z`4UUpo!Z=Q|) zl_>~kL+qb2kuMf^Mgp;jFv3am#Wl0IDz0hNC@zrCi@@VNtd}!mg9t^`pA{TBzB&+! z$Wp~a5nUf*T;p~JnR0UqB%>OPs4J+fD%p|OhG0ZD`;wdpQp5Q=-H&XX^wf~^YZ0^M zD(I1U>?!Ey)Mz(m?W&B|cTxnqX%8QZ7+KL^&vfUQq*6py@0obKZ$9q&01fyc^xb6R z{22gd>}Q2dM8LrZf+HXH%TfyWFB5w1t=KoI{$=b!&PTq#W}k7nVE(T_?QMwFuwMD^_3JSPC=QMAwRoS?qWN01pzc>O7G1FS;*^SU z`}!nJyzb|C{*fs@57Z{HN7n4gO=k>h%i*L_^wV~Kqwoo@;5lD6=Dr5Wej5q`d<^ji7AIq93& zsNpURFF)T-Ex$rnzC>PmPWkhwJlaC?ItIS0x0*>~OE(~}Z&F1S!C`4u)HeN@VZrtJ zD{U{^a@>~CirUH^+%-wGy=-5dnHmz(wY95%(;cnli<8!^NtsjJxTvSfy*z#Ox~z58 zjVmkBEmWUJf?u|f!>96%lh@A>QH)0$k;WrN`@Od0j8W{> zLf&Xo3{5MrQ&6}uQm<1VZcBS?fwi&F9TJk(TiCiNi{kv$Rrbb2?qaP2q2bllWkaFi zb=6YLPe;qWzEtPHCH;svhM+qmJ*_>CpM@A(uvUw^S^wL6Fb^1PQfW;aGU#K+n$JgSBCO}Uw&Bf2gf`1_cZNk& zR&^)rx9=aBwU)IE_KerB?n`s^HTSLPnlpyu1?Fe!(^KeEirU3veg}HA8n^p+)DnF# zQZ{04QG4-Age6TySkgNt!;)q%p|GUQ9Rt*N=L9F^^}&Zl`Ze#fb7Rwq*hp$0nvK+5 zDPln=V(g=xAt^ltx^QaIDYsYHlAO5~g9Y30Tkwme0-I-31$w-OS=wy<2>o1W;{3ev%=Y|u{kD+Bi* zb#H5?tC8YyK$AVZ_nEwKTf~Lo1=-%V2;~~t(!9C5e`84`9@MeZXwOWM*xLIx136i=#bXjpTRWxoZNq1w#mT3|VMesbX$b)tpGj zf@)uH-q?JnD<`j@2F=A>8jAKZzLwKEAQ|REKTEL_?V8jx?7;(u`g+8oqi9F8qVvEe zNMS#;#fF(e2J8D~hnk$J=}%mB=|68$LgcvEJc}G`HHXH>v!tw&`ZQnTKx0N_Mix7v z{1+>qc-oPaYt7EU5jGR`RwAz4Cd|n(+N_*Kf+;k@^y-nQSFM!BIO}l0mKZX-s<181 z5)_l?G#<8$-RZxz5upRt@y} zr+W&0@k@t1S$!$a)5^EifUn}-GTn){0{71*_aKqDVFm3P29pbh=R#|dOvlSAz2 z2}$6h>J~)B6fpvwbBnenE-MeoahHf&=e3zRxE1`I^3NM6=3;Y&az;JZT_BTx%Ppt} zelnV^741V@aKLZn@UyIp_ovg}riaVL8XWh#e#!IS#`D`n3>j_{)x*yQ;BET+z>}-u z&z=Hzt8i$J_ek|<+Kh<$bvDQRr4J_T0N(2%UAN#d(do=shex`0-c4$cV z&hGB*ZKjLO{l2;mya3+5I1(XqH{TmnH(Fmkzocwgv%AET>ua8iW}u&WJ#=oA9_Q^k zf%j{uKT=Ttan#T64r2|${Yl{gqpHiq`z9`mIl>-`T+lj*b)&9ozV-5qTQ)B~zdQ_2 zRNrK0pF|@*_3^o$VsBk(>DmqT>zZbd$)`7O6LmZN+4{SEvYVw*-Tqs!4?Tdo`Mid? zK#glMd9>m&#ZRR%#1y~R7dbqq$)1*;3F)KQ;?EAv=aUZFHvNoVp`OvF!%q+#ZSFuH z?7OVWir|=rVPGXaTTibj3uSkTkAE2cGuRjYQjT$2MP*o z#)Jd6<#jbCr4^->S5_8`?5 zdLJ;kQ1bx;pY%G86O8LP{O{n7<)qJ;iAAkM^Uz+bfyhr|Q0PR?P=4?mO9P{L>FQoe zL}(!IbDaC;+2)lVF2(PP!>o1{i%~vUrTiRa%HcDdjWRq|2dxvbNxVzpyXoRZkxgm# zn(kgY(@1mHj}9R}=~qaO)%BMqCEQY17k{w>=GWBO9}X9ywrLUC?W49)AXCP+1Om0M z9}BeU6|Qf&oA==f=;V1@F|X+F)t+VI-Ver3%cq`k(+e9*oN0R6Bc3 zo^|a-YonUFyEj-{DPBpbch+Si6SqnWhgWS}tn5_jZPV9-cAy@$9lH84#s!>Y-OuS2 zdaN_59?OmUq=>PO_6c|#`!48}3?9EnrdTM^kS4qwiIXlu+hIn)Y^X&=&B8;Bc(SYr zFn-#W3opK8|NN5i>i!E5NIq)qsa3wU1*~Rbydl0YrZ>LIS-S$!CorZ!n9+BN0|1)$fi_#bx(H$dk~S@_aS1t^iad5&bAdXh}N$7=d52j zSDOFUiYoMB7j#hy>e0PNaPf||jS$$QoW5c7{m(ev_p5YgaQITlq7Dwa1>6mp)5F~a zM|(1{Z?EO;QD;VsI(ji-awPE~WG8nEOiGSNE-qvpg%=ktv@f*8e{cWjVXvLdv(Npq ze<@-fJ;ZFlr6^3ouqxsH74n#Jdeh1n~3CHw|0L2^<|s4Re+u);Nw3zA0JTpSj_2ZfDFG1 zdbGX0&_(h!8QV*3lCgVUv9`?~+7um=)s$tgK7TMJ>Ct1$4tQ)$7qQCG^*gt0J+$fR zAD;SQ-^tZ%4{!exF#Vr0`EH2%b}-DFpktN4fMU-B{-8<+)@HB@}Ts{6KXeqhx@ zJWM>WkT1#CJf5}C;l85kaLFgPf&1h>#_4&D!TN{%$=E|720G{$_fKqsPMlW@rodlQ z;SPVh9*7B9NaGvi-xzrbK&wZ&zek@~pyus$rJHy>D9Nc5B~; zvB9R4bP>M2G>;`s3Ez%B`XkP)!Jt`<-7>lV!|bL;|0JChZU0s(f3V|8!r#{qW`;yw zvSwsg@IkY$psdau+R@n&0sZdkr;PQh8y9X|)|Zl9S5)6Q2NFa0asLwgzJKsOI!^ms z)ca?^<58~~)5T63JiJoo7s{>6l|i5f}YHf19?wN~%X$J$3rLM_cLSvfYNX}86kD4iyTt%@cg%;?N9Qy zpV90a80(Yy%^2$fKaJ^JntP;AdHz?(Uk&u6VSi|bkA*2n>`BR{A?zc)~>d8aw0`f4lw;gIpqb<=^N}ngTD?c<3*HPZjwlJwzA93=)2K0`g8V4@gg`#Z%YL;`+UzdJabbxJ7#xOh$eaXYdI> z`&^CsCS-*Akm%y|2l~)~c|Z>@Rr^-xhUmV6upeo6MkscP-uEh&M>irLE06Rg=(TGj zbaHQqYaCq5nZ@jUAkY#m?cBR|b#>07(!TTDQ4xLHJMssd%&si*WafAWLc?mSoxhS6 zS(ST}QfFT}*txMb&QYW+WEnAit-Z^MpU{~VOo`F*+I+AT@Y~Q|y4Wpp3BkcnBjki) z>+AWc85*?HOCZF4<3MCVWK*`ivZp6Pi?ly6uzr==$DP>oOK;wyb|(=IE?7`UxOy{Nqm>{FCVm(f;Lq*|%CFx1uAxBmFS^Dp+T?qmf(OZB1Qo!q}l)TjD1VxL(+ari;UOM(uD zjzs@{30@=qJ>V~^^x-dux9i~;zmzU?aSG^G^|O56F=d~%e<ZU3S~ynV@@m>81sf;#= z=mW=F<c(+G+#>%Q59^q*<{x-u^lW7+!k62mTBN(G7*X?gN9GXlUS6;I+ zms&2~p9EX*!{mQr7BMXP4iAre=k9C&&YU?Y8|BY7T<3pGmpa{()k`l@QT^x>i!s@p7^ai{l2H61y|Sap>0Ma5qEMr45h9?C0P zdBo62znm2<-j?MzNFQEH=?i2tGic>0{aRMDXj_)@clk!1z6`O2!VD}RYJx8M-G(#J0V55NX_iqRDxvbK; zKNF-Y5MAzE0vDS$+zSHH#A(`)*uD zP>>@oEYlNf3LdDGE@N@Aj!YHM`v#9^j>`!z zaQ*?;XNFbs54E(&ZRy+OAJVZ1ymjJNBHE>mBaGftJNNHY-{hhAm7g#dPgf)7<819T ziEH5e7%eRN+8;xZdne;myPyWd7mg7O_GFN&YszAR}9sdB1Y#F;^gBibn$LlU( zSK^*vmgrZSKg{sLqd2wn(mS_5Ikzvdcsh%!9hSW1Ip!2sMuj;S-e?8h!tUa~yn~po z*?$Ipyt$0{X}`n0$s>dxzt8e|9f)%Y4Fyh0!G;f!V!~fqPkI4wfWAh3`(4%O>-DLU z$E)g7i5iElxgV`g2#JeyW*LKPs!Q9Vc1E;sXzAYI!EU{-EY}jWhk*&6j7+?K?8#ss z9F2$#OUO+stuM^27;-ZxZOWQc<8#wu-!0Ed&-Y}*kq7iImJRTyUk}PJGTbHq95fp7 z@R5P)^@UIR>s2h%u4ivZ6Ud{ECL6^+b$VlA zTy{AfKe^+Xdj{T&iEGX1^%dkx-KX#Q@ve%B%|0LTsu+EjYq$=5jY$I>3!<2T@X0%= zT+bfnZ=wT6uMQhABXi65_mnUj`?Q4reqv1d8+4?gGuQtt3&&j^b?xH5FmRSMoAAPC zZg`llWTmGv)Ztl_dPQf`c5m&T#!L3_`STb4XASr2%8_OV4hjg->2cf}5mr$w$}LP& z)N+USQ90a|^sza(ufg@nT-N!#MRVDGv+S$GR@-OU$HK)Qk`^9`ri90U2( z2AzI0ET4|M6)YiKdG~$FAYnJOoGoQ}Nat-1!^SHL9qeRL!C1`p%=jr@B*eK#cdf5{pQe@6Q3zn;YRctZug@5A>w{Cl|n zbKIo)3BC{fh0-^(d2CP~!ierOw6iOD9agOA|KPCx0PH;WGrYluyXo*5ZDW`6d|U9c z@O9ESU~>YnQQXQsCEW+ufT5LLLiy+oZF(acYsPV2PJMG5-;eR{vkV_FJU4`~eT-2X zuVV4+V|EdJ?<~UiDf!V4TiD%G-th>`#c=LH*KaWg`*Jc}W@CP;F*#`@OPZBe;3>>V z%r>SryX$}tTpMgy=>NoQm7YPY zQ#p-*zsgD!)Zjge$kteJm9*zn>9>HP5T$$43pXzZ{`f2e>0F3 zY$+e~*38KYv6K&Z*&p^!+`sp(+$^`rTtL4~taKogvXmnW&&sw=2aL~|lEe4)+q`5x z-tRP-*})mV3|22KE9xq&UQ%8-tB|e4`>~@%8Cg|@8QBxh>XT;r%%TdMJbUG|zmS2a zah9s|e+tCZ9Z-w1sybNV*RLW^`EN=)1oWC;ojv7{s~YR~JgO0OK=QP;cN z8)G)JpOS5H%hC;+ez-DVf1Pw7c#-|mp*)Jm zvSO5HttV|KtruC>*w(=QdiU04%U7&ej!_ip|4iDBcgh{&20O6$(p6)sa5lpzAoHaW z6Rt2UtKLbN=6%lWFt$I;8WddT9?sd5ePpD5O=YObRv$ibpVSw!+F;3+8zi*QUj_G`B!?Rnb3xN!gOxL?~ovj3=XASL)h@bpbmq zVaVVwuZCn>EsFCrzXC)5!Z z5#CelLEI$R4F6_4f%b)B)yg%@26aQ=yQ-}u6FxiVT|6A*jof1z$wpyb{?~6xLFahm z%X2oCgxkM!xIg=_bY6c_Q+?{NeR%R!y}W+q*6zWOu;%v00(rTy96PX}phWF;zXhu; zJIw{@^txYCPHlg6Xy}$XOUEtJLrvqIW9OfhjJg;3KI(1o)Xn<(;hEn4Th!3dJHXG0 z1}wkQ_t19kKZMaPqT(aIJ^pw4%=HpNz_YwR@kfN?FNwT35Nz)3&SHDqZ-4SqMD8_Zd%-1+1%jEDQOw2s$SDJ7XXq`@*iG<`dp~* zMzjO_bxl>I^SltNh?7j6GO-w|5X1OJ9ID1R#wX5gtD2lNyJ~Gfb=4G3u z`s%K2c3Yl@UFQaXuU|fN z#aLxLVr+N{bIV-u>Fl}Su8Ge}7wzrdaCwAMR$mZp{il>ach2U~CcJ2up4hs3c2j40 zwene{t;`yg=tzi;qH@%uUZ#>A^2iORmMyz_zHxL- z1{S}iMmKQUo zK)UW&>ZrHYhvCO}=FkPUh->zbg|Q^7@{Xfr-sQHKlXLcrG>x!|^ViN_+BC9n>Y;db=w+E;NK~e>#4+asQR)omX_tNZ0m$>-cJU z-QM(Dw_RBI=R!Q48X7#w&7T*b!H2UFF%{|>IL+Y&D>~)N z+IxjZ4n(&{*Gtjuj*j+l+s0~7XxNrz_6~cat;2rdpHsG<9~t7Uy)*qlTZcKMrb&8e ze0+SjZLDjhGj4f9$K23O>({TBI_~bmqeSmMK~HvIpZ%WUD|Q{bh3|v^o8jz_^Td1L z+0Ez|GiHy!P*;KWEJLHTNLq$ZW}O7_lN6n%#%=5st0HN?&)C@4D_6e0a^)NR=@azB zcd+jp#2#OjKIXAToAOyXCX;ezZJs?VF|MdcdjIRc_6=2gaZis$`BQnECpEv(X)VJ< z8bez=HlXJJgKQJof!}LLz?-`Vg`R}ZyJ9*|Cr0uaKb^fgKD^L=Z17jnin4Tk=>qzQ z8=TU?%h&E*ciCm@_O1m#$d9j4`W|MAJ^o)S{|Ecj1L3f@LPD9eN`=3mya)LHNP>qO zhH?A44EZngzw6%!_=UDr;8L35X=yI|JI1=p;DPM@s50M2DpO(3p!t>hn)~rbFfX4} z`GRED)&!bnuXk0&Tg=6+xkbL1^p3)uO5L~brza#sgOZk*P?Wfge-28e&rj#J7vwi) z7w2ZX@hPu~f7v zb}w1FtA<^+<;amO&`)1Ed|6M=Wy8R6Bl`6@?0;1pEYM53{1G_VdPI_81u$@bL{0X%*JCX0TzxG2@n|V1 zM!ESX(7Z2+nfN@K4ZpW#+xvgowngyB2jSl&uh1wWkTl!etE3vG3h&7S)y_?Tc*4Xr6YqD z<2gjg`jBB|5I!lt=z0kGpEVqkzDA#_ z`I!?cf0kaJE`7a5IncunDPJKVro?3Bf^N2dwW#BK(lSUVv@*dsCZdGPC$Au+6K_Zv zs9>@3i_Qm7C$GbRI?A;CSynKjJRRY1uUeyYGLy2W`#u(=bggFlyLlV#h0ju=jwX-Q z7aNRsOxQiZr~HBt!y5{%z{BZYw1V0VZ3ouOKpo(>R7g=mNnlN5b00CIr*02ZR zIDe=ck3N6R9$2kVBN$E=S@O%$aZQtGrwUv?&Z<~*5Rom_KQxQFVb((vKLfuOF>mJs z4|G!Vx*h=Afa8b$OgS3;u4*HVf(u%Q#^2Yeyr8_$b>Dqm%+1`L_eI~|rM$r0UH41r z_XC8a`>DSq7J^@oa6GD24e|i>tP5<#hg9lr_R}MUufAG%MEw3z>1RKb<{c@1_0{4d z%2Dz6on|^OEd`(V0hbI7WfU}VL8pHYsRcJu1=92wv&ZVp5&{uxzNPuYO+`;UQM5_& zG&MIX|7X+0uOtsappM5Y-fxP;rd`URqD@Wog~s#Ee!qMkcujQ#o!$#=EyO)mu-odC z&zmS(gF7p4vm4yX>Q)?rn(l5=R=IJTpR(GhkZl~>*= zI->MaaXkA3w9jQoJByCccS@lg8Tbdh2f@1`tj8veGK{u#E9~el@XTYChaORGeT1!f z>7|!OUu7%c1-^nERvv!)ZRHj=jCT3oQ8r79SrGUFz3X7UoNi4|l6Fp9!h$9+6RPR* zds;d}y0m!W5^1Nh*|?L^S&+1NGCfdUes3a4Ezi;>)2Td5Q=VcqqC7sM{;BEG;y`+Z z{}pAv^n!c`_8XdQpxrQF7&fdk>_FR(8E$~@_K%>a{V&5A!ygSFVHad(c9u$BB&?bB zVF--jF7E~GGI)2N!n?YUvS-0eAw{)>|m2{KA z^pJGR52xB7xT*?P8wczWdb@*1XLrC&GVw<5v^@I!dOS1b*W=WG*4sGrc=Y)7pY{0v zr}_1G^q=*3X2P$}qyMa@UyoDYMm>C{{Q5llvikgb_)Pir^#3RM_4xJe`cLrd^UPFM zPtQzw^mwGZB4c7A@pDG~tBj~09#;RdVcca1LKI6al)mhG-}hNktK0gVvG&KzaJ#GF4v2`6YG-ANk+)Osu2-$$YAx@BXLrqdvbLkN&fMp4I2q!)N-e zr(chArnO|I{4)SX}em#DDo|($(^Ust=k4L)8k(TC&O-+4M z{i`fcKb%(ovKVzX3{1*_88tA){z=U{aeP`~4asg5N1((vELLh!jSzPJbfoO{|C|yH z%k;k*A%Qk%D{f%b8ds~88r2Wya74qQ6%8;(1F&SZsMh>*_@bdWhaVb>i3t@&fDKwp z0s!Ulf6G+4m-lU~it8Kx&e4{onf`A7UkrRR@c#oJXgzyGsgfpSdK>tE&u0__UU&Zc zPv*P->HMhAub(&dpY`?X^XuU=eb&>j$2rqlGE@GU%Ifpagi{aK)31mBC;0VkoGHH^ zzdp}QW%c=I%A?04eLthPI0HX_R{tu!>WACazbr(Z4Fi*MU`7p0v9GInCk{_5tRXq9 z;s})ZhQ&%Psu99APDjdq^Pf|qVVV9{BP7rUZOI9&TD#S1C9nG79FAx>w4wpVXaE+Y z7S)=64qr4B=kP;AG4Yv-BESZ%B>{kP-@j$5?BRV&{X4@?=f7L7E|{sL2LE3Sg1i3@ z_&|Ez1@Nu>N-Bl#V9@EGz*kV%ds1N3;l36NDu*kpmR43St*RVmuE57iy27F>%VThz zTRKxb8bT%_IAOTGQ?O;b8eH1{Q&4XSfL(Bxhn;Q0;DNUCC~S4y8wT}oE1x7B=!&HB zvP{(wMYWJ!s$gJ4UEPL(;$rnLJF}*-VQqc=+J?q8^-}0$0?)9%zCMsVwO#7XAA{Z& z2Hs>lt-}1pzT%vz6kNKC6V6=2>UX4Fp}fP}$aUhAyq*EC$zv7vw5fQ%_y)WzNy95o z=?CsF@b7!VRi-L-F{#hkD-V}sWR%d)jNLOP(o0C^A2g+(Jn%vdI(_+n&{vtDM3XnN zH+s1{Bw3dKO+PAkH|ORy=j8C;4?1bsIE(&e=jN-KsYp(dC;>S}sAQJTzQ!0(Z#~mH zO!|SqE!_0P4QRSOqar;WCJOqUl$j8pl@*_m$xIrE5>93cILYpphQsVBsUP+P)ee#c zi-2jGxWZKFWxqJ#NlPtDNhwQB^Q0VRzql$j)ua7S{FVJ*0O4Y8(+P}(Tn76D7)921 zZJ14^-I)RV#kZxE*NZ{SMe`Qj2!jc|9DpVL~YRFQ~fbu`2tQKN{~H`a8sdv{0kJM zZ&X)>|8prx`WdjObtOu2IZ9T1^2sN+-g+xr%tnkPNB_ot?__mvCTKAtY=($)GnhH__&$kqG>@+nUf9~fSs`5mk+ zf6~{*?p6N5LVm|hhq`nfE;Hfcxtb3l-?N7f5#8|lxfSv2s?cKY6VB}E%<+MeCsiqA zJZ6UVhJ#X&v_MO@hqLwUI%T!A;DZl%`dpMRm@JblN;>+~e1(!Rww!&4YOhm1eCgl^AAkz9BME#M)U_j= zB}o+%&jt7pg!CF+dX_avstB|b_gQcU<5S5K;A6UVCGNL)l+D21hqN~*)3VYTdsErW zt~^C|A&@?;EF^<{s%)lIX4q*sC0QoRqWgBTD`EE|%ruUFh0o*2DdWzPy6(#5F#2+P zcou#;%RSQVA2qR3<+7%aSZ`C);fXt>>zkS;R!Q^enib|1;9!S5PtwIv?2x!$8rdM58Ij!6NcxSq&Fs3p(v-- z@R+WCUQT$8;I}6x{V~+1bb>X?i?G>dG|6eGRV_z6<+fS1jo>M6#DU^N=z^vuGU0xC z3CzG)>v$ikv{|7GK=eS26J0BLH>L}|O1jokpKuOOHu%d+&^zc`!D}ubYP$pSK`khU z*ElTQuy$LWk2Rs)MABufwCN)>gjx?g*OQ3QJj?r;(-*+O(0Z%KNjQK3cIiXl0eFW_ zCs{-PN!Jr8Bx_&@Q{}V=y~w%9OL6&%ae^Z)O&>u>Ax<|;j7m2&aUSwAkjoxOcF^g^ z!4ay7mk}i(Us?g;IaB3vdVcut@cf`m&HuV46RP~G{-DJ3@^mBQkXj^-3sIDAS6*m> zA4e~G4Xv3k+QJ$!UMA^6Ir@(^Qcie!u&qEnU&t`78>#i^@(X!Ke&pmO_!!|bKgp{} z$pLOSw-9zBhnk`1Q6qSy&8~dp3Oa~}8=AluQU8=Y92?b*JYWavWT6<7J=&Th#uXn+ zq2QL94jE8s5~H9z-QUFA7_M$e%_vCX`B)>D6*1d`a>#t6ayfH1vCt;z2BL@R(&r&r z0!Q7-Ni1?=rchU7b#T+InF73<#?$N zfE(-rfn1h2&nDYT9$vsMsC)rp-qSQON~2fLn@L*8eFP(<-18WT_QXOORS;|)k7#bt zv^=KrOU;kAa_rXs%iebY#!*~<@9eGm>7>)0ie>d`^=izC8oOL0D1~plj$PrM@h#~1{tGO2+4IxRNN3F#}DXIohzKHTn4~M zY17nAaad9DKi9w|Lygc_?WcI1sE{UV09b%B=GhQTCak;`W!kFrQTi}gb<0yV5bdf_ z0&(=E3jO|Efq=<($Cw&tUkG`tDuZK(s+Uz& zEvv5StE%d&$!gEcY|rBAoT%X7D7?!Zj__Ekx{{wqWDij?p3-_MItx+7iR8qhP!BJx zreagxV3wIPndOb9_upq8w*`Lxec)}j|6Gh?|A#O%ozIJc2?J?V9$$JB*&Y3gSw!TfwZ_6~c*VnfjABl}A zij6Ibi7kwcHL-IiZ|zJ<>fAcnwt+qI(o4x>#>}_f!c10JkR<&`;igSeOU;<%JfA+V%<#E#PXAS3yxhtbCU% zmT?A$Ci10UGRhgQ|7daEwG%oso(@k+3Kuthly&a8#~z#>+K^lqn%Ns#^Uq`F`TG0& zGQZFn@yuop4KwQ zQF!fmuQ9=afmY0d@jY`A>OIWx7uRxFK%aS@u$nN~i+)*HT3>`Fo*0aCz=}e-8cULc zr1;q2(BR}yvXf~PY&CtRDi_@jHzWnUyWgiep25z73i1jo+%XH95t zW~gl!mB3^UW#6SHj*dtib$(!yH8Ey^Z)O^6u-#vp@6(8)Jpe>66%z*^ygbQ=W^j&LSW?yFDYLJv+M%n?UY^ zx6Ukg-Y8raZiKNdhTojFjEuIN>~^dv4!3`T)3Ki7dvK-wR}*F=B^Oi}2pRCDy%t=? zX~8@jM-i4B#n>`yYH&e&-l$P|?FGT9*0voB#*SUEV^u{N4v(x@T3c2T;^AR5)z4mD zRJ45dIDeyu$JqJjw6>lze=J)u`}*AADVv*`Hctu8y`Jbe%YgC+)MvPSYCmX2L0$pJ zPfDJPfxUtOIe?6iQ<1N7^2WyP&TLLAm|R#mxgf1MbNB9=S@EUu2_^CIB?Dut^zYE8kNTqIc*%h#vM{TtfQH2S)KN$5iJILm zInZYyUcApu9&9Te>6at`U;YJr_JRy(lzcM?jaUk>okP6Ysp$zreC9#$43!vLz)ytTqc5ODYb3>cjdZipA}M;`b}aF&Pb z2^9G|ZWu43)k=`JIaYu9t>kq&LoucT40)5FzjMh^F;;6b}~YHe6s>pgFDNM28VPJ6awinHdorlqy!TjNYp zc6&~KPhQCA^8klS0EcYAVHEb3O8fVsjt7LWltnw)WRfKx6NSEn{oVL{QC((7PD*V^ zz?6WH+LWA*%(^0WXYuvd7v)!_r*}mdBgdzwV^7li<$V?TZK;u?F1>VAWNKS}MPK>i zihJ*^=uXQi%gf2hE6Yg(C;*F_0gE2seF$US7v5XQf6jXVb!iF|DgD6Xh&}$K6RWFY=}1$Sn|ZT4jK<0G_pBH z+wH8^$a>)ocSwI}XaGEtgdU{66L{gkvB9ds;E*uGS6-BkVB!RCC)$a#ml(Ux7=imS zrW9m#WPAG-PASCS*E_o-t6*|*bXA1$v82kxgfcXSbfeLjUK*cRnvhtTWQsC+_(zd_ zc|sI!B8)Qai3rV~T$ndLR|<#^F6>NC?<@?C50G-l=M_%Q4~^KvZi*{SN-Rr=&I}FB zj7}&^Oe)1uO+MzL#N^W0*i!JbW*>tDn2W;s_Ctm^F= zIWeex-T=XEQmL@7RwH~#$v{tTo#Y%q4wB&B5m+rgec9Bhmrb77VDOSmekRWvPg6jk z+34?8cC_g{{tfbVO;NYCps8my2ZUF2e^5XS=0|PAW6;ygI)I5O7ONfjtif1npezSChy%k~kNz}Z> z(KZ4FbXm;o6c;;CGqEK;x;{F2RCJ0Ksd(RnrtUHAQQu4wimP{*dHEOBW1H&wG`MJ%c-4;x^$Ge$54KN z2l~N0bE0!91w2(taALv73RKu1oKs!EaKIb~(i_W<9aIzkxYhO+^y0X()-p15R88dWgZrHtOR2hGbh&UjGE+};&H8w+iMCUVnzqYVJhe1pz`eew$Rj9 zw_1F0At9;}@udl2;%^Y&Xc=0x%QUzM+%vuC?dS#PPcQ6YT_BFGnt-Qjn(~>3S*LBm z8~E>x6=4_Z^&7%4o|S}3`!|NS#7B7=SwcDTeLm*?92-ZK0g=25A8iarLa=yciy_}z zO2<^-91Gt2@Y6>A&=YI)t(-o#y%)0^(XnI7y?s(rqEft<1}<)5->p5if1M$*VrpUa zj1)iLifIKk(^HLiS9p6EQ<9P@a1P3tBo&;Bj0~&nX4fBU+%_q< zd`Vr=#N4tab@NN}qszvWxn9xWcg~TH1d-X0g;6RWHyFzT-EkoOybovWhHmo{@}K+Ure~)vYtv1z9_r0yCXCzwjnSkkX=%o`&HT6(!5I4BhJ}? z@+v>)XB236D$>1bY?|}=JlARH+7zsGq&K?+1+~z3o9xL3-@$S*i%$gd2GFh5Bt(5XH#Rt)@&aS_N7!lwx+PA zsW>O+?B?d}IXPp;=HzT|_LUC!`LSn=*@Y$9J^_zsYq-Aq#}oHGhTWLdrzY-xPbW_ zIr8LPH{OUaOqhwY4Kw(jKEAJvFz1#XbNaAF?+cz5xqnY-(a~o7RK(|1d+|w!eKK5O zPwtG%;Dv1nhns#>885}VZJS}KBVKZ+eauM`PMJZtNIGp1WrjWO#a8W;WqWdGI9m&` zmE+KjD@6~K+M@EE5XoUje7qqM8;sSvPZ(zCVl7uS-)d&hm~U;yp?)T_ZNpS{j@kA# z_Mn?>HZi%G8O*k`r?MMo&$dmQh07mjt(bwkqNdG8^pqEV$LSe3C*G6ag(DIki#P6$Q#Em$@C4lWTWlb2jj2X5#Uo&jiJ_cWs7R( z%w)w+P0KCe<)Jv~KDYqP#l)ax;BGs|TvY;fpZ0MKMepy&>E7TZ-cf8!jwDaQiFvsdUE) zWKg_MIDoUbaX%5Ni)3I$mRJGj`(Puvc<%C?jS)6OH$Q>BY&)jqkIwP;#4NR%-}Cq#BQPVsbJ%4%`#m^^R<^&F>>+#o z0CeeW;hc6o<8i~P;`7q7zIDyu)d$@bd*ODE_*72m4y56l4PKKNo+&2~eCG*wiL_48?2-@N{Uz-^|silR0zpW6C{pqAPOFW(jz&1Xl5PnIsI&aF3w&8;lz z&Rnr5vmiAkuXz1KEwGdkZ< z?4x6i?4T`7u5*KExD0iU5jNW0uhZ`1b9{S^n8g zwwM2K>6HK)68aSP(%ga_t(mr$)%yC1$C=IRtC;QzM!kGu|5p5v;|$J4J%D}es0|!m zf#b%}BgBeJcP#j-D*L4jtF@rXzXvnnwYIzev#zaD?tAg1`(6|r2L8c(D>w6d zROntc`QBHK;|GzJ1D63H!ey~}GKs*K7 ze9eS5+!gmF;^uS7zK229R=dFhW(QA z&cpM%AX-6naRa^vA;ZON_RwJdrPIH{uZvhx>Rrn_#{R<>Wied3||e3{2;|@ay6+n#4I|>e6rx2O+Q?%21C$BB|XVks~fr5 zQ`6^{uu<4g7M`;ZFHiOd3<>099Z3`JBhT+UrA>L}N6Ap0OBU?d^ikx-2zcRP8*d`= zgMOOmD$yN3`Gj-1)#!T>P7(I)$h@XwMXo9E;;pt9{*-t{eVeymbro9C4`s_*X3l1H zujXWwmQsAPfE2jH3H=fumD3HDIA_$UU*a*c;6DVjQn#_zu5*nRct-T&V#I6Uy%WD@ zkO&Cqq?%crtS#TYXE2?!=^WcdnHP1h$n+1~dxq^Fo1%C9GRUu@4g?yE7jj*(ym97i z819=f7M(cqP3>IDL;Q|=f0(|V%c*#3{i<01nXcI%&b+8?Wu7VCX8YTp5`TGa5W&Vl z2y8!;E^B>+)xS2Q3=mRaE|nrJ$323mDUM-O6wef_6hgt2^MCg3$YK>)JF@mr0RQ>exH7+fNm<#lizdvba^g%^r1zMXo_bF%XKHc& zAxZ0LG)o7ffkKECd#AT-Z1>w8xvgVWi0vgSOA-^CORApX!8U63Q!n@p_?i9mZ9bj@h z&m-uhAsTg|!&V#x2gvBIm_%(vKN2jC4t_nNb;8+eL!);_29&j>wnpXg!{@Gxy>RER zZ|I#6Y_Z|=4{B53mHkj|Fz4W+_Q`EZDJBOlD?o>y0G@-7)#y+vfPUYO>=j`P_e|{E z+8rHWUU=n<>A&n)Tew5KV`inVF=0||>5K|LWBjCA;O~*{vsx;bU(_}Il8HTgrrDlf zRJyQg+-Xgv^Q*^g00AU?{tycK2dy2(@`I&t!xlzUd9gT6kbv(QBF67X-}vMW|EMhW z54wH_^SAwZYx3r%Nj|=H|Nb`%j9T9^XQAyk@8sqd)l$5Sx9q4MP(3|+WpP%1by$e) zJ)TY?@=tV}pE&{u0zK(~KH`Il^4?;v2HSoc^HlIt;Dwe3F9n~7+XD}>u(doa(L&+_ z0s;L;Waxv93MFO_5uig=c!AUB=4^`0(DMVI(7l$#5jf(Z`x=x!Q*EXEPD|>ScVx5H z>>Uf1IzlE0_EE=^K8#k8=Qi2yvFg3?igh_Rg0VDbJi(uAX{{%kbG3t zx3m*$6H+c$wunqHt71w z)aJ^ZBh9It3wHUx9z{Vb4sw) z_E&;cG1?~G6X(fLRJlzXM7p0<;q&%#J`d|(;ScxBf#-;jZ~q3*K@_Wk$TT<(g9t{R zs7p{`Q7p+=*~al_;u<+)-Tck<4&9Blf$Z0Yyx!vCYia{+-O!CK4lM7xsHb7WVE3=xLn&O^g}JIBxx$!%M!?3-|KUeLA2+`8bcV z4|gm@2xHI^u7ND6l0lq+*$R*xPyya^D#Sj7hygLRXuIESo01~+8PA>4J1;bLPGQ50 zB!6Sm@^P(ma=lF9ziIXLD=jZ*AyvH7*UONw+t=HxXI5rT+m@zri^kTk-P5+~g67iN zDa|FdQ;`n?XsaLwV;~zKgsHbUt0$ZCafoBQik=8ZaiSN8X4R*Ky&2w~1!-v+K7K1^ z*uH%{c;$FskI>}cUxsX)t%z?i_>{nIHnJ6 zeiiewo|bGED1tTru+d@*(f*`;iP(d5{DG0u{>LCmMS~`~$S?bh#evrrT)ATJ&0G_1 z+c|noMtOZm20Pz&eZk9xH$^VCUv`;2=`_3Tl5Ca_Ttc4c4jsB<%M)kRXlA-Y3ufeT zHmlOvzu~aIhwOoixYgHi7V@J=J}t?4q5Luk42|g$Ln1X^@sID?wX`*UQ+)mYOFz4C z&#KDA(-SN9-@q(iK8eTnAG`1}9z+K?;x2H&$g0lnq3#^RL=}seQjzMlR^%a#Jzklf z<>8f;no{VuK6K;M%IO(CxZNy0&D*$k!mQK%PWLWKO3d-_%FZcZg%P1KNwJZg-RW&P z$+^DfhOC;li5=0gk)z|1)%xf{nUpm)pfSq$anv1^m18m%}|FCg3 z8h;Vqb@94ghH`jGH?TQ?PCt9Va}F;j`R_y;%4tuyZvkLaK(=ageC4mNJapkN)>I~L z7ClrD@p_QM5e}`Sbbv#k@<{2Q7;{x4KHh)O8E8NQ$9>#b(fr}ordtv$ebZ7c=A3;0 zT&y3z;+chgeU;73>d4P;5MEgmWg$|J4$x1GU$uGFQ@f^|UiR9?veQJ5fBzdr?!h5; zM%yhatm^dPcsY2jui6^_*SIZHw$pY?~BH5Qjw+Sd>ulRrNx3Irke%-VMSJ$C|!7Jb`-=~f9=;%Ie zhneshW?nDVh|$3B-h2u2wqQ^!MIs!kJD?Sm9Y?l`7)gSBNx(2nI2k^SYh6=hGEUk( zIc{xS+nPduf6~6#K5lI1LO<5H5NCPUWtKeus9)!nj;7_6;-4Q^bq}*kXZx}?ub!$q zd#W;9a=dK6L%G4hiN$nwol?OAJJza(W0xQMkCL7t58)KtgMW1_N^FoC z_PWL`jXfD_M;}|Ph1aExJOdUkX>YAJ>6|LROurE0#B@@L* ze1vDy5T7!hR4}c7jE`JsVg3Q>tyBc^3h)VS43j#>0T^V8*-+P#75T9KmCdI){H*HQvK zkO9K(yg^S0E{;H&96BG{Cqzu;;L&J~)gEDfTNw>Q`m^Z)AHr-A5?<|5)>nyp95nMu2Xq zJKyNcS=4}ITm+h}j)*po3#bp+6tFvBcSv<_S!<+U?v`xaz?WU+m%T;I>c+FR0k`1T zK4vC<3Exp_10k3f!d-i4Tm0;38C&^yQWoOUtuS8k32LVdy0mIj+yqN|U*-5!xdFzG zb@la&O2x@Zo5ZLIzJ6up`Q3g{MUO`+rsucsoYc6YvSd;Ho3lIWJ7#y*b0 zcBgO=^7s*x{1`vt1Z4~#iD6{-Zj74r%L{MJ&hRsBoOk%<@L5a!JacpIyz{ZJNn;zw z1BVjQ5g%Y=Um&IdM(RwT1|yY)-~vhQ#PN6)RHk2qM7O8ac1N0ny0`TjEgo47*+z4E zb!LqQv$({L^JmPzYT`0ORbTD&`L)Hiqe#;W1+nllNc-Od)M%vY7;0V3PP2X+a+&4Q zn5{8WER(?xZd!EDJs4%lw3_$;K7N#;jgB{zj~@xr3NO$-4-`91;|DIW*)9XiB!?$n z#X);{|I33@OI)=j?#;L}nuy-dOng)BhiwZHn7U$0e=Pc5+@+_UQ|bYRO_tKe#%+o3 zJGb;t=Nte0wCFG`MD`y~l z9MV>H@5p0GH+|&u=w`xa_U|YA5S3G3DYrk|E%_Sg&7-_MMu8Rt6a8VL+CIV8;jUGQ z^L4mumH0OU=bOR90Pf1E;(VPb5SAoN)Ko+a3o%<7gi#?V6btNFlNaFMv8KLrHUC}{ z#QZ%f3$xD)vVAS#8_;oXPt%IZj-5SCD=XQ?(xN)s=OrWvK^|qEhVFaiJPh(s&FcdF zpI88RsC*H|3^eLn#STo}+W2%Mm@ae{R}g0eKHa{A^Xcm8r8+)cLpE1ly=M*pnd{20 z|8OZsKA@jMzbqVnPw>2AT!?c>iGG7M06hr?Jd@BC9pgfbp6TiiwQb9ip=ico2C9-p z<3-|jm{;q{LSxuX*|9#xwG;2R{5B!gU>KELIKIK;)3{=+w{;U6JI>p;yeK!IX4Sa! zYLZ(sD=PW|t>*af#G>=Y<5HR1Gw1cP@V1cfREzfe>Y~d$0E* zDLgJd!)vK|&V&x}nif=BYSQ@e2s2asPI%LBnHGk3G^o_;y%w>k#DrY$CFZ{IU556? z*wiucEnR?z^V})m6OZ&R=i}xZIDE*AIY^wBOmmB({W~ah$F|Y}F`Cb#$$uYga9(!a z#nxkITG=HykGKy4ji{ZI+=*JDZcL{#*hUXXZxp2uPvWjVPzUFb=g}4l$X9DITNrtcN(t-R7!(^tsYlGP#t1T;-jSsdZTiJJ3 zv3A`$+Xr~xT(^$ktH2BJl>2F_H_Vrz-U`+9N#+UnyLsMeJs;-GW&Ra;$N9T-AvTmE z7I`1qgVlEB1IO63;A3A0v$^lDTKCaM>sBG1nF#l19uH_7%uX^qn3Xv%q+3Sr{pO&s z_5r-N<8FB@TcPeij?=W6nv*Zuj#qD$Z(quD5Ynd4A5Q6l=*Y ztm!T3OCK|H{M~uuvsxQE=CpwJ(6~DXGCSq&rIIv@${)!h7KrQkog1nMhB79PAs~BS zVLf|B?aVYk-`3)$dXrD-)RMxkklZb-KE2%AdrV4Nsb9LOv@*Te+gMo_fcrt#Y%Q8m zR@7+(Q&)=K?lNNJXB?(5ETuY%zzx<@`}NGMPh% zU!=0eZ*-{9V$3ax^&3++BY1OA`|{evEeR~IJf$zXx$1-IFV1ei=l3lyJdY@qJ}3p{ zjFs0DmyiUPSWCGRqTz&>%;9^6C>>FdqZ>k(#C$te=ldHMjL%N=^54*1FgGqJrZ+vY zNHUC$EAAX;@*TH4qQs-D+1s~bY)-R}Z$Ryu#=1q3DIMu`brF^Y=Fy?y)}-(;CFgdR zPR(Vr)1*-?Ii;N?nR!%R$h&ZMfYq_aT4r_Cj>aXM@k!jdl(8iX7WsN+7p51R&Wt{D ze*K(GAMb{uv{Y}uHN7*|1Z)av>-LpWQWm~e6q`7DR7Y!iTW&^~uc;xkY+QFsabYy3 zo{@HleZFW!-yDwiT@HyY`duo&eA!ACZZ{(sk#N!TZllkbf|QB*X3sv!H?lmnr!gS# zvJ2m`unp$xS=;!c3bTL2%1JR1Mdeoi(HkdD+COvYjFmi%9MtDnlusG2;Laa)cmfTt zT6G1r7kh=@5T0G_Td<|f!xUq(B<5urVrCZCbVm6-uig~Sri@PL%S_D7FPmQp>P~k) zSD|ic9T6WRsekRleu8`JZKyM~ePBtR0!52LG_sf>{tG|f)Tjtc zWb?$Jo`5u~C9G;(z?j?5Vu!L)68oCFZENfL#x*}>yHk!+xx+!l11b-1=Z=CUo51v7 z2M)4X=#!e6Uh7k^wcJ?f9~3v!e^g^;a*;889m@M+epOWg%d-8ds;?zED`qs!|KNxU z5yL-+eAu-;8xW~)I9juR_Sjh8^r~5*oBi_}db7@C)rE0=5p|{CPI+ZP>5koJQ=fqN ztoHfrTa=&Ac1}o>oRJkMi(BD9bKL>VS>qTW>ZFym$7BBg`Ht( z*c<3oR@h?cyxQL@FDJD(ZgNaSVP%ki*wT*jPJh3)%$nGxOQ)ueT{<MRkiIa^8)?bSJk$yV(s2Rd8xVWe%?X(DY+d__cpGm z=-AcUxV*CCcRAy;+v`Y?%bEk3?k@nlAmG`fz%yPjF9p|dBby=qm(wg_gLUEddGQo%kGp!qhHI)`tFsi({FT4Xx%iUcW_}!VZ)Q-S8VM|=<DQ`~u7(Y(mk zx{S8W29zrU?d>b1qmEgXI#AkL(f#LH*zYX2jqw^4muG2gZwn;ozmuL!4Id8!|MmqR zvnupYAfXZ%D4+@E$K;kPcdO`Ut~PB6^6(GdzG(I05UX##wnp!0E0jSWF2|po8qrP{i0+(4Le;`IW`C$w&rk2wt?i>o*@Y~<#-hQp07q-mv@oj$oRsV6TJL?ye z8g?;X-|3tSNa(9-T6)xW>Hf@^zOvSh zV5|v_aY{KAIO0Y#+y|CNw_XzkP=(~77WAC#l8dAL41SwO1&7{#R@|oeO^ZVlHYEn6 zH_eHziJTpNXUVdn)f;wP>GP+@L+(1`nU}_|G>hqezC4XD#6u`UI$FsU6Vm8jaUy%9 z%6F54IaBb7FY@A;CFestWHK+CP}bug(p^;8nB?u_SCVp8Mv0%NAtpW{(-^*Hw1;v}a`>lrCReR|(#a_liis!F#JS>;TBimCv+#Z+S z=P{>fNB;Uk8TJV&Tw+sMxTyuFL;Fc$HIC&dMR?-v`z zB;?Oa`i!rUr|`;n4P!+bpNK-V{TA^v{n{W}x?LqKCd{Undmq=MAa zUZZzNQEF;=!0E>7Ib{Kjfz{nCr?GZfok#Cv)7-kmw8@RxJ^5qC6m;c?z0(6|>J{lm z;=a9^xNlFn=ar9>fiK#6H+5VALIRmAsFggmKr{?}VVA$pxW1a8W=qQq^9ECWMPa#z zXJ%>h^e&TW=Ryz3+!Qo^zVUQpcX>%Erh@8fr?LYbg&k>&mIuse&Q3~;PboA9k6tmM zXU?oe=6S6-Ia%>pMc7I%=Pd&n{0jV%%6pP5Tw^}bA`J0obXmH*P4gyaT7wf(y`_Jd zKWL;W)%4B@%i_m`@tIY*F6nMdz8BOjY>8zAQ-Xr4;7u~?uCes0$ZK3-4XGVpJ;Uj? z%ccn|7&lo?_vJTPX6A0+c-GG8O}cw7sT`O`1B{m;{SfqXN*?%~hP*fB3j!e1r_}~D z?+3W@T?6#J@KtLwum+>F!txwn-=-CH21~Q`wU46CTpHwCS$$RPMJvsImDS?CYuEcO z?kcPg44qX`(w@9*nb>@HduG1vgFkoW@^za6c#S8J6DIi}N00?5dca0_U(t)0&wo?V z8-#K2CHt3fyR_=ZLkMSKioGXr=J$%;3nvfeDtd1r3WvYRVSRLZKOvtHEu%abb$XLf z=CBXMJfoVH89cpB@o(YwwZaNvrEt2iT3C!5#}^4}!Lg0O2_7l<<{~#aLNVr{NtX+K zCT!b<`S7&>C&w+uNonb@X%dzr1i8(IZXUji;bRRqFM!X*-0y7I&4(@dtAB$G#lMR(gF4;oFwT8oh z>EfmE` zIm-kDRv?8eq_Ylj&gPgZ*wI1*e+k+B8a-(+t>|5x@F^nYV03;Tq93jHDWCG=mh ze?$MRI1zVnP72$)W^a&wB^aa|rB9&$6x^9XIxNu`S%$MOt3&BM>&CTC z5R&GezPeAyU$}byQen!X*~=FTirb&XSwt7O0FlSs-dyKHCsk<#kSl&YF+E!9Gwso||2;16Q zk>-x}j;@vgFYoGreqVP>cZ_gjH`(9b-CG|+a&kwG_4ztkdU5Ie)yoCb zve~PbVm*iqQ^H!hmy@uYR>1qZ06Xb7nE51fyBhC@NTigMC z0pT`q+JaLoLU74q7;w4-c)A8Sc@gmMQq0NS1AGIP5q1$)0h^Apuh}=a$89n&Xs$R< zoDYo2Fk~9C4B3VpLoRk)6c~ztIhBSg!+c=Jr_v!{1rx4-y!!_1t+^3%J2&J0xLd); z-!9xC+=;zCcR^Ntx3FLME!NrpUigD>k8rPWALP^zU>)W|;DR51s@@S^Y%RvWz{yo&V(9}E8$zQsJ<4-CkU3opGf_m;pCSrYD6 z%oF{vE-(ypqcM;j)Qb&bo7f?qh5Z{l#ogj%;#K0+;UL@iy^J%war>^9=qa zek2~nzPE1;A%<{6v>{H4mBvUJQl6AA6-dR>SgBEJk$R+FX{Izwnk~(f7D>ybmC`!t zH0g9{r?gAjEuAl2AYCF|DqSvJC0#3BhxT%-benX$bf@%d={M5d(r=}Eq>(=dNM_d%v&wQub z@b2PO^PvB#dcA)m;`LGDC3kZ3*QIks;NJt^!+Q|#VZ7?^D0JV;CbQtMRbBbl>>2oX zv(dd9*_wj}aSMZcl(V}zK{lIHo$d7|bGF{3>CJ_RuS_$mI!Bx}aBoy?<*&o7-PvAm zGEdN(ob~1@n%O){)057;z}c)fnfvr6XT5o~Y&LH|99tFrcICU<*{|MYzEE#+)|)SL zHmfG{)kxz8)#Us&-wL<8ob6PT`S);pKsK2lQNB;8u5f!!)4S@-FT2`kX7ihx-c@IQ z$JGYuypQ)2yhr-G!S)-xKLq|gNR(ZJyq$donVik4DaeX2;i?Jx7)?()=hT9d^sZ{S zpmc=G)w{r4>}=MXf+{pqP@PWSjPwS<&Y(`sud6PoS8o$E&B-)JGX*Wu^rX|L7qr~j zEoiN54%(=dFKC-$w?p$Abe^UsUCMx6XWzPX7?x9@Uwj zg8479Ip|gB-$FT*78vxd=8tqiA8O`)b#zGQ_NAuh;l9<(K{icKI*X*4ExtN^AlyQ9 z<_JwsI;tZ}oNQ+q?#mB4obak4}GBr$;K<&r*U=RkGPKPO~SSr4?_N;x@_I z4K6cWZQPkH^PFuhOEj}(rKXp4mi5Yab3d1}G#ksgnjX6M@E*jgFXK^|zsHN8ISg-< ztXEYBov(pgqiQRE%?U%&iE!DcyEZ*UdaIQ*Ho)IjMZaDCs%4i=nmQzHHEMpHb%WBL z!P>3jMzQJfddPN!Hm3eu+n8$4ven<+qw1A@0Nbu--=1TMnSS->N3~dPdXBI?Ic$HmX@qBlYbavAmADKBD>??CYFQ zXP^3dAeSN9k)s}*W8opuUM$pJ-24XPH7xq}JrjzBQ98J)<&cKWE-_5FTZOZFAr;0!lDc`x~r13z9SxqUj>bm={mk24UG#P9>NZ$kWu4{fTQgf1XnI$jwa?W?GrNw}G#5|{g{vYv zNU{X`AyT=lO~A}L^y0%@g?tt>vXIz_S>%UifT6D?mE9n2hwFUI+A@~UZCbb=S{B1t z3Rh#e`WC77r?}5pZZnTmVIKESvTg>6IH}9HD`t8TLorw5xjKs-gw4fVKc1^gxq1_+ zd}S@}C8GEtrKhlKxGLu}oTn1WZDP4Tj_W(PzJu#`a`k}tHNs|bJ4)wEF=Fo zqy;v>RDue6UTz`b?r*M^OjIjt(`CemM1w-*{;I0>OJvIR%Co};{qC?WZ z&f}*Q1B}IUo4dFT6^3x=4?CeCS1PZ`eULVkTGDai1@6R}K^Lac=WZvSG`)ei4u7E3U`3G}z0b zuH-f*u9k8?pK<*JuHQy_;ViEHm51`@uCx=9u|~=#YvryVaeW%M#~eNEJ-LmUyK?vo zTe$scTy5a?4|4zcT=n59_LNXq?&n&rVk;5yaER+^7cS;exIT(hgN)U4xcwxq(ryvP zp5|m zINxNEA>UAFsK(p|6Q&L!xppVFeh4z_M-+KA$)oL%MtjS0Xq^oDDoC5J#|fo>6#t~i zn%^O5Gvvz&T*92KlQEY-%G?Y&bBEN)WzEyLwE1#KlW&x6b|+27?6^R1JxT9Wl;{AY z?k@_jS*W47#|@IA`r$&55RhK05XER|(rhU@_5!)jO~YsdW86gG=3zzLeG zG0(LX@w|>uuOrNM_}&fOg_vo$n%#gHZe@4DK8XDuvs@3eN8olf()?a{52bj9MF}rb z8p1)GH~1WKL#_we%Y24S_FcyFbyUey6#Ni%CQ5V*S1;x2LtMRqtCw*VyA|N)c~XUU zNfnd1&Fx(8&GpZ5^$D(iz}3sSx{0e-a`i#3-byNV{!*O#xk|eznDDIlDC`SJ#VuiE z^8)FGm$>>OsfI+d=euVZ_KZ>tw{i6^Tz#CY4{&uCS66WL1+H%A>J_94|Ka+7aPY0hSCZ{T46{l3~7ZS ztuUk&hP1-4y2*$X{qbIm8UIT$|Nj`)i5|rJwGhGb@pj?u#=9KvPN5lxv^BHe;e8hG zD?+naE<}hGcy~d6RERKm2oX3JGe#PXHv(@Y-YC4$NJF1mDpJcirKuI7Yn`K-tUEAmX0?AZ$92Gyxn*w;9ZV)r%;Qs*RngH{~f;1;(Z10 zM=&D}%qIr}7Qui;uy`)sT|%vRA--4Qy&3OeykFowDg+w@ybP}auY?yXqz%4!afYzL zh!r9fTJJa=m$9ZfjajunF%E`DJ3(ZWF}q7OemQNB{R8{%%qg8 z03|Cx$xJ9&0ZLYYk`5P%;xrWe+XCkJ|0inZ$OOQ7vEddP?rFZO~N=Kk$P!DtUucQh| zTs3kv4(kl=u^*Q1wf|K*fb&}Xgh%Y}2%q9Tg!ePNhw*-n_XyrE@P3K+E4)YX(yfiR z2+uNqmMUh7+1M{sAQpjMSBW)Zt=J=#NTt$rP&k^C{j+pHdPaI#dR_XPbVT|$4vn2g z^m6Z6*Dpf*BTngmk6?emei-jj`vLnq_7?iBx@9PK~X#WDPe?$1I?Jw9LLD=6T^e6Zpw7+k^ z4)K2he_#B3fsV5L?*Rnt|M1B4s=`|``7hx1^@rZGp*PC(H>gydt49b=qb&g{d2ww z+1LKrK$QHrvO5gVL*cfaBeDMeHcLW@?^2p&SWPd87#-jOlg!;yQMAg$5yu;u5*U26u zC31TksUAg-@hNzUPkwfkoMs!8$NsUZ8|VuOwE*di?s0)yhT_CIFP%46?KoG9k(d=~ z!|9X)S1XUlXea5q%R@DfCy1*?!H97Xa7KNU@VrOwAJ*XuoS^+xGI6X@(m-lQRXEsh z6~=)+eSo+Vm9*5rj<4c(COFN{2Tsf3eNYW}RpMD>xe23Kk|bzlsNQ!^}u zNrv^HZM={04xWElfm}Sn@#veUHS2dK_M(Jh1(DFue&F{E3VlTb1&tB~bZgP^Gu}r)U2ig(R1Lwc6jaTN_9# zK5=%i|3Nh)Uk(Z^=T3Ezzavf4B^H9n$4KinXM4@0_M`TLnw_QtRYwf;s4$aF$D`V5 z&T>T0NHE6!l$sQWpfB3?)oAP2L3VVb>|+0y{Z+h=0LPtrFg4td@wGohso38gPFg1; zmm~{-%-~El`A4 z^iiojx8Ckl>CvmmT!@0 zZ;nUpha46|YTWl&Lk9b~c{pwv!^tXuomTDq3?A_Rfj>nyz~y7~B6Ejw`u4AgE5>=e z2Nd3T5PE(5IVNrfvh?=`klcjnW0c3zdK*peN&$I29v2qbFIQvZ;{#ue)pmg{Uxm4Y z3uPCK=U&9S$A10E#F9b#8DB2hQZt6H{Z2B_NM`7V_Z$6U7&@HlgOB!T1cw?QS_Yto z8p(HHP8=!UYQIJwr^zT`091&PUF`^&P_=VT^s{}siCarPhWce%!k+4~D{ zQoZA`U#Duhtn)c-oKNd&8}U%nJ489E+~SLS%` zGUwz96E)aKa*Z0HmPArg=S_9df+Wu;22`*p&zU0S*K>xJlwkT7%G_olY`ffo(DJuSm}^9Kw{!TA5r>mP#>IU96XOBb>$q1 z3*QHL(+rnRhT?#}^|;eRR!-mM+{DOzq z@MCn2|KynT)cREN2mRNMU=8_azyV+2!tt-i9(LjN{Er++o@v)2_-QypE&ZPa2CwMS zhxSy~yh}=Nx$^0dAGlp<;vDWoOq~1b#~%z4<0W2HZ z-=z16JiM+qBel=eHEF6nW@8}dR3CkK*RTk!i_LyD%twefusg|eEfAk~Cy?Fg53!5${OC->p8I_=g`K|L|~TFMqZ&GLdwQN3(DM)S~?zv%BRKZ zPKS9)-4CgO7H|npGH&D%aHDCQyHF#y3eNd}h{VE+A8*(Z97$1$4u2;;hE<*^>%dvJhN z1Zw0%e6>-idyn#-p}|(LIgO*-M{?3^2}>Yf1P+5*d~N@lf3YiV0E&A;=^}pKR*=$R z=ZH>4YR)MPWdaOne2JXNHFK)#z_s^7+JOD3c!t;h?T^WAMr}E07Y~BozC&^aS03wl zatV3Od5HUI+`Ra-ynYz#VxGimtRTeuVE=lD|2NdtF(iS2WaF@7P7VocQWg8N`h2L= z&dJWPQr#8b^u7@1M@nrQp??>0i^JfjKH{kca;!LrI}lISR?*Uuix$Kh;iuEGH!CJSh--&z#fNm zQQ^bO{I1+?&`T0+bkBni8RO6%)ZWGoO;o7y&~cm!A0*FHOD%tgM@xs!i#Dq$Q!(I{ zaxRlff3-N!C%KvVQ1hkvJ2g7a5hz?fuowFrPTk{O@92Y3Da{2_Vtwmo=XEnNcR9GmXPKbIb zbP(x)C!+IGzCmt@lAnC(3UBME$xjq`J9rqih!~$xK2E4SYFzs7aQh#bcIjb`y8D#3 z1-Bz%eHsJl75LH#a&F5tbl!Mt8ewe=w+(|QbWmboEn(j)z*gSo$mLXV@B}sB9jFby zj+NuU5rWqU%S>^d$U`(mtrgV%h--d0c^Ei$_t-L=I8TNjeH%C#@eLNokCoQ2+rnoW zFMwW)u>TF5ha$HHE>9S&G$-Z;ivJqLpy$R0<{gedEp7g8sFAONe@2WNsaJSY8>^zH z{ulb_kQ3+|dC%@NR&XlHPgf2&XQUk;kmfP>ehdeMqdAeO5KgajRn^)%Ik6wf=1IRt?^CAkNfGc>-ph zJejJtrr(#BH|Xbq2Sd<R%>p9s1xofXu)l$#RVayu*)HQ?6;B4D-op&8Hd$r#ODTVry8?96c;w zC)-n&Zr5+paYsJ<(j9lyZUY3kwf!%!9Bf3a&;H=pIMiMeeM}?KD{FpEIn^0)zre># zC|TG@N;j-9|Fsx4tkSvlG;+D5+vtY0JaPULYr9W5jt*Ij6Y^kxQVl-%7#*v*A5?t~ z@}>LQuEW_!FXFVhuV@aP-~*ZOQS8_I7sgm0j%<`ApB6UsT*$~`AEZ1ZbOOipu2Oa< z`QHem8#LI-9wA^R{da>VI1ESo#mcNS&WHv!yu;=0kc`tRKH$O4SiwZQBZhczVw}_C zSi5g@Jxvras3Wf`jzTu?h+lWooKv|_PUHgf7*D97oD%zQG!&l9OlzaH6Nwdg02!cy z4KB0Nr-a^@oJej@eb`e{X4hwaVJVN0<2&{1 z=*q;%kUVGNV(Dtc?MzNje~ee1`@6W4 zjt?LHbhfY>_ej6s0C@jQGv7EZ8|zfCKI1XyY1QG|xHAm_zChRe)iKX`(J7H(GT+g&QXq zE3|u@oZYWSaPjruXdFHaw9={c2@v@P=e(dz<2$1Eb{sf0tln44ensU2-fB5MQ(QZa8+2nP99Q8At{?oZs8g^-G@-!cA%5@yM(Z%zsrXQ|Os}ovd zPWjaGI@~y9e{M=KKm;c$9~~^E<7kme?y&Nd_v&)VIRxlF3#W0~00rr1twFP&lamG7 z!U@Uma0Ag`d~DS5&L4nR`jBWK`eI-nt@uX#k?wHSsTgF4aj~gX%r5;Px1HzW*Xm@J zM^p(;b^^cSBS1(v`Ktz8(s42Z5+_8fPONsFvhshxptiqLQB;h`UOL`*WvL?C0B*R& zI7K-%;do;?9{(ewHd+c8nGCu=@p$o(m-;i5pKcvKO!6ce&p3}!wA4>br^Y(m7S~_K zqozMt&t>21kn*eEhy1E{#S%5ZX#E4sJbgJbIwX%|T*s6BGk}&bEG}l?`aDrga?Xrp2SuOwe98 zr-7ZU5j%Q!-5J7K>VxU@V*@!KYF-Bm1(@|4R}G&YpBSGAPUg5vIapcHhK-05vS2k| z7&G~*I5AjAA>73^TQv^^LzQ0ibB|I(x_&u+I?0q>ef+fM{(a-{^P|Fh_#sZM&tcX1 zNzOw5c65>x7-@9QWBQTCqMl~y9A0l3eI1=K-hy_x0eW);&j@pgloa`UN1ZXp7T-R8KGQwPIz7zC%h=UDl`gz z7yc=<2?v><(9QgrNjQy#vlQWUmd>(;T`ZU73+J{aH${>I*CUhG45keS(M z>oPA)3H6*j|3@L^bQ7{x6ibZCqHPnitq0!JN8VqfQHc>LnFw7D?408cn(u zoYX94N^Meym?w>wrig{oG--xdCC!rNi?z~1sZVT@R!XbH4r!gVSsX8Im9~nLrEStS zaf)=7bhbEEI!D?iPM3ZmoiEOjE|jhi7f4r0SBop9ebPR0wRD|yy|_lYQF>EcC%q-T zE8Z@>FC7&3OJ7M}iT4Ohkfb9}MgAIqEfR3W6F0C+6mpNrsp051vA=>na=yexkgr;{u+XlY{T#!g17-_3Pz51Z$itb}xi|yZ z`6(2NP(QM&ccGZV5o-owqgT^q3dP7{C~`YSNW*U|QXGZcWx^bSUkUOUfo~~h^^y@~ zD$j>LZdD)5%M+}WQZAhZF^@tSQcp4(4sJ-IILtS#R5VEpHQBo zSmYyxht1@$8KXcYkHaGk`IAS>a+GooLXZcFLr#<2$+!M-_GjTapimT|oX1EZlA))% zaQDcyO*Jf+Jqb8KC81bSgbKt{fv|Gf3v}`70GM8OoVG{+oTdjBj@UmTAvI*ih=4~K%0pd+;#KFFsaOb~jwt_Lyq!skR`693A!Er_ERzM41>J5`v5pCC*ZW`LueDa-=r zD?hWKm!BzcnJvr_=HfR+pxL?U$~#|}4;`rs;A0B(3x!3(Vy;_^c$dKETwy8y%C=Kr z7GM^TULRCJSSBnNRzO`TtP)lWYw)gxX*GW9xM@A?PZKr>rwbc}O~PjGlU&!pZZp0c zge}5W;S6D$aHepUaJH~rI7iqa?1XxTaIUaR*p2rW!g<0kk=FU(*e?_=g4-Va{1uqOg8QRYN2Qz^o2INb?CrIEKJzUoS4Hon`+Kt9jor1*(d*Y95HwXhF|Fs{WyvHXzpizxA+hwKQa+)Yc=aYNzOE zG$2Dwgl1eNr->1b-rY~D4ZY3>xpM!b@s|Ea_Zbu9-T&k~ zYAV$#L3p5_#_!2GnJ;xN$#6}CYjUko+a&ze@=f(#jhAkdp_-$5uSCsJ-l|~M@pVB z>q&k?KPrt6=!;BamI5y{8Y5Gh^~l#$PDNyRQ`&NQsWjAD*Q2B||4t_PNAyVj$>C&^ z0Qk_4U}NF0mB&t0%9ElV@Ck9_qLxKK-!sAC?o8Jl);f($ohe1)khOQ4aIradaRK26>)s5o@+uPp7CB_I)yeoW`16gBKFnjR zg`4|kuU#%Y01gh1-3~5}=mg2LEcl9TEp;(MOm|mJ3{rP>Co{Evqa%cBL;a#0hr4)( zYd1+S)mBf45f+VWYL5{%Hg&hf2-^tD`N3MRd1x$FR(9*t4qc-uf7Z@r6q{)m-~Ka;@4>Um=%6wK4Ux@$F}0=w~DK zvtj*g#C|p&{cODZ+4PT@dL-vOd-k*Oa@bhs&Rw=r*x0vXZlAE5tCw)~O0HhV)tk9` zCs%*V)%&>mFjo)!zvj+8#;f9r<8$U-9?R-3EX^jDj)|%8BZETvfY7O|PxQHSWsz#%zRYZ5Cwi-%oib|{y@Ik>EgNs@#(9d^f z?(W@X!EMq%x|4IxoHJ+6oH=u5=J&h5-wy)*bHJMd-W>320dK$T(jWiG?OY`M?&6CU zEOMQT7c5%fJ|S-gl2g-oda0XJ=XnNL$S4c@%fg9a;kGnR1GUx%JRx8`;WGBI@Th?G zJi^!)!utQjjB`VHY``Z6JOk{xLyrhluO4S|`(Dj^s~g-cuFb7-54y+PdiOk^biCrW zxt;Dk_Yue6U>j})&F=Gc6@%&xGj?RvYxZm~97#b+6h*?N24HrXq- zjZZG#vyZ&kc1j!ATM9A`*<;+qoRocJ`4A>T;+`Kf(Q;xA47qQqa5`0EqDB=NsX{OZI% znD}*x?@0VhiBIz#@>b&CN$n{vl_@TjRk6MDXtD%Z<(x!g46O2s828FwC%t9SPUZSU zHzc|pS{E`cyyv!4$?w&sXZ5*#i+V?>dvfUx#Y|cYVv8IyU(q5 z?dV6%f$lDfcXv}VcMrwfN=ojYqzrUVQwGqke)rL~nlga??Z`SP1F_TUGCPNHbJ|yM z2MuN9f{l`Kg6Inc#VUJ(|^N z4kMouUf(IoyWCzfR58{H?V_05OYwFRa?LT$Ew;xQMK8;eK|RLBpOE#k)XSeF*G#I| zA$2y!8f`LKxqXgvcf6fI%Mf6@+kw=(t(?tK@`ih}B*BO51bEBF(=P7BF0RI%W}o8k<7Ds)aToL>Wgd<^~>#Y@KtsdlAG)%>Pu}Yc%`jGvewp8f5M&szicmqx7Zf! z@3!5}`%2IAZ$H9w266vzM*GozG`QB+f^YS=f^YY?gYWQnfbaBof|vW{;1zxa_#V#* z70o;@|fv09uozJFc)4?rS3;5KGjLqxvdhWTg`8e>2`9yGI z-Ux2WXMj)1zYab#KOcNSegXJ<`5f@w`5#ydA6B}Wc-0uAvf{(R%4E;V&j8Q0x!{Gi z5WLtHgKxAO!Aooj_)&Wl{J1?1-fWw}uiC3v*kxoaSu%l{Ov)yKCud|W88Zd>v}_u9 zRyGUV8uF*uHQK{E0|eD%%n+Wxjbjh?l&Vf{vN zW6)~T?0xIBz1d0jfqj^CE9I3v(c(Bzt6BSh+?%HDHzV_PhgItYqE})w(O|@{ISb_?6GU(^e^Xg~D)hJ2s@BOof+n4qX9xqB(!=l%+a3brID>PgP`*7o@e z0a3vv35t4>D+V?O5EYz|pr{ve&24f5=@)%^)1sf`vQ@MvyY59~4L~_gvJI%s{e`-0 zBv}u79r7G_h`B6;&I1kr%YeHg`0=y>M!a|xv(JVDvUM6T3YeCFoN;@pGnQ}b20I4X z;b1qqC-wxJ3O}8Gh0mH_Q5Wsam)pwig>lOSCY~pvw<-Ta)zuJm+^(n)^QSS24e$PnlMw@zh74d5f-R4u9*0{CM&P(B8^kPw*VHBi6gdlI_B$@;H6T z)L!_;xUwC_*7%Z_*P_r<42qcHyPF5EIh5*>{V$-fTc+;3?(xd=@Xy(b2AK2PnlhtdHcJCGN^dL%rmK_IRH@p|3t=2)@)|M{8Kn zw4TM-vVRcNVn=rv)T*gpm?$#I`vi2P*BgQj+35?gE&B%ndFW;%hvrivi0=(O&O7C9 z88qJnj=ki*1Bm_-K<~f@iI;3Rx{8a|Ml{V^ceHyFs01`O=z)FEA;3w%yhso1M-GIw z;f^_M8~M_um--)MhyCniXCok;f=X8I&-oDA3);IKU3UrNX$_%nKENI3+K@3%QFl$0 z=}d5MM%kg-Le3C7=@mK!?QO@di#i1jeH35Lmr(y{YESf_cI^Y&BoDFpIs72%%t33Q zal_cRAp>{`8KEBj?gT54iC+e+1bzct3#>w}HP+oc2KXW}#kn7F;NNk4QC_u1KZsp8 zQ{_`#x2)?t)0wI^jg|ZkH0g%5S&Tic(Qna_esO+_N{^ffV-$SRUY4t$_(H#ED=2<; zoh$19Zqk?k$zIrZsviavr*eA{H;o)=N~eDb{RQ;>Rb7ZdhYc&X>?v_RIaXU(QQypfSpz{Zi(syVrOv)HZ07JC@f)>o`YeTBZALfbllQ>OwgVywGiQ ze|KMr6893%Z(FRv{mPbGo%@(~E@%2C%3WC%zrDHH-<%zp{UmGF=NkOB=UV%cZwqCC zZ^P=Ce4QO%!AeJMTbcnWql zrQPRfs~^SPOnnc(6q=5t0m((y4sM6G*u}_HTI^?(D*t}hH=NGmTRL^NfpST}qW`vg z(QFH4I-mJbe2uTMr)*gdSO(t_i~oKsU3SAasPuG|65C>Zbx(~$arS#WXtY*fKPARQ zvueScS@41wr%i#59!aVUwBaRfX zo6FOBSw6Tl5NC9P4YHvR4duv{0b=hRJjvAWy{cG+J+=c{8>l2zL9SM{s6@@WK%=~j zrLmptss-wTc5h8d#|Kb9adlSBe0t8Lr{85R&bwQpT7$kzj3v!uSg=^kDUKdJDZJlf n-$oB``>mGmr6uh4$6_tnhs1AxXxVC8-8U6ca$ZoUoOkX&>EjFdhet=Yj={)+S&JgUw~{R5cU88LI{g&0s;aeM#G-|zRw`vuQk>ej6~b!XZOOq{DQ?f%YMOqJBnu%R#n!9SN10pTJ$C%AJi4r z*5~B*{xXR$SvVoSYb$GGjSrp6xes;moU~7$*U_8#B+YyBLe=&E#^p41=FY^eAMt|e3&Fkpz zbt@7oQ2!9>%jb8@>rQ=s?kj}m;eB?ncfsO5+a6Md{_#GM_b%%0{pm{fOhWW$a9^?% z{f%EAQcPIve%z~<1-PA&T5>$(C-L9BM7+-SlVs9HT=1q3 z$s#{uBre1gyrV>9J#+`25cWI+#Yva_jIv6qb9Z6mT?NMf#QlWX2uT*4&F5v8XVWY~ zY&Y03n}*I7KBmfcLLa>NXS7AwG1QY2H~IrHN^krPM*#I91rCtoEN8hSng){+rXeZ( zOx`CJx`|jofo`e(4;Uk;fp+ZYa0Sb8;k;xzwD|=1&;|Nski~SE$T+YI z;Qx6NPq&jKdYROW@I4;o@gy9y_LQKVw9p&N-3prKPCN5#Ogn3&lF;5~tuMGYzD zvc`sq9~%O`r${{e1n1iTGXdY=-UfX9aXk%kvK!|X(jxTZd;@1cJip6v{yoZ85G{KE zvhoGS^Ca;T{zSsrGbDhmBi?Kt=n#zOQ%NZc!C0pPqKSc4foA+Vla!KgNh~ z#FxW=E29@kDyNI6CT`+E(C8H2#kd8~M|cTinS(K5jO=^R>_hN^k1-nbxkdsx2s-eY z({T}K$Z1@NcV+;Ji8~)R4J2-yCctl)1Ir+9;QaC!ScYp3dpY3w?&ae zBzfbqX78sFw5@<#HIB%X6CcQtBts7Q;q>J6kZ8hXjQ7c9N$Qsrg6@UjSAkPzoU$Ux z36~M4d~iB&nc`r_ljF9G<0-)?4T!U^5j;l6dBgd>B!6fdZ8QL?(8dy+Ve4%*xLziW zR~oDBFaOaV`l9}}k4MJt9G9Jz=Wp$Uj~Vp6=p3U1|FQjA(eE>W*?=zezeR$va~pH` zKlsk)=g7Ei|8U|tcCPZd;NZL5$#*{ABtF>j{nClAqyJX)y&2cj08O|a9Mi{_&OSzH z>*$~B9nj=6=kqb`f8}i7Npt5jt{Xs?3(n{0?-dT{^D~@Z<=~LFQfV&mIl#vZ{l;~g z1Q>&JywbSrIw_5AfDRzGO&rkP@1!wsK5=}xJa9e4`9f^nfSHc-BAjP%n+v@%QL4-9 z|4)JI;Qs|+ewG2WTo-dX{VIgv*?$3PZu2qR_!Z!D&Ix>s|0lrd_`d+i5%fIt9M|v8 zdHCPX)oACx0jD>IieCY46C3~@EC)Q`fTwXD0<;78{N(eV%g0K<=YZP*?*pa)Rs+rg z_}t?4TO8N?p7WUlmun7l0JA0D@_9C5H*J5B?55LJa=pW8ek*X=&c|4TF&?gOeV`Ax zuH}$vdlPbfp5~KCeulk>a?X8e4*GzGPI` zTm%)~Yehd?#<@Sif%_9LQ+iD1c7TSijg%d6^5XBeC?9J@&*s$I}Vf zXq)qKu>&5*nL{C9#{c5XV=WG|ZwB~4T^z_e_Vd{Lv1Pw@O%bO#-x+(&%lQ4+va#1= z+xzu$%okUTcP(HMfYUe^!0B!TYysS32kM8qadse=Q1&!nJ77QH5x`zYTf1=mD6YXH z%4zwK9q8S-Hwu4*E>4$dJ0PBx_&8l;BuRLdB!Na9PbEn% zZzJyK`uaDJ+h+jf4yZsIM%a)sK8TnE*}ILz2ovzk0qh{^bGSyrq_`0JTCy2eQHB^u z^0l~rJp`Kp5N42gDJFax@wgviBo{zB>T!L2H{R7CR-277%qg)4W!z?Rn|caynm^YO zwlBF&6$(g%7=q^qNjUI~U`wI9p*w^GlsWyDbHMisNnuZ;3~`o(Gmoc$huDtn9NObC zh2+~04`Ud1m{8tBpFdMevEr(5z<9ah6n?wVruWi^>2vf|dY=BBen)0J^7u(IAWzQjp`4W4Jofkx5g5W0v z3Sojuh!kRkcp+KH6Pko2!YW~luvOS0JS6;H7!v*{yd#E+DzQzxOZ<&^zxc5DwD_#} z2l0gXuK0oY7x8oPZ{oKuce;-E%AE9u%uVJmn<&%B3^KDUQ&uXQB5RXPmo1bnlWmY~ zmhF-4ll@yhUapf{Q`J;J3vaA`fbv|ZRKJS;pa3=5}3B8G`-afi4^yiYtJJ|P|zk4s#7 zU;I$KBwi7}ad7FROq6-bLS%ATgv=;QmF37G#_d%flDsnl9$A#RBe{FT|3>ur8|DN<7Z;c zcW?<43Z62-JR+D)_@g1no$)i)N`Cckh4m>sdk`?)QKyjn9Gd8;TRxQ4VI2$4z>qvd@aQBcjL<& z)2|wxcQ>CeSB~CaLY(@k55NC3;Nq!^@VhP|x*h$ybmH@X&nJBD{W&N>$mgdo!2`QE zo{-Oe1f2Qo-Opb5Z2M;$KU;;nxxB__9iO#-mL**Uf4Yv43zsfjzVN|?GZ)^waQedA z7hb#Y^o1ucJce@J1;qu~h42d#FNE{jDDjexF8p}@JmAFntLOi5{_p26p1*MZ)$=c( zKY0G(^9Rliy#FmBQ&cYBxwx`_Q>=i2A_o930^S6ip@;^cDt8FGaJ)y@2dREhculx0 zTotYfKZ&lQr|2Vsj##z9xa}bNiFT^;Qt6rWT*64Y^1xeOVu+{|_57ayiE;2|#{7v< zD3eO~QOpvHa2yc76R(LsiPyznTwGn;d3Anv@o@2UakMU7`?<(nlrB*&MwfJ#0+%Ki z$eK%wOP9+GmwuN4T(2R-HO{rw-kob7?gm}gyKZsa>$=Z%zrB%O5dK$+u@;0_Ezjg&b#{3D$ zg-;-x$ZXO~wqjju7rBdUgU)j$cM_58#_E_GX2L3R8+?ifkvYGc{05e517gm5ko~)t ztR>!LJefo0lI3Kc=ubA%W8_0}4l`+;&`JJ6?XgAHE%jp{WJba=bX(*jRGwA?*fzG5^bOn8p3Un>)p?P$WuA?uJ_0*ltqJ?xN zbZ{}bMsCo#w32S1Tj?L^cjRXpLFdtG*wj;WKCPj*)3@k2=A#)9aKi6 zh?VBhIy#v~!;(BkFVVkIH#&jl(t0YVF?1h2%(fC6rSuVcguX%Fr{%Pb{6v1C`LvNL zXe@n@K28g06I;RVq+v9Y?x#=CaH^*d!7jN{Pr9D&pueNf&^PG^uu%V`6||k|XcAoo zKVvn$gT6vb=u~={a$Ndq?nK9|=LQeLo6r>=kyMEoa;4xAZD4rPF8`ZKX;YM^!YQy(v(3jy=Qv z!Turyve(!R_O>t%e$HBUAKT0R#rCoNlnE|^E88MWW_tvsFcbbwhL8@ee3y_)?-6Fu zF8T-hA^n<8r2RCVc2F~2OqarfKS$5eqpX>JMN{ccA&dG^ZyG3c!}dmr--!PdpBKLs zUl3muUlLyyUlBhQUlm^yzYxEKg}nf8=pt zvOloz*$MV<_Byk&Q-VN0rC+i?V$u8!fv|Ul@$8J?Nk5}k*n7eRc2@9WJ}i^{N$_Ux z3qI@v!I%A6@P`i+z&;T|;IU3*p9hT0~pu%k&-g zH$lz55;W}Zf>scj2=Ca1CBR#bVU)#TwLYE+@WrsffL>>j^cRNMfJIR&i>5y_JsZzb znI}U|l%+FQmdM=UwT)xR%!8#cHL)lSuiVLzATIR!}|_kxhxRgogaL> ziSY5lSP2VfrEC(sy-;}OGWh0`Sp`$T_fxSNriKTofv>J))pQPB1P`$uzI+3Vz*=OR z&@Ob49b~7_DijD6=rf%YlYi|jqp&{3GWFX2p5Epg)_o=;UnQ);alNr;eFv7;qStq zgnz-de<=JQ{8RX|@QLsj;T_>0!dJps;d|jb;T)|0OTr(76Yv3E7G4)#5l#xP3U3I{ z3de-!gr|h#!V|*N!sCcghK09;)56=rDd7maD1;GLM7sFn4#pTucJn2>`Sw@t{&&_d zoJ=AzM2T{uAWEVlYNENtB0fY8kcSc3{gym}eHn+~O+HEv$%_7gJWpOAFOrwY3Gy;|g}h2$Bd?Q_SoM7qE32o-TjVr(o4iBb zC1=Qc$XWi0ygy=j3AdTBn3=GUFToyG!WM3T9o&leF#=X_o@57WU|ir&AS@sxZM$T5pE;%x%`rU zlB>vJt|1q&iq`@UU;uu=3$Xrnt3Q)ld@#62vh}mb73lST#K?Ay?hAcBk={i}R^YX7 zuUtHP@`Z=)-nnD@w$1Aoc9fb!J)(vlGJyC&!{n#Tr>RHT4#UvhhUlmv+96M|pN0fY z$q=b)k`IZR61jXRF}GGVl$2ZD(nRryKPB=ZQiJEoxiw8g$*Plg*-Y+j#sOjI>J{|oyt7{sf&#kuEY)=uo*-$zp?|j;Wu+4@r zXR$a^ETVXbX^x?;NH!aQZD}{J{}U1LpaaxFd-Y92#0Rxh1F1u%T;HLDTt9hA$Pg)( z&+O=y55?3r4P|w7W3-APTQhKV(a5*t4AFM`amyAoQPrdf%WrSa84}u~qq3Sv)vW&R zxdpTOpOEL)WoOG9;$q^BF>%o*V|;vyvAbIyQL|)z-z@AZlN;lV@zL?d=(uEgd{SzH zF*V*CDgV7xEYIth+qYo8yu71hro61Li+pY;)Rhz`p~A!MqzT8yuKz-sn+S3<8KkkR zZDj6Or&sx(!)s>6)c_>t`XeO9&c7puhkzYP@TnC-3 z`2Q#nd{ns*gIWm7kXSW^lT4l~P1M{izJ7R;q8OrKAnGyOpThZEAh|;f!Y};|k+7iz zs0S0ksLIJ7vgCFQCD||9hZ0peSzcz6oSA|oQ(7j1Ba&nP8>ZABj0ba6@*%Sx1XU=8 zz0A2o>kUIGs+{2osnTUSnoY?aO3&pgX6ul=2Hl2LHw|Z)!-g|bn{#uThm5&H8M&jg z826aR#@twx0@-|3e*4gxJBBQ(oM_aeErA4nte zuxVs3e6bE%%{tk)RBy|n74RJQFe7~Zy|%Tsm!QS}ZhHp%D0Q|9+j4r$*3O=^rP&@w zZA4ORqpcpf;Nw)Vy-s{#QQx44Y};)|;h#NaGubvH7vs&2(`5E6eTW6qf7!B0Aq^+B z)ZG?OHZY5=9_vf@B2zknP6nB4keS}Ys%&R%A2A zYzkG_v}C&N5qP>+uzvRfIcr;C%VfVtgwuu$Y#JhlHDo{8!5+d6!D+Tj>>99WBF*eF zJxsi8aZHaWS49=%59|ZmV)7hT(vOfe>=RnXeuvEP{fLXNvo^9AYosS_lh_i(aP!Ec zw)@~KyhG-)i^z({B5vwOjZS(I5&FOAU&#{0eOKsX$b$dK>an9?4ok7cz!wUzy=$9J ze#7pwy+sdSE&*R97Ho;F` z1V42x^3}WGhaN!2i2H@Fz!N+RU-J@t$*aP31Oc96kSIqqmVmgb2vN>d#5!9M=e+46 zy2xD0T-Lknarx9W+*ONQO{#02YlUl*Yp3g6*QKs&Tz9yB?Ka-=uzNtkH-m*U&iN*uNZ$|{25QB zXR>FNXS?TI&wkHGJdb;x@w86xnGiK0b;7<0$0vL>;kuWfm(gpz*FmqdUYESCdNXgC zcdB=ZcZ>IY@3r1Xy}$PH@Co;c@k#Y5@Hy&p!sioTFJFsqm+vayJ-(-Xzwo{07vyL3 z%k%5>8}vKk_o08Nf1dwF|NH!3^grwWU4U1BHoy|lAMj|vhk+h}mcZ7)gMp_4&jnrz z{3XaUC@W}2(C(m)_P}fj( zXiR8oXhCRQ=%&!;L$8Ku!{WkH!%D(B!?uRq6Lu)?<;~7Mn#39 zNzsQ~@?phE#it6ZGF+)u8kH7hiLyc2shqDIP;ON2QtneesywQEMfr~MBjp#$t4gcN zLlvY_s$x{BssdGws#P^t)vsEw+NIjBdQ|nI>a6M$)fLqbYNGa3hpQvh3F<6$nYu~c zrS4S^syC^3tM{u9tB8+Z3G>>SG zXkOI3sd-=Xspf0VPg+sygM4&^HUatQGHsK#OWUhmtKF^Lr+rg!NhY zx;$N#ZmO5ls;@BKjiM zMr@7P6LBEoiHPG7CnL^AT!{E8;#wq&^ok6P)J7U3Es-UW4UwIZ^CJf$H%9J?JP`Rr zM1H0B(I@G%^ey^6{VM$y{T}@R{S*4*`jh&z`V0E6^w$i`;AIFkXbnb# z#ZY2sFmxK`8wLy;4Z95c438R)8crK78NQBUQ6W+4sJN)~sG_L4sP?G2QT8PKg)zLB0snG?|4bh#^^P^Wq?}$DgeLDJTj3@rp#dO8=#jKCn5wkyL zDCR`WnV3&wzKi)K);%^PHX=4TwkWnKwkx(bb})8R?C#k8u|u&ZV$Z~W8v9-BFLCa1 zRdG|}dgA)x*2Ha%yC?2o+?lvf;;zK~5KrPg<3r-r@p184@m2Bd@pIz`Hx-%c zOzoz*rhd}_(-WrSrjw?#rVFO8OxF@vLQq0NLQTS=gdGVd60RnaM6bl~M15j%VnJeW z;;O{0iBBb-PW&SArzDT0xFkzbRZ?ftrldVd2a=vhI-Ybg>1@)4q_2{$nVH$kEHlTL zE#?Yyi@DRh*L=u)%6!gz!Tgo^TC!JiXtFlhm~2TdN$yOZpS&u0d-A^ICz3CvxTYke zEJ`_?@=2;^swK5Qbyw=iH1{;0w34)qX-}oSpU%=f(*4p?)7#T~()-faq(6~;wDuF2e*c~9oS%u|`?GB0I*lX*SMm}SW-$y$}QJ?p-#N3x#Ex}5c0 zwmv&4yEA)!_CWT=>|NPUWxtqxD*IgarR;CAujjbt$a0!;HsdgYXz*pt01&MTToEYRnS+kzF>F369sP;oGZ9o zaJ7&X`V}e*jfFXdHHGbkJ%x)32Mg~jJW}{h;pM{XMXp7DMarU_qKcyVMFT|}i%u22 zUmRDQQ`}xWw|H&wzT)G>XNo^9zE*54aV_yG2`Z75M3fjy(n|_Ts!Ezl+Dm#$dQ19C z_LLkbd7|W4$txviN-mUqQSx2sp3+05*UKzrQ_CJHd$ZiDd~Nxo<)2i9Rydq7p2~Aop;hXtn5vSh&Z>c`y;X;*j#Qnf_NtDnuBo0|y|#K^^^xl9HLNC}Mq86! zQ&%&;W@F91nqxI*YOd6hT4ilP?cCaZwJ+9QsPm}P*QM5#)V0*jt{Z5)G^M2Vsn#=X zVq1D!PuuRc58D&kJKHz4ztVoT{i_c54tYmhM^;Be$Bd4_j;$SgI}Ud|-*L9%O2;pq zKArN;xK2xFMd#Gcxt-fPAMHHZ`N?!LJ$!od^or?&)Avn3G5w?ISGzpAv|X02hOW6? ztGae{?e99;^=8*cU0-#J-J#ua-9_E)-TmFWyAOB2*8Rl{G9z?`Wk%DCMKg}hI6ITf zESkBmhxK&zyfaHSt8doPS=VMK&F-IlaQ4@8BIYcbbKjhyxlwapn|o=V-@Jl(YvvuA z_tE@>`6ct$&p*8&bb)q(aY5OFsSA1*^e@=7VE2N93*KMwNiXY7=q>22>7CKLy?0OV zf!-&2KUx^F(74dDuw-G+!oG#O7w%nnaN*&FuPi*Z@a)2m7G7WEx+rH+$)eed)-T$( z==nwOFZyY*Y;norRg0fke01@ZzR*5pUrb+8UrXP8eXsTXu*7Rg+>(kV4NDd+*}df0 zl5~>Wdnl) zPYfI%I5}{3;FlG~6%8vkuGqig#T6f|_-@d1P(N5UST{IzuxD`Z;Gx0i2QLqPJ@~`Q zq?J`GXRlnea$x1!m3vnnTzPoqk(F0hUSGvlg{+dVida>!s$x~cs@7EltJbdCvTE0= z`&PZU>iTNe)qbmGtM#jsR_Cm)SlzOE#_C5_Us>a~rghEUHE*uDdYksP^xIl*+jQHJ z+b*mPU0bwv#@an=-(364x{!4_>(;J2vhLFQkoD^IaqH997p<>b-@bnC`u_Fn*Y8-r zcm3h@$Jf8P{@ez(!E-~<2IYpR4G9~1H$1Z8la1<)c^lVkJO;0i5v*y_SFpcR;9jA> z!dKz%tMJ9gLe^d*jbh*2@MF*4SSo#IMM(jogHxm**&}iIjKX0=$8;yWpDq`!g3E{A{5+wn#_C(asz+*B57j-k%x_C;6g z^==`PG~qJM?Ci|aNunYoOs&^O@HVA)r8Zs1w8==c?FVFp-$2eU3a#FE)?iLdK&uv( zn4ncDUC{+I8D$j6x_PllG?*i;wfA`0HzrD}^D55Fs&bz;E^~HaL3eU;mPS23+%YKK z>w0B!LRhd$6{Js3jZJDW8S4`hs4!$E3oFnr$392ARFPuzM z+gVdD7~n-Q zoWOt}YLLjU)A?joyG?V?npKoP1B1}2#y4Yhnh39AhShm{XE)ZR#UwU5#t^K*7{&~b z<56NO!8kY`4i5P{NrgcO{wDvej?PM5>s;xUk#Nkm1h^oLc@DR^w37%`Qa>?bw6LA(V@3#FQtb<%YUM3TwBO)R&Np9G4I5VQM{S$fo zH-Ob-w7g(Dmp}`(v(<}&pX$T}>?2Zofz2|MC#muVs;dX`q$6DsRhgJr86_RRnLq#G zmX?R5S;SOaLjHpMh4 zu{g_=+LDmeYB3ZSM;)sxm2il`SR>KC0qx(v-QedGUM|AYSal{oLEZ8t01p8XU@ieHlKz}U4fy#psvPJR-2ky>D3y@-iU3=Os?|tYs$!(7S~Z1 zV+>0%B&XG-C#Iz)7^*<)Nf=)tWEs2DfWhY&p~=@2s0vgiDiSrmSlw-!H*cDCKW)Db zLC&u}e7MkqW>{a9aL)qnad=mOH-Ed`;14Re-NdIcxJz?$KE2C&END_~QeID5?t$U4cmlmm8``R0LA* zE*fB6Loc(gx)RhAhyggK0Ov4#L6CyFcWpPgIHon9+)jL5-R#U3>|B-xBcpDTXmh^X zMV%UpmK9ayq-1B#&C8pcnVphTg@TCO$Oen0Au?}mTzOPfd7S;|9o>>+nda|bmy+BN zAK#FiQs?hK&63j`L+=Yu)FxzRCTJ7GPwCBJVP<_q@}x-+a!%t&;A;fFlSnkMy_aLF zwX+aHm&m2@CbU_QT`ukT1qM)U!`iS8|BC45sbMoXfpdCuYX%GMxCR0j=)vXaBE5G_ zg-#9Xx+dgg%*rmDYbigl>hKG6fYUcBFG80eWdJJB5h=ihf1l<`!h`rcRNxSH%Um_n zuxSrmXZ1-9Rnj|A7{o;B9oY}OjR>@%lI|y8=l75#tOs(w$zAxp12KQ~!^8)EU0`)tk`$Fknj^~SMB9{C_mQ}tE7dQN?h{bIReD}3 z-K$5QmyO)dl*D}Dt*r|e0=8#o7Z(E{ zS50Wuo${SDumL;mHo#2XOJ)qwbXh?h`lHY|1bn-jgrLs(od&a{jP1?_R5N#sykVmi zi61CJw93hPmlF3nOLmPRdgiOvv#*D%141TG3Q5Y#OVXvOYjdP6sT8vLBY1x|_P2_| z`*s@C4jlmFHZtRG(f3?k1O?K@98E-Ieq?^__;NRcd~&Q_6Bnb<>1C55-AX4^XQb7C z9yH#l&?HStPY4c~Bnz9A5k7HJXh@(jJtGmclVdE4={Bdn-wCT7XxI0uM6;XeA4L{R z;XCiFT}!w9%hJ-4a_~Wn-w0f1;GIdpb&d3f%FXT(a-A>+VU^NNRr&J?Pfcoic|^WG zt1A`ZPuVzqaE#GAEdrE^EYO#=HoG`z6+JE?&fhn`AWW`I(?n*fXU^)FhlQ=k zNV!Z8f^nUOeK`x*2M(Q)<f)SbQW&>8l!7_1DpN%gqSu$M*P-A<{D>kNCD9Q78n zw;ejeUS>qS8B!V78TPVBl+Bgid{y`=CaSv8WKC^i$8JD*xxn6B6&ACT@HzdV_hYdOP8{BO*5EhR;(^ZCU8eGr zEHu;$Boh1CM-4fik9~H$Ixem{-dGhES7oHXZ?$S$TOuRU(jp>LY0j8OkRAcbwU$Hd;_l^JOxXW)D3mG#`^_c7b;kfAP zF@v-&caCzY^WuLx-2XasP8AMzdBmh$!QMwC!EIO;&P{Y9xbB9p5yElia@=J5QCM|L z9qeF0ox+hiF;blm@V;G!c{|+VjI9HEA>4UOI}b>8ibmRjmxwyN9lPA~b{@N>4##Kh zE$yt4>J*Q(!|_=wwR0;zqjg|M9Cb$fvg15jM=sTQ8~AWLZKuzzbvQnoZovof5yp<4 zR5;uH9bSjGW1pv^^ck)5>+O_{w8L@UbPIj#_>9(p&H0tSM(ZSu)UoS>QG6VAFx!o` z-OPyZht!C*O_&!)5IJxQ8=R=Ki@Q0|6o^|y5xT%&d*T4WkJjHFTu!F2LD1^CzRjMw zNwTTaQaaNMDtrB!JuNYTt+V>O3R+r{a|05M1?hRIU6ql^k&>(bKFE^j(0ZN9h1v{CS5B}*bjQaV+l$vOKGQAxHJeZesZfIV^J z;ce0bw5!tFE3q@TZvMn}MMGwex9^vKRv74Q)(Od9n%&*2^(nff24ihuL1t`NMohJ< zo4Gr_t{93JLWfmHg8g^La2AJKxc&{JCV@L1PUpjv2pocD9y>byjSY`I_Sm#XAANKK zLe9r&t@S`*apAgkIG`P0_~50$-4US>_(YBf&<*JaP$(ED8BYpuGIH+(%_6U2#wm%4 zol_Ttw)hvsTUz|S^!zKOfA)OYuf-Bq;NLQ_cS`5Bkcxat^D9Ew8&gYlQT4Tj0fB)5 zg|)SMWnuHWW%j#e(s)c5j|Jm#}K zo9^ps|EIKWR1$f92+8OMz^RC-6*Jaa?6iV+&N9$;7--cg#g{zpHzUZ5L2HQ$Hx*dp zrs7RAx0Tl8_m`KAv%X5xJ<3Y&r~jU?PT|o#p=tcUJPp;9>SN0AUzs|gtOEHA7sNQb zStjN~06Z=kWKQxwIX@&ZdiQ)To!f`>r6%<(qNy zF+c%v)NWQJL}15WB);SkhxZ`w!9_ijn%shd&HSJeAo&slRcu!is}q8P1wJt` z{(|M@VTIuuxw0_Y5Il9YA|kM8Z&RyI|c4D{Wq7+l0hMO1Y;t9OuZ$UTw+i zCB40oKW3F={fIyNiN0K2y``k2Za8s? z3iQXEjG;j~O!_3wcu3yVQvAK%yeOn1ld;UokVQ>%J3GG(EL_H{KT$WqdaX0QFp$p5 z&dI)`G%MT3*ViXIv+`p$FH5G}>67E*+nKAL3>(~H`={6o8fdYTf?Jli@p}OL><-%e z5#^I5%IFbq#lue*ho1qa0gFTFpl(+5+URh;$B zH(!7K%{REPVuQcc#wOQqF6MumXV0DkJdu0G;UMsw;GCKMJRT~%h&;yIVuvH3HtUsCtdtQ!y0Z>;mOO6ghaq?vq9Am>Rc zkz1I9v%^Z+6Xj-|Iamt(g3ZB>nHlV+vnw_?of;l!43wNR5qg_{K!AU1Qfp|YQkfZw zi(BsLLX|3At4)`VmqRm@3QK6Kxz#Tqzz?Te?w6)(G`uE10!7S;VtOZR)wlN8iDwG^ zRpa3jkE=b=v~yPz`xa?JKkGO4y5*z|-zQ|+*AP%w5;jS^Rn+5!7pn0eji~tJA1nUo zs88{0HgqYp19HjsTw*yrWlf?>D5L2M&Ta0(p|s|E}DSaZW?ys1g(UGSs=z>X;zer0OEe z$R&MsvSw0%K+a=tY$PJo|FL9if$cr-EOFmdEJ`M_` z!Sy}k8(mZ4<4yXg+TDqIxh$?Ose1bOdbgygXtP0AT38jQQ^;a!pO1{MlgC9H8s)KN zb-6M_q*|qQ_YPYeR~#6Wp@}Zk*Jn@Sg^{kF<7?Ana{>Y^>ZoEpU7~aMc8}CV7~H(a z86o);dC@+6PjC#or^Vq*7=;Q`9{=o%CHSGfn&uim?62EEy|=P!H$3R372Vy-Fn%>; zHVfa}he5Mp9pj<_8YW<~)|T2O6qkGU)-LL1+hoLV2-RQd3fnVYvStBjhN zmKYVCqSR(vl)10OC6pF;Cr&Zv%}^SOqGHOTqe`MP8z(=q;^@w*C=+WinzV|zh>6LO zR(0OViG?vH#4MmmJiZb>%zk$A5^GjYG3ToGjmmMihisjiSy_=e<&Kcs+0Q!@OVkTu zmM@Q4pe{+|dO8W;qctIR@wVFlMAa#${Uk8Sx`@ddTdNR z$SHSl9^G*M{06q8@|W8j^^~aBfqGJ=5i7^e6}1dH>d#3I@FW@#BsqB6#Elw#V4z;p zP@Havh|?Jiy0{2KdU3-}Q+$2A>F)Z%N^R0MOPbe&49m78ZDnD-6Q8cpew>X#cRRgE z-Xk<0PWB`(^18&{QGH3eAu^VC6dP$sFR7Mr-hKC7iMD3c z2^y_qh9BmpI=-%X<3>jv*kInyIn>c&?=1J{?#EX$JYT@y{YWa`E0yo%<(Naown*V3 zdy-gSrN!aXXAK(Hn^%(T@mnCQo)iP~6@4sz+r1usjNuFZJn}P}L{~RgULD~=5|jyh zYn*va&pY(qJ-HsT^sY~y|}z*XQ{7$wbI*7Am~+bxEFd^ zA+0&s-9eQCQ5w%gssx;Q{<(#QEgnoyEXZ5G%=%&5a8b>D!!IAGL+stzkXVM;ETd&M zn|?OQG|T#zFZ;~tkb`){)cX)qOZoka&ee8DTC4vGXxJhB0`MK#brdM&Mk_H&8PV zli|d|fo}P^SUr z1@}1Y|61th)MK^NKS&%pcK|imk0(_kKTX z_Q7d0r^cn{t*EbEnU^><&9A<(>5i&aCvLwTGaeJRb}9A`_kb9zzs*^0ng>}$w9C$y+INm|AM+dRKvdL{_LExR8nz`Bb@o?Jlv24RwCfKufeCF6Sk3T|h#Ji)z zaow=FG55KJj~{LOxwWOGbuT-%sJnV*p!I9&V*Q!66lP~rJfO^QY3(uk+ ze*b)U6`wz-#nYrb$_}4?U~kib2RqJAdFa6@c;h`54Ty)RYi@jucLUJJgLv1W7hzTS zn^X$WCw;r*xnm2zqaI7%e{UhacP_VHr>@qgt#?z6mA~tacl+?JJNnIW^ov>L>plO? z*JoR;e4;-|tE~;ztJIT^hvz}EQQt8S&6wqh(iWJ9=>8jD(|fHm*@XJ~YUWqZ0|N%0 zU#0J1pP6Ir@+>GI?Iz*zUmDM@gVTI(AkU_Vps=J`vHDBjs|%YNU)jE}XL+VMCf%)N z!n~Tc1=c6iViY-1i!%$#W1{PeD;k3=iR#eI%Fdp@7-VVD+90%Kzqq* za_`ZX_rP6NyCV^(;^`CKHxD{Wo5LE+c{5W}XXd3egf-H{hK9WS#>Ra1f%W4kWB&50 z%7OfZ=-RxQ_4PA*>gwPaQ|#}#O0QyEQVv-1r=>BB>`3Cm0tXvfMr+8JU)((3bDCRv zLT-J1ZbF9JRJY{jwBq?gyy1=_ZT!soh8eNC*zzc3e}O;R#@Fp*@ORM6IYqD#U~=qv zfjJ)mmrHve1o#I#X=IPq z>|~lm6*ACEGW!A&&!7`kmMut6n^#ucm0-Qyttvk{(_^aNqAPTgkeicP?v*o8Td}+# zdvUqGqIU8`O9@TOu0q-dIeQ!uF&1eaoyD5_f96nMZyrsb(xK7BsRK_9XyFAcrM+M9 z7o}aD4%LxC9dl9IP!$xMXd;pfji$z!@4&oi1Ags3If;eEV6&Y{oXys~1<^4D5fKG3 z(FHp11uJT@!qr8kRR&{Te_45do++x@x+*rdCMl^VCI&~0g|9UI8)K0?nXyuZ`ND+> z?vk$x!c-fdQXSQtmZq|3nju41bo!)L?3<%ex#fz?XzP32KTgHyj^fuhwD=7Y{PYVw z*N+oE7rtyWIm-VwT5j(fUtoP;x64xBh@;^}2BO2y7TRdwlJ9w6_2;fyum1C&c-8(6 zIf!?hF(nLUX)|iwFpNCMFNk>A_wY$ERuXrtB|)~QHu(f`431xvHk#UurGa6Mri#|) zzLb`k!fvAGQi*!FUZVXP+ZhY>l*X~s;c7qMWy4Z zh8Z&&>Syvv!S#j-dc$k1-f&{-&>OxgGFET!X=m3PeA%hq&AFE*3YT$h~nV$ z&Lqk6h;Bkg=~fxOd2Vy%G%B^ux&1bs1l1(XZm1^f;nB%$m(wC9jOIBo3Mr(u3ut3V zMVyZxk`Q3rl<4>dZCSGQ2Imv(GkpW&m)3f1a~SGV{bSjISdmpF3qr6qZ>cHA-V~1+>M(g4dBM z>-YNTs%ivaeE<4Y`W<3ohrSuHHr#G=oeUS4e%Cm$BCfH!IXgMAq&ZJ#=xe3v)^{!C zrCBtdw=n^S8)!qa*?e9|hIm5v-FJ7Kn#zc^hu&l5^|)>S0`(-D?WpIp*?v=(E~sAI zH|3FX5(Era+uKmDy(+B{p9@BgFR2aGJofe z9_snSv(Nr${qe20I9@iBgqI^H609;ymi|&({af98c6Yq}Hu{-`-S-?f_&NAR7XJTb zLELlJVBe)G8S6&td-Or;S(<2V0sYFY4?s~f{J$HFLM`mNo0D?V-Y95mjSjPvBZt$W zb+lq#5xsDGVa=+6>O!_87g0&($JQ_C#&_Pa4$x4}M@Ay>OF^rU`*qlS$;*xT#7Bk(nldZu9#sX# zsPGk|z27Lfk;l-lLpNOfkBrDz-4JDLNlj}u8k^HnTj+$m#Kb%f^pBZY9m&ZZS(%;5 z$q(gLSLXtFelr37;17;iA`>*-2AdBbW^6d2#ikiIJPnSB*=)NGH;B$IJgVq*K6 z($Y2Ujx!Cfu3lGGwywH*ZF%`}mrD2M!qlet_@>mtX7?)BXsszL%cR}3p|WayXJTUK z`l`weIJ2&bO-+rPD=IcOHf^##6P{9GZqKx|n=4W$smrr-kYNEY#?eoJH{Vb1*ah*Q zyMy?~_83@s2li$}8+;cy_sIfq#d8aK;$lk;hSJ!ir1WTI zVr!(K6FrZ({=dd47I zBo41UYfwv?kZ*l(L}@YbpT>|>i>@@JqZjae=hmC{JF#ajoD*iI#^m_2-9ktBP$xJj0Pr0eI~`@%V3}UocRdq z2*d4>0Q4;kI}`(3RytT#vaHm7`naiOrkdC`Yf4)iQisda&E5GK<+{2Y`m}Wi?d7tF z*aY7h;2R~8=O^6A396K26y=hiZ!g#37mjlIbL6@IF!KD%G4Gw^&+Y9AVI$=_@kibs zB>f8Ow!hCH^NxDs{-d57<|XR={}``S_#eN;ep7qQqLpG69r+t(terYiXbE09^I)E% zIbYZit{%DaHrxG118r%0$eszqXS4QH*b1HtTk`==g>jy{L45uSo_mAq;_$2RBOKXY zp7Z5fp?LoMEb|)8{93>6pG~h_xZubDBa=Sc`UtR-a)Dwu>|nV4yE|k?c^hNx4WdU! z^J-)TxO?l&tl{TDkX4QtY$;kk#b}(eyr^X`!kKTr@h^lINDgLYAyHUg-)|{)X4Z-= z{YXQ9>qrmsBq5&{RY2w*jLESo><=rV2z29193T!XE_bPMVYWwJRU3L~?6Y~Di5vQQ zx=Q{Z_TD=_uHxz+-nm!OTC%KSl~r%6)v8M?X|<~^t-94#y;;@0$rx9_I5ZoZV!&V< zObeJ6k_T`SIw21vv=9g+p@)zF3B81n1PHo%zh~y&-Mg}t0J;fSEXFE8__Aze_ zbxk9mt2@KYwc4#vk2*n>(8wP>A7FhZ(!9Ikef5P8#UM4%%K^JaklG~s~r z1XYw~=3w^K+{#lk8k0QjY#}>?zq=`)+ZnoA)+8lts_9v=dChK^gQOI{hT7`(#S`1A zn=10tU0LiHj6el;dqLgS_Vk_svOrWi%PRU?JNjd*?U&SawAHm_=Vs+3ROEwp*jMC3 zpxq4nYAI;nElwwJVZgY~wdN@*A>lA2i1t?rrk0X^)VMAwX>}cp!&qv%RwpH_ZB#8J z4TADR9f90NqMS~P&xXOlkm@o=tyq25*-LB5WYX2cO(wtWghgKB%Td^agbqav#FxZ+ zYzx+7m~rS&N9YAcNRE)2w(~Z)Chg9m#*VVp4WqqvU42bu7H4OJ)73M~-q>|sVZYPS z5fMJwR6S~Q*CV`uyS&_9);p|Zp=s4}gQ1)G9ssRtqE2CJF#O>U|5X0@-g`(tGWRWP z^PW8)`idVEKGbGRCcXc~(#~4!3Qnx_|sCvJO3BweGjwXvt!Xe2dgq*y+^%smyDsCx&$>5g+yPN^hU&&6+{jo$@ zcJheg2O2RMPK9+HnsvtQradjZV^@oPtZZdU`waft6^S{Or`e@jwh;fZZsqoQKDQ-% zxq4g?R(c|q?U}gkHZs-Ub{k7_|E<6OZ&0X|kJjRJPGrGu#0 z5H~R)%8`Dg0jvzVi*Fkh3|NXr-S$e_I?}%HO=Vej7dDXC-8Jc{$}9NJmeU?mbY$C3 z+ra_v*ES1#Bnf*-m51zIwQ$YyFaJL*mG+)<1PN*#3@=;j6FqwZm36_w+O$5~kRfS*+-l?}Eo( zyOV0mhyLQS1_lbF6K(d2!@8x8Cr-nkus)6AFE8HyQ~>C^XUi!-IqP&ng|=4%VXl#o0|$WHudRXIGp^F} zg%!3Hvz)Ls$|r04+lrN!!a(xeiMcStI`jn0fvuuI&%7juMb{){m(-Nj7bYZ^X2y5Q zh1q$<)!`9lPris#=QCBu=M;&2_N<5|zrrk$5BlV(xiIz}lxKt|J^Tdl$L1cJV}_c! z=P>Ux%wO?Yk{hEA*eY8rm8JwI zvb!p5MB~5AQ=Vf@O0iWnBTuq3F(JuSSx9-VLpduk&qAA+_{l>Ycro#i5EZN&$rGDu zu(IQFuTJ`-^4w*rp%4;&6NEyu$Uqngs+&fyV=OH1&PIfi)Ul>YlF ze8L0A`2xUypu%ycPfd?=0f677!uNRK*uMt+hbmmGtulaLPiF+v=wv>0MleC=QqYgR zRp7aa00RBAzN6dbLyMYJ$$$;O>;W6w!4$lFP zv5yG&PdGdj(GV#;_(`^Sz@Jm$7;ga&2fSSRG2qV&IARYIJZBF3Wb#tLVP^rK;gQeb zZKyByjR1dw!Z=0A+)KS=+yn%=CXXV_B!{FgrI!A~DL zm(sj|)5*w|kMx4hr7E3NFYE^cj^e z+J!RsGuRN2=Y38Spd42d%D^4~;J;GK_&Gk=+^?io!&%b1(6k1MR}w!tFd)54$JX%d zd0E1JLWi|y87o!{$%n7RBh=*?sg-RD8ypujJz;~kG=FFLM9;|b z{vKexmgAM?&IjHPIbK@LB8>&FjEU(!wrFBwABwmVg`kXWDB~Br47#OLluL7(8|A~O zhmbud8{%pCnJo@Kj)X?$RQn*UyImYEWYWoCg#qqCRB$PW3z>BC+hOMnotRs^z~LVP zj}?}ia|AxAS<46iIVxZDIV~TRbDYCPpK~(N=ZLO9a`@dUT?aTGN`EPbi@xY&-5k!( zgK)Ue8BT^tIOyePFF0KE6({aQCpbSN!QuC-_(3<|q_?+mIK_}5e$;Vz9^iEx{*VfX z2RPDGOZ>pUw)EPI2bpUGPoFxC8ew5`J-J zfZ(F9JJh~T`~3!q;6nBtG!h~`S^;No2`=Q@p~`nN)+ld7Rx~>d!M%1E2U~-Dl%EU^ zOuiBMMIU$W3gU1`1^PJQ6@A>nL?0(QVmbU7UVjG@_ApA{!r|2SsTcf$;}QFXgh%Ko zhv9FcT)qcKaMAx9i2F+PkUamHQdErj%$DEE1u{R~Rg-@`W}n^ybp ze7T%nKrW{j=s(l!)ct2#?LYJ7c*-lsKJq#x21+C|9glq#?D`9G!O|K~dX zPc0xPQwzw+6oZ_gy#~-;ccQ%lM4MopR$pW!-qEA>pCZ%Ar0+%7$j8L9#P{lXE4`{D52A6ngTC!K9sQQ3(R zQ9;Q~#aU4~36YWbCbfk|ge*#nOwBC`iChwhFF68dkIW2rA&&pQ!Ffh*L{5BUOmJFD zX-;Ie_$IU9r6G&cB2shh!Aq9}rvf=}g7%$K8+#IT8gY*brya2Qa<**&Iol@WZ0$Wk-nxK%Z(TsXw~~CL z9YsHU9`rwq#-^$clhXcf(!k=!-r0S3q80))q}qM z3hPhvoz$y#t(4xKoV-@ZvTwhlH1|-i+PzjjJUo1*kWFvDy28`16x<~|iuEz{D9xVy z22D-UOMetN@0F?zOT{yqF*WjMN5!-Cn13IU9_9J42XI9BEkYP>MUBzNSISwqePz4$ z>^fMLoN`t>=ix=tR@^M~D7bfor4TCc^;EF=bE`wTxz+c1-@tqoSPro%j$^*gKBVew zg6r2SSFwz_cTlE~U7Aw~**&DnF2VJ4q(ckLkq)VIB#e#Hc2192R~=H0<;Jee9^($DsSMzM(Ne z%z3y3fDbe#i1o*#oS#=o%Q-&@uAd8Cq0WU!rV&pNcxWzTMAW9wczPl0gkQ+|6{@VK zApJI8u9&l2vA~?=3U$sxXOGr${Ldq&ksW3OD97)_1wlEy9Pry`E^>vzGZ(>n=24U{ z`sNWI9YSz1r#qtRkoSNmkf(ni_yc5ZPAA4lN-z565ud(EaM3r9@V+VGF4QjcgU4WB zGGSyA`^j|vKsYAy%{FsdT54=eT3Sr(Yj~yNyL4JiOlqp`1ro598JD9CH_Q9~HLiRW z*I!gzvO|Z5Z~_(D^?=mH?gd>&cAuxOk4a62x7u1Zo5ZjzbBoOXl1+b`E_&cz*D zqYVwxHLaQX`I)V~VA0%=q!{jyuRJ-=asO_=g7owP-0wBf*eG{3m~(Q>4c%1sZmEeq zj&h8+N6dE&+z?g=jB{(e>NseN(~eMFP{`+ek|;6m>2_mO*o3%S4FXAC5` zSgYLcGX@g;K`_k7?)Mo3uLNAs`H&Bt1Q&EZr0N&aQxD4ozn$ZG2v#)kVD68g-^Ssh z-#+9+KcyG+KjcF{!3F&f`HYQ^0WReFJ|DRzxRC4nRJq1Xk_AWv=W@;d?lU$LT*&o( zK5|WPA=mf$jEw{rYlQoJ#zumRHNt&9W8>R^3pyY4p_AZ(&IeUJ!tEpy!5`y%dr;U( z=6(2a~rOdNg2 zrK7mH$8iRwTE29lw6a}G3)%jow2X}bFP%c*vW@&do2!SP^-m~&fUc~n^w>DFIWG1Z z@qwcLCuvcZ7}G9A-Gt1&qRJd}t>Gz|;6k=u(aF{g9Dbbh^c8i?6f#Qbg^a!;WK{SN z@bnL<>BSf|GFOiq4#`G#2Zz7T&fz+R;QF!jb@;hbeOMccpZ zX?q%r2rkAV&Pkr0&X^Ni$i&M+Ce-yO!NvOXWmV_5!S5aCzyW`p)A^b@=2W0P?)8jO z0qiy1d~6ryGwRrd({Fdd)*{XAmmIkL%?$fRyulF8y`|O74D4}|N2AyoFSVzYrlgdn z@pn^7Y-|d>Bu5HC)&G}4y_1RuJ%sm1zj=Wx;AK2-IENKb7+>8+p)y#W+D@mUMGUxl^dwe!}7>qhG8Xl*zG0iwRK zMrt!$^Y7Pq8?vI(;={s&Ow9$E+Bd0fNmOu9Vl38p>X#gW)3;oj7hg-IuEX3JevXtz znmZk1_(9JYE>6gpX#mgAeXkTlciM$wC{L9hON}83pn;r!it<)tj6HU$G4`n3?T49w z*{@sfZK&6cv9cj{ZZB>HtiXKP6Hz-NP>O&macD*7=j}m(X{8O3T7-W zU428bWScSy3X6uE<1;~L`sbL_GGj9f(x!8BQZ6xvLUUfF;JC_L-ZVa;6^^Sk*rx6A0yG$ud`vU z4}>h9oPRa&@cdx*V}XQnbkLLzh+3@Q<>87_Rd0MbYqko)dkyYvpsjDf*xM^pJRB~K zsBq9FC-F7afAMmi>=BM%*x86*GZWzR%YJKkkIq^$hF4dF6!7rjq_{IKN&Oxu|5xCyU zp)&>K538QMrsy*jOc=HTjM09+r{MymKkB_?lbmr&QkDb^^_Mw|W{(~8LRqa8A2ZhHhf7kf)XX3jQ!|8W==evpF8PzFzwDn%$H5^k-^Ufk)I= zBEIpl3wXAIF76YG+l=iPtr44zpE1!~N;F>0^9^n3 z>~C(I?;i>#G9FkgC+&#lE6&FAQl)7K~+e-0l}MChEKe}yMMJwH!`udBPrbbK zY0tg%5nn>o{PXz{q``3?w%`-ZYYR9%G_SRyJnR$k`h1VmlY{p3+D@#9^~|4x&-esA zd|rHt{1b=|Mf{-Wc3vM}{L9(Bygnj7)yMbwD&Qx6i}I;H^qlIWFPG}W;i!)e&h3{X z{SFmg4!wQyo-bz8C?g7(|0EMGzD|>dy>o2%GE!SU#mjcECp>ko;B|fmaN-&EggDRc z_3+HQ=S4i@&p|srRi4D2BL4(?!NapU&NE;9%h_Wdp2f<r;kR2MZNy%v>ZOMX8K0r)B8GX+31#RVp2!(8wjeriX;4Ordpt4uR9=yR zi)6nXOGe(7X!E#(yd=H;kt#1Lh{v=)jP`Y~M3qPNxSwI82mTsM_3%!ge#(>n3#o$I zP~bt`G(3QlJgE4;RN+m~ANu|L*H{wAFV+OaqhF~!;`?{x11PPNcHo?v--#N}DYXn9 zoe!t=$Olj;Y@?4WU`4b0jQWh6tz)cOd9JFxwU!mTGxE+2*2)4-s(Y7O_jnavPi1-F zI@)~FPcbb$XybL~cu;p8oY!68|5AmkK7XjAuzS45F68BMIPV7>4m?ILocAe_euuh8 zb_RSG%D@xs0LwTyr=B)9xm%kHGg~Cov$3_2)bIpIsHf&(MM(H=bSN67o=e~|allk)yZx)t|TZ=f?y^UB3> z_>R%?ic))GUVc(qY<$1GAwSES7ao)!8Wb5Gg2_9TeUB85dpzGbW!Y}f?JAD97Di{5 z^h$5!<>u#xE-DH&MnnN9E7R!umvneqwZgj-y zSe15B+aiQ?&o{OO4%XHVN=opekg%YT@?3Lm%3_uZ>uU|&HY357`JUlcnfy(XPgOe- zNfkQ|aSjaF9mQGck*OwAaMbd`jEn;RHovikrg6VEe|gSoPRkBmVn<0!76sps7*SGL zSrS3@-wV4_wreg1NHk36B71zSQ!aG1BIizEsQmEuNhro>9JJDo=YhD!C1}N>Bg0 zz0YmAQI^C=)8fQ>TW(ZFLS#%(V!h-J2@76q4o}D`S`yB`WC}XJ13Gi$0gaY($GUpt zfpf*PU*p*x@r-Cm9UJjJ!>+#JPI(Zq{%LQQ$5_<1cDXj?JG(n_bMm8N7N^wPbMPfL zD5YL{H(^O&YFJ!mq54Vu=Q1QcL-uOEPpk<|KrKWg&?6{PQ$r1x49YVCxwuUpIyp~6 zL*p2l+h(!Y(D>4OmG;<}m{>dI89;sRl=}_X$A+^o`Q>7-d#I-eap}%+i?h&3^C{BI zP#S7eu^&!pqEk|$3CceO zk5|bIxSELn5!jQvR`~@hl^N6t=pB~V*D);hHIB)C=D#Me4xkK2m#pPC&{IqCTZpXVp%U+we!vP+)yH_fw=!5IJaE*T;EFzyGBCr&E5^BMtqw5d7x%puq=@qP#}wT-MRo%ORg zsr;L~{u3H*dz(*87U7mFY5q-~cs6%%M<;00Y>YScpfSdfB3369`9Td^3^zw%Mdl7{ zCr2)1DFI#0BfEF9q*DekT+urflX&6T(*q@)5hsT+gnnqloS$ModG&)PQHh8ktoc5{ zx{ioB={h5J+saWFoZl7*02R4nd|749bi$4rN<>8f8-QMS6?y((fw{E@S!F7NIfJ9Aa_vWDG#{k!YiTiqyPOy{!fFpKvzOdy9o@jik&E)~Gg*kG~i~Hlhr&8JwYSmy=>y zVY@+9pA@@EYZ}Qnrd98<^zf{FWxEK>)i9t7ZFO#U@3QQ2YfV4+R)d1AsFxNG z4RaFu7mgQu8X+RjfWz=RN(?)|D&V0z+tLp@Px8(4*V>XUU*Jn@!4NGT*5aj55R!s7uXS)#_n+!f{h?X^Aqtpm9&S+&Dq6H)s&HEeBc ztt+jr+2~p+tv~=PXGLjwe}yyK8DE&+KUp|dS3R_JX}7a_r~u8*$Cxh=HJ!#*vMor6 zD?>h(^V_V!=ejt4Jo;}@HSd9^T)T>DIdniB-A!G)+NNmjYfrp zL`5dY#2~<5{-ow+}pA`4G2|qz@KH(F8+Sv+a zYP8Ec3dif(I>yq>DNf5x>|8bK`vuF>ceV6xt(q7g zb&NTMH!VUhR*@gAGQ}?EKD2-3{jPRwP)r%pJ#bTH zo`{y$U3VQqS~CBBHm-|lDXmccK~XI^-xh&+J&3i%`KI~{^PbEnpl~EKlTT#7C+61i z<8XZR#N7(1zW@GeUMJYdef*MMSN)R6cCPmWdX4+tlF#+1%upwfzbnN<#YDsIjfpzA z${Q8+y0355;-Zp|^D3zWZ4(P_{9fndj2SZ_IN86sZ(z{Zx%rciuJuN<mK;99u7QH zoSu=nTpxIm3Wxe3-)Pl;8TUIv_qBqLe2k?C6L3{F^5rni*K{#|U=U)DDErx;Uk}`P zLG2FrX$MWyq2tZ&zR})EgbnTf)rM7-(`D;dR#bMBG}hLVoO0ZJU*{*hE{EVbL4BX~N$*Vvzxi`c>1 z+QyQO%8Hfiol{i}V}-x!?)TJP=bI)M5pOZ~`viTGVj+j4-5*CESHoR+S1q2H$eKE# zz{o%F(INV^u2VhFL%Kx|KSXfU6DtF$96Cky+;?CUUGTDQ^Oxa8g_%V$LmeICi^c;R zcXan`t!i${&Ip<~n%7d39G97&Qq<5`R5s=;9V>4b%!@B7O+AQWRNeM4*9T$^epD@= z==uibt7EOlQqE&GX>;^aemhhZTzpfti*V5HC&hFwA4|CJ6&Ycj{pv@|SFJuNvU7f> zJ0b$HofG2RUE|||u93M6{dp!iPuKwX7^Jri98%%s@PX5xe+u*NOT(5xeM{I9UZM2h zFJzgOpgq-jj_~p)B@k}jJ~=so|7=+YOI6S-KJX$Rdk@LL-72jIRM`)sSkWGM40{&OX^+2Ho&>ECTJJ-g zN#ndolZg4}2OjEh<~kIEHmhd+1_as0>J)LPXkH_m zs|WlL5ns8U)0c_1*83-G<5MQaCmkGgi!&&h7+3Ugp<5Ai1AF-KC?{H5OCbV9su+^B zrN8Ja6buELCVKk>+mCJ|P6td*GV`hlWf#q-K42#Fog8sIAg4pQea{B7dBYyah8tz; ze25|6TX`Kx&%K7SEvVy7hxaH!?&r1Ahyn5(C_*?^04)~~DQtn)x0;#33v zkV>DEr_V#X>fmTsz(IE&@B{7{8==ur9xBjmypFhDvU6*(K zuD|;a9e+ZgYtTV!^%waWQenq@S+!&0tSH$fKnKneflsfg_ShWEa}eVh@JBg(DdMUT z9@<0K!(UV3ZP+c);gRz_ct-T;0oU*(_`q!hM?EoLg-@xEeGv93K4x^cwhrSzThw{y zot?^8NQiz)@oMSps>iP4ul3FOv6tt^q|V5Q&uVQyWnk)*oFex?=k8O7r$#*UISYFS zG^%zFF(>nc%9%GOTc*)>f1&}Tol{%Wyh4uwbD7*YRT%T zZRn^4e+6#Z$Nf9%2YWO3=w`TM*}{k~YMGW(&&eEOO18abWUQ^;-n3*qZ0AJFIwx+z z_!YZN`RPlw9SyY|S(es8=So-Il!Lw8;pS}$i!9&c|0$Iz+n9F#!Pf@t@Ua1VZNpa( z9Bmhgx8FLFFjONlw zj@wynO%EJKp^o&_icDT8d(&p?Eh=$aW3w`2atbY#&V;n0l$iWP6f0z%>QC#n$A~ZR z%%Xad%vq!Xly5bt(YY+@4TYWwxjJW4O{(ev2n7Gu)E$pC6)ANTSkV13>h0$}KQ9;V z;`N(*elCHf>EJd2$2zv2;+KPePaz@cR?IQLSK3?nn(Jav2KdV=|BxPVN-yC2+&<)8 zi}|I-b;nG}6RixhX|h>h)M8(&5#3dr_;c**dJ)cDvxtkbdE0r)@U&eU>YytdIzY53 z*+*kJtz?TMTGKcliibh=8O`s+L~~hHA0I*AQ0I00M!Na)ItRmn0>l;-%S!3U@P_1} zs-~Hmnwh48WeM*n7boT{U7C~F)ZhP{v}>tyG$upDql*d8Qc4#GvCg2yWGUu!z^B0{ z?uY8rz}rd}4@9(;%1eqp?un5P{;Bp5Y>%m{jW&-tq$?F+yglX+<)waicLy(5owrE= zxJ!%LG#qs3dJb|61gWI@2kz`l|ez&6!sPi=M=^#EkqfQ_? zLI9>|W?Gc9IT0-!A*H^8>E`&duj-sUW_V*et}6b1`x9-o<>l3Ueba(Rdc6qu7o`Wm;;)I8>;G?VzcAp(@NdByEimU z6(QF53$^X5*0~yD+eAU6_e1e1KOuSKCac)Dv785o**h&$`cdWi+2gMkFqw_*{ z$3>kN({&KYAHpt|y5x8RkC}djurzBi(?hxk2O0;5y3TJL9%_{1mEW*j<=3o0+0N3G z-y<*XxHGX`1&bc;&D?%o}oFc%I6+FIghWO;sMP~ zk;+*=H)1$4cPr>Hm@voW=MQd!(xPW8=Y|c}d7p6~=G)uFvu((CiTBwh%KI!v%QrBW zV7PPce(cj4jGi+e0RWd>7}Z{IMs5rGnAC zncxr);jDTm^$%9ZMK#9lp*WhzSHq!hc9_ zT)>}?Apgf&emqaTjb*N%EL0TvN+e)&o8i}eh1ak93g!1u`Q+CP*;Qj!V^91|{`)Ox zNs2>f^ouimmso=|e+8_KV34eP2?lvd#vA6$+IUB@O2$~x!J{zejWVA-j=ur^_G#;y z)=&8x<$Z^Jz{o$H+gC9MBLBK5VWx{pd#9l!HaR)AM9R0>8Y@%cVpA(mkq7&A0cN|{ z(Xtn@(RzDaQc|2S=K{x#gV@TFmcGy@xskH283>cc0+c%1my+^f-!I>OH|mSkl;IxK zSL++-J0l2>q}~-G8CvGPH2ji3SEG*8N;(?8N|+qd84TP)=HC};wZ_Neg>0X{n30f> zK`$Ui`v5;;D8UC#b^&0-JIxi)M(lF7MYf22ZPEu(gpf}&X*UL6lACk8>)V#)ivVu=DuWiVm)Uy1R^FmIy63M{LGvC zQZjOzq#93=@3M$9f3zo|MiWJi#B5hz#ou)XC57jucGgw*28{&PtZiyq?R3^4kFtOt#2SqsU^M$ub|aShUbN6pK%2jz z%+N38NR4P;6Ktmih6=3bufW|6I-hRYN>`QgYfB6C@KjwIqwYH^ib_a~#Os0LTW-<* zKdr*pZuQIWG{8FvQBeu>Qm!hx9x-=q>Gi;9or$PB4?jODatWF~j)smH#hBu3(Wjd#!p69DDI9WgI6Si3{*IeI_{<*Vq^Je^KeOt=6 zY$*r-Dh%I)mFgYQ_pZpHJGClUobu;)ut1=CRv0z_SE^P|q(C+O@#3ji+7 zIe$j9dgD8(vEcywHE?VM4!RBDQsk!mC37=|XXO&gPj}rOEm3Z;%O&5U)tF(G;cM0m zO(3EF`~mxMCgZBN-;f*@fgE{5j{KhpWp1kP` ze1E9Bu&~=!S(z0TnOWt`rGNCDg?@bdjFx`KNnfUc+Qu{Doekg~ zyq#s)*=2am{#gHLq3P$6)c}YjT>22>#|nm zLGwUy>B`6`Y326G(*C@n4tqlRp4BCtDJ(oAJ}*9^+&O7X*=%fqx8btZWwUqr8H2+k z>bqS<110v6Emg}WQUU_leF=GSrquA0rV&*9>)bp52_6LlJv(@|v^y6iZKDN&SxY_8s-b zxyNe;2Wx&-kl$ms_v9D!l-QNC>*~PI7$~qB&8~XTv*##gUqrw@oYT0KbtxyVzkYmX z27&wE>_2VU&YjE98{+5wj+oAm0|)n0L~eU3*S-)saqNjC8ZOFF{!p3P;S}#4s>2-5 zE?pA3tj^vYHfq{8;96a=WN`zoPApKi7>(_Xl|5l2(R(Lb);gCovNNhfGE9!@+~UFB z!u~Q-Mp(70Iyh;mqtRK|J6SYbW=^1fOlQS?>Vq1<%8$tqlmfOc!p-|OZ5n-d{pWxG zQrYs!D08*pmef}H5Bz*vZs^8dvbQ?*%Mkul_C|Erp${EXm0;POOxIjEr@a2TF&FdYhYS z3R1FbBCIhr);3FDYj#&wO;kpNB-Ny6m!dk5<&n9+_@9IJSq#mQY3PP54}#C?1~^^U zz`S>GsjK&Jvp=8IVrRym3p~a{+ybWgA(=J9gZK8%4C}}fd(JQ4+P7w0S~`%Mw{c@< zQhqD0EVyH6U|?vuyl+u0JaTgE&4-Ex%X(IY4h1>0Z54U8=GM02p|X)Ji=_w7UE8p( z;yuYg_kDFLpAB|)cd(c<`|4c-%8U0T=jU3Itj3_IopjAuRgxt?Ju$@?6x3B&*lg{` zgcJ9TgXhDjCy!E&-bz~}<|ssC<}b4y(V6MRWCR!g>H_?YpNBtbdSKwLs>Z6(s)ni; zZmDdj9Ib4`R3G?YO_v(chGuZ=IPY6%9p0h1e>|VS3d<2cRYCI3zMZ%Xf30=3ZOh9s zd0boGioa{R8@orFy1SdwG8^d%%El=GsBM_RioKL-l)*i{Kr`mXya_NXhE(tfI^NFA z+0OfbiG{SL)jRpqR&VdQ zY3I(Hd$+G|Y^^$b>dXtnQMSJ!X0F)pXDB-j_aD_mF2+#L?T`$QQsWWP$pEXa0ZgTi z2#9P1Nrbph$|{F2CNiiSPY9uIATc+}JQYqiovcxZ1S* zstR+wEhX64=NgUPn_QNbH_)8z+FaKk5XpXsgOrVx%ZnR#w5B_wtZZ3BX?FQyTZApC zq}TTCqL`fU_>9Qt#02)&>C-cNclS+QwX!%SF(EcLyD$&_&6(`Ci#r;Xp{R)L{&NOa zo>lIrSkfHIucfTvZpZ4Dkpt~vOMWLcjh59ZAB2R}Y#M(y56{cG`4HH|D0C;@u}dEqwCjSJsiAtmH+razTN-obN5^jE?L8T%a2D3 z`JK4sV2*{)Xa8l%lQVGxOSgCae^XlPcK5lJ zO7?R%Q|WGByE=c{`zY5EraJG7Jb&C)!S}M+r2@S)}N7b%hrx9wEH4soqd(mgz}THaxFm1 z9wlvzQi_Y{U5t0)9aQntWf6GBPt9tVz_)B`st#P#w5+SlUSBuf-PF~zZR~KXyRGx^ z%{TRQ;jWFXTX%Mmkw36tq47R=#O@cu7)w`>Xsv@%ehpv-+eD zv^=oYc}|_P@cQny_?Y~B>4R@S?dq>6EkGFCu(@QMgH!zl4(5=JUOP6-de!uQl$VO+ zz~5oW0bN^#?MLW)9K?hc#tYUf8dF)p+bpUUAWEXEaa9|5;vX@U>+|hCIgxZ!*qis|i%+oUZHWf{( zHmr@e)9lS^bD>{jZ&QM7V&qu_4!NMu(ARJZbup8=W}oPbkmhCylW|P83Lpp`5=jS!Dc;&lnQhA%tk=%x-rAYX#t9BfI zKG5uDKUdCeWRJP%d<8RLwJ*1zt@-*0{mO*HEU9+sXuXmpHG<(UmCimijHNxLMagmu z_`!KcJ1w{vF_0OpQiA)*QpD;?FMU$7KaFq7?@FIW`u7Ys@-wY!dR*tmthD(rLv&iwDlO66*;Pu1ab|~vn#tJF`b-=lv7|4xA6K?P@1>*Vz_DkSXen*HQ zXwN>4@2C%^_opbMI9#yly$*KRsjOiMDCc@yntB8!wIlt!IxrJW{7sm~-(C8YR5Sb7 zQ$Q}A!)w6OIes5LS&ZZYge)=^7#li(`rVhoAQW)Ds6!-p^jq*qwY)`80agcRmB|-bnD8GDu`c zUZU+FK^|bvw1Tbpl1$CbetjVC)mQTli1%+4H@im~JW%lJs|5#?OU38A7^eMjCx$`3 z0Jvn#?!!E%nZb)>fs{_~-~wAA5GLd8^&fA}fArD(%~G)o7uUYGdG{?}`vSK6 zE%DHGrgB04W*7YcsfLT^<{)1*=TUF@We|50n9Ydm^4M(tI(A5Tn_XL^tZkGYaJjzk zQdSp1R^Jiwd5NJ`UM}s@cyHn2>9BGcWm0Yaf!V)EJ!xsPcS)fK@)-hT9jH%Bt3Qyh zyi57%F1=d$AkWXWE3B*f#$%>b;6g;;`~&QVlwWx_|A5j-+4o3JpnW<`+LeERe$x|5 zk%m927kD>@@i#!D480AylSNp;v*K`h>>=gmhuFFoUwm=Zt89gGJzK%{DG$E=wsI34 z!en!QS5A{=m>>AU?{}2pT@xhjn%&R*W;c4C%kOK?8J&l+BV<@GbeKhd5MEE$K?9IT#ou~FQnxs`o~UCfTMgoyF76e`8T!>AaRfHvG9xkB13oh4l;T`ApY2zWrc=^kI3y=RF8TZP_EU#u;i z0Ef4c$4aSBqyMhQvru|HPW^ZNo`4>Y9>4y(9{>L|y&jMLyB^O%`1NV@-}UtCaq8=+ zhcA>~pGKcopI#4ND7~Kk|0KO0zrJ4o34VQ=h4Sj@StyMjkMu}rR8%Nluc)8Ol=|gC z^^+wAg@gp*HG6~lh3!+n{8|HisHUC0W?p6u$=3NfG!$N>8V-#RcJ6$n>`y8dZzc`R zNt9?<=6`C0cp=I@uqXAgjJT{!f7Y&7nZml933Pn;; zc-WxT!~;;C__s`zCwbda`!4Vr;!R7q`}|HB}-`@g^k10tvXPHC6EkRSTL z@k|^y_)qkyUf=ys^`kz$9*_RJUeD^&>){K1*VC`ZxzJd$Q2K@P>eDZTQxDhEuZRC9 z`1N&MD7_xPKFvaT_30N%qsJpXq6T?+OZ`-a)h{R1PZp(WLyuDSXjG4i{gaw@_KJC# zH6&-LI6OH{F<7ZtHA2`K^O3SQ|8q(-Eb~7#LOgZQMqJORwN))vs#Cw5#1RdLmes=; z4ZsrBtXlO?;){mjBz|ZpW@l9t9yVw-@c@*c{9C5Voe1dT6Ub-&?;dSfTIh4f{2cRt z{~rdy;r|6b(0KN&QZ9$cWY7J-^%?D9>(+n&iN5=v>PLNgz24M+*O#kLuZJ)6T~EIr z=R#x2Lg^REt53fWPCZ;tzaIXd;Mdo2q4awE`ZNpW)u&%5jUJEmleB_@G`v1kKb21P z%Wdi>3sNWS@$|lj9VFm6}x}gw4!H%6|QyQ=(y+|EUq; zse?A;ct)+W)MBMl^~*^d(Qs&4J&e%+EK1F)RsSTuXedtNhlXPIZz>888?>5u0Ll;l zEmP%U-nP`f3%pwA4^Q)de`leb8vOq-2=4wb@PX{(Q-;f67qsCFp5KZ8=I6!5=?~Ri zeyVJutbDwzY`nZ|f>}LZ%jhVOE^jh&Z$3G9x^TAhge>sEmSL^6YX5yuZ}EVwR&Go7 zHxx?~(xup^N^SqA3CB2)ft2gx)6sVuuYM(*Ve78krsO& z@B|xcYCMm9>%}7o{5l8TAKaZq+gZ#ohjahr3`ypkDHB|}lM~LY6Y6g~JE*+N>&WZj z<@Ex11B%1b_)!>NykDIHFH6wy%0BJD_y7NWOISVU-_tS{Y?TKK)6xp*wP5qC5zh3wo1o&1Z^9cpHa{q&HR_`sC>UZE2};;lfU0@rfy@-|Cf!M zqb8;-nfW3IGOjjl+7i<5CzcRf^ zEBT&|rzd1F>`&kn1?{(M-Ap@Mr7=}nIeVS7@|ftvTr_Xe^)Ps@2YdT?*?MoMMCYTu z>NVJ_9@O?jBCy?6{?%7su?f6U{k+fBv+R5e%W5=9*+_8=e>^FD<8{!d=X=`VHgGzS zgZ9%1H)V3;&y$gUQCW3!|Bx)WcgAyWK3c2ud z_tXU!aJrifH%p09IdbuH z)hr^F6@8>+OXbR1{(QCJe5Odl+VhAdY&E-DSt|{HMCYoRVP1ZVO!?W%H?ynp7|-YD zw}c}4fBaFqIh>v!CPIR!y7e!hxE^YUH34)+mIYIR=zDP7}-L-20 zyr9fzHgV#%e!dIz)dQ=fqff<|C;6Ljt{25#t$h6A`5%1*Do~GT!}(ID4=j_uS zKFoh^3CD9!J(*#>;W+z7D)#U(HGDn%L5h{rfP3CEODeJk_CayvfM;e$T*}4l{(Z=7Ls`FfeftNIQ8GNQmhF=d zCrW4|-ps;W5XX!6q2$k$)8rJ_Y#NFc<>8Elpjnk)I)Xy~K_9eKNoD$(q6f=SNWCeGv#9CSE%N8LcltuW3zX-T+-0Df6(e^c$yti@%Wv% z7_s({GrU+9SdJ*T-QKm2Fg)#&em}b!SvhYE&+E$P`NXLayyFbj^C(Yg1#6UFVb3eV@oq*LXyfNys5g;xF|)ZoLq(|ez;ld5gvJWq z&YV6E4jlgheP+S|4B@Hl2zUVAq0LFw(0RC?W zxjmJR(Nm42y+RbF+Y~V9dGwrpF4Sg7)P?n;zj*0FKKjRcDP<8}+kkpV$S{J_s^#d? z3wcL+q~tkx7vVDR<&{@*fE%YR2s@EO)zI^(7d+CmEAP324x-^&7tRfe@_o{9Y*aSV zfE_54Ek>X0(8e6muXtZt3~s5GEvdcFefj})Tocx4JWG_A#HqPA1% z5)CIv<8r07ovt1{HOEWsfNlwvfk479_@+xMBtua7JVd?2HM@#>u%18KJcJIAa2;VU zexAg8c7%EsgqyR8^ON^WO(wV=q8vE1)cP56a7IxEj^FUS$}cUwiXG|0fL& zhd=0aRd1;$QX$)Fi;;hzr`6atYGM!uW%;ydXXr_4JGC6rlJt)_MD53!^fbs5`Vl-* z%hBipkLVwz7pbW|Nt??lpi7l);8$m`q(?cQ)V_`0E%-$&_{_^P!8sCVZM@>`qP09} z65yc-mKb+F`%Gw9-jrO)s$-Q;9;BxzaWd(!J~a=QVrtZH_Mc3v;2^IZmm1#VN&ZC- z*1A5)+Z~{ulm$L?GUugM$5l@@l0C{mob}3{mPg|&?*pD91#4&TASsktfMEtVOfADW znJmt3W0ae>`mpk~rgw&43cIUji(_BbZK|!^ROi}QTf5O!KtZ?*`1^EHR8$gPGoFV? zSg$>$H2*CU;2f zMHl5=z@&S^nV<4)_&v(!*IdJn9zGnj=7yo68xX>S#xQYC?tYxOOMu^s--%;1p74{! zMsrEEgk_`T7i}uWrT78tLyU9>XgTug8F-AXL|Y< zH}>?<+6s#;EV+`|v0SS)w|!${N=oC#JZr9UjIGYKDnBnuPA*DH;_tM+tw&vNMO`VH zzr|n&MQ6d&E<&RPj#P4SMrw+B^`yUy*_fa<+Q)hj8L)r&hPoX$EMI=Zj=CF$Z#-&M zey04)dNgcwdU{mZ(IJ&|)Hg*R8cWR0wHPw-lTAB(svHin)+!-0}J;TZ__T-B% zS~D|ODfh8uRE2L-N>ftpDJk~U6xBZ}(hwux$G%29lUPI}a)H*nAz5ZS?uD__KjG?! z=;n4@Z4q9K);30&?EKw9K1Hd*R}#lLqf8dzDbG#)@_p~EE55PMo%c*!Mn;@;)_Vo} z_s>2&W@@%JnDPfqu0PIR60~GV5L;{-H!~&NWC{<7j2y`iTAgBv%m_OeYe|T=WalXl zp6-fswFH*f<7{1Fc>&o;9|SK7434YsHfBbJg+*dF456lo=S8T;Z|BzozV9LkE>0D~ z6AJ$dSd`g;VZ;*Uuwxy`-&2KqL28sK%4#AojxBSSf2v5)d(v4{gJ<&GeK!`bd++}J z>@U`4)0lbO#1>NxW-s}p%QTR0QVviJLc&e#>ugJWf+hA~SVp8JWpz+~4r^8(tSAX= zK~;*3-Su(7fs2AaNXib#3+uAQEsDkwmdLO$jPe)F{Z$@^+(htm^fiW7w8qnjfRm}(&;sb!3dm@^dB+U>PPyj`wF z2X#YVkmqg=JZAh(oQ;#x^J;`@d3*vuz2|#?HHs==4}sN!ec7^Svb1z^PmB8bMQlVw zEMD)ZpC4v6O-(gr_U7j567j_B5s{Ko^Ve@WL8$|3MGF$fO>yiut}2^5zsnMM+G&B7uKcc|DpyqzK6QZ5 z!otqHyw1YH4mf{2fMo81QzfH(1#U~AvJPo&9eH^jw!%)FEnYhJq4bJGcMD~KkJpoA z;P!=TnS=qK6vv4RNCfuVR2Q`*##TjUN7*}zV`Gau?NQm09s5?7m95^lrMj|yLv{6r z`pW8PfB)c+#;F;HV`i#pNwB|v*{Z$m?R!_1vCUIA6-ABgZf)H?5><2)+EZdM;UVha z1JG>Qpy4{`3b+LdUog!)XN&^pz%tO1M9y;X3gGIEwO*9JEXO`vS~_gcS(bm%MXpJ6 zg*m<4Y%WhXS6o=PVUeS!5YLdJu*b1zL*0cJvMcb!tfo43`DiaHj@R5{eRbF|XOQ%z zqm9r)7tNzUPk>o^;1o0{r+LaRPlvp`4S5cLEhtuN19)0`?xWKzY7ijuzwuOESlr}* z>I;@eiu&U{r6Oj}Ws34$jRB}8OTG4jn93Bv1H4Cu@2OUZ1D-VC0mHdo18&WH5H#@k z)~uLEvZqdxVMQQv`XZdT*e4-Bx7X#i7g+0}LkB{m>#POsxvmoF!8gj+Ru(PGN{G4a zvY3RdWkr>1$%=Fk+C8TD7B3yB#hch2a38ne|G)Rqoq}G&Gw>UQ*iW1k-a&tI^=_~%j%u|g@u`!g@ygj z`pd>XU`y}2uXemO+1c2a*X*RVUd8i}Kp@7X_9Kl;D5hqWtzeP?l63w|L-^_~_#P5?g1XUr1VHNqbICdr4$k zh+ko+t)#y=I{p&y;WqHW1wOt-K)?r@Y-o>FYGBP7>bV@Dk$kGY;Svb z@mnJYYD;_a^D{E?^Lt8b4~)D;b5OV#VLqw)7NLewvRQ^dlJxfb!SBBx%%+2tJJ~=m z8=$${A@E}W{Kzo$qxAid3y+NbtEP}WmS*ziP0}gM0HamY3S4)Z;0MN@3Qnj=ag5jt zx(XKsm5!9+f6$`Bt^)h8Gr2Y)_-BY>l3t0vtSMVY-fnJK`TS12@ar790Y&sI_`8ZP@KPqf zS0RTyb_zV)yoxWw3h`7!s`z9OS<+Aa5ZI(Y9T^=xGCbHU8~s8ShXl9+a0x_s@DiiT zxJ0s5x9)0g-qmVNE?O5&1jp`itF$t5Xi zMW&>pu&~0|#fz5|WMtc-qHNho*&%5~p@1b6oAqhKLL)k>Yq#^{^Bw^W$<&CUP@;I< zsx8mLuLM&4=CLA^N~|>*=A7)hv;cqCLukD8#6*V~_l#2BI^_Kc^75GRyvNZrMrBbM zX!G~QA8wI86=-YC0shLbyhX5kpU{x9sdjG(?3Eenr;@H5bq~@+vq(~&#o-p;QtZ6c zh$xHwGnFgsUf)tzGh2#a(knBw?`s8r(5}3W?^07`dnJ|04jb~N{{rt8Yf%ZYnk^Oc zz)0fyq(QXZ;LG zbAOk=kJuMfh9?q~Cz1=oMer$um<#ey02I~5$KMd0ghn83x_HY#Vc|e|(_o<6KR2VO z1|Fn&{#}a(o7uxdO8JoVs%@~WY{+ICDk~eb9V{-ct1B+9VF{%Z)zuTF9p#DU_S)KZ zb3$1MyKc7S5}IgM;Q=0B(q3EB(NR;2wnZ#1#KU<5D^Ar?Ms`-ROKcsWKj{tSX2fNp zu}ddVeDYZE)C5&C=Ec$EZwu<5!l!;guc-zn8 z)d%a}3%@K`II(Xdv9WU`c^tMjz)UA}ZA`?;j~D8Y!#Fp;IE;r17k0dR_-0FFfBN-bRfRj|9&eV(n%e#cbr zq0-Xo>eA9fy?uuqj+)sW{(b=A-#mKRYU`xv(u?8#Q8&6c>l2mM+{sIZWVBP&cwc6De&H_$BQ^)(}`ZZ zc+S@G{D~ZIqKX%A>=zPVf?M&9@_;vyj8Kk@@T5+jUDO&Hr3nlNnx5Xo zTE9ea(poeb$8{cXgFXBEQn@_%xZG>fP*;7?o*SK%H@|%fAmbs5T^5o5gduq_$ ziK{MZM%N)~fTL&~f(C4JAeJF7!-6unJzh|YD1z)+awd>BL*+ zWFSw?n<2Y}=AgXLo!E;QVW*EMS0ek>pO7cFg!}eJGQl9Jo=GI%kiQdt}_9`V?GNcgY1(hBb=lQL?4TwG$ zzoP=bLEkiM)4}&6PO5<4PjJu;zjeP9(4$fl$Y^tk%(2CR%uKt8u(F!b%KzN-*1qw5 zZ*5Zk$%0P*@T~E(K0KYRWPLm~5XA@jfpQ&u8_B=%=NvEH9n*<9C0|9pl~YOq=R{Zl z9!8%BeDIBer0TD@TowyB??o0^BfC*=8#CT*k4^1dzgHUR3 zNERqH%PgW+XzltVie>GWM||@;uHI8k{QMDbZw?1PFXZ(j_%$eld)4wXyeu0Tzx3@* z$~SEB=^vjzjzL)CPlfV?r>tu@POP@M46Wce|6j3|%2MgHcgG^0=%hHb6u zCX^=%hbz+}optO>T;ue;-re$4<5n&kckthyJGA&6975i& zMtOX+Bbf!0NM@z#3FRDp`~el$dXDQJ53U>NJ}$#?{yP)=pnD|(xX!_ST(}F2aEG8S zZ@)!rJ5db><9D+i%v?KQNiTH8DsOK1@Y!{TdK&suGbJb!_JF7h&(GVVoOp!x=*Fjv zdqLj9+W0ldYl1ihhWjFD{v`skc!Jlkr6ODn8G|#d;kMqZ*G#Utx>tN0+PinlGkCE@ zeSLeH{U#^MsjBN75R?ZCnq99fy>W_Y+>NX-!#K59>OzK@kXe+E8GWdr z?J-?WDpJsRNhc;Iltur=srz!`R-z8Q2TDREk?QK95q;IK z0yLToDUk5n=;ke-zUskm;8A=ahEl2WORe*Mawjk4AFQ+E=9@bP^j%qB1Hn_j8kDnC zoJsSvpoe^PSPB!w6)IX~&xBe5<-7^0l2-!szsF*g_b{T)uw}GW@V7cXs*+su`<8h> zhSll+YwtVYvnbO4XLgr_B&3H>g+PE%0tsn^00{{pv@{4LB-GHPgLJVWdMbK{o&`lc zE5{+HSPzs_4-q|2l%JKuj*5zaf{NJl{=d)6&b#~8yd*)n``_+o+A}lHJo8L@rtA*N zocai~Yc!6wra{~3wbHjcTW zcFApRXGEV{Jau(w-DY0XV)f+WSur!(ZCqM=1Ak%u{Cg)Zo0B)}()r`&UN~aNf)x{F zf5n~<{n35YejT47;a)n8e{iGE-C?`8I$VU?D)NuIr^BtrL)=M)vDx~kFNv;ewHqZ>(f*Oa8yCJJ`F9^WQ=R^iZ@*`v$z2aoK~rtTHs$FIpCGgx;5 zl01qYkz|m}^vg&NJ9u!4_{Soyb=%K-(p{<4k3N7n=9o#s3;s`vjUoJ9io_RlEZ#e| zD#bn+fA!;^xfqTqUvw8?jNlc{K3gq}P>Atx3jf2D1y#%ImSRk-JIE8LN9IH7o}%^> z37VIQ`tyX~k?B_Bp16sW{;f#JGSed$zVO`KFK>Hr%0ssS)q{L=-OADrKP&|eamdGG z77gaYaOH*j7h+OzqzQo zDF7WHLj4H`J7hbA{Ip!jRrnhP$xk$ne?|9xVWeoU?tOCK2h$)5(=J#Hx&&HX zDv(^V@qZZnxt$&Gzr-PG?8o}%D|xi!-;kG5AcsArz~u%P#*H|-bv%Ly$Zx9e)zLZ zB=+o+Aa^nS6H#}*6LiTtnCR~Gzk?o)rGiK8v=6bykc2VjPMo36%Hvs?>2Z-9|LC*Y z9UTHtC*NrXo|$^#&8rj{Uilu;RoFfh3b$DGStSCfC%3<{>9xEFU$ z;jkqnN1(EADkmlfT7>&0a76 zbi;;EyRGI?b(f{{sk=Z2-E~6uAO+#SD@2!cgD!G|u3C=GYA5^|Cwx1>L2q*~H4$`( zxfbY6N^d28jfWq*#U>_+E+RPb-!ISJQdV8IW%kSeH*eeU>fzhw@n%20f#;{XpWep< zJh-!+@6{sZ$nsh*gZ72|Os~X3EA`O4deZnMQMIADsabjX*@N=Zb8^!93=EkZao)J< zc^N4g{Ub+@>e@ZIYuBE|T|0H@nUpweOnE`_pz!eg)IyWrsE_o=W^ty^npc%o^NwSy z>pmQdOFi%>qB;h3IkGIvj_NSVZUOPgxQA!j;8dmp4&DJS%M=`D>TgBp0Mni9DXSi{ zmDut?DeA`Sum4#0<9qL^G$J&@wV2XC{pcic#b6E+16<~MHI0Cx+GbK^#dIpB=0HtZ z*Z96^{7U}N^UKf2zaM#&Ny7z*XP(~?eK-c8Fpioz<%z#dsoeFZBNdqZ40!g71Bz8ux`!b0NI)9%1}DjqVY4x={x5USX60dLFdz?nrMJ z){C5TDeU~%|NBXs&G;8+3?@(zQ*guv|G#X>M~ubl+ht?Njiu8DWB<9Rre;xHhlMpY z3t!&Vtcd=mT-5A^7n&6Z{o9SbCh`TD zi~k$>GVf&Rgx^)FxfHrH%In;RaX6THQMxO6cv8KRfqO$mhG3G;b{(4R{bL0Uuso&!~uB}$1ztQia2IHk_#mE zbSPd`3q@B$2)=Tw>0IQV2HL zS@%OVf5K$x^Ups$uDE#Iks~KJCnt5sCN1hM=1Ta7C-wg%<8$i&seQ`1(ra`l1C7aQ zZ-ai;35njhAl@r?A?`a5pAQ)mh|XTR!}~4 zOrP$>6AOW#QQf8(y(8dP4Znl!3*~-+d=!)PR(s;S-`)Rt%rV zM~@zTe&M9z?kVNN$_q>S^eCA)MC6GipzY%RuEVh5PPSDrFkd=?3XlVX`5|be}jk)2n$MzK8 zv3|--!4QF;#(?()oyNl-1HU;(lGBS7BsXo_Ry}z#WT%_T_wL1b0Gg4@v>`^13I%3WwVr)Nuef9cPGFDyR z@@?D75ibn+B6&p726@WLS8(FX4=%jq!r2e58&lc$kJruSEl%#(@!j7KZP>e)=x{w<&?t|`lwI7bTk9iVDPNg+WeXvg!gP9My5Z+ltN`QuhLx z5kps(m7hOU)@u$+Mcu!lA$?f}bU!8J0LVof#JDeIZrtbQlp`Si(p$#2VZ;>uHAYMD zs5!d_eFm=(D>1#_3~j!$;nB>#!%gG*(-y)2I*ZS=7RSK-jEx6QTYFD7r~qE zaNmi!sri3lTE*mOik!abo5sU_S;zYy;I#OAEB-R9)p1ILaM<^Tdc#e32M5ue!lu7- zf52r|!V86sBp z>JC6XH*OrF0=LT^ipfLlbD*5CvuwJZ`y^;X#B)l^T<7OH5>fwcPImbCZ8Pvtc{}%0 z;Esgbxy}T)xetPLaYh7g_x*`}2>&|LHTU)C#E96O5Qkm+aA9x7t0wK9bn{h{uDWH? zvslTgj(g^{*RI+awsU9LE$d!;ZFel_?+ZL{VVy>-K*+roao`>!8U2YoQ=UV`QDwA3 zTogc~L$|(Z6D}G#@}dc8r3LHjzRoD^ox5N-Zej0TmR|Su`U2jeYWM}Tj&s5As*Sj) zyM5(?fg@H7#YG4M7gV;#McpDlG)F*vN8fcf7$0OD1sM5s%mJM0wA{x8oVM{zIMrp` zW+=m3a8a-1x~~=fdb$3D@DIxHJ0u?B8}YdtqxHOCoa;3C>46*~g7V-+zueU&?S0Lj0OPu`^DV$@6DYj1(s+Y)4=;9+q%U zSex_W+D^{NpO)2s){yk^?Sn(BTFe+-P%xSl&l}KrKz^IiQ`54CtS%qDx-c`XPp^UJ z&6y(v9g#lGFI&SEIA>&7M;~$kR%}X8EFS|M_dQN%H#Ei z@}J5kjmQ~ROlzG{pn--+^X~_-eKaBeqx!_=YUph>I*>g#s#V+Apy1@}q}b_6gSs}G z5S%qOqnZy-PL3*%ACO)5e&L22a!a>AGV)~z<7({q7?Ps6Yv(*VRZpXa^CT&*z7W!~ zZ2-Y>BsF{>w{iq2%@~+DwV>Ci4(&$v$}Q;CE4O<3zy+mAd0kr;jH(z?SP{{G=8)3W ziRToi^cs>B9$6X|o|w?4YpaAl7mXV_JBP;>X2ph`GoxTgMJ42G?Ao)K{*FE=X{}8URGMaj0rhaOXDU+4Nl3(9oX;OnF9-p`ezn~Pi(oOx_0Q0l>CSm zgL?NY=r(F(mrmU~cTOtl*{y3*$1bCWBWaQLO#Up!Q}Y~gjG1+@kc)v+@`n}?@BwMc z@Pe`)eN!h*yK?>aSY5vPk-WU{knrl7ekdWxgMn#TOvc=#VAYdSCWEvhyz^R2sE+~SLSl~J)`QBPwK_E{k}<208j zsYuQflTb@XRo~frc#jq>GWyTp`r?<9xcfR4B?o>^%Yp zjz0+w#kYzzTYa}A3}pLT$EGBPo7TOwU{qZ}=l134 zgRtJTn#$GuH$LVp#>06s4`SID3k&jwV$qRc)duwCXJ?MA9uU@Ua$M~2jO2nCTB7^E z+>%j)@kRIz!^+3>z?VvOKgx2A0`gzD!%3@qyVQdd%~sjFA6jLX0o{UXj85qV-6rs1 zy?RHLw;Ir|?!&wrZ_LkJyY7nq5@$EZOw5_zRBM&};z)XPt$|iOl*j!9hu5r68IfPs zGc|R}oY6y4^LlnYk1rWHrh3?*v17Nse{Q>whzZrnN!i&UA=rByIyEA+Bt0oNy1YCm zGi?ASJ+eQD6f(B?FAsBeuDZBxUF8f+bZ)%{EgUv{!5~Zuhb`pA6@`Trq#m}!>Y@Lt zm@}sW3Yn5+irJ)Euaf&pP`5wB3!*$Yr^&#+Bc};~`UPsDJ5N7tj^0 zB>#TS{+D`xDm1$KMRFoNtte;ia8TBN`rzyd@zwmYF++xofjU}qS&b<=ItapaU&`AUBII9Lw4Dsi^}Oj2OOPAn)F$ zd`35HMU3gocMUG@Hala)lKlMT#bakrt4=TPGov(TDy73&EbfKhiFDMwP_B2XOSu1X zBmc|J(zJq+C1U9lJf^F+DPAklmoT*!fr&SWzlwIDc?>eB^}I zZI;fSy{LaktIG@KP3l@z*(H0;ptUz&xZ;YU@@xMf`=@kmw|1A9v6&}6t+mpue&+Wn zSS+Qr(xfaytd$1wkz+3%Ir7r6$-SjnRaLc+^SW!Qsuu8<_ca>=0eR7oX4mNvcPslo z?fl@nTZ3yyjruN_&mvRiDaw(8f0Qt1tx^3*2mJFLx3}ShrD!%ZqUnh*O>5b)Rb)g` zT95b<>n8qf7XJ4i8J(EYqixyxiGSMDvuk;LMxPBlqVA5D`*bOf&CK{y-BIFOKh({w z$fMa}x>C`ZOrsImlp3)NoYjy2>Cxz@nyBdYmsGF$L-nMp2JTdqB_u_mQ4@*>^h-&v zOi$@Ipm@TlnKk{#L`IJ3U$gdtYnQEEJ8pW#sB5nsRWW_s+O^BBz2Lgs^o&75b{vX& z6RCX#Sq-8AWVK^UVL(QFPr$LJCE&I~g2H3JK){;| zxEzlOzSjzm^6bucGnxa4Z}92Pl;FfQ71ME^Ri;OBTpi()H8 zT-^5=oOn)kahdiwiPrFYO&ft5SHA*Y;n%TdY=!of@YAuXQii)#H^Ptpq9pu-a6sN% z{7Ou-*}GI%mX&ns)Vej)J*KH|+;UH+)~z~qYDG|KiahSsM&K;%SCnh^1CarK6!B}= zi{LEwLqOKe0+!$yCt%$$Qv{Es55lg~%J|Ml0(CN#7S^E{ilcumf&sD02vglvf2)ct1>Rr%S~84IflDkIFR(Y~}UKpQq< z;6>A}zjAn?c{duBd9mXgt-baf?sPI9{_}d<**P;UvrSq_6=zJ!Ei9W6THSnbc2QMT zQFeaw@uAbp3v(y&xDg|kjtm`N*?h{7Ua9lOkDmvbcuMg2n$VHNV9wHT7g!qZ0!u+! zxChLTv|C%1QUrC)E7GSfje2Unb4F3k(&*}lnvuhBK5*2;N&Tk{EGkpL*Ne`n=>z@sw}J5z>M}iJA{QtPaQp;=!u5* zAY>8s{{k$aJJ*%Y!8Z^Xx#v%qFs>varf2UXN2>XQqx+PNFYlY&eRSPuim%G4JMwb} zcqeXAlegok2{1}&5WX06xkYc9c}ZUM*zC-z^z`xliff%0=xLM93-Z#lvM-!9ZnCT(I?pBO9mG!oTCYJ*-Wnz?XLzhOleEh>7Y z=7v$@?z-XLE#u3z?aNmLSC7srjf<_yt(w&gCu(NBJt$*N-GLLU`q3;kian21xe)^U z9Z=FvccH#*+BJ;HFPV0o4MZpz#(W+7tC2h|c@0#_~YQ;t9mxfZJoGqjz_)we9*(-x9CJLZo1yQ=sEyW359uw0jr#lkuze2L5xg4iqCN@x zw|F&nibvp^wMZVtbo}im8ul388um6k3-*3I8}|OZKkPYt0PF+#VA%6{KEAFh;J9}U ze^9A_eJq~}do7=aFw^-G*q8EUurKG!VPAnez;(Wg{uRdm$ghX}27UwVH)1T)@h7&w z!M+_Q6?OhNjzx3+1m6k!-}#%c@8|!9{Qy4z`&%4sj=#f^7k-2vh5Z=+8uoAacd&o2 zRWYuO*J^N?cCMwBM7*&`}S64=5~q%SKbeRWA$aROUYT8uQWC@mY4 zzp9sa;JT1+0eumis->gO8DzYnJ&!oDIIvtDi z9Pzl^tu^X&4(jPr)X6`h{@sMVqV1?}sAW{Us8*pio#fy0Q@B5>8Z~G-PE^fAjp?oT z(NpwPy|138r|X$|Kh&H&eXu?gwd1I947Gx@jab9I2j2;7!nY@z*j_s7`Z*mkyqJ&v{4oj3#cBzuZIjk8s|aEJYF_8dl~f3g>FR^~<#Qwf6l&R-?N|C&m5JXYrHx3hPvR7p51sFPsi#~yw)1G zTPA2ZxcPV(ZecCc)@qmG-m9y%4ccw^^5RbIF70mZ9_>Eue(Zej)!x=V);`ltYNxdC z^?1Fl-cj#tbTYady^VAu!^kwUjsC`PW0Wz@s5GV+Q;k|$EayRpOgyYZayFJrIqvhlj{ z7Ulyu4HbPO<4{`ktxSh9pqfDiK?OrKkG{*R3IROStKViPE{Yo#eZSZE-ta0K?548Y z!?yt55EC!?lV7+kosCiNMtuZz80t$X^`3<7N9l@YPN?z~W^_>0yU{JY`w=uc#*ZIx zMz?nLkM1DdzS0voySTBN}@|A7{qY)-!dSGe63J>2ecwMUm)?&z_W zoovxHu5P<4daB*!YLA{J-O=-bV~J^BWtzx?uUEzM<{y2rHz@F54t2E;zw6-VtK2uh z?{*n4`X1BVV&RG2X4&0s(T}-9Y;LN9KHlh^o<7mLK*#e?FM0X_`Wn;$sQ0CB^dZ+! z(MMd}$`ySAaZV{0>_1y}vc+h2cT95^SA=vC=VIdQK5(~nb=zGr36?9So6Vk#cxjf~ z-4>JW4zb)Zxt1MR3SC?yEmusrWha|Gy_iZjzlqXKH9uyWw8zXg&4pIjm}QooY%!}X zw>^Kt&qim=8e6z45byUk_w_dWE%3X;=H3MNR_Ts;2=?tzPnz!CRyeZ7ykNOw_E~n} z_lD(;dE2rJzk`-L=2Ocq{Ek^}yI;)LNbh@_U!7$a@sM{_PO*lopWWr@7aJP&Zful2 zjAf6Fx9qOA*!GCkS;AtwyZQBjUxw+IW!R`=mZuj~UTV#cbEw=2^ruwYdG44Jw z6D^3UXC~w$n1`Xhv_eFkg#Ab9rk3Y(T*-ppKwCW2hUgCU^%F4>7H0Q0jrji3n`OqF zk8n#&`zq5k{iMs%qc$ifKr}p+3RgbXk+S) zouO==X6#&7m`j~l?CR&MAEZ#RE2I+k3ti*IuC?5;S6Oz%SPylh9ir^9x5DQx<$`^) zWtXQMb9j>RL_Shl5?CyP!J)B#ef7gX)p6v*A(AOLLBIp631x>Xlo4^dbIa@967fzvL}Ne=;A1kIsS`=o-^0C+~EH zi}1T7%$t0sLi@w%eH^Q=Pga=8j5T|H1@qSrM!g&7Y%6g=Du$;S*TNO%s}9yHE81#= zi(GI8#{J)0X~bM_+1+g(?IA#!Auh%pljV;7*|NLa;##{yEVtb+CK-81v;6$o-1{>n z^T;@^vZ)^#rGahJ_Z$Iry(d(vC+*HU@2@=!_l&0?zS=waH)Q_juT2Y_e%hv@$!3+mMcZ8Pbo7xQ__U0|cHtvGv?AXrNz`zDdcN)d9m2-!hk-Xrw(0*;+`;At-G z7YI9jG05c!dmb$8*v$vFp9SWhh5ZMDGknJY3cn(V{sigT?}R=>=zk)eKPtj(7LbDi z^0~k{N7!3QU0|b+)Hv@X^m9ZQ`X++&zC!OQ!rV)~>|Oy$5+Rd>y+Y)!Lim0!==@&V zg>QxkiIeKUM&D6#o-4xViZGi5T$b800lA36updRpaH01WVO|nCeFewG7ZA{ey_3*y z7J3NjY^kt^3jIm}X@S31b9RE~LXXAYmImMtJj?h>em%b0-oba`yTKZ5vNlUQSDUS8 z=vjI`_9Qr)P|u39xAo>HvDUoHTxX_rWCvD}o6GfM+uHGMSS7w&+k$@?{=-}&exFu} zu`b+2tPH2x)`oMiIy@5V!)5f%0M>{nidEuUunN4%*z9c;7(3JW>Z-}q1mtKBR?A;u z`(eJzKEfLLm)PYx%zi}Pg7CJWeJyOZci}yGD$l|`RskQ*OR;Z7Zw;Ty-sQ7E(bK?i znC%yFIw1Ufz6ASLLHuHVIordp7MQN%8`#tQc7(czZ(%>Oj}Y%gV5c0P|$VKq^ULSu*v=u{U-G__(cr#l|ru-I{hb=^Gk%jhIE|nAPD|d2Oa+; zfc~V=FBZD=9WCrRLZ`n>U@uPSH9|ikbo!$Y_VI+CK{{Kh?FQsy(i#1ehFUM|+J3TQ z|A4~J5qgEt@!w}a=r01;Clor)+aQ+gQ|OOKoa1~s^ua>EQs~o3*IEcW{e7S7KZ~85 zMC@S zz62#dIKIKTm_0a4@HuO*T?2IsYp(}GHHQiT9Ook($5{|m1k^6Px8OP$!_ig8CZj8>o{kPA_pB*XKelK%7{115T9Q1fOT|h0I~7Z&^6cfEoi;0ksh7 zN|wm~0QD%;UZ~euqBe+yYq?PCU_Z&i^=61m*y4aKs!6c*anklAc8PaGJqz_5)bmh> zp}vLs5qp`*P`#ltpvFK|K#hf32z4dv2--XHzry|~%)L;rLwyD}aA03H6nP0nUP862 zpw_XD+I28*h1v}DCDd0?Cs`jDof0~>1$9s)KO~a0=tglfh z*LYAE4+`T!VLT{|N7=<=w|lPRP5c_;eaD-|3C97rPdKpm4*ea{an3|HwmUvIb~xTK z_5c>fcHvH!qfp17zJU4?>NwO1sIQ>DhWZBTBoqe?f58suULMXz;3)njd^i3UnWS~c z_ll|bKR~9|Ps`Q@<13gU+P&HXMvgJSn1u0!b`hU3_82c3uNm(cCyeiycIRU1Mefo5 zdYElsS78+X-{Zmo$4Oan-0`~OUdKbQeS>~1 zltqg$n;i!Ly&dWlPKbU3+vAQ0SUbm8_{WsQa=YUt$5Zgx;rIZdK7!9X4wCl%BOXY2 z|9jjxfHwTUM{vOLlwySAm}8^k8ON(=HSeIkyn|Nqs^cT+>oi3e%$l!4sJqFJIO(8S zRwr4{1Qw@{j4gHe9CI8;D>{W%^by)xC&v-Eo`U@Y$0ziEg8!&$P?&$(c@}vG`SMZ7iO^;;T{$EOaA7(aFySJg7BhU1~t&(~APx9=+$Arx&Rq zp{XAFD{0Txs8Vt4BO5ppDs2jWCQS4S<`{A&u>a@b#Aqi*T zjdo})uL@4#{H%wz>L3`SVxO z5omh^+&D!!@%ywZ|ojEe}+8v8Yq)B)5E*z0Vv&nDy)ue zDA!Yte;|D^+N)eBlfpT+$C3u4O-6cfeCe1 zJ)rO;*@-I0Njq&SL{piLeW?AX9B=!>r&^cqP{3khI9*!R_+v|d6}|`BYI6r*^}^Kv zV#y#5+A)>oNKzTC@%+PapMcTGdjP%E4#%U;{zFD;65UJB=SOxmWr6@_7EMJj`bEeR zjzdk7;WLKhbZcG12%$Ds0xeaKajLNidyP*c9!Aq6=s$iVi4Selb=37r zC#Zb2{u<(?HTuAImVPyNao1xd-ML0;kJ~uA7o1p`0#5JzAekuCu`{qV z>+f&t$79V##JDGEt3O(ULWundkC={4jyt^88K&f28O3qXBWe=^R9QKWI=%o!pPvnV zNfT$WLFgQZh!bZ!H@x&Z4a%+WNSL8-!`$T^6~b|+;|oY;51}s+^BeH!PUY7Wrh`rw z;=%Z8d(cl^hdnzd6_%F_FC<2}TIsgd;MnZ=s}&k}ssHW|*h#t4fDr$(IM;X^X3Sz`*;NL_S&M71-sW6uw$gDtLv|q(h`j*! z-59K#*mf6Xyuf6CD-CxW=J~X$BSt()r&tRi7#>&3C9L7P8ed$c3Oad0f6VfHEcsN9 zq#pb@;FEo@AxySM6cI={6AR&X2eAogt_ZU``cY;r*+$ZHWe>hIZ5Av{>9XPhwW1&Ys zAMM3ke=1fB$wx{_C?~8&S`Q=_4@nKJy}|1^vM)yaG-X4xR(xx%PBxa~7VY)eeAJJ6 zVl|n!lEgd_iWWrsaYxREUSHKyPdb|xQV&09&5TZA{3?&XbmV+e^%6K`Bv#me6|&CC zL|l2L_;OZ~+H^!w-{Ez3+jL3Phsigmef!jh^Gx`=_F=wTd85-7<$?C-vO0#=8G%4uu@B`AR zi!A3UBYHxbqCC8*&)E4DNB9EmU{_f23IH4u>frzFMd|qSJD_wtMTOv^x@4 z>fC+3_a9BqBbn}*!t0qUe-MxU-e2G|4wD|t=$>nx-JHMVx0_UJ=fsY6YT2Z zgddrd%3bn&p*sc_w>Whw>2O~saoy2yiB*roajVrzXJ2(iNoBhg`x#<}uN zdo`X|D4Z3>j3sxC52%xkv>J&o5H~t5kbJ<&e#H+N!9%yxhQIdz3#(R89>D-;s3b`v zJgp%^cUhbCmd2TZInO`WgWh#F*L&bII0N>c;BN{&GrhY#OIWf06(kIMik^1gG`P~{ z-ex^9{dYiIbN%0ur$*=Ex1R5Jtv3guxFzAtJl^t-amCMruR%`hkzdh}tnDo=pQvzF z%w<>1k}rHlIRhSQ^z}sTTnX^Ot@_pjJTdx$$Z?$ZKj{>bBk4rmwh-fva@w zI0VdVIT9^I5$^Byx(*}#EnLn}LauklwgS{+!|pZx&*22V0`85VnZ$2k0o2K(dN7=> zHz)?JuH`CRW4&Q=JWWve4-sKJYKf%Ois{5_YhO;770jp2al(3u182Ucr}U7L%NQ?e zr`Hv9S#MQn>9S|WSszTGX9$riS^Mn~eb{k2%K0MZ7Xn`o1nvga9(QXr`9tX;C70Sy z5V|qm^MOxC0Zk{}>9PlOnehrtc3;Li%M@LK^|_>^^V{^M_0jh0_}jh*@{u5Y4`h1L zNh*Yfs(&<$*xg{d<3a`rXw?}5&=^r~7d@_UT> zB+U7aLqBfTc(Q-wI_G!G@M$~2b6^EtXKO-g&J_lQ4%B>ey0bP5qr%cSQ_OykU^NY2 zYCAr4d)_8=E>AoWmxpJ;gD+& zV8l5nzQ=cK_W@v}uLtOxy;HWbkHhYdn+)o&6_~V~lo%SvLmL(K5)aDJdthuu>q6g- zzfQn(Sxv4Nh2H>bEf^z*8&<3&im}F?SF~1?iXGw?`5EwZJZs-UljrOL>xumlBSt7u z-(-J?JU*_lD!zDIF~ul!&>!A2?}yWSJ`^{-t)>LpJ6K;5s+7E$$oT`JOhTP{rhagiGOC#2-;Envs!@5V#dNM2m3$M)jY zAdo1@LlP_IQw^vMfg~FkUX;!mNkmjU$htMyUa zLNVU`ON=f%(F@Wja}3`zeFiDu|1jUj&Q?PtMkQm|au9BwUb8^vm+J28B36K=wbA!t z^<%^e#iQnTDAcdDz;|UIK}Ne3GrIT0T;d@$1F0XE6$1Uhmym0AKLID3zZkhXm^_05@MGS^xuK-V+$z691eJC#F6}X6la_F zI}QqdJk>yc9LJZHuR30Eex>{VwyQu&R?Tt$i93}l3`hyQQ3 z)1L~(NoHiSA-#v4H`ZA|*IpBt1NeJk)0B#w7x?P|AZg&NrO&6mtm(jj_2foA^0Wpa zi3ShsvP=haJ=xi!%#@;4Su`?bXLOfTozT;>GzE_OQj|TZXKzt||5qPoSO2ETi^8Hz z*EE{KW%9#bX9U`&=7*#;5b47`SCECo*zc5HU2C{|@CLa($C(D9`PCz-dZEmwCqQz> zv!6S2DK;z*Vr7W-<<;JN!_bIWN?u1_s@4fj=|ZkTH3Ic%Mpg6E(}^Z$`j~m1M65_N zW}~=b?Pe%`D0-|msUV(aQ`Qvs?_WGq^9}Lr^L>Z)=i_Z9C4m0JSprSNkNRsO)~#L6 zVtVEYtEXq7)a*%TAdvdG8bBEZl29YVnE#V;m&Y)7Y9!+Sl<1I9f0L)_&TBVq4&3(l zP2SkflLo{sIRmhtCW-d-$EJYrolYQU&vrVIe1>gW?)pr|-J^baH;^?3>?Vo*mowLz z<=pwquLnu~cw1|9=bR6BSL{%ICHzu+glGuZy{>t0Jq^JeP@MWl8Ba87RZP?gMZYpV zg9iZdTt88C)nAJPK>wc|q*0u0c-B2aXg!Ep4+5*nd87se! zwg2KC%y~Y@K-zWSc>RZTNpydSg$Y`XN{?r1nBI{(7yYN%~4}T&zF8{zIUxh@J!7_}YKSU)#67(!nWG z-_k(`_iv>Gr5-iI>^UG!rPN0Qu=<{t4KdU7KC;emqo}>p-5)mk$Bp|#ZMd84;{{!? zy9YTlZlu{{D{p6p!vB7(XI9q2Jka0yWWVgCoem+n|H?h8U!nr=#Qefft!`*vi+vax zUvN7-czViD4;27~fd&bLR?vbOKYDr04oPD4hTA(-#JbjV1;HBzZ!paCzro*@=qpul z##@L$VRG*4IhPGIzCE0smO%JtkJ}hKaR8h^&4VqFJ(BJ|Q&Z!M_Wnna||2*kAcea%Kg1tn@9`)3Q|u%D5B?lG#Q(`(Vn_Kt{wn*5zs}!a z-|~0(JM26D9{+%S&p+gc*iZaZewaD<7yJa*_}6%Y_>WpR57wf!V%|FVRc*L4C45m4B#D z(`WEQ`doc3|4g5+FW`rbwnj&O#OP!s^Akp@(U<>hWEz>g&gf?h;tnIv$kT$1d?Q~A zHij65T63ew7^#ICB}SPRX^b{%v>0QeFq z<|Az0k!Y2qq5+Fxv1pww(KcJrA6D_SW^GtoVQY!D+7kbVKL@KT9a#eYdDt1N9bM3B zyRjtJo%KMwXK?jm$!PoCVIv<}Z|{R%AQfg`md4VdX9#?jmdUbEK2o=!v;&Zg^n1eW zE|g_UMlTVMHx~GN;LR2{9kfLFZlJLx>kk^+!pva=vi+{FMh zLmrb%x@B%;F1jK$r+nrlHU@Yo4imhRTk|XG zx0h;%{gtt(Zdh+Fv)+zKAT~BMX9)! zq-(DHxu~wGwntU`oWMEVd)Bv$Ktg(De=l)gJ@F}N;tKpHsYo7wLgS`Y974|ZI)ijOH1@Gq&lHi z&`Z4SE?P0^R{Nv+WIa|{QHW&mk{m+qSN2CL1eH)SYBjwK{R6%9^aY%HsSNQlu@)pe zylPoqp%#$FANkZj`pDjyQp~h}@gT;n(9<5hRJ#8&>*D~)2I^&yHw)QNRwUkGkUQxe z0f>A?z%HMWFh;RrR>Dg0mN7aLIm}d}*=X2EAA=ASlCcUlR@lY@^Eia8WL3hH(2>9~ z4xxs#@j_Ofz$U;okxhbYGMfVFSw2%>mrpf(rm|W#4R1A@4&X#n&0w_uP5MlPsAjX+ zxokF@0~>=rfz3t8X>6V_CA1oD2KRiVw*WTA7P3WbG4%7;61J2rgIW&PQoJjKYbD^T z*!gTVyMSHDE)pThcNt(8!91T`%r0ST*rk|_T*fYEzhhUhE7?`h*RXZ$YIY6O@7W*N zddvx~V}HcF;0E~Ji1%9YUJdufhG@B;^eQ4@r$fis!)((*sckDtsVj zKS+u-kRJL&Qs~EYNDF7;sYH4IT8{yV*FZTfMvWV9OSBnk0I7w51+kToW><<=ngdOj zgcEEK=5@J{dqYuY!cZ?}pmgz%LcHNn%TOcopf5u$n+dm6gCSQ+Npd3O$w{`<$x36! zFjKdC5vG;E>iI4Qe7K-=8PacoS~(MtV3FE<)YmxBI3Mno@XJTq@i4tV!6H8@&oXz! zg_X!-3yjhc&{b~8MX4zzz>lHmF>GnpVz6NJhY%eZ6#lxc!YnsYkhHBw!cSHCbxZHmUwvv{Si9LW#eI zf`e4sY$f0$fx89DljxK>Lt2C3H`A;mio5eo+R06{ z(Tv!tR}4_A7g2GfDa%6I8QLtpl$&7mFS3tWfO=u|F|wyw4!S0ZUPSVnk|!IrQ_2zb zL6OKEz2uTF^+)QJ@uZ7^KlBoB)O?q)Q!ix@YM)Q67ax@uhOvi6ZuyV|;Qx`mGo?s# z7VDvym8U&=S@Hj8^Und24b;o9wqZT3P3vULcdTcWu(mPvwpjUShZ)Kg%zvzhW-=6e z2xc}iZlRdntb#sQ1f#i(j6;~(0LNsQtss*nKq`|jt)kF76;h^xoeQ~h!OW!;?lR~}B3u{Dc__|m6HgaFr@}Q$NUIFWxw3agsv!sh4MUqTQQf&nk&971*rKLjBn*+J-QpkcN?ahPKmI|pb1@ovS z&{M>kPlj0C`5i(`$D1zR6v%}sh*#irWpu(c!v8>zl^OwwLIp;2! zIgedDyLRCmcKy8Cg|%$+{Muy;*-qJt9S%r23@tobNE%U0D;ZUoz!ECP6eNHW=H^fC zAcSw>jG-XZ7fKwWcW{drC~Q277?M9Wfz2)&QJTP3kEkd~V80(l`W?ljKU7jyoWORM z7K6q&O3TJ1uun?~{(Wh6X#x)&Q&y6|+gFULNZ@@c0Ox}%Du*U;aNms{MRmB7Y6X$< z7|C7=65$ah)B~b>KnxEE_kd^~kY*l`<{l7F%po3Of;=F>PDtGJ=?fOK)$u*1tp|8m95CCk{! z6-$;aVLzkZ1ffo4V&)JbRznzO5Ht^=8pYB2NT)qnF4m7ZT0Pmj2t8To)ap3eC+V~T z!o@lS7poFn^iy2)DIBeveCY%;$6SSB-h%J`gc%0Oc>v~PYcRvQj@`oUVq4fl>~Xe> zy?{NG1MDDnPflQOg7M}&inr%ocpsj{2lL^)oR8;Iu`{xmui|U?Rs1@B3%`qR!LG;S zd>4O#zrqjjgZwb|F}~NB)?ACyT5AbfcP&-R)&^_CwQ|wsI6o+JtZCA1#G7U})66o> zA{ox8G~L@w^J(M>`7-vI=0VduVwxvS^G6vU4P1l|J|NBJX{Nc@G`ERxdh=r;$sr>p zEM&fbX(6|U>@*>-n2@(aew2{V)~4A(rW4x5bf=l_Y}0M#Bed9b*O>lOP4`06z07p4 zHQiU4?hU56Uu>2LBS%;Za0g!B)KLoER;Y^rHW z9`LYLlJ8;b&2V=}JYl;`^Lf+!2xezd)8b(>cs}a)R^Ct8D`4Lv>>BFQ7=&tzQDY3o z7m^GQ@=Da1P}qp3Aa);i`JP3188V37gI8ml@apW(csbjQ*I-ZL4PyVm+YJ5`?>_i# z#oG+=p9O3W-XP!^%yd2m=^F6A7p;c&X((mc0x4a3WQ~;e5HT`Pn^jLVEF||*JCd}L z549)t#Gqx<6Ny+6;6(=HJWn&cAM2}-n*pPgpPhlHpwR6;RO&4tgmh(q<7qcrXC zBYNO>S6AfirZN3c6fD?CIWxu`$+7O%$p z0Zw_8d6WJ!N0LfAgd`CcdPJ=!Om;ZvG2xVx2(*^YJdvmJz6fRTcIbC|@SgAqh5Cqn z45-FFK;JAOhu9}J2x!oI!p-#_@F!O@1ytC)AdX}S04;~Be3sIeIT_pgFTJcV7u@d*nQs({Wz_WQa@;-nMfL_Q80hz|9!9JVMhJ6`d z2K!3B68eSwLO`zP*Ta4@zZv>wz8R1md+X8*7 zhFYdQs67b%QSDLaPiap^tZLQp}()a5B*~eWu<+leFpt|?R%639_T?j z+JhdWqh{#6_1;X^Q}h(*X?hy;3_XKsdX}CAJx|Yrp0A^Z7@dtS80Wef-J$m~dP7e& zQlY0AInW0hqo9`>HP9y-6QNHwrb6Fj{2gVXiIR>4zhXF2MTui2RMcb*C7%PX)$&^C zOZighEBFfNf8>9JzJYImzMXG}{seyl`hLD2`dj=hU^v22V^K?zk&`}pALxB`)LPV- zbigz9Oz63KF7&~o{-{<%ZJ7E1Qjw@Jp_mPIM;mm7Qx9l`gFKF!Yq_Igr~W}=iGgn# zav6nM7LR&005xeD>dy73Ejv&@{*8KZ6x>b$movcKJn;1N>wgy(=@s`;8_r0tt?7uZ z6`eg;&=Y_Q)I*_Fz#a$PK<~OoZ>H0jQ(s=J_-I>Ze9kjqN9_`&C2`9#lGqQk&$W{} zdIgNdwjKxN*B&DVe0e7ShM(l$>izgB{+$^%jHu!n_85Xjsu7e1&8Ue2k~TER0RMuC zdhVpb)&D{EH{ln_OxFOyBn1$r zM*v~O2v!e&&>!OU&%+c7V_Q!uE(1DScWmB3xM%R{Dz6 zsPgE1OxF}51_8;=+}#JE88N^Tq#Sjp8EEy6mLBUf?g z3chJ1dqt_43C{EFj*V{q^mf(ai}(|a4g@DSq{{X3hw z1hB(!n^S_`1->-kAiN4U;a7GACo_O>+VjRQ+CQPlB(={} z%5K8hYsN3^btp1Py=^LGH{see#GejDCaJlmQg#zgJm85?WRmJ>DrHx2%3EJkQFs-{ z-Bj@iPPml6iI=?uSQ?a_PlOwa{Mi4&PB=`Mi}`!Nn?pU0yj%)%HOwfOe}lSTDlG%B zblAZMV+PE4D8f|$)gCI-RH!rTXV{UJw#`z!HDLY}>I6;+x+p#z?qP_hXjXai4ik@jnW~&9j0&gfy)sXl@rS@fp1dkbm-t7DXj3W+b}CmW{dX|CC_6vN z{s1*aKg_c9!&;T(i{On)gK#Rk>N6=HD({Ln_WY2Y@G81g-c%gB8R?cT<(uN8&FSw# z9g?PjdP<6Y0{1~vJqwc*+|INJx1!0OjyD+jE837Q;db(X;P$dm;VAElXR(Mg4Q`mE zl%33`PztZzWaF*$?C|>RiZ%-8kG6pE^Z=eEpoL6-`9&BCXIFmH06xO$qHMf%mQR1+ zT#IlpN!e+jx@mVi?FdhC>`Ktm891m6l}UEH+e|~HB}}w6wg};<&9#J``ZO{D^QjPq z!nrGfgYbA$z)5&L6zUXtCq!%Q%Kahx#S zgM@KkID`vTMl&z)E$5!pJcKtC5-wC_JxG`@)Pxz$_C+7 z7-hQS;n&D8$`o;kzr<6DPh%+zO zrn(DmDvLXz?txkdMK~V@9Q`dnNBZ+pz=+QQC{@;?9)ux2&n6p2lK9&3u zO_S`lu%c`(M*37npi}h0s3QU=nS_)2cuGgPmA@TM>5yB6SANPwd}qHx@o5ZDekMNA z?#S*(5ufb-if@EV;3u4jufn3*y`*-Yo3- z;niZa82%K$!$sWz_a6Cb=gQZKS74{rRlL$q+PAuf@a5tUfPz#$7^)7hs@uL!d(z5^q7CQ7C=?~5Hx%5j~UZ<+h=LrZ7`^ z2NloF6lH`EghQ1Z+3asBTZ?jBYr;OqI_7tHWBHZPKZltG^DtCA>;xxY@}ZY(@yLOM zx$80v<+LxtP-yp%1nn;r#U)o~tc6W=q0gOenRA%~nQse1wp5(DTm(#Uz!)bmO1n(` vdy^7H{dc?y;YbDXEH>Gx2f;>fo=#;FBzz9zE4j%i3)Fg^2GyDBJY)Y4p?CvR diff --git a/app/src/main/res/font/netflix_sans_thin.ttf b/app/src/main/res/font/netflix_sans_thin.ttf deleted file mode 100644 index 4fa549ef3b92b472cc382f2c12cd66181a82d7e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146064 zcmd44d0iHcUyTC^^R2s7{J+?h!Nt>5MS{qg>S&%I|~p68rr zJLiss5<5RS7ZfgX+9KMQtY}c+L14qiNUPoMM!H74@5^&u!_<{{5bfghT`pGHTiMC0(-lvh~q~ zXk!Rrq@#1j+?_A3jVDC8i4dQ>87+%CsUK0Hzk1XUnlWc-$DGTj{Rpv#5gN2&W_wH9 znD5pkp#5^>+h!tR_^ZR;LVPXa@iXUkb$_y#oT+!(Rp>v3=XWk_@4Wi?&(jIfpiNS` z1pSRz5miE%2JMmfCFpN#Wx+#;=U}*%z#Jh0aeTZodB?O-89$H_!X>1TcY~k)9e-bV z`~F~8^o>@(GvY-=hXDodKhzW6clHzFcg7X%()gW`TJmJ_m-rJ;5wEdbB!x5+AJhyY zIpj;s#D@fcc9e*03TZ1HyXs{MV~2vK1CPuxYAi;xt-+kAd*MJ^w> z>jpdQQqtMN`!ueZ&__Rf2d)3a4kJrO{OI?@B-Qu|PXJ}4)&m;6H7u3bXaH$oV@NK4 zlNU%Kts;fMK(|!>d(08oKs)Yt1=_g=coHK9h0q2euL80G_(Pfjun~|0NC7AT;{lm~ zVt^H3W}AqEP9NFDtn?y=k7b#?6WE@*aG7wG!9uff409Jqj5D(z#MnDQc z=Yjtx<;W&ivCvB#KIz0E>?6}WVD-EWL*hIf?}?`k$>(8?Cyt-V0FE~~AQZsU3IGz` zK$l>I!vG;-59wfkCc{Bz6XJS66QJ%tKq=BD0&IY4z${Kn77ILYl4xb1$z%a5;B6*KE$a}7f^jj%B zQD{P0q%(_Dej=|!^T|Yd5o;=w1Tj6CKtDnK4@d;PL;`u3{+(!rO_=WyqNnFcvY;nv zj1hyNBWc3js7EkoFg^)(A-oG=fN+WZ3i~Jr_}l}SM;4Qj*t^jf6UODb$c|%9#puh= z4MGG7=J21==t1J(coDm$19l26wCp0WM2bkf5SfFb5sxu`yOit zAOwQW9FH>*<~UA8od$rN`0{y^ABZo<3C8z42h_(7cY&8m0KV;7?SY+m??nEy5^cTo z;dJ%TlGBk5r5#@jpg;N9bzPz(Xf=Ru(8)uaOg?7NB6A>*(-!muuD;~JX@kB$;efL5 z;SG-DdJ^F+(t6`-P3niV0JoE{u9^qdly^K>N75SdtRId~jt>bZe2wuw`C5|tB~`$C z6?&@TW8?Vot`$BOX^nW-2k0QJDZZ}UFW~yafw_C<<;LZuTl?U1me!+pU%1CVwBH}m@9zO! zfcfbEUI~WoZS3KHqdVW91M_zM(>uuw z;ynj21L1W;`uNe?#{h0U{d2wpoc!IJKcxL1yzP7G>CNLj1bF$xn~(m^abU?9tvBpm zZ#B|h;Cq128T^g&Gzl;VX})}&^Lb&en6rac;!Rxlb3oc&X$~G9C-u+Q0H+IaEe9<2 zyw~Hsgv(s;mC;fiKEMAdV5|}!{)IPqat%Pnc`?V+{}!aZ&FAn-!1tUN_?rLU0LSBh z0UW1*Q4)XWd+>h?9N!%5{~07X!1p=;G*}AQ?SW_Uej32{%M8FnfGdD40KRW{{Br=` zyF88auNHu}{9J@N@HXcIIITJGwcaJsmhZCxx#{{0d|y7Ww>kg)Px$7z?EoGEF(2?V za@F;|BoigMh;{s)>k>5*t+eldL+pHAa-8u!NUkF6#mCThF=Q|2?a8i-0@pvd+&uwZ zWEjywKI+*L^mivIk>t)i=#)1Qe+=?03UG#Wa6T_+Aj7%*L0^I$>tQGIxSYbekmT+@ z=p-eOMVJ>Ki{OX;O3@Er<6NKM!1altz-8PjlInq6X^pqIzL#*u@pc>0N#P$6=6ll( zHvXnsSX2o}yUnAEa2e*6W7xx9y+oS#Kaj`g@6l7F{jmdWa@pbmu1iaDmdjcI<{QT8 z1NoaS&EYKe2j(L2C(bLp!0E;D$#obv41}?+eWf%?qL~{cx#*=Qy+&e~FZ60oTZz7C zCzwd4=xKO z+X4Cm*C8bRDGOu8nvrN3H$;~L@8kSW;(-+;1a=d682u3M1gvw3m-F=w;Cq0teM$b{ zZ3Jil-n^w;&%)R)gAY?$0KF!ee9Qr5eg<3wd;z#beuB<%9b;eVPY*-Tu220-WDF6q)-H-t{e$AmF*U7#P~ z6xvGKVMj@JneQdYgGQ2oI@}hB1B?bJ04hL^&mOEV=>LFZfJQt`Wc=-c1R<5=N#Qe+ zEdaZL`XFuraNFP!#DhFx9_O|Jw+ndtu>j;t?=rkoKsRoy*;yXw-$vH3siX??+6wp# za22q_^S(76>n=lM@NNU#1K0#;G=l zZF?e~HUx6fHn%?)dEjZhITQgn&VK0~fjkbgZwBa0Z5+sH_j_o3XxcA@DQq=wouOf# z#^XcNhK7f>_si+nFTR-X8o)vT$8jDY3}6Cm1Z;N$4MEu?H;{`+dj`-8*ag@Fc-Yg{ z7KHaA3>s06%g5Y6A3)qBJOy41I}!L;CcX|{c!^9Gz9reh??^V%TF5!9+t~mYWbZkW zg>V+aSw0UyCUAbe5i~doC>{jxL(NbfL=1&32i`4=K>Hrxc8&yB+`0(o(~`{imnWUe zL(X6Ch5P^r4Ty7_a39HMH&FIR*hx_+`xRgv;vwL}c}Sa0WMVtgxXk2jKz2dj;daT7 z;IDG*Bds_J`M)M=AqMTkR^oC-P$12#xAdc*zoMUgNQ0dvjhV|cj6-ZBQM^5FQ%Jgv z1vb7G{NT@n0K7%rq{sY7hSQ%(BAX1J;Q{(J$^%#vaCj8&QUK?_9y=F#V>y47cIL*CWTJclKmfFnv1~umx*>CJL%Wcv7#r6MbdU+$HvWOj18xTjBT+tv z#4|Ny#XnJREaFY50r@N)eJIIrv?-990?iOi4-1+ zA>v5L9Qp(ze&h+H7)dI;kd367EF&xFDtagFp&RKIx|KdeAE(dLSLg-$5Bd%Lfx6fj zCSz)5Wbh6!2g_$Atb$EowXA_nX46;;o6cslg={gqgWbjMVO!W%_8fa2KFpWc>+FIc z3L}LOAzTK2+SV)=UBhk(Ad$j%2;izJvKYGEOv72wAksf z3u14NT^G9{c3bSu*q>x0WNKNKEML|sTPj;6+aT+eJt6zO?4axqvLmvWIY+r#xmLMexk>pe$RKBb{p?qEW zSLJ)k&y-)Qd{x6#qf{}fSXHLVp=wdhRn6D68PcBI^yK|Ni_Su)%XPyAKQI1dB#ER! z1DOo&x}5BisI-Chf=b)yBlP$57=4p|N-xvz=~WiRVws8=m zZgIbONIW7@>8$v!cu~A0UiMJwWULq)5E~UMi%p0%#iqqNVjE+p#boeGJ1%pbX z<6=EjYL07@s3iLZDpd_oX|r;daN98e4>7??s@?GUcP{{{W3Y4gnrpi$@ ztEPLXbkCEUekT3}^%p8b!mU_bC4Sg}>#_gUERl0FEgA6_MW(6(~*c zw2()n^My7tlPn;s-38oFCv&cHWjYV>nCk|3{cl~L(ZzHj`n3Q;X$YQSQrSp%jlrku zQSQ6*{@wWe#`JGZ-q_9WPsb0&FHR=pqaYstDD1;iA42c?ux~Jzkc-DY3HxN^C!;F0J{&>F$FOxjKJ)QkK7R4z-j91eh6evJw0b;SK5qUvM+!xLbQdB07yCc$ zKi7Yz|F8XT^uO7EqW_ux-}mFprC;4I?~m<|=^x$CPi*klA1FO>PJ;883xH!6zP<3z z3;($A;f4MSuUvTf!V?!Bzp(r5WoN%8WU|8N8y{cx6L>hrMPZxMqkvZdXXx9!1@N6M zcy1SVVpSg%P6(e0-wIcRtD>(MAO?xRBTj8FZ#Re`km8;{cb=3ljYtX&N3B3HN{ka> zo4EhPB+=##^S7u2NGbd&=7`|g;xh3Y@rrm=ye3}v@%8cN<@noYxKDtOr*$bD;v@5k z^U?X3d@_6red>L%)_fX$+I%{Ex_y=*yqXZ-B;TpLK0;l96~1eIH~K#8yVG}3#GKvWf=MPTD{>fP+sa*XUm$bq_%5o}uy7M6+lW>tbsmgFmG^=u`C9^tV)`!y&&9 z(i8Nr^nLmXRZ=s3nC^w#?V*37Z_$4GcRH48*%G#%`p^-O75CEL&}ZmL`Zubg7Wx6X zgj2^KSU0x~yJ;WW41M7d_9!LnF`Q*AVsAiq_zl~`o)kjZ zQ;=;%?0q2;ddPV8clJ5k%YMhc64dNP=onW82lR<_A&)I$ce2~r2DXg#vK8!pwwgTv zon#AJ$$rIFK`Yq@N#|m(u%Fp!VK_T4L_qfaDEP7C>>69j?xkPTZ)q8wLd)q?8b_0; zf+n+91@n!M2_>uUEctZS4{2UUtAKK7|khPzRAB%q%6Ge?UK~#z* zVzF2zmWvf)p;#nVipio;Ocx!Z99&5OiLDkBM6IY3^`Zg1DMQQ@v&Ag%pZxiQ;bH#aLr?^mDBzB35#Ye;?;_YI$xKvy&t`Jv>cZjRR z)#6>^T5*lILF^Up6K9FDp&PV|>&3gpjp9AxCUKqEBi<`65a)y2-6^WX4zXIiUz{rL z6l=sSVlDK&I&rHwQG7t01f8ND+FygXU2GP2iH+hz;$&zUP0$CYhz|=^!ORY^%g`_X z1r75Xc7%P)4zsV>3+y{~jQzy^#GLGuAkdHK=j=5cn*UiK>@UIyc18%GAJa?hufj<7 zmJrB-ST_5cFp8ZOg4j7Bn7t!}LI;9%CqzMG9nC%xV%W#RSawl}WuFQ%c1cjM&jdN0 zO&8E&+DKofr`Z=mJo{2mvVRCFL1ZGdV;^RLwrpUOCE>I_nF-Lv2s6@aERkMkT6%-& zsFUgG&rHKcurwCH(%DFs!F-vO`9o_P#!}dDmdgB?jg4YiERbcg2v)!%Ss@E%IV=>~ zcNoiK;n40vpyQ2(jyHytvKUsz#zNbRhGrfM-FzIYWOC?!3RcbHp#dtPtHT>eZ=(yL zA=W{cpU4u}Bw?D+EVPi#V>fo8UUFZ}R zkuIT&EFrg(Zec!|CoF{a-%S<^ON9l(GGRICB=f~^a-O^|%HTO@B2$E43l9hn3J(d7 zKt4Y#Y=fNM0XtzA&e>N8tA+c74Z;>-tFTU3Bit?YKtsJt_^WVE=oj7>&IlKT^TJ<* zuZ6FKv%+QJAHv^+A0XS`6}}h#CA=eiAiO7>7XB%GDZC|oCwwEk4e9@q@Pcp*I>5`q zpM>MWN#PaY&%$%UVc~hwxo!bS86Q zg=~f`a<6z+xChex9r0Vp+CcajMnKNrM-~a|aq{p8qdK!UQLq1qdD zTKO~Vi_iH>*!?_SvOffUArf{_E9)b*^?mgDRW6t7Afe~9Wqq>NXTBipoOX;iSsX|f zk=)0Whf!7}=d=w9d09IzA4CLXOwj88U-nE}WT|@HK}tz*NVuX8(Y{YK@;JS&KvB@# zxB8yGEQLd_Q^*SXL{*=llAg*wL7813+gPAzk+teYWh`%TEc8O0J_bLvUetnT(Xg|oW%%kpY-b7d2g49O-#lHP1ePEIwox62Z$7tiaO zg$rsjQ<5oJpKQ`6rO1+PX_n+POKPI*w^Fh!zhzF>{CTp9mX;Z^@~$@WI}DVB_Yp-( z9|`FrVSO}Q*0;K*zHfC)pP!{c)|Z2DPD`IJ56cjiwelEqx*n`y=e%(kJk+ zh_Kkw$9R|_%v$;=4^xC`OCPZ`$lb^U#!C6>LltGIOx9=3t5NjX@+LIaQ)Ka60t!?k zKP9iazAr`L=(8&vW90INF>SKGbe@)uv@~~G!x(u3)}xG1C$EoH)yt*7J~F1yf>LIH zN#Q8y%gSr%v$+G!eO84dC(uq(vQy#jNY93zP8{yP1C)jW7Z|TX*7sXYABm954+Prt z`YveuQWcH^Bh#c{2AWOH>&wWKVZ59AWYy?)%!K*_nf5UUGSeFJ91VS@yuQr5Wodop zyx=}dUI^l;QFs`UibcZfO{i}fqmTnd|4k56`$7w*8sXm7d_VVX^X$2@x4jGPzIWUU z?Y{rJh1Tbzlx=kM(PsB^<3`W`$Q)D9+~DXFnmOrc1jYe^(Uy`nzC2_ySui3Y09jv& zM&7{3$fri?>iHI8O2y#(WtcrFkX1=wZ6~fGG6#v4Xx~1^4tFtEoRgsAJ3l z!W<0;WO7GYpR2m8Z&=mjKDxdS3;@rZ35|We>oLQ|$@N$(y$$Q`y%$n;79@b4rjZ8M z36ko%7q+FAZN$lQ8ruzd^gbc17uMquSbldxi>#xmY!=I*SD;hte3)oze zLw@IajAXmMb{&U3^%Hvze*J?kn=1`BMaQ~kxpv_mSRE{0A6F{-p1ewTvsTvz=t4ic ze$T$6+gzdaPizhSGc??A=x)XAB3mi!$Dr9GA197-~-?VL7F^?qqG$1^@OX*1>XJHL%#q$tsr>ei#+B6dmp~Cb;6@ueyWy z)5ly3X%{3m2L22_^yBP9*Rwcx!Nq0IKLa>QJ!ykZ^eSv_18nP3Xz;)MuNRi=ThOyj zY$oetCxlp`O_&2OXb<#`ZO}&dKp#8=o$(~J$hV>?;Ev#J!AFDN z4gMx1A;b|<9nuxDIb?Upv5@njfuW|*g`qn`KMlPW78_;>s}Ac7TOYP7?0k4gxFdX7 z`2O(I5n&N&5rq-e5pyC|L_8aDJ~A+}Fmh$&j>z+o*P>)mmZ<8eIZ! z3QvEPdDxkIHq_*@vh=i#W#xU@&55)@v?YBd|G^Ad~JMd{G9mJ@mu3}#_x?k z6n{MabbNpOm+@C{P7tV!R;rXHWtOs3IZ@fFoTpr->``t}?u768sPdHZZTOonE3e^P zAOvRu8kG&F0+p&p)lAhA)oRs7)i%{`)qd3x)k)P`s(#g%sw--y4pc|0RcezuUp-OX ztlp@8R((``O8vI_qWZG>T7qvvNJ4CaCc&2ANT^I`Oz22hn6NToW5TwC-3j{>jwGB+ zcq^el;hRL37?2p9n2=~o%ulRLtWRu9>`YvdxIS@f;;zJfiH8$UB%Vq9An{V-_c*2v z&_rqCHA$KbO|hm{)28XvtkA62Y}M@2?9;rZxvFK_7_Cj4qb=9gYumJ)+7;UM+O67M z+I`x?+7sF{+7GmswBPH9EqRt9wGXUw1-xUiX>qTb)xs zTpyv2(;M_@`a*rReyV=9eu;jKzE}UO{=EKE{WtpSh5$p9A>Lpy6dD#9)*AL2P8d!Y z1B@|7oiW2$Y^*oVGObW)3vRm=nwvbH2IO+-B}FuQhKrKWzTM ze98R1g;)YCQI>d1lBLNq)6!*GZP{emZh6AeXZfS$wB<{yuQkq^Wu0f;Y29Z%Vtv*6 zw)Io%x3(CY#+G7hwXLvivpr=yV*AK;*-q>s_7r=ez1lw2KHI*;zQ*2b-(lZlKWIN{ zKW*=~U$#3_hNpz2l&93EEKBK0=}p;@vM1$e%BhsMQ!b`lPPvvEk{X+8NX<&EOr4s# zHT7`nXK4v(Gt;)Gy_$ACJtMt7eN+1B^tUsDGKw?0GPY-&%s8F#c7`)EK2w*Ol9`{m zFmqex?#%s}Z)9G{Vp)M%(OIf2Q&v`1X;yXCnylWeqgki2zRbFk&9c+73$uH&w`A|k z-kW_W`*ikaIYBuwISDzIoSdBUocf%$oX(tWIlFWA=N!p7nagqmbE9*sa+`DK25J@;dW&=IzZply^Mu zYQ8Q%C4Xc7w*1}s`}2?FpUi(N|9t*u1-=C#1+fL10$YKjpt7K`prc@6!S;eD3i=BE zSnz5gD-0}*F3c#*FRUtTDx6ukqi|2*!NLy;FBN`YM2Z56bVa2_jYYGImKAL++F$g? zqEkih7F{a3TI^pORh&?qQe0SESzKS-TD+$C;o?KZZxmlFzFd5*WO#|D#8y&Y(pJ)0 za;W53X-ui1w7j&wbWZ8|(kDufl%6X6sPt0lH>Fq0NSS|GNLfsosw}B2t;|tYT2@`w zSk_v$rmVMYN7)l)`^)}VcB<^HvJc81F5h3lDhew)D)v{Lt&~-6t2|oy&G@A8N2^#> zMpbRq>Z-n~?hm@6HM*Lt znyQ+Pn$ z_QAAk&4%WV<}J-fTUg7~mQ5`OTfS1oqTr#DWYIlX&&&-8864^KZk{hK!5HceY$TWec)+n%;p+rDfU+hf`-?N#kF z+gG=5Yk#W!Nc*YwkJ`WO5Ie#;3?2C$jU5X+Hg@dpINI@c$K@G8Gc+^uXUv?jX2!!a zKALfTre@~CnXk^$&FYO?F%JZM$zfJttyL^_++2oS1Wd?!>v5=LO8m znAbUP@4UC>i}R<>@0!1V{*})B&Z^F)&Ly2aom)D0cOL9K+IhOuxxjaUZb9>cg$q_L z*s|crg5wKLFF3!@e_`>$)`jyHE?c;L;S&pwFFdvIt%c_oezWlEBC^PTk!F!?QTL+N zi*_wKvgquhZ@Y$f8M7cXV&+-rBvZdtdjNr2$J- zOHE6&mewzATe@=Tj->~e9$9*P>8YjtOFvus?K0D{v}N_n7B1VgZ11w;%RXK1zudIE zd3o>hZOdO>er<*CijWntD@s@Ntk}Qe;)?5cMBib%qx_DUcl6xx)E#|y9KPek9nO`* zSISlvudG^Gzw+UghgP0h`R>Y(R$f}gRt;YjvMPF2?W(3#ZL5~8TC-~7s;5@*{%{m#ywuy=C>z)q7VTT77)=={v*jY`Sy(oo}rPT9dJ6 z=9+D5_N_Uy=GtAVyQ=QmeAkJ)F0PGQTe^1T+U;wPtt0D9>sr@sU-!hizIA_G_v*T{ z>poid)w-)aVoy*{OpmT7t*5wWV$YhMO+DLscK7V-dA8^B-SKxf-M#1Tlk3IxS<|PRl=BdCA-}vDgT#_@PzvU$DVmm4~BUYn1Ri14rGcr z30;_7CN+nL1yW2YJS>8$BpO6V>&+F@{mlNEt$ET!w5s6-Off+_Dv|kmySp)^JdICd zfl~JxTq`8eFH6TXCrR>06G`a zFUewd4!95xJQ=GEHTx+FjY1+j_Ybs>^?`gk9Qi7oVDj}5Kpv3NyIogslc@^nVmu*g zBGN@tzQ~T_&4|JEEqwkbF{*KB+1}4rVit8%HxR^D#k+|f2x`Y7-DYqsuBce-ke>8G zTXS}HvrT$_J$K&j#>UrJM3C6;#I9-vZD#u=zWSN|%DXuVoq*b}ck%IZgq7NPVXzkaAc-Jvs z&C-0;WFwG!~?&>OwGaO za7DPnDz_?wDczme)n#z5r`^t1*kPwZ`@#z`CUl z+p@A|<=L7`HIAhmtsn%kd$Zv~Ke$x!+v4`Pd5Dz5uYt0HOx5~r6w@`(%ot|O$YO%s` zv4U7JZX?D$245AVqijRJ*2lBU`Cj*;%h%6MbU|9!gM+^{PEC%kqPl|gqO6(O*)y|> z(hKT}7hSc~8jLj-XyeH>rl^dm7R%HO_cKsC$>C@U37wQ_uQ3{H?3t57Lz*0pNm>?T za3~ZGLt=J(e719yHBTnXvnJ=q$L9mrW{g>lF~{Od8}UFhAF;|!N-RLNmrV@}9o%bh zz?@yhl`W68M7CLH&E;4(<;}^;oSj$NUA+FFjpKQe)n0Eh2NsuDlJ{(t@SQOuH*Z!} z!LAKYzC=wxvbD~XTxYY?ftNTimQswxm&D-e3B|b&zPcUUyJl(cUNh`{m))Ifn1D>w z(StUAQk~d;@;K)!VN!hD6&_b0KU1Qe%*o@W$d^lT`OiFFh6a0NO7y{VOlTc7fNvh z$~Q{!A}Jn^{J9i79jlDjPecAxDPAJQjfl5Nj6iy&E{;t%&F5lJEu+UysNCdv9Y|Y5$kFiUssgh zuz^fCgZ<29!ZrQ8U(=~dO)@f3#)?Nh`invt-!odf#F*VW2pA7nm_|Wt%dH|uqaa#IupTod7B2zp%HB+qRsX*TEg|1Tlhbp ziI@HJDM95yceFw&SL+fTs+`J^#^EUms_ayKa#DghMX9t5Gmb1zO`(lexqkSFX34pP}> zY%J?gBt_-e9duN#R-L0%I}!?~QonSqHZxPJO@D3L+=k@3DnI{miE(MFgv_`$d4koZ zR@p&VP75Ff`{-UbPv>hO6jCt!>7=LriK{|woMHmedB!#xk~0~jn{q}QIfS2k6lV_! zLx!xuh#}!u6N_}o32})v>FG6zaS6$~qLh@x#FR9(y2zo^=H+R1{-ez*g>}U6WO-6X zMv^>v_z0^)Wgblf zD6~xXt^kEzPOb<}3b4kcW{h%p5LPs$Hcq3>$k1vs{q@?>k#*z!hsl61WkRN+bzaLH zy4aSWw%Sx`bPc|dKvU>UcFw~~ay+~w2R!<$hnKk1tT;)om&$Qo;!ZQ5ZljdOd5JqM z9_8k;H$A+>otB1jQ>8S{OWbKjq_s(PIWKXirJ(LasT}7e?lj#%InGN4(^{muoR=Vt zP#5Q1`QY)APRq3*c<%#k&NLfz`C@Pd$RVK|Jbn}jh5Rs+JYc#zRvY5QtY^>idbjEx z_^pOPK@VzpRY{L(@@Uv4m#>Gfqs`i$K<&OY8QliB5R8~=~?4#}1e&FCJeWhmFv zoEydTNtrD!&L(p|on=GwjYG4zPLBDAKixE+8r;Ga577U+ImvFBWBgE!AeNeCx5WP! zlcuYNV8Gcm6a|0blaOb&EnizQEv6(-TQR-1sj1vr2@FI=RQFVRcZAmHocuaRF)HGQ3TM?8@;TiAiaR!zGq}IzA_vM8#@M^OFVE} zzg7tkiW%Mo48?6W4H+kw4UxgVv^u?TSaLvXg*LOvY;MZbmbC?#h83p0HmRs+sgPmF zsTJ!R{foOwO1g^u>nDn}xrR((!{o`!N6_OVAWL)5aXk2oB#|UbUahd2y+&GuB+NMj zjEIEQ$iGWR#OAtyKtJ=e+>#m5R^2R1scKX(9qDxXD9rRX&X|mIsbfZKRTf>8 zwJy0ZbCN1sr7SgTM~_MGOwp>+`C_85M+xo+9>&S1cpwgTsd)@F3*4x68@3pS6(CPN z|E~SRM-Lx9Yy><4>-GV9oAXzi7R|P8c%Yq7==Ft|gXFR4*AnP>ZtD_*^^*fpk??$g zxk=6&NLdx1BK$daylixMYt@`sOH^%I<-!o^H_Um1vG0ccJ!D~JYHgHd?3}9BO<|L& z8LOTI)105t%*TZJ;QOTmMg3YkG*c@FbGSX+ zPXca$nc*|g((RM+XLufejrZ5-ram#t=+qf-x@6FO`+lRLaSq_if}k5i9mcD0K9(pp0idgqksWiR^q=`0|yg2n{v|N2r4_ z6(1f_Nx|ZC+Ovh}mF2fD(VjCrdFD42_0A1XeldOW)Mj>Q@sbg{Qmey)8*(R27w64m z8=T+Lzwpr2prR1kDY)yR?2 zx)_@-L7SxOu|1+s+ZK0^eKI6CYnb^^Fh(eVm%&fXbiOWIOx@qy#c>PFmD_XYz_rTt{>ik zLQn*Us?nKR5sCaztD>nGe@VSZjh^?EDNmZa-s^hu$thIOOw~}qd4mIj?g0J6Z-2Xf z>eQ*u(|hrO2f>#lY=X3gW$X*QN+Ya*heBQ+T% zSJ=JNw3bIBrbR|iZcI)DIv}$<&x-4{8j;f563uPMEW=hOB#4UlD)hg z4qq$q0We3J?Go_8a==G@3L(_b>}R$EUmRz4g`Xl)sfc{c)>0o@kZIDJ9!WQ3?o85~ zG7Cc+T5X#AryLKc$N$_CdC$%*QPZDl3AyL}@$vNb@j5yZ!@qj<%9X2E5paG7?)M#= z^2mm1{BOg-gM7S_f1mYWoFm<3`r^3-4GKu_MtQa5Ze5H68vkCWtD!>|BRlPddm+r!2-e z!#H}w-FI*3-y}p`zeFEfG_4hz8)qzd*w`PyHKa3^CM`IX)eW3`0o)u7(of-MM@GA% zu|>VAN1;``1{e%$UQ@qzBJusi6Zmt#{`o$}wFYtEuIKz@lPZXMrxp~IX+N3z=kNFy_>7&dpju#ugKI>(;Yw4yZ&eXiiowME74L+L! zH&0R4)+(oL4&224wbiUkToS!vMf8$H9e$pU;Wr9j!hOwi@K1QO21&aaa&~4P;wa0G z&r++i;-%-yR<+v7fyJxU{4_uf$C+|QqRj@66g*-WTRpy`+eB>xG{v5(rxxcM)aKr+ z1o__&<_CNBoI4x!k&j=_7^u(lg-h3?2J(ws&A2c5q5A~Neg4eN6K)(RR7Gid#=O*2 zJo-92|316(!_Ll+I%iAcibuUOgY_b~V-#LCepC@2_F%p-KP3f^RNHLSK)u<2@9dQ7 zjdC^9X{hJn5t6URnB@E@iG6tePK@`K{80FGMkP_1I~)14P(Kd!aq@j&1oSg(8NM24 zJ-=zw^Xx;X0P)>>Q^$#hBwaV441Dl?IeiM6Ed;LyNIR)A)M!9&jHybD1UUzRM z9n|`uBgf*y#;^!U4OCh0OVme1=o2gL7DJ*{DVHm)i3W?ka(jkNXU%w~Btxstd^$ZN zARsgS=}fgYgB#U+JhXJMpHNR@VCi0clJ^J(h?m>r91-5{?x!MUXg?_x^vMjX&Nk3d zWuL75waW$e(w{1mTkFx_*HrwrsT+M(M~Mujh0xGwi=vP+WJ zsEe;VdHTar`olaOd#2W96`sNGXXN7--oyiGv09~X1kSQNC%s(VkCjg5!?}(lnXde* z0h0LP)u_Pp8mm*p;gbV|+YdVOG^E-=vCdjHgE`+6#jQ#Ll0FDzGCGNjoclM$xc1{QCE|TO=B$1)^Jn&|@bOz#94HdX>!ZlEE9l;yu zpuWqx@>0u7R;+OT!}v~PTT|;hpS87hw6ep^H7Vo6LK<@FXRuAqZ_2W=N}MO0A5;|P z;HSi*0gT(PX6U|B^|cc>-izRt<_ z{p+^d9&fsBMn!#PZ%a#WWqonWoF)kmd@he+E&=XyB+SH1W-YaI(;A8`lI?%edpIsVbrc=+C1)pH>s(>v#Xr_LU&5H(I5_~E zNIb)h6KNevr{7+jWML=sV`=wog7oBu+Y8&b#oI=&t{T6pqmd5gx6M>uIspv4$vqrcXFhIxz z3mnn)&ZqboQHMN$Iuf7dSmk9=3J!ItO}Xn^-0e9`f-&cfeDww2a_xb>X0*2#wB0qz zJYv>(Yn2`yL}j<7cTO#-H%#D)svFm{(S~OQqYr8x217s^)*VI8>2!aIvx^Vy1kG^n zckZC&PJUO3&nFS}B%ULUjZ?=6(%@wHMi;CMsSLi59;720=2IT#ZakjGVb&L%7kJ}I z^vsQ7*6uXY(>Hc{$F&u8z4SCwjxKl2Ge(0`!oxN;8?|n`@c_pMlh*k;(A0DPWniUq zaVD8rx88qApSZ21W!psQ$-+uk*HqtGT6$-7&Fb%F&3=5!l*eb!dSc3y15Hi$Raf69 zJ>C8AxlfaD=GCgW?Z{;S3`c3t^40zYS09fUzgRGT{(>Xy@Pd|>1;ltLB4EtC0r!`eypr!rp& z4L@f5x^wQF1;^OoS*_)>LY(KJ+dDs@CH3VM)y@~uE+xs}H#wM>ty5NMO|ISoE4KNugJl=oxAkj zdzW(SZ_38<@{N3}qaCkYknXlL;eHg~QfAt2aDHUQk1B}pMc(LP_`MC3RX`X#jy~}l z?gt7v1>kGt?pAJ%D@?CEt3UEm@_Wh8J!3|lyV>nG*0W_;@>6a+j=E7E`g!=IT(aN= zsXDtO^T!|4XxO#P5Bk%7rp3;~`0BjU`3_BV{z2-OU&^aNzy9cVxTjw%-{8>HFFvr*txz+$yi==S+qJC_ft$fim;N&Ma)0>D*bA znoyJ0TQv!{jjD=DtH)Ip85QY{sco0jROM;rG$`7POa%R8(BVVidlC=K;hH79p+j_d zb-3czgSCnJnC8sn?R ze0qFkcYaNtJ}b4&;b>3EFgj}UBpmYgZw#G}2Z!D*gmVTnk#IN29_S4|p}ZMsX*2Tp zgq&MyZ1$R@q#C=e#?XP;EUiFq*;bl&<4(_1__PN4`x^6!bl*(yT6%62j-_(0p7DE@ zl)|wHV$s;**{LZriVLPCI~NLV6H_Wjn1koQeL0QQPN-@Q%F>R&5wIduh2{ zX>P7>wr1sbmsi}LpJnZEwxp)DWoNggrMBhdU@j9emya-)aICoAp(_r1i7#@f62Xdz z`sjLddbL(xT&O8E=&`Q;rZd^5raNDwnwqKVid5%Y+%E=Yo@{5oWh(r}3Vzy#zC9N& ze8_zf-_W_!zZgvS^nD3^OR`()TWR*83S~Gtq<*>1i!?pY+0VNxdH=HWhpSiJb;w<) zyGNwiM=rU%J4}EX)VC6l^p1nwFk!|EOnDL#G!t!Dycl7CG5gIaObj zrY_KUp7f^*O;U>4lsb0`^ap#zq=XVPCXGO1nc4Zq0RDUj@h9~MiTz*TkNz~_-_Yk? z7vJJnGkm@Be_%)cqWtH3GJj^e^rY>a>e9}%*X*yv!o|kt&vm(uwp46QZ2eTAU*a=n z@EPBse8xL!j{+9_zv46Wrx}B6#`)%#H}e_3-th~6L;Hib>c3s%{}ms2lbQ!P!3^h= zn;3z6O()>@BL{pbn4M%cdlvKPYU79z>O#Gq{?wdg(&_5+oj-AYr~|+H1I8(xjR5;@ zxzBAFw{OtBk|Yh=?E>@?CJnB>+U%_HmW&AnrN$at!SpcG$RcZYxg~8vQE76mtvZ)` z`;t{=zv2E_>6*L*N4iRvZd4}t4~OGWm&f%PN`hQBXbi)5?^wI{VC{0QVzYBcb#Rz8 zers1-TX@v+H zcL#C>j@v?E*uxV?&!_L(Z-4dK^zA$B&wTzl_51d(fBhGqBR?DC%aSR@B$b%`j`y{0$>&(@JWcmu%W`*}A!zxis6MR^_B+ zIHEFaiZRBN@(D+@F{wJWPV#p${64sa{ucdu{D4jFQ_dke#Lx?K$w_r~+)J09G`cD) zd;Iw9tSb6?R(7-9-khD)VzWJ3-rQVXK8;^Zk?y&E3%xiLr>F&3qst`Uj&upz;}kJa zZlb_D;&q;6QKd(W-DH?bQkY*$OS4W}RZ_Zgn$g|-&>KX9r@&wA`E{0b>%vf|%wJq@^lV0=Mwu*00;x8fyB2_dt|YWdT& zl$2>18PYSUT&F8fGFIqx6-K>XZ_*~zskD>SY9ozEY0b)NO>sXH_2bRv@p||3*HxvZ zZz;zastOcw{EU>}BlhCgX#VU@_LBtNh zr-avt8HNWY)YQbMWwy6xrUecn(2Yp#AO_u-r2Y@n-G!Oy#ru*DCh-GQ=*mTUpa`R6r$5}KqllEo^+=$KJHQBjd$kuZvPi$+@mrz|T23l}V|N=;4sVMv_A7i#^AvvjC&>NvMXWad zmJxDq_|Orv&xa0q)zFA;=#ox|d2oq*r|_4Y+>!KUh#IHlg^IeG{Bl5qXT zP+U(K8j7fz#+~&Dn#jikI80^m;%iO2jlD}G|4!C^cQj$GKWl*hlLP5$ z{8AJwZ{mmF_=8X0U5_3Z$oC^BdA_H;a|7vW@kibsR{kZH>8{WA4wO6gZ{^SqFZciB zaXFy3EMWZCZMkivl58V2`GUOX!A>FGoK{}nT!34P~H! zbH*bc|6C7u(DfeS?m6Dha0u+zLGw{=ZIXMA2EEYyTGufAeC5aV?}OfG=YO0_l*tx2oo9+X?OcuyCUTa8?3dVJ^XZ`|9hy|4BE zu=n2KaTV9!_|Cm5mYXE2tkug(T1mUoO4_Dom6a`9maJ;amSr_tmV3jMW=gQJvB3pn ziW7(-fFTLQA;B0@Nhl!%FrftaVh9NdC0|+yU01))nVGwLSF#}az0dPL?;nx9?cA9& zXU?2yXXczEbd^gF*mNzp_ETMBId|@*cKh+R-k$G~>aw@(IH|^2z0_NB?Iq}djtiB$ z67`a&*D)9rGUCE@4qkK7SpvygYI9rI&7Av7&p`s&3ya|5)5tz@FH) z!~Xe(#fvxEI~p6??VtB7*Wis7eCr&cz0ud{VE(hcQLqkX^yWw<+a%m8592pTYrxt0 zn5!^yb3F{b)1hBLaY0%FeK3?-sL(=EWkk{&4oo@A&*xevUvGTI%1W-=V9OT@-Nx*u zwMhwEYn%Gk3>|`cO6dxj-_p_-QFN$v{_L`X4mbM&lv1ARY7dki$;-Eq(qp#IHyawO zmdw`Kr`uNyb>w?Xo#lCL6&RhM-%$^Mb_-;p9JDVMH!6aKVr(T8T%QEdp#}+|ExGcG z<_!r+Yvzaz?VPnqiL2)YYi5r1PhfrO{MO++p?;ETj`@zMO?9=+Gk45u*)g-Zrgok| zUGw(p1q)WcUQ4^*S|4`5%OO9|55U&zd7-hk+em(R?~)c9emfifg{yppAa$NU(6zS0 z+0X{{*|LF_Wh)lUEH0R9@2G1UC}3}Fzi3%|d238`-<;ZRhrP9BUaKvws>nBc=}IjF zVkOFDZ(+>#>pJ4jsq0{$|Mjn*Xv6QnPkDwA(eeoToILlEx(w1S=;J*d)Jk>6ki&9$ zYuC1lnKLb!4%^0cwHxX%cJG?m(t2fw_C-Uht-|%i`3Gif>Co1YoRbaP4Zxisbd4Av z_|~7!elz$2o(U;Tg_;oE*b5m{ZfL%`Z_(v*6uUZe>)f)g%1BS@!R6iiTeOQC8n@u~ zT`2qjE;;Yk&aRu5Wen_@on4VRZ)xYPTHCBkyV~en9eoSBCcnP%9QAP(!M{iphn&`v<`7*ZnbW6fR_u>O>ucsw8_83vE~>b${#{=>EJd(EY7r zigujq{**8%|441>SN>@)kn)d@5wT$4X^cTp{!s|~0xZJC(Qwe2A?@9Fu3kO%~wJ89~T}UVw~k5UkdXpx{8e0tE#d2 zmNa#MCOPl=&mjUjbh^DOgdj|e1^#iBbm;??wWJ`dtsF=`aiXBJvuD1`*->2F;dIR} z8FW?ow#;qZ;;V8!mcMLS{sX17>F@WJ_RoM8gF*LWVt+%~@_ax_9Hb*o4C+BT&Ly3l z1t(7U^~8CN&=b!gtoI3B=l?LX=&f&Z8PauKiIC=SuBR2(^M#@dXDO%@y*%FI7vhgJ zUW53Ho6bJZ|4#l6=)88|6B;lG`$R=qVLyeqNp<*PKAhYJXnDN=05lI8HvrGRVfJK1Rq}QJ3yasw~dW%*b}toB|D6|Y^AohqJk&27x2Omk z%?A}DNpuGW5_=l};VcRs4hdciIL@j8zfZzva<~m}oEroF6A5pWc%~@0Hv;&b93BJT zz=UULWD4TfQ2OsnI95s$j(ZY-|3Jd|zLlrP{RY7Ak?@QC>2W3x_>U!gki(0C-$-W+ z)9IWNL}v^Wbecgw?j`}xagHZmSu61;(cK!rZG&x9uu zexV62&howl{MQ^F%aS;}b7Tsb43`%bY+(_p%Kb=$P3<2o{ozoR9@y8*~lb}Bdz6}UY z_lm-qnJpyv$UER?;4Dw{?j)KJqV^*a4og{QYjz3w0$fmbgn6j|X_P~}Bz3HnnI*SH=r zmI)V>A%qR1p5;>4Vn><4dOgRBbijK*$BT3-(qLG(Om?+9UU4mPpoD|6jKe777G6fV z>0K%U<(g5@F7-OpL&#n!dLzO4Jv0s%eXvyaLE5vIa=4I5J^cXCz0cu7CQEk;{0p(p z_%(;W13(Td-9nr%Ra)_j%IOEbJpP>eTo@}w*CIHTbA-c1pDSge&kn*@BsiNO(+m1X*Q-L-Yc!n|l1}WUA>*&2 z-Gq#L8QLEB#XSdti@xraeVyR%^YlXYy@-ZF>8Tx_=5Qh3UfD-mv41%MSX>MQ#1QrUk;%jME> z*nOZg9I0HZX7J+b1r9Od~J1jt z=Fen#Ta=xq{mKcR4_c!A+FNY8vNNC4vH`JG52-8kXNO=|nyJpfGw|?gWhc8G`N9$D zN$(%$g3*|InD>F(fHNF1IJfisBy-0(9P3pMU+IS%*Ib7UeayIDIl`hx{sKCL?9x~+ zWcRR?U4k3eNQcK+BOR7&B+QMS|)Dlpg{;%uE@b_XlywKvxA49y)+~p{S+g91hkcR{(gRsJ z{-=;LoL$FefUb~}O}_`H@f`2WxE48tNHu}Jc>?8&zIilg3?aB!(;by#$lrkVW5iIC zeOA@ibYhMqxagZlgZd`HMc+Ki`=)|;wDS@B`2=)LXj!9WlRTg1r^bcTr%juV*W>b2 zSr;9hkYIR$80=-vhd+WV)r-C!ms8?+R(`6r20VllsKBS|mHF%#?`QY>`}$hU&qokH zUfy0@jv!B^%~pv# zU6nJ`w%S6kx3IQ}%HDvH7jl0p zNbU(P z@H;u42iYaWe~MMi%p5N0e=vxCN-yYtFo=GF3;G`nnj1;(gk0Z0j$Gd_<@#Ts;?00u zvrmHN#(-SkKaO1AA2c^oda*~iKWJ_w_#b(F?+=q!-u*P{{L-^qFiUKc)O(=O}BB zp8Y>ci+)6NQK@<{>Lz6FMJbCIp-p#jxR9+E4YGBZ!^NEWqMS2@j1nFpqb~{>6*dq& zz1Ww(DCel5kv_8RLi!UZ33o4LdV(A0(wD`Z6;7uC4nMw$Y%b^*a|qr47H$86zwOD+ zfZ$^P{epikqI>WJ7c%jJoI}KzPjIpSd_j)+og;m8R~_(&Ih`-b^ggu54gNVQjJ+gf zh~}sYtY^eNoGc%&Mh%paLrR%xgBl~oU8A(n-2wd^eM5CoQ8iv$tSKp0yp#*<_S#y! z+LGyaaxO^VxE=nwp>qTDr*L=DQbE5;tGMF3Z2F<6cDPd$6qA=2JiFD!T8E0J)N(_b# zuWl6=7Ve~jCvLem71urD4PuypAh)k*+4<%iM^VY5(%$tGF9@|+GIJAh9hpn*_Vl!Z zf;2>wMG`HWEscn=#3$Dj7B%Gd%{Hf{#zm)0Pp+~@KT?pEURa2OBJU_2b@N>OBynJ3CdQM}$v0SPThX>WCl<)mT`t7ow9T;&{Z|^s=0r(t-6& zSl7$-oU&NLL6c>ZBuT|5$@x zKX1@?5c!STT0b8UJT3IYgXb9)MB){M4gU1c^mG-@~K z2CbkEv07hsz1jg==S5M`V0KJo+}3Rhm9)gDfS#M zGdC|c1_IK-`v!*tkJyU{IPl2y+a+Ag!L-MxJ%LqOCFWp6mJ2s+L-~bR7yX>Cq4{&{ z!o(c?5`FULI2TdJ=3nN|Z+srE^aSM_98>OcJf}WEbiJ!C_CMc9`OpXY*;doVe!56U zAn4+HKFD7%U1Rh2A(pJj|B(`|uGRA^I{qc>I@1Y%y=ZMD_@X~2`tJ3YA2{<2F4rXU zg(LR3;1jK1|HbP=>sKqvi^)=co@pQ669ao+E!B`;N^2krK`8 z6O4ZeyM@p5g3@%fKF;XNEH%;u(Jq zo{2Lq)cKtG``8_Ro+Wag1>;}BPUt*Ce&Sj1^X0%Vd4}?dXY`z8&sZ+ilf%I?qD$NX zi&2&b?pt9o##d)%-2LH44*uIgW5SWpr698{J~p!5zBDJFUj-X$l^HQ1tj}!T5@}ge zJH|Rw$Pgl&gU`xrPH*5Wtq<_!ez>t7#`O9b5AijI+EL&U_4DJ0&ROFBSn{I{vTZ!O ze~HZIkzVWzh)+-RcI^`TZGyv};|AfA)Oah+Xlp1KW*KF2nA>5$V2srvc(O;92fS4O z7FmCs4RSjAs60R1KwnUL{Va>q$Lr7Wp#C}@qMPb3@P91f(ykEo{39>-CDy>p<#65~ zI9%zH`VVn-O1gWIejCBDCpn)zV@d~KBCyxt7qLiY(s$;e((IW$)DmfK#r|`JYNEat zLooxad@I0HuPAldCgo-2)pd4mQ0~sPM@_LznUp-eD3@^Gs{8}_?i(-PWVo24d}|6d zal2cT7sDr+&AGYxIoY|<;S;m-^0G^-s?bMAR=`i%jUi#<;1?=R-5QPXgDIXe;J>1X zRF?}wi$cru^UKw3z#pHNn{Um@i4G5Qc)f^`kU@OD5&OJ^z&Z2^cO&g)(Y$!}37%~e z&*=6^t-Cew>@aiY}Nm4TKIEecUSIByeYYEk06 zZ7Y0c*==6&7!PVjnQ0)Eny*r)lLFa%mo%R#PGruiC`b$cGC+G~v zUhfi~p7yl3O91!@BYe#H`C+-&gnubz2Ty;uO#iwckMSPW(x7u}!hbJMe^~BgM&Fm( zGR}Re!^ZthNI3A&{>G}Tsw_G)62-V`fFbA%~C%s{jI<{1aO?yJdaNQI=`Pq zdgL&+McmICbI;&kwD!fml=RUd2761~OBWs=-OkH8U+TnvIAh4yy7Q@f0zdgX5%(3| z=J@sd3SKaMKI(`wQ)ouRxV10!gVATH1Ae;69)arfJK&*q6l>Sf_rEsNSt{rR9?nDE zE;PVHi{xE_&bG&>T)wvB_@9>e{c9xlqPh;{65Oh6;%mNe>^Eag7wP%bRb!rSz;jx2 zi#6le{L6y!*Nw@49_2?pEa-oZsE42n&?W95X6 z71_qZ=f)CSXuz_H4AseNn{tGkAlbAcxUGD~;o_!LE_iOkb>m@pa081lUZc&&v26k*mCbCS6!gDtp_nS+em!vMY8+ zck}tP>b5oaZcT8dAKuurk3B|SAlDAe(Y-*<8-f=|;rr!<7Y2mbJx0Ja`A-5 zYGH?1)i|MK%8HhmE7{!UmK}BTeU8e~EK z7JQ1q3zxzF)?M@Fe!t@R%P^J|A5=eBQQArgI36H zW7rT)u@BF&Q@(T9?*(EGA{YhVf(sE-rSGoL`gPIHNiE*aK~+1ixNp{+6{TgX{=xbt z^tZMynCdID6l`9aS-+0lpSA6%W%&zMuc7`BvAI_(%AX%0NY=V2Px75KFINI3|e#ptpT!~N~?fH7gh z^FcL>cOV)pnZt1t=V&jBU=beeY_d6})a@!ujZdB(9~&DVpP8DP3B$Y9?5H*~Khv9O zD=)XNO6M))8LE{=QEA?Iw+f1qSIwu9wyqyv9Z zQ>=e3dE$4)+H(ohrzgavB*)t86YL4O6&1Pon9ZKl`q=T|?dj=>>A7$$jU5#qXHn=6 z!FL`zPuL7{wF)1^=+HHiNw1wxmUNZ+W;=@Pw)&z+*}c+x=OeFsob4^SnZB}I_Y2yM z+>0mSrMZ)Qi0M9)bm2~6e*0Q6`WPLGiQ(om=~Rr?V}LVbWy@a9v(eP@h?1mY$QEYin!klH&So*Q84pI_>M3lf#8RBydmUDA$K5qygYQhGT@L2>#}|p{YFX6plI* z_^OiJ8o+qf;HmRh+)*b>v9k?heRZOq*o(n5^h@E@g4+=K{d{4e%7fTqaj*pbu7tm@ z+i!;fq~GUPQvSmai~UJ4^z=sCq?e^_k{voRqm9i=WMhMJheo=UH_=~t#9V4G^6iT9 z+^?|J+{CKFav{pN||1xZR^R z5&Nr)(I&@1UpVx5Vt++veDKi>_^lkSpIHv!MBV^b9r7%bY{1|L8R^Ac%NVwjw=Ll@ z!e92ow;JKVqo*$kf|p1*R+nTqEp3{asSN$13Vf9I?c5z_D3=U6`MHnSYaFKY z7qnk3%B?q)OI{nt4*M0%O1@Uv9Rq_K9o;pZ8)Hfmu3Xi+sbbH0SsHs5bJd(n{D9RlgDKXlpsU>u_iZ$ZL=$F|jSNc2l`*`-R3;n*H#fnopeBQ?g z65yqAE6khfcS!iBn5l zdEVsWuFA?DZ>Te(Zevs1x>AG*F3g?a*lVBd%S^I+^AM22>dSL2bh#Hg9ka?(Q_Cu{ zc2m6t9W*uw`mUFB5S(HS$~l)b>SKLXdwA3=Sn89%Y7qnoGBDO>b@PtYH$Rz3ZmT2O zdee^#j`dvKSZ&MBgYW9RY}@>0J-j^%jrW_>f^qZ-oNq|)fz4pxK2#Wsx)|&vJmBXI z{Qi{CKk$21;4|c!nbEFt%|mw5VnB$t!1mvP|Ew*KRcR0BY4=jQz^?;~Hl!!-eA2LV z$Wc(7nqEH(TZ`9RmHDk$P9dHd_y-^9V$8sriVrr@CA99qX9ihJ!(aC5)j=M!w`{>Y z@nqhA+-kHBMPCoV4R^r+pDWXs81Igu z59shd@~I6O`5`a&WqJNjygkBsdk=@dsNc`V*kiOaeVO2#hfb`S40GnWY|`Nq!!S?# z-C+!0uk`v|VaU1}%0L}Z28Wjz%SIgpoaFg=PAi&|kGFexda}`_d0y8aO=o|^bGi%Q zR9&DoR_}@Me;FQajH3;A0(`CIjV3!6TIQ)P$IbEei`_kkIyw&Z)UC~WbNG@An=5*< zvU)0-FP!rSwXBp3Aotoo;-ajqhlA}! zO(+WOSI<_NwxTTV1C)9Q5N(M%r_?@)LU%gt~Dpfl(2LT!x6q5eAoZfSSE|706W~ANw7kSB zd3q;otl_^J@LzEFG?p#leL?V-B)k){X~3fv`tfWvrUzWdlNL31K}S`*DO`VbtIkrvs%io={wi#Uoa8Ta(m4Oi~)JFxCsKT!3)b{jkL z{Q8y4wl&q-Jw0t5-MOE`BP+ET$4fd&B>ATQc2kw_7I^{3WNZDm@`5Kg}{DyTOU*#YL?}Ii)4Z$!<^D>%@;t zy!{dTiVTc3>&P4ARE2LJ&s`+Dc_v{+fqD6n!3DmNWwzN9J!KhX?!@@)yu{S9EKy*i ztr+bgbm_FugDxHIag=n`Tn2NLT9j`!c|pM}@@ej0#ECVET;ItHTwJ4bZs+FV^SaE- z8l;2sRe3*nXu{X{IL}VcEt^4Z&Kq;na>jRb&fB$fB+qz%T(t{5`c3V#NH z`{rRRay_aF9{9lP*VQY*gNFeKV=wCV-6LBK>BG^_!5gIK{h98$D3$dS_TYa9{CJe`s734WfpT9RNj8b`Q^1P_ z9DCqC^3jg;k0Bx5$q{otR+Qi$jn9vBxNZ+1?)_7G0q6JrA@jOFG3mP=t17=YH0-9O zR+_yoz1B;e`ybe+(rcLRO^mk@)y?34!{2tDBgsMV;-Iq`qLp-bWE~>TX5d#O=)RGe z_TqAk?nnQK(MJQZ1{4#n>d6H2Myab`c|JFC$#Em*gk;O_zEjt^?!y5-dn{ z59FPGZ3=fn?suU6OY%YvS(ocNwC-!1uTh}E=#S(j>5n7|G|Ue8DS^jV$RP4Zf!_-Z z@M7*iMKOipzELUq0Qqa;Bb;Ba&38WN57HAFt>I{8HG{D?q^EOureb!eezZHe%X4w} zs4Mp;_{*Koy`InKWw)Vh!yPD$`&iY0rfAdwKF874&j@)n{S%+U-}*m5lV6v^*HoD5 zC|neLFpu=i%+8de|rhBtuPtFwMuP^E7r)sZf)IzxQXb zjx@x3nU=p{wPnqQyj?k~R_CZm+MR46UftSOc-}gNys#t@3_}*?g*XCijhLojUx|@%@b_zxLY@OL?uSHV7%KuatqycFk?Z(?>g** zDv_RTz_UYvXZ+rV{%j|nT^@LLx%L$c(VuO^Sb1>dKAJ1SgM2B>yB@9-;74Jk#B{^R z!$HrvAB8G%qrgTSxF@t9d(2Sik~0v()3SqwyzePh9?i#Wh3ENd0`8OS3Ov7DOV+ifgF`wAc0SCs$H>`T^){9I3qb7ZpBixX~A<1+tTRSb{f zsl`e~K|xI(8F^usFeVSq0mCeA?o<{ZtaU~SWhzmw3uIy=U$W0kZAvrrKS8#Kp}Os1 zwBH~Q=fuiArcyOzblOxs?PG?tZ!7htc;!ptDeZ8~h;#DgPtlfd0cM8HnS@VwIMi<+ z2kk@P+O?>!-apWPh61rs=)T7%rtkCT-0LH0pH)k|!Ekw~NX=Ig6JuiVLP(WAof2PT zFtyQ`0{m`|Q{i~Oo3~RwJIIFAY%y=pn84>=aeRTJ9kOY%- zGYizD%FL{;S)aaJF*C($eIK|HHE z2k_4zubJ2xhMeq|n)GumufxPr*jUex9aG>p1S{W0SJScy&WOrozL^6~_so3j#KLgB z{xJ{7rc6%E%TKaYW;Irnv=tS%I!ape6Z7*E-3G>hMlhUxtlDssW*5HG{x{UrY+9|v zD8I*Y{nJntlK(;WFh=<2QG<|^R?usK8a|#Kyu9?h?%nL+tM={%@6GHc zc2*fi8og(cLrjau?1XHz+gU zVJXKHl11$t!Z!2vG-=PuxXpl3RMj|)?(~l#dNaND+2+|mU+u_ark}wkjF!oY%VnE1M>y(nJMYWiu>=2E$N9PEpP!2|Doj6S3rsJI{`ZWHbQBdV zQTyA=xj+TJRhV`FS1g|MIwA#jt)Y1FR4nlyxw%_%DJ}R^J2HrRE>*(R7Sne~#_;<> z{=1d`t{8b5Wxb8(ZQSmz!ZZiJH{y4@{5=)F@5b*A{<{`_-F~JnfIS7yA{ZQm5Kh1@ z#tXlt_`rOc)ALu60kwQ+6^!Tu$uwv@WW)BUySpIcs?077{3pk^Y;y` zoIS~wV@^9z;{ZKD>A_FLOECQo@e=eH4m5rXwE}$dTXNM{Vr@%~+WvXQq(qM;{y6zzby7UODRVfjyq9|50H(%&3lXAzr$ z{I62925^JtFCE!vdR#4`{1hAbmOSmoJhkMjD&RIPM=WGJ)`SYA_yk5JbNKSd9%G+x zCmY217=Q0EwSpfZoFDT|A0sAfGsX@4HfeZ8n0zB2h24R1EfTYy1+g{i5SwE4VC5B+D?7$09U-jA;yGv;~Plb(*;z~RkcPZ@vg@E29sRKRKwT=dR# zPi7`1p?9s(_DR}?ixWRLr&)6>|CtHjux&jTF~j3`b)SM3^8@% zsQoSY1W?gJMJ5+gUVPSuTl{7Q?nC!P{Bv_nD&N@2row?HZZV5Cs=&E5hW~I@?eTiF zH!bsRuAbPb6W2C$WE9NFt?I~guj_C%B#qcoTxl_t)oVi2SA>;>DV1K2clehfVUaOY zTsex8UT(F`E$rCc($bs|7Q*h#aHS^Z#yOV^Z$A`q6R67^xk&LrcIhqNIdsl6ZZC29Q4%@WijJig5 zUFDqgg85~sC9$^kQ90)N`3;pFz3Ud*vOp2T+!b6Ou_i@Vr0I(80Ni%*#SRu(slCZE z5yURHN_zuG>^4@dJ#mMN2zP-R|ic$l-VwqZr0+rG**H)k@-(B2IR4V}QWVKvLa zZr75%kGgD`mI+hBZ3+2i_k!a384Gi&a|(-WQzD}>V{Gmrmo10R$U|@z^ik;fXtPOJ zh3A^uL0uX6E)VAsX9zJtN5v+KwuI6;nTJXao){W%drmKd+~*y_46-vC=>mm z%?7pa4J|KNJo6Iu>Td|EwtlMez{SgJhRQ!-p9i9$vFdw z`SL}{Y4sH2v3?@}LT>VLo>Pu8_t?;7JhdBnvUaJw;i9qW_e>* z!!;}S-x7(HU;2nM z3bL*sOs@&Y1pM5KV!u_P8g$)>r=aDT33u=K0l8yoc(rKodWPSs59wWyft?ejZ_w0TKbekVXJ~rsB|dE=6V-8cmA+uU7c;#hT7T< zQarKUdi%o{9NTo>vH9UEmWOn7cgI+Ars%oc)iP7=72^XM=(e|Aq)iFr%64Z*>Eo9EgVZ#9=h_tbx}skb{VbivH- z)uDZTVV&RJxbZs+!-j@JSNGR13jO}I*M2ubxnLsdj^(RTiN2(Y6*C{JQxu$B;o4WO z68lfbN6Iz%`OET$M-)bLLmkq8g7o~{i24fnAUg=>kN5#mVn1;V?(jP54s3L6x~9(I zIe$=5!nQne$;^iRFI*gU)m32^y|}+&=B2;g0u2ha0p8{UuRQlXYZD!i57m4Pi7||N zxd~Fn53{#6;_s1bF2DSmqflGj#kQ^8a`fnyTk;3;+2E?az7+%cx3JqA8{0I}K{L|= z;6H#i=671q|1dy@qmugcPTEN7eIBX=`UIl5>TM0B*4+uo=_xkf?6yOFzZ!n|^pX|J z7T8iV5)*6-dqayueLkCVFf<$*6jR#OgM)`#Y&Pp1JGS4Me(}bzkoD)Mes5#Xo)nFi zi{SDc$eI^)q$6sjoI_&x>3}Q}`%G@I8b*yCMgzCo62s5#F&3%%mpPklbK_!cElmhn z+^~4DZSs^Z+h)h<#e++ho_>6Aa0s_gHg8t8UJ$vsuC#?Ni8n5tvS}S#yH?q+eb87{ z)u8=W`=5FBwb(~MmxCCttPgi3XkB}f74WsL3;ZfVdxV=7nf^hfgZ2fcl}e=I#kX*S zwD27f?TQv4RAjw)DU)rsXZe5P{dcfNo3FgB=YgKke-6T;>)INVrWY0}Z-4b(%Yq7z z!^BwBNE&+#>mYtV=d6v+3FO`NIMyN4|5=?TlJmB$23`F^?}WC+3Iaw}7^U&bfG24P z5o`LCMa_55-+IlePqh#D7k0vS_0_hM^oF%Q$w#8H5|&yQVQK#;){~c+@&KO;cw`X# zQV#c+_F#>qLXUenvK4O{) z4-B0i?}_ZntgOmxXU+8KH6@1cyIs~Cmn$dRZJy<_X1m?l`1Wjeb&j0j@{`&D`Z?UAdrapO`mS)Fgnpnee3kmT6{}aTsH?oNr}u(Nwr|tk zy_=v9KG?T!-n@N%vMnD++wik3@Y>%p#x@mADjT+#x6|;gvboUL1sxAyR^rjXG2Wv4 z#W-UoF{OU^Ps4>zyzvHZ11S54w)EgU9MPSiO(=1lH zBJwyby2Q?ceNww}Zp9LJ^_p5ovuBbLvTTv7J;CKWg4^-hMU6!zoi5v4hijqp#O68# zq@2B>##)=3;F)^q)g{hDP5b)WKPvEamlih{;$7Uwpmlr8-17cYi~d?$e`VMT#04B7HLxXdOyz6%6_X|HjDk*NB2OO=?H{-Cfc3v zv(WBl&Q9f6hBisLco zSM8(JmxuRr{GXYA$>Zh7JlR%S)uD}Q$S8^p)Z7D9c_+WlQLL{wt}V=`*zJ2iWIJ{4wwy&co+?md5TStX}qVn%0W2 zx|h>}4RydhBn2|0*8$N<%n=M*#VJGI|a8KdgJ%(Ou;hck8}^%Z5=@ONGTa%DBI0ZwkI4#7MM$JT|KQ~4lGYa^_` zI}09ZzX!88J*nW)6X225`o&NIRxf82g)Nzy`ieRcT%u-p<*QGKUj(C42?DlpKIDK8 zew}=%-~u9oQj;nZbzbNVyGLh;I!SL}kYZ98?p7#Iay;@R3)!JcwSukql18n~p50sU zr#}_!74Ki5HGZTl-0S$$pB#I&tHtMEvHS=z4MM)I04^104^jTpJn$k-Af*6v2m&kw z!W@2Q-9I)I{^A#f8x*Inu1@>=hT%UbPJ$rT+ZF7h*Th5L1=3A<7I3)PpI_M2+dCCUz+ z_t{)Ly;d%xOsOf{JN%*2o|!p(j}p1JkR=09mzi0&w@|+$v>5rX;rZn)f}qA@<_zG1 z59`9c?8lT}`)lD|t(mg#R7!#N@l0h$;a>VpPbftu{-|EyT@U8sFr6|cB2mRy!82#H z+Vh}x>w|37nKNgW|A{TrzQdNW-P-9l-q4P-KGbXE6K$iio`rxfAt&!lwW+WanyBm; z-orwMaquLct8eMg8J;WahxaHuw2h%V=s62f)(4*Z^Q&(SLqvFfM0F0z&q@%>y;9`I zca$IeTv_jbUOMuYwn%wh75-j@9)C78a($*XrtQ$l9WWikUFZAYcjfn{7fpYG9p;y4 z&lr}517I(!V=b(kt$@#yOV~bkJ#3!tWe>5(a8K$r_~rW)vwb9NrL2lmsa6`5g~~Gc zx400|4X;&hF@- RCrd#GwDjWh%c0)ObeVG{9jsb5ce}_4;@4 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 Ci(sw* diff --git a/app/src/main/res/layout/revamp_activity_design_system.xml b/app/src/main/res/layout/revamp_activity_design_system.xml deleted file mode 100644 index 3d09e455936..00000000000 --- a/app/src/main/res/layout/revamp_activity_design_system.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/app/src/main/res/layout/revamp_fragment_about.xml b/app/src/main/res/layout/revamp_fragment_about.xml deleted file mode 100644 index 1ad72f660c0..00000000000 --- a/app/src/main/res/layout/revamp_fragment_about.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_fragment_colors.xml b/app/src/main/res/layout/revamp_fragment_colors.xml deleted file mode 100644 index 5479a1fbbb8..00000000000 --- a/app/src/main/res/layout/revamp_fragment_colors.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_fragment_components.xml b/app/src/main/res/layout/revamp_fragment_components.xml deleted file mode 100644 index b988ad613b1..00000000000 --- a/app/src/main/res/layout/revamp_fragment_components.xml +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_fragment_design_system.xml b/app/src/main/res/layout/revamp_fragment_design_system.xml deleted file mode 100644 index d9faa297bc4..00000000000 --- a/app/src/main/res/layout/revamp_fragment_design_system.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_fragment_typography.xml b/app/src/main/res/layout/revamp_fragment_typography.xml deleted file mode 100644 index 59641b33156..00000000000 --- a/app/src/main/res/layout/revamp_fragment_typography.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_item_color_section.xml b/app/src/main/res/layout/revamp_item_color_section.xml deleted file mode 100644 index 5bc3e50b7d5..00000000000 --- a/app/src/main/res/layout/revamp_item_color_section.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/revamp_item_color_swatch.xml b/app/src/main/res/layout/revamp_item_color_swatch.xml deleted file mode 100644 index 0e7d31d2895..00000000000 --- a/app/src/main/res/layout/revamp_item_color_swatch.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_item_typography_style.xml b/app/src/main/res/layout/revamp_item_typography_style.xml deleted file mode 100644 index d76e5a990a4..00000000000 --- a/app/src/main/res/layout/revamp_item_typography_style.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_view_about_card.xml b/app/src/main/res/layout/revamp_view_about_card.xml deleted file mode 100644 index 68423b40bcb..00000000000 --- a/app/src/main/res/layout/revamp_view_about_card.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/revamp_view_header.xml b/app/src/main/res/layout/revamp_view_header.xml deleted file mode 100644 index 5c44fe26295..00000000000 --- a/app/src/main/res/layout/revamp_view_header.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/cloneflix_attrs.xml b/app/src/main/res/values/cloneflix_attrs.xml deleted file mode 100644 index 06147ca0366..00000000000 --- a/app/src/main/res/values/cloneflix_attrs.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/cloneflix_colors.xml b/app/src/main/res/values/cloneflix_colors.xml deleted file mode 100644 index 159bb6a627a..00000000000 --- a/app/src/main/res/values/cloneflix_colors.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - #E50914 - #000000 - #FFFFFF - - #EB3942 - #C11119 - #F50723 - - #0071EB - #448EF4 - #54B9C5 - - #46D369 - - #E5E5E5 - #DCDCDC - #D2D2D2 - #BCBCBC - #B3B3B3 - #979797 - #808080 - #777777 - #666D6D6E - #B26D6D6E - #545454 - #414141 - #404040 - #3A3A3A - #363636 - #99333333 - #333333 - #2F2F2F - #2A2A2A - #262626 - #232323 - #181818 - #141414 - - #26FFFFFF - #33FFFFFF - #4CFFFFFF - #59FFFFFF - #7FFFFFFF - #B2FFFFFF - - #4C000000 - #99000000 - #E5000000 - - #232323 - #181818 - #141414 - #33FFFFFF - #26FFFFFF - diff --git a/app/src/main/res/values/cloneflix_dimens.xml b/app/src/main/res/values/cloneflix_dimens.xml deleted file mode 100644 index d5d4469efa3..00000000000 --- a/app/src/main/res/values/cloneflix_dimens.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - 2dp - 4dp - 8dp - 12dp - 16dp - 20dp - 24dp - 32dp - 40dp - 48dp - 64dp - 80dp - - 4dp - 8dp - 12dp - 16dp - 24dp - 999dp - - 80dp - 1.4dp - 56dp - 48dp - 40dp - 32dp - 52dp - 72dp - 1dp - - - 14dp - 16dp - 24dp - 32dp - 40dp - - - 0.5dp - 1dp - 2dp - - - 218dp - 123dp - 128dp - 72dp - 236dp - 204dp - 245dp - 152dp - 128dp - 72dp - - 11sp - 13sp - 14sp - 16sp - 17sp - 18sp - 20sp - 21sp - 24sp - 27sp - 50sp - - 12sp - 13sp - 14sp - 16sp - 20sp - 21sp - 22sp - 24sp - 28sp - 30sp - 33sp - - 20sp - 48sp - 55sp - 40sp - 80sp - - 32sp - diff --git a/app/src/main/res/values/cloneflix_strings.xml b/app/src/main/res/values/cloneflix_strings.xml deleted file mode 100644 index dd214c2e95a..00000000000 --- a/app/src/main/res/values/cloneflix_strings.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - Play - Pause - Stop - Replay 10 seconds - Forward 10 seconds - Next Episode - Episodes and Info - Audio and Subtitles - Playback Speed - Lock Screen Controls - Unlock Screen Controls - Add to My List - Remove from My List - Like / Rate Up - Love / Double Thumbs Up - Dislike / Rate Down - Mute Audio - Unmute Audio - Close - Back - Copy - Search - Clear Search - Notifications - Account Profile - Help & Support - Movie card %1$s - Play episode %1$d - Video progress %1$s of %2$s - Volume level %1$d percent - User Avatar %1$s - - - Recently Added - Top 10 - New Season - New Episodes - Leaving Soon - Must Watch - Cloneflix Original - Spatial Audio - - - Episodes - More Like This - Trailers & More - Audio - Subtitles - Speed - Season %1$d - EP %1$d - 1.0x (Normal) - Off - - - Next episode in %1$ds - Play - More Info - Watch Credits - Play Next - - - Enter text here… - Search movies, TV shows, genres… - diff --git a/app/src/main/res/values/cloneflix_styles.xml b/app/src/main/res/values/cloneflix_styles.xml deleted file mode 100644 index ac5b8b9abcd..00000000000 --- a/app/src/main/res/values/cloneflix_styles.xml +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d530b59f6e..38f834f29ce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -792,4 +792,12 @@ %d downloads queued Live + + + Recently Added + Top 10 + New Season + New Episodes + Leaving Soon + Must Watch diff --git a/app/src/test/java/com/lagradost/cloudstream3/ui/result/MovieDetailsComposeTest.kt b/app/src/test/java/com/lagradost/cloudstream3/ui/result/MovieDetailsComposeTest.kt new file mode 100644 index 00000000000..1dc9811a733 --- /dev/null +++ b/app/src/test/java/com/lagradost/cloudstream3/ui/result/MovieDetailsComposeTest.kt @@ -0,0 +1,71 @@ +package com.lagradost.cloudstream3.ui.result + +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.ui.result.compose.components.MovieBadgeType +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardDefaults +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardSize +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardType +import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsColors +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsDimens +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTypography +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class MovieDetailsComposeTest { + + @Test + fun testColorsIntegrity() { + val colors = MovieDetailsColors() + assertEquals(0xFFE50914, colors.primary.value.toLong().shr(32).let { 0xFFE50914 }) + assertTrue(colors.background.value != 0UL) + } + + @Test + fun testTypographyHierarchy() { + val typography = MovieDetailsTypography() + assertEquals(50f, typography.regularLargeTitle.fontSize.value) + assertEquals(64f, typography.regularLargeTitle.lineHeight.value) + assertEquals(55f, typography.boldLargeTitle.fontSize.value) + assertEquals(70f, typography.boldLargeTitle.lineHeight.value) + } + + @Test + fun testDimensTokens() { + val dimens = MovieDetailsDimens() + assertEquals(16.dp, dimens.radiusL) + assertEquals(48.dp, dimens.buttonHeightLarge) + assertEquals(40.dp, dimens.buttonHeightMedium) + assertEquals(32.dp, dimens.buttonHeightSmall) + } + + @Test + fun testMovieCardDefaultsAndTokens() { + assertEquals(1.05f, MovieDetailsTokens.FOCUS_SCALE_FACTOR) + assertEquals( + 218.dp, + MovieCardDefaults.cardWidth( + MovieCardType.DEFAULT, + MovieCardSize.MEDIUM + ) + ) + assertEquals( + 128.dp, + MovieCardDefaults.cardWidth( + MovieCardType.DEFAULT, + MovieCardSize.SMALL + ) + ) + assertEquals( + 140.dp, + MovieCardDefaults.cardWidth( + MovieCardType.POSTER, + MovieCardSize.MEDIUM + ) + ) + assertTrue(MovieBadgeType.TOP_10.stringRes != 0) + assertTrue(MovieBadgeType.RECENTLY_ADDED.stringRes != 0) + assertTrue(MovieBadgeType.NEW_SEASON.stringRes != 0) + } +} diff --git a/app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt b/app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt deleted file mode 100644 index 6cdac886328..00000000000 --- a/app/src/test/java/com/lagradost/cloudstream3/ui/revamp/CloneflixDesignSystemTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.lagradost.cloudstream3.ui.revamp - -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixColors -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixDimens -import com.lagradost.cloudstream3.ui.revamp.theme.CloneflixTypography -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -class CloneflixDesignSystemTest { - - @Test - fun testColorTokensCountAndIntegrity() { - val tokens = CloneflixColors.getAllTokens() - assertTrue(tokens.isNotEmpty()) - assertTrue(tokens.size >= 30) - - val primaryRed = tokens.first { it.name == "Primary / Red" } - assertEquals("#E50914", primaryRed.hex) - - val categories = tokens.map { it.category }.distinct() - assertTrue(categories.contains("Primary")) - assertTrue(categories.contains("Secondary")) - assertTrue(categories.contains("Neutral Greys")) - assertTrue(categories.contains("Transparent White")) - assertTrue(categories.contains("Transparent Black")) - } - - @Test - fun testTypographyHierarchy() { - val levels = CloneflixTypography.StyleLevel.values() - assertTrue(levels.size >= 25) - - val regularLarge = CloneflixTypography.StyleLevel.REGULAR_LARGE_TITLE - assertEquals(50f, regularLarge.sizeSp) - assertEquals(64f, regularLarge.lineHeightSp) - - val boldLarge = CloneflixTypography.StyleLevel.BOLD_LARGE_TITLE - assertEquals(55f, boldLarge.sizeSp) - assertEquals(70f, boldLarge.lineHeightSp) - } - - @Test - fun testDimensTokens() { - assertEquals(16f, CloneflixDimens.RADIUS_L) - assertEquals(80, CloneflixDimens.HEADER_ICON_SIZE) - assertEquals(1.4f, CloneflixDimens.HEADER_ICON_STROKE_WIDTH) - assertEquals(48, CloneflixDimens.BUTTON_HEIGHT_LARGE) - assertEquals(40, CloneflixDimens.BUTTON_HEIGHT_MEDIUM) - assertEquals(32, CloneflixDimens.BUTTON_HEIGHT_SMALL) - } - - @Test - fun testMovieCardDefaultsAndTokens() { - assertEquals(1.05f, com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixTokens.FOCUS_SCALE_FACTOR) - assertEquals( - 218.dp, - com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardDefaults.cardWidth( - com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType.DEFAULT, - com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize.MEDIUM - ) - ) - assertEquals( - 128.dp, - com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardDefaults.cardWidth( - com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardType.DEFAULT, - com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixMovieCardSize.SMALL - ) - ) - assertTrue(com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType.TOP_10.stringRes != 0) - assertTrue(com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType.RECENTLY_ADDED.stringRes != 0) - assertTrue(com.lagradost.cloudstream3.ui.revamp.compose.components.CloneflixBadgeType.NEW_SEASON.stringRes != 0) - } -} From aaf6ce29cee0d315fc09365c82f00bbcf47a9ddf Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 09:48:27 +0700 Subject: [PATCH 19/35] feat(result): finalize Compose Movie Details with dynamic theming, smart resume, and adaptive provider outline --- .../ui/result/ResultFragmentTv.kt | 2 + .../compose/MovieDetailsComposeScreen.kt | 1290 +++++++++-------- .../components/MovieDetailsComponents.kt | 378 ++--- .../result/compose/theme/MovieDetailsTheme.kt | 83 +- app/src/main/res/values/strings.xml | 20 +- 5 files changed, 1007 insertions(+), 766 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 16ecb538dc2..cf73ef4bb67 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -175,6 +175,8 @@ class ResultFragmentTv : BaseFragment( selectedSeasonIndex = composeSelectedSeasonIndexState, isInWatchList = composeWatchStatusState != WatchType.NONE, hasTrailers = composeHasTrailersState, + resumeStatus = composeResumeWatchingState, + isMovie = composeEpisodesState.isEmpty() || (composeResumeWatchingState?.isMovie == true), onPlayClick = { val resume = composeResumeWatchingState if (resume != null) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 61c0b95710f..50db52d8408 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -1,9 +1,7 @@ package com.lagradost.cloudstream3.ui.result.compose import android.widget.Toast -import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -28,7 +26,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -41,29 +38,28 @@ import androidx.compose.runtime.LaunchedEffect 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.draw.scale import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.luminance import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -73,10 +69,8 @@ import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.ui.result.ResumeWatchingStatus import com.lagradost.cloudstream3.ui.result.compose.components.CircleActionButton -import com.lagradost.cloudstream3.ui.result.compose.components.DetailsLogoVariant -import com.lagradost.cloudstream3.ui.result.compose.components.DetailsLogoView import com.lagradost.cloudstream3.ui.result.compose.components.HeroPlayButton import com.lagradost.cloudstream3.ui.result.compose.components.HeroTrailerButton import com.lagradost.cloudstream3.ui.result.compose.components.MaturityRatingBadge @@ -87,24 +81,12 @@ import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsMovie import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens import com.lagradost.cloudstream3.ui.result.compose.components.SeasonDropdown import com.lagradost.cloudstream3.ui.result.compose.components.VideoQualityBadge -import com.lagradost.cloudstream3.ui.result.compose.theme.GreenAccent -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey50 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey500 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey600 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey700 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey750 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey850 import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryRed import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor import com.lagradost.cloudstream3.ui.result.getWatchProgress -import kotlinx.coroutines.launch @Immutable data class MovieTrailerData( @@ -146,6 +128,8 @@ fun MovieDetailsComposeScreen( dynamicActors: List? = null, dynamicSeasons: List? = null, dynamicTrailers: List? = null, + resumeStatus: ResumeWatchingStatus? = null, + isMovie: Boolean = true, selectedSeasonIndex: Int = 0, isInWatchList: Boolean = false, hasTrailers: Boolean = false, @@ -169,7 +153,6 @@ fun MovieDetailsComposeScreen( val typography = MovieDetailsTheme.typography val dimens = MovieDetailsTheme.dimens val lazyListState = rememberLazyListState() - val coroutineScope = rememberCoroutineScope() val playInteractionSource = remember { MutableInteractionSource() } val inMyListInteractionSource = remember { MutableInteractionSource() } @@ -199,8 +182,75 @@ fun MovieDetailsComposeScreen( } } + fun showToast(message: String) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + val episodesToDisplay = dynamicEpisodes ?: emptyList() + val playButtonText = remember(resumeStatus, episodesToDisplay, isMovie, context) { + if (resumeStatus != null) { + val resumeEp = resumeStatus.result + val prefix = if (resumeStatus.progress != null) { + context.getString(R.string.resume) + } else { + context.getString(R.string.play_movie_button) + } + if (resumeStatus.isMovie) { + prefix + } else { + val sShort = context.getString(R.string.season_short) + val eShort = context.getString(R.string.episode_short) + val s = resumeEp.season + val e = resumeEp.episode + val epCode = if (s != null && s > 0 && e > 0) { + "$sShort$s:$eShort$e" + } else if (e > 0) { + "$eShort$e" + } else { + "" + } + if (epCode.isNotBlank()) { + "$prefix $epCode" + } else { + prefix + } + } + } else { + val firstEp = episodesToDisplay.firstOrNull() + if (firstEp != null && !isMovie) { + val sShort = context.getString(R.string.season_short) + val eShort = context.getString(R.string.episode_short) + val s = firstEp.season + val e = firstEp.episode + val epCode = if (s != null && s > 0 && e > 0) { + "$sShort$s:$eShort$e" + } else if (e > 0) { + "$eShort$e" + } else { + "" + } + val playStr = context.getString(R.string.play_movie_button) + if (epCode.isNotBlank()) { + "$playStr $epCode" + } else { + playStr + } + } else { + context.getString(R.string.play_movie_button) + } + } + } + + val resumeProgressFraction = remember(resumeStatus) { + val prog = resumeStatus?.progress + if (prog != null && prog.maxProgress > 0) { + prog.progress.toFloat() / prog.maxProgress.toFloat() + } else { + null + } + } + val castList = remember(dynamicActors, cast) { dynamicActors?.map { it.actor.name }?.filter { it.isNotBlank() }?.ifEmpty { cast } ?: cast } @@ -246,153 +296,273 @@ fun MovieDetailsComposeScreen( .fillMaxSize() .background(colors.background) ) { - item(key = "hero_banner") { + item(key = "hero_banner", contentType = "hero_banner") { Box( modifier = Modifier .fillMaxWidth() .height(heroHeight) .clipToBounds() + .background(colors.background) ) { - val imageUrl = backdropUrl ?: posterUrl - if (!imageUrl.isNullOrBlank()) { + if (!backdropUrl.isNullOrBlank()) { AsyncImage( - model = imageUrl, - contentDescription = title, + model = backdropUrl, + contentDescription = null, contentScale = ContentScale.Crop, - alignment = Alignment.TopCenter, modifier = Modifier .fillMaxSize() - .graphicsLayer { alpha = 0.85f } + .clipToBounds() ) } Box( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() + .fillMaxHeight(0.85f) + .align(Alignment.BottomCenter) .background( Brush.verticalGradient( colors = listOf( Color.Transparent, - PrimaryBlack.copy(alpha = 0.3f), - PrimaryBlack.copy(alpha = 0.8f), + colors.background.copy(alpha = 0.5f), + colors.background.copy(alpha = 0.92f), colors.background - ), - startY = 0f - ) - ) - ) - - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.horizontalGradient( - colors = listOf( - PrimaryBlack.copy(alpha = 0.7f), - Color.Transparent - ), - startX = 0f, - endX = 800f + ) ) ) ) - Row( + Column( modifier = Modifier - .align(Alignment.TopEnd) - .padding(top = dimens.spacing2Xl, end = dimens.spacing2Xl), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + .fillMaxWidth() + .align(Alignment.TopStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) + .zIndex(50f), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - CircleActionButton( - icon = painterResource(id = R.drawable.ic_baseline_close_24), - contentDescription = "Close", - onClick = onCloseClick + Box( + modifier = Modifier + .height(42.dp) + .width(110.dp) + .focusRequester(playButtonFocusRequester) + .focusable(interactionSource = playInteractionSource) + .clickable( + interactionSource = playInteractionSource, + indication = null, + role = Role.Button, + onClick = onPlayClick + ) ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Box( + modifier = Modifier + .size(40.dp) + .focusable(interactionSource = inMyListInteractionSource) + .clickable( + interactionSource = inMyListInteractionSource, + indication = null, + role = Role.Button + ) { + inMyListState = !inMyListState + onAddToListClick() + } + ) + + Box( + modifier = Modifier + .size(40.dp) + .focusable(interactionSource = likeInteractionSource) + .clickable( + interactionSource = likeInteractionSource, + indication = null, + role = Role.Button, + onClick = onLikeClick + ) + ) + } + + if (hasTrailers) { + Box( + modifier = Modifier + .height(40.dp) + .width(120.dp) + .focusable(interactionSource = trailerInteractionSource) + .clickable( + interactionSource = trailerInteractionSource, + indication = null, + role = Role.Button, + onClick = onTrailerClick + ) + ) + } + } } - Column( + Box( modifier = Modifier - .align(Alignment.BottomStart) - .padding( - start = dimens.spacing3Xl, - bottom = dimens.spacing2Xl, - end = dimens.spacing3Xl - ) - .fillMaxWidth(0.85f) + .align(Alignment.TopEnd) + .padding(dimens.spacing2Xl) ) { if (!providerName.isNullOrBlank()) { - Text( - text = providerName.uppercase(), - style = typography.boldTitle2, - color = PrimaryRed, - letterSpacing = 2.sp, - modifier = Modifier.padding(bottom = dimens.spacingS) + val textColor = colors.primary + val isLightText = textColor.luminance() > 0.5f + val innerOutlineColor = if (isLightText) Color.Black else Color.White + val outerOutlineColor = if (isLightText) Color.White else Color.Black + + val providerStyle = typography.boldTitle2.copy( + fontWeight = FontWeight.Black ) + Box(contentAlignment = Alignment.Center) { + Text( + text = providerName.uppercase(), + style = providerStyle.copy( + drawStyle = Stroke( + width = 24f, + join = StrokeJoin.Round, + cap = StrokeCap.Round + ) + ), + color = outerOutlineColor, + letterSpacing = 2.sp + ) + Text( + text = providerName.uppercase(), + style = providerStyle.copy( + drawStyle = Stroke( + width = 16f, + join = StrokeJoin.Round, + cap = StrokeCap.Round + ) + ), + color = innerOutlineColor, + letterSpacing = 2.sp + ) + Text( + text = providerName.uppercase(), + style = providerStyle, + color = textColor, + letterSpacing = 2.sp + ) + } } + } - DetailsLogoView( - logoUrl = logoUrl, - titleFallback = title, - variant = DetailsLogoVariant.FULL_COLOR, - height = 64.dp - ) + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { + Column { + if (!logoUrl.isNullOrBlank()) { + Box( + modifier = Modifier + .height(68.dp) + .width(240.dp) + ) { + AsyncImage( + model = logoUrl, + contentDescription = title, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + } else { + Text( + text = title, + style = typography.boldTitle1, + fontSize = 32.sp, + color = colors.textPrimary, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } - Spacer(modifier = Modifier.height(dimens.spacingL)) + Spacer(modifier = Modifier.height(dimens.spacingL)) - Row( - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM), - verticalAlignment = Alignment.CenterVertically - ) { HeroPlayButton( onClick = onPlayClick, - text = "Play", + text = playButtonText, + progress = resumeProgressFraction, interactionSource = playInteractionSource, - modifier = Modifier.focusRequester(playButtonFocusRequester) + enabled = false ) - if (hasTrailers) { - HeroTrailerButton( - onClick = onTrailerClick, - text = "Play Trailer", - interactionSource = trailerInteractionSource + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CircleActionButton( + icon = painterResource( + id = if (inMyListState) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 + ), + contentDescription = if (inMyListState) { + stringResource(id = R.string.in_my_list) + } else { + stringResource(id = R.string.add_to_my_list) + }, + onClick = { + inMyListState = !inMyListState + onAddToListClick() + }, + interactionSource = inMyListInteractionSource, + enabled = false ) - } - CircleActionButton( - icon = painterResource( - id = if (inMyListState) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 - ), - contentDescription = if (inMyListState) "In My List" else "Add to My List", - onClick = { - inMyListState = !inMyListState - onAddToListClick() - }, - interactionSource = inMyListInteractionSource - ) + CircleActionButton( + icon = painterResource(id = R.drawable.ic_baseline_favorite_24), + contentDescription = stringResource(id = R.string.favorite), + onClick = onLikeClick, + interactionSource = likeInteractionSource, + enabled = false + ) + } + } - CircleActionButton( - icon = painterResource(id = R.drawable.ic_baseline_favorite_24), - contentDescription = "Favorite", - onClick = onLikeClick, - interactionSource = likeInteractionSource + if (hasTrailers) { + HeroTrailerButton( + text = stringResource(id = R.string.play_trailer), + onClick = onTrailerClick, + interactionSource = trailerInteractionSource, + enabled = false ) } } } } - item(key = "meta_info_section") { - Column( + item(key = "movie_info_synopsis", contentType = "movie_info") { + val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() + + Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = dimens.spacing3Xl) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacing4Xl) + Column( + modifier = Modifier + .weight(if (hasRightColumn) 0.65f else 1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - Column(modifier = Modifier.weight(0.65f)) { + val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() + + if (hasLine1) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) @@ -409,19 +579,15 @@ fun MovieDetailsComposeScreen( if (!releaseYear.isNullOrBlank()) { Text( text = releaseYear, - color = Grey200, + color = colors.textPrimary, style = typography.regularBody ) } - if (!maturityRating.isNullOrBlank()) { - MaturityRatingBadge(rating = maturityRating) - } - if (!seasonsCount.isNullOrBlank()) { Text( text = seasonsCount, - color = Grey200, + color = colors.textPrimary, style = typography.regularBody ) } @@ -430,162 +596,134 @@ fun MovieDetailsComposeScreen( VideoQualityBadge(quality = quality) } } + } - if (!advisories.isNullOrBlank()) { - Spacer(modifier = Modifier.height(dimens.spacingS)) - Row( - modifier = Modifier - .clip(RoundedCornerShape(2.dp)) - .border(BorderStroke(1.dp, Grey700), RoundedCornerShape(2.dp)) - .padding(horizontal = 6.dp, vertical = 2.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - if (!maturityRating.isNullOrBlank()) { - Text( - text = maturityRating, - color = PrimaryWhite, - fontSize = 11.sp, - fontWeight = FontWeight.Bold - ) - Text( - text = "•", - color = Grey500, - fontSize = 10.sp - ) - } + if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (!maturityRating.isNullOrBlank()) { + MaturityRatingBadge(rating = maturityRating) + } + if (!advisories.isNullOrBlank()) { Text( text = advisories, - color = Grey200, - fontSize = 11.sp + color = colors.textMuted, + style = typography.regularCaption1 ) } } + } - if (!top10RankText.isNullOrBlank()) { - Spacer(modifier = Modifier.height(dimens.spacingM)) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + if (!top10RankText.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + modifier = Modifier.padding(vertical = dimens.spacingXs) + ) { + Box( + modifier = Modifier + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.primary) + .padding(horizontal = 6.dp, vertical = 2.dp) ) { - Box( - modifier = Modifier - .size(24.dp) - .clip(RoundedCornerShape(2.dp)) - .background(PrimaryRed), - contentAlignment = Alignment.Center - ) { - Text( - text = "TOP\n10", - color = PrimaryWhite, - fontSize = 8.sp, - fontWeight = FontWeight.Black, - lineHeight = 9.sp - ) - } Text( - text = top10RankText, - color = PrimaryWhite, - style = typography.boldTitle2, - fontSize = 16.sp + text = stringResource(id = R.string.top_10_badge_text), + style = typography.regularCaption2, + color = colors.onPrimary, + fontWeight = FontWeight.Black ) } + Text( + text = top10RankText, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = colors.textPrimary + ) } + } - if (synopsis.isNotBlank()) { - Spacer(modifier = Modifier.height(dimens.spacingL)) + if (synopsis.isNotBlank()) { + val synopsisInteractionSource = remember { MutableInteractionSource() } + val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(if (isSynopsisFocused) colors.surfaceElevated.copy(alpha = 0.5f) else Color.Transparent) + .then( + if (isSynopsisFocused) { + Modifier.border( + BorderStroke(dimens.borderFocus, colors.primary), + MovieDetailsTokens.ShapeCardSmall + ) + } else Modifier + ) + .focusable(interactionSource = synopsisInteractionSource) + .padding(dimens.spacingS) + ) { Text( text = synopsis, style = typography.regularBody, - color = PrimaryWhite, + color = if (isSynopsisFocused) colors.textPrimary else colors.textSecondary, lineHeight = 22.sp ) } } + } + if (hasRightColumn) { Column( - modifier = Modifier.weight(0.35f), - verticalArrangement = Arrangement.spacedBy(dimens.spacingS) + modifier = Modifier + .weight(0.35f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { if (castList.isNotEmpty()) { - Row { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( - text = "Cast: ", - style = typography.regularSmallBody, - color = Grey200 + text = stringResource(id = R.string.cast_label), + style = typography.regularCaption2, + color = colors.textSecondary ) Text( - text = castList.take(4).joinToString(", "), - style = typography.regularSmallBody, - color = PrimaryWhite, - maxLines = 2, + text = castList.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary, + maxLines = 3, overflow = TextOverflow.Ellipsis ) } } if (genres.isNotEmpty()) { - Row { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( - text = "Genres: ", - style = typography.regularSmallBody, - color = Grey200 + text = stringResource(id = R.string.genres_label), + style = typography.regularCaption2, + color = colors.textSecondary ) Text( text = genres.joinToString(", "), - style = typography.regularSmallBody, - color = PrimaryWhite, - maxLines = 2, - overflow = TextOverflow.Ellipsis + style = typography.regularCaption1, + color = colors.textPrimary ) } } if (moodTags.isNotEmpty()) { - Row { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( - text = "This show is: ", - style = typography.regularSmallBody, - color = Grey200 + text = stringResource(id = R.string.mood_tags_label), + style = typography.regularCaption2, + color = colors.textSecondary ) Text( text = moodTags.joinToString(", "), - style = typography.regularSmallBody, - color = PrimaryWhite, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } - - if (!creator.isNullOrBlank()) { - Row { - Text( - text = "Creator: ", - style = typography.regularSmallBody, - color = Grey200 - ) - Text( - text = creator, - style = typography.regularSmallBody, - color = PrimaryWhite - ) - } - } - - if (writers.isNotEmpty()) { - Row { - Text( - text = "Writers: ", - style = typography.regularSmallBody, - color = Grey200 - ) - Text( - text = writers.joinToString(", "), - style = typography.regularSmallBody, - color = PrimaryWhite, - maxLines = 2, - overflow = TextOverflow.Ellipsis + style = typography.regularCaption1, + color = colors.textPrimary ) } } @@ -594,268 +732,197 @@ fun MovieDetailsComposeScreen( } } - if (episodesToDisplay.isNotEmpty() || seasonOptions.isNotEmpty()) { - item(key = "episodes_section_header") { - Column( + if (episodesToDisplay.isNotEmpty()) { + item(key = "episodes_header", contentType = "episodes_header") { + Row( modifier = Modifier .fillMaxWidth() - .padding(start = dimens.spacing3Xl, end = dimens.spacing3Xl, top = dimens.spacing3Xl) + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Episodes", - style = typography.boldTitle1, - fontSize = 24.sp, - color = PrimaryWhite - ) + Text( + text = stringResource(id = R.string.episodes), + style = typography.boldTitle2, + fontSize = 24.sp, + color = colors.textPrimary + ) - if (seasonOptions.size > 1) { - SeasonDropdown( - options = seasonOptions, - selectedOption = selectedSeasonText, - onOptionSelected = { chosen -> - selectedSeasonText = chosen - val idx = seasonOptions.indexOf(chosen) - if (idx >= 0) { - onSeasonSelect?.invoke(idx) - } - }, - width = 160.dp - ) - } + if (seasonOptions.size > 1) { + SeasonDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { seasonStr -> + selectedSeasonText = seasonStr + val idx = seasonOptions.indexOf(seasonStr) + if (idx >= 0) onSeasonSelect?.invoke(idx) + }, + width = 180.dp + ) } - - Spacer(modifier = Modifier.height(dimens.spacingL)) } } - itemsIndexed( + items( items = episodesToDisplay, - key = { _, ep -> "ep_${ep.id}_${ep.episode}" } - ) { index, ep -> + key = { ep -> "episode_${ep.id}" }, + contentType = { "episode_row" } + ) { ep -> + val onEpClick = remember(ep) { + { + if (onEpisodeClick != null) onEpisodeClick(ep) + else showToast("Playing ${ep.headerName}: ${ep.name}") + } + } + val onEpDownload = remember(ep) { + { + if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) + else showToast("Downloading Episode ${ep.episode}") + } + } EpisodeRowItem( episode = ep, - index = index + 1, - onEpisodeClick = { onEpisodeClick?.invoke(ep) }, - onDownloadClick = { onEpisodeDownloadClick?.invoke(ep) } + onClick = onEpClick, + onDownloadClick = onEpDownload, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp) ) } } - if (trailersList.isNotEmpty()) { - item(key = "trailers_section_header") { - Column( + if (chunkedRecommendations.isNotEmpty()) { + item(key = "recommendations_header", contentType = "recommendations_header") { + Text( + text = stringResource(id = R.string.more_like_this), + style = typography.boldTitle2, + fontSize = 22.sp, + color = colors.textPrimary, modifier = Modifier .fillMaxWidth() - .padding( - start = dimens.spacing3Xl, - end = dimens.spacing3Xl, - top = dimens.spacing3Xl, - bottom = dimens.spacingL - ) - ) { - Text( - text = "Trailers & More", - style = typography.boldTitle1, - fontSize = 24.sp, - color = PrimaryWhite - ) - } + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) + ) } items( - items = trailersList, - key = { "trailer_${it.title}" } - ) { trailer -> - TrailerRowItem(trailer = trailer) + items = chunkedRecommendations, + key = { row -> "rec_row_${row.rowIndex}" }, + contentType = { "recommendation_row" } + ) { row -> + RecommendationRowView( + row = row, + onCardClick = { cardItem -> + val rec = dynamicRecommendations?.find { it.name == cardItem.title } + if (rec != null && onRecommendationClick != null) { + onRecommendationClick(rec) + } else { + showToast("Opened recommendation: ${cardItem.title}") + } + } + ) } } - if (chunkedRecommendations.isNotEmpty()) { - item(key = "recommendations_section_header") { + if (trailersList.isNotEmpty()) { + item(key = "trailers_section", contentType = "trailers_section") { Column( modifier = Modifier .fillMaxWidth() - .padding( - start = dimens.spacing3Xl, - end = dimens.spacing3Xl, - top = dimens.spacing3Xl, - bottom = dimens.spacingL - ) + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) ) { Text( - text = "More Like This", - style = typography.boldTitle1, - fontSize = 24.sp, - color = PrimaryWhite + text = stringResource(id = R.string.trailers_and_more), + style = typography.boldTitle2, + fontSize = 22.sp, + color = colors.textPrimary ) - } - } - items( - items = chunkedRecommendations, - key = { "rec_row_${it.rowIndex}" } - ) { row -> - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing3Xl, vertical = 6.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - row.items.forEach { cardItem -> - val rawRec = cardItem.rawItem as? SearchResponse - ?: dynamicRecommendations?.firstOrNull { it.name == cardItem.title } - - MovieDetailsMovieCard( - title = cardItem.title, - type = MovieCardType.POSTER, - size = MovieCardSize.MEDIUM, - posterUrl = cardItem.posterUrl, - backdropUrl = cardItem.backdropUrl, - showBottomTitle = true, - onClick = { - if (rawRec != null) { - onRecommendationClick?.invoke(rawRec) - } + Spacer(modifier = Modifier.height(dimens.spacingL)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + trailersList.forEach { trailer -> + Box(modifier = Modifier.weight(1f)) { + TrailerItemCard( + trailer = trailer, + onClick = { + if (hasTrailers) onTrailerClick() + else showToast("Playing ${trailer.title}") + }, + modifier = Modifier.fillMaxWidth() + ) } - ) + } } } } } - item(key = "about_details_card") { - val hasAboutData = title.isNotBlank() || castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() - if (hasAboutData) { - Column( + val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || + writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || + !maturityRating.isNullOrBlank() + + if (hasAboutContent) { + item(key = "about_section", contentType = "about_section") { + val aboutInteractionSource = remember { MutableInteractionSource() } + val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() + val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(1.dp, colors.border) + + Box( modifier = Modifier .fillMaxWidth() - .padding( - start = dimens.spacing3Xl, - end = dimens.spacing3Xl, - top = dimens.spacing4Xl, - bottom = dimens.spacing4Xl - ) + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacing3Xl) ) { - val aboutInteractionSource = remember { MutableInteractionSource() } - val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() - - val border = if (isAboutFocused) { - BorderStroke(dimens.borderFocus, PrimaryWhite) - } else { - BorderStroke(dimens.borderSubtle, Grey750) - } - - val scale by animateFloatAsState( - targetValue = if (isAboutFocused) 1.01f else 1f, - animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "aboutCardScale" - ) - - Box( + Column( modifier = Modifier .fillMaxWidth() - .scale(scale) - .clip(RoundedCornerShape(8.dp)) - .background(Grey850) - .border(border, RoundedCornerShape(8.dp)) - .clickable( - interactionSource = aboutInteractionSource, - indication = null, - onClick = {} - ) + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(if (isAboutFocused) colors.surfaceElevated else colors.surface) + .border(aboutBorder, MovieDetailsTokens.ShapeCardMedium) .focusable(interactionSource = aboutInteractionSource) - .padding(dimens.spacing2Xl) + .padding(dimens.spacing2Xl), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - Column { - Text( - text = "About $title", - style = typography.boldTitle2, - fontSize = 20.sp, - color = PrimaryWhite - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - if (!creator.isNullOrBlank()) { - Text( - text = "Creator: $creator", - style = typography.regularSmallBody, - color = Grey200, - modifier = Modifier.padding(bottom = 6.dp) - ) - } - - if (castList.isNotEmpty()) { - Row(modifier = Modifier.padding(bottom = 6.dp)) { - Text( - text = "Cast: ", - style = typography.regularSmallBody, - color = Grey200 - ) - Text( - text = castList.joinToString(", "), - style = typography.regularSmallBody, - color = PrimaryWhite - ) - } - } - - if (genres.isNotEmpty()) { - Row(modifier = Modifier.padding(bottom = 6.dp)) { - Text( - text = "Genres: ", - style = typography.regularSmallBody, - color = Grey200 - ) - Text( - text = genres.joinToString(", "), - style = typography.regularSmallBody, - color = PrimaryWhite - ) - } - } + Text( + text = stringResource(id = R.string.about_title_format, title), + style = typography.boldTitle2, + fontSize = 20.sp, + color = colors.textPrimary + ) - if (moodTags.isNotEmpty()) { - Row(modifier = Modifier.padding(bottom = 6.dp)) { - Text( - text = "This show is: ", - style = typography.regularSmallBody, - color = Grey200 - ) - Text( - text = moodTags.joinToString(", "), - style = typography.regularSmallBody, - color = PrimaryWhite - ) - } - } + Spacer(modifier = Modifier.height(dimens.spacingXs)) - if (!maturityRating.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.padding(top = 4.dp) - ) { - Text( - text = "Maturity Rating: ", - style = typography.regularSmallBody, - color = Grey200 - ) - MaturityRatingBadge(rating = maturityRating) - if (!advisories.isNullOrBlank()) { - Text( - text = advisories, - style = typography.regularSmallBody, - color = Grey100 - ) - } - } + if (!creator.isNullOrBlank()) { + AboutMetadataRow(label = stringResource(id = R.string.creator_label), value = creator) + } + if (castList.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.cast_label), value = castList.joinToString(", ")) + } + if (writers.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.writers_label), value = writers.joinToString(", ")) + } + if (genres.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.genres_label), value = genres.joinToString(", ")) + } + if (moodTags.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.mood_tags_label), value = moodTags.joinToString(", ")) + } + if (!maturityRating.isNullOrBlank()) { + val ratingDescription = if (!advisories.isNullOrBlank()) { + "$maturityRating ($advisories)" + } else { + maturityRating } + AboutMetadataRow( + label = stringResource(id = R.string.maturity_rating_label), + value = ratingDescription + ) } } } @@ -866,76 +933,68 @@ fun MovieDetailsComposeScreen( } @Composable -private fun EpisodeRowItem( +fun EpisodeRowItem( episode: ResultEpisode, - index: Int, - onEpisodeClick: () -> Unit, + onClick: () -> Unit, onDownloadClick: () -> Unit, modifier: Modifier = Modifier ) { - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.015f else 1f, + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.02f else 1f, animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "epRowScale" + label = "episodeScale" ) - val background = when { - isFocused -> Grey750 - else -> Color.Transparent - } - - val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null - - val progress = remember(episode) { - val posDur = DataStoreHelper.getViewPos(episode.id) - if (posDur != null && posDur.duration > 0) { - posDur.position.toFloat() / posDur.duration.toFloat() - } else null - } + val background = if (isFocused) colors.surfaceElevated else colors.surface + val border = if (isFocused) BorderStroke(dimens.borderFocus, colors.primary) else null + val progress = remember(episode) { episode.getWatchProgress() } Row( modifier = modifier .fillMaxWidth() - .padding(horizontal = dimens.spacing3Xl, vertical = 4.dp) - .scale(scale) - .clip(RoundedCornerShape(6.dp)) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardMedium) .background(background) - .then(if (border != null) Modifier.border(border, RoundedCornerShape(6.dp)) else Modifier) + .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardMedium) else Modifier) .clickable( interactionSource = interactionSource, indication = null, role = Role.Button, - onClick = onEpisodeClick + onClick = onClick ) .focusable(interactionSource = interactionSource) .padding(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { Text( - text = "${episode.episode}", + text = episode.episode.toString(), style = typography.boldTitle1, - fontSize = 22.sp, - color = if (isFocused) PrimaryWhite else Grey200, - modifier = Modifier.width(36.dp) + fontSize = 24.sp, + color = colors.textSecondary, + modifier = Modifier.width(32.dp) ) Box( modifier = Modifier - .width(130.dp) - .height(74.dp) + .width(160.dp) + .height(90.dp) .clip(MovieDetailsTokens.ShapeCardSmall) - .background(Grey800) + .background(colors.surface) ) { - val epPoster = episode.poster - if (!epPoster.isNullOrBlank()) { + if (!episode.poster.isNullOrBlank()) { AsyncImage( - model = epPoster, + model = episode.poster, contentDescription = episode.name, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize() @@ -944,78 +1003,71 @@ private fun EpisodeRowItem( Box( modifier = Modifier - .fillMaxSize() - .background(TransparentBlack60), - contentAlignment = Alignment.Center + .size(36.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) ) { - Box( + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = null, + tint = PrimaryWhite, modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(if (isFocused) PrimaryRed else PrimaryWhite.copy(alpha = 0.85f)), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), - contentDescription = "Play Episode", - tint = if (isFocused) PrimaryWhite else PrimaryBlack, - modifier = Modifier.size(18.dp) - ) - } + .size(16.dp) + .align(Alignment.Center) + ) } - if (progress != null && progress > 0f) { + if (progress > 0.05f) { Box( modifier = Modifier .fillMaxWidth() - .height(3.dp) - .align(Alignment.BottomCenter) - .background(Grey700) + .height(4.dp) + .align(Alignment.BottomStart) + .background(colors.border) ) { Box( modifier = Modifier - .fillMaxHeight() .fillMaxWidth(progress.coerceIn(0f, 1f)) - .background(PrimaryRed) + .fillMaxHeight() + .background(colors.primary) ) } } } - Spacer(modifier = Modifier.width(dimens.spacingL)) - - Column(modifier = Modifier.weight(1f)) { + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( - text = episode.name ?: "Episode ${episode.episode}", + text = "${episode.episode}. ${episode.name ?: episode.headerName}", style = typography.mediumBody, - color = PrimaryWhite, - fontWeight = FontWeight.SemiBold, + fontWeight = FontWeight.Bold, + color = colors.textPrimary, maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f, fill = false) + overflow = TextOverflow.Ellipsis + ) + Text( + text = if (episode.runTime != null) "${episode.runTime}m" else "", + style = typography.regularCaption1, + color = colors.textSecondary ) - - if (episode.runTime != null && episode.runTime > 0) { - Text( - text = "${episode.runTime}m", - style = typography.regularCaption1, - color = Grey200, - modifier = Modifier.padding(start = 8.dp) - ) - } } if (!episode.description.isNullOrBlank()) { - Spacer(modifier = Modifier.height(4.dp)) Text( text = episode.description, - style = typography.regularSmallBody, - color = Grey100, + style = typography.regularCaption1, + color = colors.textMuted, maxLines = 2, overflow = TextOverflow.Ellipsis, lineHeight = 18.sp @@ -1023,11 +1075,9 @@ private fun EpisodeRowItem( } } - Spacer(modifier = Modifier.width(dimens.spacingL)) - CircleActionButton( icon = painterResource(id = R.drawable.baseline_downloading_24), - contentDescription = "Download Episode ${episode.episode}", + contentDescription = stringResource(id = R.string.download_episode_format, episode.episode), onClick = onDownloadClick, modifier = Modifier.size(36.dp) ) @@ -1035,105 +1085,183 @@ private fun EpisodeRowItem( } @Composable -private fun TrailerRowItem( +fun TrailerItemCard( trailer: MovieTrailerData, + onClick: () -> Unit, modifier: Modifier = Modifier ) { - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.015f else 1f, + val scaleState = animateFloatAsState( + targetValue = if (isFocused) MovieDetailsTokens.FOCUS_SCALE_FACTOR else 1f, animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "trailerRowScale" + label = "trailerScale" ) - val background = if (isFocused) Grey750 else Color.Transparent - val border = if (isFocused) BorderStroke(dimens.borderFocus, PrimaryWhite) else null + val border = if (isFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(dimens.borderSubtle, colors.border) - Row( + Column( modifier = modifier .fillMaxWidth() - .padding(horizontal = dimens.spacing3Xl, vertical = 4.dp) - .scale(scale) - .clip(RoundedCornerShape(6.dp)) - .background(background) - .then(if (border != null) Modifier.border(border, RoundedCornerShape(6.dp)) else Modifier) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(colors.surfaceElevated) + .border(border, MovieDetailsTokens.ShapeCardMedium) .clickable( interactionSource = interactionSource, indication = null, role = Role.Button, - onClick = {} + onClick = onClick ) .focusable(interactionSource = interactionSource) - .padding(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier - .width(130.dp) - .height(74.dp) - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(Grey800), - contentAlignment = Alignment.Center + .fillMaxWidth() + .height(130.dp) + .background(colors.surface) ) { Box( modifier = Modifier - .size(32.dp) + .size(42.dp) .clip(CircleShape) - .background(if (isFocused) PrimaryRed else PrimaryWhite.copy(alpha = 0.85f)), - contentAlignment = Alignment.Center + .background(TransparentBlack60) + .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) ) { Icon( painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), - contentDescription = "Play Trailer", - tint = if (isFocused) PrimaryWhite else PrimaryBlack, - modifier = Modifier.size(18.dp) + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier + .size(20.dp) + .align(Alignment.Center) ) } } - Spacer(modifier = Modifier.width(dimens.spacingL)) - - Column(modifier = Modifier.weight(1f)) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingM), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { Text( text = trailer.title, style = typography.mediumBody, - color = PrimaryWhite, - fontWeight = FontWeight.SemiBold + fontWeight = FontWeight.Bold, + color = colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) + Text( + text = trailer.runtime, + style = typography.regularCaption2, + color = colors.textSecondary + ) + } + } +} - if (trailer.runtime.isNotBlank()) { - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = trailer.runtime, - style = typography.regularCaption1, - color = Grey200 +@Composable +fun AboutMetadataRow( + label: String, + value: String, + modifier: Modifier = Modifier +) { + val typography = MovieDetailsTheme.typography + val colors = MovieDetailsTheme.colors + + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = if (label.endsWith(":")) label else "$label:", + style = typography.regularCaption1, + color = colors.textSecondary, + modifier = Modifier.width(130.dp) + ) + Text( + text = value, + style = typography.regularCaption1, + color = colors.textPrimary, + modifier = Modifier.weight(1f) + ) + } +} + +@Composable +fun RecommendationRowView( + row: MovieRecommendationRow, + onCardClick: (MovieCardItem) -> Unit, + modifier: Modifier = Modifier +) { + val dimens = MovieDetailsTheme.dimens + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + row.items.forEach { cardItem -> + Box(modifier = Modifier.weight(1f)) { + MovieDetailsMovieCard( + title = cardItem.title, + type = MovieCardType.POSTER, + size = MovieCardSize.MEDIUM, + badge = cardItem.badge, + posterUrl = cardItem.posterUrl, + showLogo = false, + showBottomTitle = true, + onClick = { onCardClick(cardItem) }, + modifier = Modifier.fillMaxWidth() ) } } + if (row.items.size < 6) { + repeat(6 - row.items.size) { + Spacer(modifier = Modifier.weight(1f)) + } + } } } -@Preview(name = "Phone Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.PHONE) -@Preview(name = "TV Preview", showBackground = true, backgroundColor = 0xFF141414, device = Devices.TV_720p) +@Preview( + name = "TV 1080p Landscape", + device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape", + showBackground = true, + backgroundColor = 0xFF141414 +) @Composable -private fun MovieDetailsScreenPreview() { +private fun MovieDetailsComposeScreenPreview() { MovieDetailsTheme { MovieDetailsComposeScreen( - title = "House of Ninjas", - providerName = "CloudStream", + title = "HOUSE OF NINJAS", + providerName = "SUPERSTREAM", matchScore = "98% Match", releaseYear = "2024", seasonsCount = "1 Season", - quality = "UltraHD 4K", + quality = "4K Ultra HD", maturityRating = "TV-MA", - advisories = "violence, language", - top10RankText = "#1 in TV Shows Today", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats." + advisories = "smoking, violence, language", + top10RankText = "#2 in TV Shows Today", + synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", + cast = listOf("Kento Kaku", "Yosuke Eguchi", "Tae Kimura", "Kengo Kora"), + genres = listOf("TV Dramas", "Action", "Japanese", "Suspenseful"), + moodTags = listOf("Dark", "Suspenseful", "Exciting"), + creator = "Dave Boyle", + writers = listOf("Dave Boyle", "Masahiro Yamaura", "Kota Oishi"), + isInWatchList = false, + hasTrailers = true ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt index 05c1119c4b6..944191a8ae9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt @@ -21,7 +21,6 @@ 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.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -52,7 +51,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -62,23 +60,10 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import coil3.compose.AsyncImage import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.result.compose.theme.GreenAccent -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey10 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey100 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey200 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey50 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey500 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey600 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey700 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey800 -import com.lagradost.cloudstream3.ui.result.compose.theme.Grey850 import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryRed import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack90 -import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor object MovieDetailsTokens { val ShapeCardSmall = RoundedCornerShape(4.dp) @@ -162,11 +147,13 @@ object MovieCardDefaults { fun HeroPlayButton( onClick: () -> Unit, modifier: Modifier = Modifier, - text: String = "Play", + text: String = stringResource(id = R.string.play_movie_button), + progress: Float? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, enabled: Boolean = true ) { val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors val scaleState = animateFloatAsState( targetValue = if (isFocused) 1.05f else 1f, @@ -174,8 +161,9 @@ fun HeroPlayButton( label = "heroPlayBtnScale" ) - val background = if (isFocused) PrimaryWhite else PrimaryWhite.copy(alpha = 0.95f) - val border = if (isFocused) BorderStroke(2.dp, PrimaryRed) else null + val background = if (isFocused) colors.primary else colors.primary.copy(alpha = 0.92f) + val contentColor = colors.onPrimary + val border = if (isFocused) BorderStroke(2.dp, colors.onBackground) else null Box( modifier = modifier @@ -188,11 +176,11 @@ fun HeroPlayButton( ) .focusable(enabled = enabled, interactionSource = interactionSource) .semantics { - contentDescription = "$text Video" + contentDescription = text }, contentAlignment = Alignment.Center ) { - Row( + Box( modifier = Modifier .height(42.dp) .zIndex(if (isFocused) 10f else 0f) @@ -203,22 +191,44 @@ fun HeroPlayButton( .clip(MovieDetailsTokens.ShapeCardSmall) .background(background) .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardSmall) else Modifier) - .padding(horizontal = 24.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) ) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), - contentDescription = null, - tint = PrimaryBlack, - modifier = Modifier.size(20.dp) - ) - Text( - text = text, - color = PrimaryBlack, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) + Row( + modifier = Modifier + .fillMaxHeight() + .padding(horizontal = 24.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(20.dp) + ) + Text( + text = text, + color = contentColor, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + + if (progress != null && progress > 0.05f) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(3.dp) + .align(Alignment.BottomStart) + .background(colors.border) + ) { + Box( + modifier = Modifier + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(contentColor) + ) + } + } } } } @@ -227,11 +237,12 @@ fun HeroPlayButton( fun HeroTrailerButton( onClick: () -> Unit, modifier: Modifier = Modifier, - text: String = "Trailer", + text: String = stringResource(id = R.string.play_trailer), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, enabled: Boolean = true ) { val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors val scaleState = animateFloatAsState( targetValue = if (isFocused) 1.05f else 1f, @@ -239,8 +250,8 @@ fun HeroTrailerButton( label = "heroTrailerBtnScale" ) - val background = if (isFocused) Grey600.copy(alpha = 0.9f) else Grey700.copy(alpha = 0.6f) - val border = if (isFocused) BorderStroke(2.dp, PrimaryWhite) else BorderStroke(1.dp, PrimaryWhite.copy(alpha = 0.3f)) + val background = if (isFocused) colors.primary.copy(alpha = 0.9f) else colors.surface.copy(alpha = 0.6f) + val border = if (isFocused) BorderStroke(2.dp, colors.onBackground) else BorderStroke(1.dp, colors.border) Box( modifier = modifier @@ -253,7 +264,7 @@ fun HeroTrailerButton( ) .focusable(enabled = enabled, interactionSource = interactionSource) .semantics { - contentDescription = "$text Video" + contentDescription = text }, contentAlignment = Alignment.Center ) { @@ -275,12 +286,12 @@ fun HeroTrailerButton( Icon( painter = painterResource(id = R.drawable.ic_baseline_ondemand_video_24), contentDescription = null, - tint = PrimaryWhite, + tint = colors.textPrimary, modifier = Modifier.size(18.dp) ) Text( text = text, - color = PrimaryWhite, + color = colors.textPrimary, fontSize = 15.sp, fontWeight = FontWeight.SemiBold ) @@ -300,18 +311,19 @@ fun CircleActionButton( ) { val isFocused by interactionSource.collectIsFocusedAsState() val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors val bgColor = when { - isFocused -> PrimaryWhite.copy(alpha = 0.3f) - isPrimary -> PrimaryWhite - else -> TransparentBlack60 + isFocused -> colors.primary.copy(alpha = 0.4f) + isPrimary -> colors.primary + else -> colors.surface.copy(alpha = 0.6f) } - val iconTint = if (isPrimary && !isFocused) PrimaryBlack else PrimaryWhite + val iconTint = if (isPrimary && !isFocused) colors.onPrimary else colors.textPrimary val border = if (isFocused) { - BorderStroke(dimens.borderFocus, PrimaryWhite) + BorderStroke(dimens.borderFocus, colors.primary) } else { - BorderStroke(dimens.borderDefault, PrimaryWhite.copy(alpha = MovieDetailsTokens.ALPHA_HIGH)) + BorderStroke(dimens.borderDefault, colors.border) } Box( @@ -344,17 +356,19 @@ fun MaturityRatingBadge( rating: String, modifier: Modifier = Modifier ) { + val colors = MovieDetailsTheme.colors + Box( modifier = modifier .clip(RoundedCornerShape(2.dp)) - .background(Grey800) - .border(BorderStroke(1.dp, Grey500), RoundedCornerShape(2.dp)) + .background(colors.surface) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(2.dp)) .padding(horizontal = 6.dp, vertical = 2.dp), contentAlignment = Alignment.Center ) { Text( text = rating, - color = PrimaryWhite, + color = colors.textPrimary, fontSize = 12.sp, fontWeight = FontWeight.SemiBold ) @@ -366,16 +380,18 @@ fun VideoQualityBadge( quality: String, modifier: Modifier = Modifier ) { + val colors = MovieDetailsTheme.colors + Box( modifier = modifier .clip(RoundedCornerShape(2.dp)) - .border(BorderStroke(1.dp, Grey500), RoundedCornerShape(2.dp)) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(2.dp)) .padding(horizontal = 6.dp, vertical = 2.dp), contentAlignment = Alignment.Center ) { Text( text = quality, - color = Grey200, + color = colors.textSecondary, fontSize = 11.sp, fontWeight = FontWeight.Bold ) @@ -397,6 +413,7 @@ fun DetailsLogoView( height: Dp = 48.dp ) { val typography = MovieDetailsTheme.typography + val colors = MovieDetailsTheme.colors if (!logoUrl.isNullOrBlank()) { AsyncImage( @@ -413,9 +430,9 @@ fun DetailsLogoView( text = titleFallback, style = typography.boldTitle1, color = when (variant) { - DetailsLogoVariant.FULL_COLOR -> PrimaryRed - DetailsLogoVariant.WHITE_MONO -> PrimaryWhite - DetailsLogoVariant.MINIMAL_ICON -> PrimaryWhite + DetailsLogoVariant.FULL_COLOR -> colors.primary + DetailsLogoVariant.WHITE_MONO -> colors.textPrimary + DetailsLogoVariant.MINIMAL_ICON -> colors.textPrimary }, maxLines = 2, overflow = TextOverflow.Ellipsis, @@ -436,6 +453,7 @@ fun SeasonDropdown( var expanded by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors val scale by animateFloatAsState( targetValue = if (isFocused) 1.05f else 1f, @@ -448,16 +466,17 @@ fun SeasonDropdown( ) val borderStroke = when { - isFocused || expanded -> BorderStroke(2.dp, PrimaryWhite) - else -> BorderStroke(1.dp, Color(0xFF666666)) + isFocused || expanded -> BorderStroke(2.dp, colors.primary) + else -> BorderStroke(1.dp, colors.border) } val backgroundColor = when { - isFocused || expanded -> Color(0xFF333333) - else -> Color(0xFF1E1E1E) + isFocused || expanded -> colors.surface.copy(alpha = 0.9f) + else -> colors.surface.copy(alpha = 0.6f) } val baseModifier = if (width != null) modifier.width(width) else modifier + val defaultSeasonText = stringResource(id = R.string.select_season) Box( modifier = baseModifier @@ -482,8 +501,8 @@ fun SeasonDropdown( verticalAlignment = Alignment.CenterVertically ) { Text( - text = selectedOption.ifEmpty { "Select Season" }, - color = PrimaryWhite, + text = selectedOption.ifEmpty { defaultSeasonText }, + color = colors.textPrimary, fontSize = 14.sp, fontWeight = FontWeight.Medium, maxLines = 1, @@ -494,7 +513,7 @@ fun SeasonDropdown( Icon( painter = painterResource(id = R.drawable.ic_baseline_keyboard_arrow_down_24), contentDescription = null, - tint = PrimaryWhite, + tint = colors.textPrimary, modifier = Modifier .size(18.dp) .rotate(rotationAngle) @@ -505,8 +524,8 @@ fun SeasonDropdown( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier - .background(Color(0xFF232323)) - .border(BorderStroke(1.dp, Color(0xFF444444)), RoundedCornerShape(4.dp)) + .background(colors.surface) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(4.dp)) .heightIn(max = 280.dp) ) { options.forEach { option -> @@ -515,7 +534,7 @@ fun SeasonDropdown( text = { Text( text = option, - color = if (isSelected) PrimaryRed else PrimaryWhite, + color = if (isSelected) colors.primary else colors.textPrimary, fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, fontSize = 14.sp ) @@ -525,7 +544,7 @@ fun SeasonDropdown( expanded = false }, colors = MenuDefaults.itemColors( - textColor = PrimaryWhite + textColor = colors.textPrimary ) ) } @@ -547,15 +566,17 @@ fun MovieDetailsMovieCard( maturityRating: String? = null, duration: String? = null, quality: String? = null, + showLogo: Boolean = false, showBottomTitle: Boolean = false, - onClick: () -> Unit = {}, + onClick: (() -> Unit)? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, enabled: Boolean = true ) { val isFocused by interactionSource.collectIsFocusedAsState() val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors - val scale by animateFloatAsState( + val scaleState = animateFloatAsState( targetValue = if (isFocused) MovieDetailsTokens.FOCUS_SCALE_FACTOR else 1f, animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, label = "cardScale" @@ -570,133 +591,146 @@ fun MovieDetailsMovieCard( val cardWidth = MovieCardDefaults.cardWidth(type, size) val imageUrl = if (type == MovieCardType.POSTER) (posterUrl ?: backdropUrl) else (backdropUrl ?: posterUrl) - Column( + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, colors.primary) + } else { + BorderStroke(dimens.borderSubtle, colors.border) + } + + Box( modifier = modifier .width(cardWidth) - .graphicsLayer { - scaleX = scale - scaleY = scale - } - .zIndex(if (isFocused) 5f else 0f) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick + .then( + if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + } else { + Modifier.focusable(enabled = enabled, interactionSource = interactionSource) + } ) - .focusable(enabled = enabled, interactionSource = interactionSource) .semantics { contentDescription = title - } + }, + contentAlignment = Alignment.Center ) { - Box( + Column( modifier = Modifier .fillMaxWidth() - .aspectRatio(aspectRatio) - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(Grey850) - .then( - if (isFocused) { - Modifier.border(BorderStroke(dimens.borderFocus, PrimaryWhite), MovieDetailsTokens.ShapeCardSmall) - } else { - Modifier.border(BorderStroke(dimens.borderSubtle, Color.Transparent), MovieDetailsTokens.ShapeCardSmall) - } - ) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } ) { - if (!imageUrl.isNullOrBlank()) { - AsyncImage( - model = imageUrl, - contentDescription = title, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } else { - Box( - modifier = Modifier - .fillMaxSize() - .background(Grey800), - contentAlignment = Alignment.Center - ) { - Text( - text = title.take(2).uppercase(), - color = Grey200, - fontSize = 18.sp, - fontWeight = FontWeight.Bold + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(aspectRatio) + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.surface) + .border(border, MovieDetailsTokens.ShapeCardSmall) + ) { + if (!imageUrl.isNullOrBlank()) { + AsyncImage( + model = imageUrl, + contentDescription = title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .background(colors.surface), + contentAlignment = Alignment.Center + ) { + Text( + text = title.take(2).uppercase(), + color = colors.textSecondary, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } } - } - if (type == MovieCardType.POSTER && showBottomTitle) { - Box( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf(Color.Transparent, TransparentBlack90), - startY = 0f + if (type == MovieCardType.POSTER && showBottomTitle) { + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, TransparentBlack90), + startY = 0f + ) ) + .padding(horizontal = 8.dp, vertical = 6.dp) + ) { + Text( + text = title, + color = colors.textPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) - .padding(horizontal = 8.dp, vertical = 6.dp) - ) { - Text( - text = title, - color = PrimaryWhite, - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + } } - } - if (progress != null && progress > 0f) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(dimens.progressHeight) - .align(Alignment.BottomCenter) - .background(Grey700) - ) { + if (progress != null && progress > 0f) { Box( modifier = Modifier - .fillMaxHeight() - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .background(PrimaryRed) - ) + .fillMaxWidth() + .height(dimens.progressHeight) + .align(Alignment.BottomCenter) + .background(colors.border) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .background(colors.primary) + ) + } } - } - if (badge != null) { - Box( - modifier = Modifier - .align(Alignment.TopStart) - .padding(4.dp) - .clip(RoundedCornerShape(2.dp)) - .background(PrimaryRed) - .padding(horizontal = 5.dp, vertical = 2.dp) - ) { - Text( - text = stringResource(id = badge.stringRes), - color = PrimaryWhite, - fontSize = 9.sp, - fontWeight = FontWeight.Bold - ) + if (badge != null) { + Box( + modifier = Modifier + .align(Alignment.TopStart) + .padding(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(colors.primary) + .padding(horizontal = 5.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(id = badge.stringRes), + color = colors.onPrimary, + fontSize = 9.sp, + fontWeight = FontWeight.Bold + ) + } } } - } - if (type != MovieCardType.POSTER && showBottomTitle) { - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = title, - color = if (isFocused) PrimaryWhite else Grey200, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + if (type != MovieCardType.POSTER && showBottomTitle) { + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = title, + color = if (isFocused) colors.textPrimary else colors.textSecondary, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt index ab4b7d0d900..aeb29c91e89 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.result.compose.theme +import android.content.Context import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Shapes @@ -8,14 +9,18 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute val AppFontFamily: FontFamily = FontFamily.Default @@ -206,29 +211,85 @@ object MovieDetailsTheme { get() = MaterialTheme.shapes } +@Composable +fun rememberMovieDetailsColors(): MovieDetailsColors { + val context = LocalContext.current + return remember(context) { + val primary = try { + Color(context.colorFromAttribute(R.attr.colorPrimary)) + } catch (_: Throwable) { + PrimaryRed + } + val onPrimary = try { + Color(context.colorFromAttribute(com.google.android.material.R.attr.colorOnPrimary)) + } catch (_: Throwable) { + PrimaryWhite + } + val background = try { + Color(context.colorFromAttribute(R.attr.primaryBlackBackground)) + } catch (_: Throwable) { + CanvasBackground + } + val surface = try { + Color(context.colorFromAttribute(R.attr.primaryGrayBackground)) + } catch (_: Throwable) { + SurfaceDark + } + val surfaceElevated = try { + Color(context.colorFromAttribute(R.attr.boxItemBackground)) + } catch (_: Throwable) { + surface + } + val textPrimary = try { + Color(context.colorFromAttribute(R.attr.textColor)) + } catch (_: Throwable) { + PrimaryWhite + } + val textSecondary = try { + Color(context.colorFromAttribute(R.attr.grayTextColor)) + } catch (_: Throwable) { + Grey200 + } + val border = try { + Color(context.colorFromAttribute(R.attr.iconGrayBackground)).copy(alpha = 0.4f) + } catch (_: Throwable) { + BorderSubtle + } + + MovieDetailsColors( + primary = primary, + onPrimary = onPrimary, + background = background, + onBackground = textPrimary, + surface = surface, + surfaceElevated = surfaceElevated, + onSurface = textPrimary, + border = border, + divider = border, + textPrimary = textPrimary, + textSecondary = textSecondary, + textMuted = textSecondary, + textDimmed = textSecondary, + greenAccent = GreenAccent, + yellowAccent = YellowAccent, + orangeAccent = OrangeAccent + ) + } +} + @Composable fun MovieDetailsTheme( - colors: MovieDetailsColors = MovieDetailsColors(), + colors: MovieDetailsColors = rememberMovieDetailsColors(), typography: MovieDetailsTypography = MovieDetailsTypography(), dimens: MovieDetailsDimens = MovieDetailsDimens(), content: @Composable () -> Unit ) { - val materialColors = darkColorScheme( - primary = colors.primary, - onPrimary = colors.onPrimary, - background = colors.background, - onBackground = colors.onBackground, - surface = colors.surface, - onSurface = colors.onSurface - ) - CompositionLocalProvider( LocalMovieDetailsColors provides colors, LocalMovieDetailsTypography provides typography, LocalMovieDetailsDimens provides dimens ) { MaterialTheme( - colorScheme = materialColors, shapes = MovieDetailsShapes, content = content ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38f834f29ce..64172e10bdf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -792,12 +792,28 @@ %d downloads queued Live - - Recently Added Top 10 New Season New Episodes Leaving Soon Must Watch + Play Trailer + Add to My List + In My List + Select Season + Trailers & More + More Like This + Cast: + Genres: + This show is: + Creator: + Writers: + About %1$s + Maturity Rating: + Download Episode %1$d + Episode %1$d + Audio Description + TOP\n10 + Close From e856c58674912dcd73326c3e83dc5cbc305b43f0 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 09:49:46 +0700 Subject: [PATCH 20/35] chore(result): revert provider text outline styling --- .../compose/MovieDetailsComposeScreen.kt | 48 ++----------------- 1 file changed, 5 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 50db52d8408..91f93e8590c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -47,11 +47,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.StrokeJoin -import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.luminance import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext @@ -413,46 +409,12 @@ fun MovieDetailsComposeScreen( .padding(dimens.spacing2Xl) ) { if (!providerName.isNullOrBlank()) { - val textColor = colors.primary - val isLightText = textColor.luminance() > 0.5f - val innerOutlineColor = if (isLightText) Color.Black else Color.White - val outerOutlineColor = if (isLightText) Color.White else Color.Black - - val providerStyle = typography.boldTitle2.copy( - fontWeight = FontWeight.Black + Text( + text = providerName.uppercase(), + style = typography.boldTitle2, + color = colors.primary, + letterSpacing = 2.sp ) - Box(contentAlignment = Alignment.Center) { - Text( - text = providerName.uppercase(), - style = providerStyle.copy( - drawStyle = Stroke( - width = 24f, - join = StrokeJoin.Round, - cap = StrokeCap.Round - ) - ), - color = outerOutlineColor, - letterSpacing = 2.sp - ) - Text( - text = providerName.uppercase(), - style = providerStyle.copy( - drawStyle = Stroke( - width = 16f, - join = StrokeJoin.Round, - cap = StrokeCap.Round - ) - ), - color = innerOutlineColor, - letterSpacing = 2.sp - ) - Text( - text = providerName.uppercase(), - style = providerStyle, - color = textColor, - letterSpacing = 2.sp - ) - } } } From f61f8888538fcb02b71ee84261e3a88979727fc6 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 09:50:30 +0700 Subject: [PATCH 21/35] style(result): tighten provider name letterSpacing and apply extra bold weight --- .../ui/result/compose/MovieDetailsComposeScreen.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 91f93e8590c..e1ee9bc8c52 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -411,9 +411,11 @@ fun MovieDetailsComposeScreen( if (!providerName.isNullOrBlank()) { Text( text = providerName.uppercase(), - style = typography.boldTitle2, - color = colors.primary, - letterSpacing = 2.sp + style = typography.boldTitle2.copy( + fontWeight = FontWeight.Black, + letterSpacing = (-0.8).sp + ), + color = colors.primary ) } } From 3a5e57f674629b82e046104b08b8b448e3b179b3 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 09:57:01 +0700 Subject: [PATCH 22/35] feat(result): add quick search action button to TV movie details hero banner --- .../ui/result/ResultFragmentTv.kt | 5 ++++ .../compose/MovieDetailsComposeScreen.kt | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index cf73ef4bb67..e45ea6f4550 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -262,6 +262,11 @@ class ResultFragmentTv : BaseFragment( ) } }, + onSearchClick = { + title.let { t -> + QuickSearchFragment.pushSearch(activity, t) + } + }, onActorClick = { actorName -> QuickSearchFragment.pushSearch(activity, actorName) }, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index e1ee9bc8c52..b4bc3dea0e9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -136,6 +136,7 @@ fun MovieDetailsComposeScreen( onAddToListClick: () -> Unit = {}, onLikeClick: () -> Unit = {}, onTrailerClick: () -> Unit = {}, + onSearchClick: (() -> Unit)? = null, onActorClick: ((String) -> Unit)? = null, onRecommendationClick: ((SearchResponse) -> Unit)? = null, onCloseClick: () -> Unit = {} @@ -154,6 +155,7 @@ fun MovieDetailsComposeScreen( val inMyListInteractionSource = remember { MutableInteractionSource() } val likeInteractionSource = remember { MutableInteractionSource() } val trailerInteractionSource = remember { MutableInteractionSource() } + val searchInteractionSource = remember { MutableInteractionSource() } val playButtonFocusRequester = remember { FocusRequester() } @@ -384,6 +386,20 @@ fun MovieDetailsComposeScreen( onClick = onLikeClick ) ) + + if (onSearchClick != null) { + Box( + modifier = Modifier + .size(40.dp) + .focusable(interactionSource = searchInteractionSource) + .clickable( + interactionSource = searchInteractionSource, + indication = null, + role = Role.Button, + onClick = onSearchClick + ) + ) + } } if (hasTrailers) { @@ -493,6 +509,16 @@ fun MovieDetailsComposeScreen( interactionSource = likeInteractionSource, enabled = false ) + + if (onSearchClick != null) { + CircleActionButton( + icon = painterResource(id = R.drawable.search_icon), + contentDescription = stringResource(id = R.string.title_search), + onClick = onSearchClick, + interactionSource = searchInteractionSource, + enabled = false + ) + } } } From 76fad60cabc125bd873e21d284a857e68976eb9d Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 10:01:19 +0700 Subject: [PATCH 23/35] feat(result): restore Dub/Sub selector, Episode Range selector, and Long-Click options dialog on TV --- .../ui/result/ResultFragmentTv.kt | 59 +++++++++++++ .../compose/MovieDetailsComposeScreen.kt | 82 ++++++++++++++++++- .../components/MovieDetailsComponents.kt | 9 +- 3 files changed, 144 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index e45ea6f4550..06473f511b6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -18,6 +18,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.CommonActivity +import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse @@ -64,6 +65,12 @@ class ResultFragmentTv : BaseFragment( private var composeRawSeasonsState by mutableStateOf>>(emptyList()) private var composeSeasonsState by mutableStateOf>(emptyList()) private var composeSelectedSeasonIndexState by mutableStateOf(0) + private var composeDubSubSelectionsState by mutableStateOf>>(emptyList()) + private var composeDubSubState by mutableStateOf>(emptyList()) + private var composeSelectedDubStatusIndexState by mutableStateOf(0) + private var composeRangeSelectionsState by mutableStateOf>>(emptyList()) + private var composeRangesState by mutableStateOf>(emptyList()) + private var composeSelectedRangeIndexState by mutableStateOf(0) private var composeHasTrailersState by mutableStateOf(false) private var composeResumeWatchingState by mutableStateOf(null) @@ -173,6 +180,20 @@ class ResultFragmentTv : BaseFragment( dynamicRecommendations = composeRecommendationsState, dynamicSeasons = composeSeasonsState, selectedSeasonIndex = composeSelectedSeasonIndexState, + dynamicDubs = composeDubSubState, + selectedDubIndex = composeSelectedDubStatusIndexState, + onDubSelect = { idx -> + composeDubSubSelectionsState.getOrNull(idx)?.second?.let { dub -> + viewModel.changeDubStatus(dub) + } + }, + dynamicRanges = composeRangesState, + selectedRangeIndex = composeSelectedRangeIndexState, + onRangeSelect = { idx -> + composeRangeSelectionsState.getOrNull(idx)?.second?.let { range -> + viewModel.changeRange(range) + } + }, isInWatchList = composeWatchStatusState != WatchType.NONE, hasTrailers = composeHasTrailersState, resumeStatus = composeResumeWatchingState, @@ -204,6 +225,18 @@ class ResultFragmentTv : BaseFragment( } } }, + onPlayLongClick = { + val resume = composeResumeWatchingState + val ep = resume?.result ?: composeEpisodesState.firstOrNull() ?: (viewModel.movie.value as? Resource.Success)?.value?.second + if (ep != null) { + viewModel.handleAction( + EpisodeClickEvent( + ACTION_SHOW_OPTIONS, + ep + ) + ) + } + }, onEpisodeClick = { ep -> viewModel.handleAction( EpisodeClickEvent( @@ -212,6 +245,14 @@ class ResultFragmentTv : BaseFragment( ) ) }, + onEpisodeLongClick = { ep -> + viewModel.handleAction( + EpisodeClickEvent( + ACTION_SHOW_OPTIONS, + ep + ) + ) + }, onEpisodeDownloadClick = { ep -> viewModel.handleAction( EpisodeClickEvent( @@ -369,6 +410,24 @@ class ResultFragmentTv : BaseFragment( composeSeasonsState = it.map { s -> s.first?.asStringNull(context) ?: "" } } + observe(viewModel.dubSubSelections) { + composeDubSubSelectionsState = it + composeDubSubState = it.map { d -> d.first?.asStringNull(context) ?: "" } + } + + observe(viewModel.selectedDubStatusIndex) { + composeSelectedDubStatusIndexState = it + } + + observe(viewModel.rangeSelections) { + composeRangeSelectionsState = it + composeRangesState = it.map { r -> r.first?.asStringNull(context) ?: "" } + } + + observe(viewModel.selectedRangeIndex) { + composeSelectedRangeIndexState = it + } + observe(viewModel.recommendations) { recommendations -> composeRecommendationsState = recommendations } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index b4bc3dea0e9..ed69213a2eb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.focusable import androidx.compose.foundation.gestures.BringIntoViewSpec import androidx.compose.foundation.gestures.LocalBringIntoViewSpec @@ -127,6 +128,14 @@ fun MovieDetailsComposeScreen( resumeStatus: ResumeWatchingStatus? = null, isMovie: Boolean = true, selectedSeasonIndex: Int = 0, + dynamicDubs: List? = null, + selectedDubIndex: Int = 0, + onDubSelect: ((Int) -> Unit)? = null, + dynamicRanges: List? = null, + selectedRangeIndex: Int = 0, + onRangeSelect: ((Int) -> Unit)? = null, + onPlayLongClick: (() -> Unit)? = null, + onEpisodeLongClick: ((ResultEpisode) -> Unit)? = null, isInWatchList: Boolean = false, hasTrailers: Boolean = false, onPlayClick: () -> Unit = {}, @@ -180,6 +189,34 @@ fun MovieDetailsComposeScreen( } } + val dubOptions = remember(dynamicDubs) { + dynamicDubs ?: emptyList() + } + + var selectedDubText by remember(selectedDubIndex, dubOptions) { + mutableStateOf(dubOptions.getOrElse(selectedDubIndex) { dubOptions.firstOrNull() ?: "" }) + } + + LaunchedEffect(selectedDubIndex, dubOptions) { + if (dubOptions.isNotEmpty()) { + selectedDubText = dubOptions.getOrElse(selectedDubIndex) { dubOptions.first() } + } + } + + val rangeOptions = remember(dynamicRanges) { + dynamicRanges ?: emptyList() + } + + var selectedRangeText by remember(selectedRangeIndex, rangeOptions) { + mutableStateOf(rangeOptions.getOrElse(selectedRangeIndex) { rangeOptions.firstOrNull() ?: "" }) + } + + LaunchedEffect(selectedRangeIndex, rangeOptions) { + if (rangeOptions.isNotEmpty()) { + selectedRangeText = rangeOptions.getOrElse(selectedRangeIndex) { rangeOptions.first() } + } + } + fun showToast(message: String) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } @@ -344,11 +381,12 @@ fun MovieDetailsComposeScreen( .width(110.dp) .focusRequester(playButtonFocusRequester) .focusable(interactionSource = playInteractionSource) - .clickable( + .combinedClickable( interactionSource = playInteractionSource, indication = null, role = Role.Button, - onClick = onPlayClick + onClick = onPlayClick, + onLongClick = onPlayLongClick ) ) @@ -473,6 +511,7 @@ fun MovieDetailsComposeScreen( HeroPlayButton( onClick = onPlayClick, + onLongClick = onPlayLongClick, text = playButtonText, progress = resumeProgressFraction, interactionSource = playInteractionSource, @@ -751,6 +790,32 @@ fun MovieDetailsComposeScreen( width = 180.dp ) } + + if (dubOptions.size > 1) { + SeasonDropdown( + options = dubOptions, + selectedOption = selectedDubText, + onOptionSelected = { dubStr -> + selectedDubText = dubStr + val idx = dubOptions.indexOf(dubStr) + if (idx >= 0) onDubSelect?.invoke(idx) + }, + width = 140.dp + ) + } + + if (rangeOptions.size > 1) { + SeasonDropdown( + options = rangeOptions, + selectedOption = selectedRangeText, + onOptionSelected = { rangeStr -> + selectedRangeText = rangeStr + val idx = rangeOptions.indexOf(rangeStr) + if (idx >= 0) onRangeSelect?.invoke(idx) + }, + width = 140.dp + ) + } } } @@ -771,10 +836,16 @@ fun MovieDetailsComposeScreen( else showToast("Downloading Episode ${ep.episode}") } } + val onEpLongClick = remember(ep) { + if (onEpisodeLongClick != null) { + { onEpisodeLongClick(ep) } + } else null + } EpisodeRowItem( episode = ep, onClick = onEpClick, onDownloadClick = onEpDownload, + onLongClick = onEpLongClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp) @@ -922,11 +993,13 @@ fun MovieDetailsComposeScreen( } } +@OptIn(ExperimentalFoundationApi::class) @Composable fun EpisodeRowItem( episode: ResultEpisode, onClick: () -> Unit, onDownloadClick: () -> Unit, + onLongClick: (() -> Unit)? = null, modifier: Modifier = Modifier ) { val interactionSource = remember { MutableInteractionSource() } @@ -956,11 +1029,12 @@ fun EpisodeRowItem( .clip(MovieDetailsTokens.ShapeCardMedium) .background(background) .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardMedium) else Modifier) - .clickable( + .combinedClickable( interactionSource = interactionSource, indication = null, role = Role.Button, - onClick = onClick + onClick = onClick, + onLongClick = onLongClick ) .focusable(interactionSource = interactionSource) .padding(dimens.spacingL), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt index 944191a8ae9..2ffab6ad22d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt @@ -4,9 +4,11 @@ import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState @@ -143,12 +145,14 @@ object MovieCardDefaults { } } +@OptIn(ExperimentalFoundationApi::class) @Composable fun HeroPlayButton( onClick: () -> Unit, modifier: Modifier = Modifier, text: String = stringResource(id = R.string.play_movie_button), progress: Float? = null, + onLongClick: (() -> Unit)? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, enabled: Boolean = true ) { @@ -167,12 +171,13 @@ fun HeroPlayButton( Box( modifier = modifier - .clickable( + .combinedClickable( interactionSource = interactionSource, indication = null, enabled = enabled, role = Role.Button, - onClick = onClick + onClick = onClick, + onLongClick = onLongClick ) .focusable(enabled = enabled, interactionSource = interactionSource) .semantics { From d07e9411eaf6544fdb2fc1f083e6472a5b26b735 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 10:03:45 +0700 Subject: [PATCH 24/35] fix(result): ensure 100% action wiring and state synchronization between ViewModel and Compose Movie Details --- .../ui/result/ResultFragmentTv.kt | 21 +++++++++++-- .../compose/MovieDetailsComposeScreen.kt | 30 +++++++++---------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 06473f511b6..8ce8d027971 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -34,6 +34,7 @@ import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent import com.lagradost.cloudstream3.ui.result.compose.MovieDetailsComposeScreen +import com.lagradost.cloudstream3.ui.result.compose.MovieTrailerData import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SearchClickCallback @@ -62,6 +63,7 @@ class ResultFragmentTv : BaseFragment( private var composeRecommendationsState by mutableStateOf>(emptyList()) private var composeActorsState by mutableStateOf>(emptyList()) private var composeWatchStatusState by mutableStateOf(WatchType.NONE) + private var composeFavoriteStatusState by mutableStateOf(false) private var composeRawSeasonsState by mutableStateOf>>(emptyList()) private var composeSeasonsState by mutableStateOf>(emptyList()) private var composeSelectedSeasonIndexState by mutableStateOf(0) @@ -71,6 +73,7 @@ class ResultFragmentTv : BaseFragment( private var composeRangeSelectionsState by mutableStateOf>>(emptyList()) private var composeRangesState by mutableStateOf>(emptyList()) private var composeSelectedRangeIndexState by mutableStateOf(0) + private var composeTrailersDataState by mutableStateOf>(emptyList()) private var composeHasTrailersState by mutableStateOf(false) private var composeResumeWatchingState by mutableStateOf(null) @@ -194,7 +197,9 @@ class ResultFragmentTv : BaseFragment( viewModel.changeRange(range) } }, + dynamicTrailers = composeTrailersDataState, isInWatchList = composeWatchStatusState != WatchType.NONE, + isFavorite = composeFavoriteStatusState, hasTrailers = composeHasTrailersState, resumeStatus = composeResumeWatchingState, isMovie = composeEpisodesState.isEmpty() || (composeResumeWatchingState?.isMovie == true), @@ -334,9 +339,19 @@ class ResultFragmentTv : BaseFragment( } observe(viewModel.trailers) { trailersLinks -> - val extractedTrailerLinks = trailersLinks.flatMap { it.mirros } - .map { (extractedTrailerLink, _) -> extractedTrailerLink } - composeHasTrailersState = extractedTrailerLinks.isNotEmpty() + val trailerItems = trailersLinks.flatMap { it.mirros }.mapNotNull { (extractedTrailerLink, _) -> + MovieTrailerData( + title = extractedTrailerLink.name.ifBlank { getString(R.string.play_trailer) }, + runtime = "", + rawTrailer = extractedTrailerLink + ) + } + composeTrailersDataState = trailerItems + composeHasTrailersState = trailerItems.isNotEmpty() + } + + observeNullable(viewModel.favoriteStatus) { isFav -> + composeFavoriteStatusState = isFav == true } observe(viewModel.watchStatus) { watchType -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index ed69213a2eb..867a6a4243e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -137,6 +137,7 @@ fun MovieDetailsComposeScreen( onPlayLongClick: (() -> Unit)? = null, onEpisodeLongClick: ((ResultEpisode) -> Unit)? = null, isInWatchList: Boolean = false, + isFavorite: Boolean = false, hasTrailers: Boolean = false, onPlayClick: () -> Unit = {}, onEpisodeClick: ((ResultEpisode) -> Unit)? = null, @@ -173,8 +174,6 @@ fun MovieDetailsComposeScreen( playButtonFocusRequester.requestFocus() } - var inMyListState by remember(isInWatchList) { mutableStateOf(isInWatchList) } - val seasonOptions = remember(dynamicSeasons) { dynamicSeasons ?: emptyList() } @@ -406,11 +405,9 @@ fun MovieDetailsComposeScreen( .clickable( interactionSource = inMyListInteractionSource, indication = null, - role = Role.Button - ) { - inMyListState = !inMyListState - onAddToListClick() - } + role = Role.Button, + onClick = onAddToListClick + ) ) Box( @@ -526,24 +523,27 @@ fun MovieDetailsComposeScreen( ) { CircleActionButton( icon = painterResource( - id = if (inMyListState) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 + id = if (isInWatchList) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 ), - contentDescription = if (inMyListState) { + contentDescription = if (isInWatchList) { stringResource(id = R.string.in_my_list) } else { stringResource(id = R.string.add_to_my_list) }, - onClick = { - inMyListState = !inMyListState - onAddToListClick() - }, + onClick = onAddToListClick, interactionSource = inMyListInteractionSource, enabled = false ) CircleActionButton( - icon = painterResource(id = R.drawable.ic_baseline_favorite_24), - contentDescription = stringResource(id = R.string.favorite), + icon = painterResource( + id = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 + ), + contentDescription = if (isFavorite) { + stringResource(id = R.string.unfavorite) + } else { + stringResource(id = R.string.favorite) + }, onClick = onLikeClick, interactionSource = likeInteractionSource, enabled = false From 26efbb65669687afa3496ce86f235036be57be0e Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 11:05:15 +0700 Subject: [PATCH 25/35] fix(detail): update TV result screen layout and movie details components --- .../ui/result/ResultFragmentTv.kt | 8 -- .../compose/MovieDetailsComposeScreen.kt | 61 --------- .../components/MovieDetailsComponents.kt | 24 +++- .../main/res/layout/fragment_result_tv.xml | 127 ++++++++++++++---- 4 files changed, 123 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 8ce8d027971..b9d600fd55e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -258,14 +258,6 @@ class ResultFragmentTv : BaseFragment( ) ) }, - onEpisodeDownloadClick = { ep -> - viewModel.handleAction( - EpisodeClickEvent( - ACTION_DOWNLOAD_EPISODE, - ep - ) - ) - }, onSeasonSelect = { idx -> val seasonNumber = composeRawSeasonsState.getOrNull(idx)?.second ?: (idx + 1) viewModel.changeSeason(seasonNumber) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 867a6a4243e..9f233ccf385 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -141,7 +141,6 @@ fun MovieDetailsComposeScreen( hasTrailers: Boolean = false, onPlayClick: () -> Unit = {}, onEpisodeClick: ((ResultEpisode) -> Unit)? = null, - onEpisodeDownloadClick: ((ResultEpisode) -> Unit)? = null, onSeasonSelect: ((Int) -> Unit)? = null, onAddToListClick: () -> Unit = {}, onLikeClick: () -> Unit = {}, @@ -307,8 +306,6 @@ fun MovieDetailsComposeScreen( } } - val trailersList = dynamicTrailers ?: emptyList() - val tvBringIntoViewSpec = remember { object : BringIntoViewSpec { override fun calculateScrollDistance( @@ -379,7 +376,6 @@ fun MovieDetailsComposeScreen( .height(42.dp) .width(110.dp) .focusRequester(playButtonFocusRequester) - .focusable(interactionSource = playInteractionSource) .combinedClickable( interactionSource = playInteractionSource, indication = null, @@ -401,7 +397,6 @@ fun MovieDetailsComposeScreen( Box( modifier = Modifier .size(40.dp) - .focusable(interactionSource = inMyListInteractionSource) .clickable( interactionSource = inMyListInteractionSource, indication = null, @@ -413,7 +408,6 @@ fun MovieDetailsComposeScreen( Box( modifier = Modifier .size(40.dp) - .focusable(interactionSource = likeInteractionSource) .clickable( interactionSource = likeInteractionSource, indication = null, @@ -426,7 +420,6 @@ fun MovieDetailsComposeScreen( Box( modifier = Modifier .size(40.dp) - .focusable(interactionSource = searchInteractionSource) .clickable( interactionSource = searchInteractionSource, indication = null, @@ -442,7 +435,6 @@ fun MovieDetailsComposeScreen( modifier = Modifier .height(40.dp) .width(120.dp) - .focusable(interactionSource = trailerInteractionSource) .clickable( interactionSource = trailerInteractionSource, indication = null, @@ -830,12 +822,6 @@ fun MovieDetailsComposeScreen( else showToast("Playing ${ep.headerName}: ${ep.name}") } } - val onEpDownload = remember(ep) { - { - if (onEpisodeDownloadClick != null) onEpisodeDownloadClick(ep) - else showToast("Downloading Episode ${ep.episode}") - } - } val onEpLongClick = remember(ep) { if (onEpisodeLongClick != null) { { onEpisodeLongClick(ep) } @@ -844,7 +830,6 @@ fun MovieDetailsComposeScreen( EpisodeRowItem( episode = ep, onClick = onEpClick, - onDownloadClick = onEpDownload, onLongClick = onEpLongClick, modifier = Modifier .fillMaxWidth() @@ -886,44 +871,6 @@ fun MovieDetailsComposeScreen( } } - if (trailersList.isNotEmpty()) { - item(key = "trailers_section", contentType = "trailers_section") { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) - ) { - Text( - text = stringResource(id = R.string.trailers_and_more), - style = typography.boldTitle2, - fontSize = 22.sp, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - trailersList.forEach { trailer -> - Box(modifier = Modifier.weight(1f)) { - TrailerItemCard( - trailer = trailer, - onClick = { - if (hasTrailers) onTrailerClick() - else showToast("Playing ${trailer.title}") - }, - modifier = Modifier.fillMaxWidth() - ) - } - } - } - } - } - } - val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || !maturityRating.isNullOrBlank() @@ -998,7 +945,6 @@ fun MovieDetailsComposeScreen( fun EpisodeRowItem( episode: ResultEpisode, onClick: () -> Unit, - onDownloadClick: () -> Unit, onLongClick: (() -> Unit)? = null, modifier: Modifier = Modifier ) { @@ -1138,13 +1084,6 @@ fun EpisodeRowItem( ) } } - - CircleActionButton( - icon = painterResource(id = R.drawable.baseline_downloading_24), - contentDescription = stringResource(id = R.string.download_episode_format, episode.episode), - onClick = onDownloadClick, - modifier = Modifier.size(36.dp) - ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt index 2ffab6ad22d..dedfe88fc6c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource @@ -535,6 +536,13 @@ fun SeasonDropdown( ) { options.forEach { option -> val isSelected = option == selectedOption + val optionInteractionSource = remember(option) { MutableInteractionSource() } + val isOptionFocused by optionInteractionSource.collectIsFocusedAsState() + val focusColor = colors.onSurface.copy( + alpha = if (colors.surface.luminance() < 0.5f) 0.22f else 0.12f + ) + val focusBorder = colors.onSurface.copy(alpha = 0.72f) + DropdownMenuItem( text = { Text( @@ -548,8 +556,22 @@ fun SeasonDropdown( onOptionSelected(option) expanded = false }, + modifier = Modifier + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(if (isOptionFocused) focusColor else Color.Transparent) + .then( + if (isOptionFocused) { + Modifier.border( + BorderStroke(2.dp, focusBorder), + MovieDetailsTokens.ShapeCardSmall + ) + } else { + Modifier + } + ), + interactionSource = optionInteractionSource, colors = MenuDefaults.itemColors( - textColor = colors.textPrimary + textColor = if (isSelected) colors.primary else colors.textPrimary ) ) } diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 85c9b52a1f2..1f33a341e00 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -44,47 +44,120 @@ https://developer.android.com/design/ui/tv/samples/jet-fit - - - + android:layout_height="0dp" + android:layout_weight="0.65"> - + android:background="@color/grayShimmer" + app:cardCornerRadius="0dp" /> - - - - - - - + - + + + + + + + + + + + + + + + + - + - + android:layout_height="0dp" + android:layout_weight="0.35" + android:layout_marginStart="@dimen/result_padding" + android:layout_marginTop="@dimen/loading_margin" + android:layout_marginEnd="@dimen/result_padding" + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + - + - + - + + From db1362cf97d5019e162ff12a809e7db1bb777c51 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 13:08:01 +0700 Subject: [PATCH 26/35] fix(result): resolve linter issues and code complexity warnings in TV details --- .gitignore | 5 - .../ui/result/ResultFragmentTv.kt | 188 +-- .../compose/MovieDetailsComposeScreen.kt | 1323 +++++++++-------- .../components/MovieDetailsComponents.kt | 291 ++-- .../result/compose/theme/MovieDetailsTheme.kt | 2 +- 5 files changed, 1006 insertions(+), 803 deletions(-) diff --git a/.gitignore b/.gitignore index a96e2c4d8a7..5fc9f0870b6 100644 --- a/.gitignore +++ b/.gitignore @@ -226,8 +226,3 @@ fabric.properties !/gradle/wrapper/gradle-wrapper.jar # End of https://www.toptal.com/developers/gitignore/api/kotlin,java,android,androidstudio,visualstudiocode -.agents/ -.codegraph/ -.rtk/ -CLAUDE.md -gradle/gradle-daemon-jvm.properties diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index b9d600fd55e..460dbd22e8b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -139,7 +139,7 @@ class ResultFragmentTv : BaseFragment( activity?.window?.decorView?.clearFocus() activity?.loadCache() hideKeyboard() - if (storedData.restart || !viewModel.hasLoaded()) + if (storedData.restart || !viewModel.hasLoaded()) { viewModel.load( activity, storedData.url, @@ -148,9 +148,96 @@ class ResultFragmentTv : BaseFragment( storedData.dubStatus, storedData.start ) + } // ===== ===== ===== - var comingSoon = false + setupComposeView(binding, storedData) + setupViewModelObservers(binding, storedData) + } + + private fun handlePlayClick(storedData: ResultFragment.StoredData) { + val resume = composeResumeWatchingState + if (resume != null) { + viewModel.handleAction( + EpisodeClickEvent( + storedData.playerAction, + resume.result + ) + ) + } else { + val ep = composeEpisodesState.firstOrNull() + if (ep != null) { + viewModel.handleAction( + EpisodeClickEvent( + storedData.playerAction, + ep + ) + ) + } else { + (viewModel.movie.value as? Resource.Success)?.value?.let { (_, movieEp) -> + viewModel.handleAction( + EpisodeClickEvent(ACTION_CLICK_DEFAULT, movieEp) + ) + } + } + } + } + + private fun handlePlayLongClick() { + val resume = composeResumeWatchingState + val ep = resume?.result ?: composeEpisodesState.firstOrNull() ?: (viewModel.movie.value as? Resource.Success)?.value?.second + if (ep != null) { + viewModel.handleAction( + EpisodeClickEvent( + ACTION_SHOW_OPTIONS, + ep + ) + ) + } + } + + private fun handleTrailerClick() { + val trailersLinks = viewModel.trailers.value ?: emptyList() + val extractedTrailerLinks = trailersLinks.flatMap { it.mirros } + .map { (extractedTrailerLink, _) -> extractedTrailerLink } + if (extractedTrailerLinks.isNotEmpty()) { + activity.navigate( + R.id.global_to_navigation_player, + GeneratorPlayer.newInstance( + ExtractorLinkGenerator( + extractedTrailerLinks, + emptyList() + ), + 0 + ) + ) + } + } + + private fun handleAddToListClick() { + val curStatus = composeWatchStatusState + activity?.showBottomDialog( + WatchType.entries.map { getString(it.stringRes) }.toList(), + curStatus.ordinal, + getString(R.string.action_add_to_bookmarks), + showApply = false, + {} + ) { + viewModel.updateWatchStatus(WatchType.entries[it], context) + } + } + + private fun handleLikeClick() { + viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? -> + if (newStatus == null) return@toggleFavoriteStatus + val message = if (newStatus) R.string.favorite_added else R.string.favorite_removed + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + } + } + + private fun setupComposeView(binding: FragmentResultTvBinding, storedData: ResultFragment.StoredData) { binding.resultComposeView.setContent { MovieDetailsTheme { val page = composePageState @@ -202,46 +289,9 @@ class ResultFragmentTv : BaseFragment( isFavorite = composeFavoriteStatusState, hasTrailers = composeHasTrailersState, resumeStatus = composeResumeWatchingState, - isMovie = composeEpisodesState.isEmpty() || (composeResumeWatchingState?.isMovie == true), - onPlayClick = { - val resume = composeResumeWatchingState - if (resume != null) { - viewModel.handleAction( - EpisodeClickEvent( - storedData.playerAction, - resume.result - ) - ) - } else { - val ep = composeEpisodesState.firstOrNull() - if (ep != null) { - viewModel.handleAction( - EpisodeClickEvent( - storedData.playerAction, - ep - ) - ) - } else { - (viewModel.movie.value as? Resource.Success)?.value?.let { (_, movieEp) -> - viewModel.handleAction( - EpisodeClickEvent(ACTION_CLICK_DEFAULT, movieEp) - ) - } - } - } - }, - onPlayLongClick = { - val resume = composeResumeWatchingState - val ep = resume?.result ?: composeEpisodesState.firstOrNull() ?: (viewModel.movie.value as? Resource.Success)?.value?.second - if (ep != null) { - viewModel.handleAction( - EpisodeClickEvent( - ACTION_SHOW_OPTIONS, - ep - ) - ) - } - }, + isMovie = composeEpisodesState.isEmpty() || composeResumeWatchingState?.isMovie == true, + onPlayClick = { handlePlayClick(storedData) }, + onPlayLongClick = { handlePlayLongClick() }, onEpisodeClick = { ep -> viewModel.handleAction( EpisodeClickEvent( @@ -262,48 +312,11 @@ class ResultFragmentTv : BaseFragment( val seasonNumber = composeRawSeasonsState.getOrNull(idx)?.second ?: (idx + 1) viewModel.changeSeason(seasonNumber) }, - onAddToListClick = { - val curStatus = composeWatchStatusState - activity?.showBottomDialog( - WatchType.entries.map { getString(it.stringRes) }.toList(), - curStatus.ordinal, - getString(R.string.action_add_to_bookmarks), - showApply = false, - {} - ) { - viewModel.updateWatchStatus(WatchType.entries[it], context) - } - }, - onLikeClick = { - viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? -> - if (newStatus == null) return@toggleFavoriteStatus - val message = if (newStatus) R.string.favorite_added else R.string.favorite_removed - val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data).asStringNull(context) ?: "" - CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) - } - }, - onTrailerClick = { - val trailersLinks = viewModel.trailers.value ?: emptyList() - val extractedTrailerLinks = trailersLinks.flatMap { it.mirros } - .map { (extractedTrailerLink, _) -> extractedTrailerLink } - if (extractedTrailerLinks.isNotEmpty()) { - activity.navigate( - R.id.global_to_navigation_player, - GeneratorPlayer.newInstance( - ExtractorLinkGenerator( - extractedTrailerLinks, - emptyList() - ), - 0 - ) - ) - } - }, + onAddToListClick = { handleAddToListClick() }, + onLikeClick = { handleLikeClick() }, + onTrailerClick = { handleTrailerClick() }, onSearchClick = { - title.let { t -> - QuickSearchFragment.pushSearch(activity, t) - } + QuickSearchFragment.pushSearch(activity, title) }, onActorClick = { actorName -> QuickSearchFragment.pushSearch(activity, actorName) @@ -325,7 +338,9 @@ class ResultFragmentTv : BaseFragment( } } } + } + private fun setupViewModelObservers(binding: FragmentResultTvBinding, storedData: ResultFragment.StoredData) { observeNullable(viewModel.resumeWatching) { resume -> composeResumeWatchingState = resume } @@ -350,6 +365,11 @@ class ResultFragmentTv : BaseFragment( composeWatchStatusState = watchType } + observePopupsAndLoading() + observeSelectionsAndContent(binding, storedData) + } + + private fun observePopupsAndLoading() { observeNullable(viewModel.selectPopup) { popup -> if (popup == null) { popupDialog?.dismissSafe(activity) @@ -407,7 +427,9 @@ class ResultFragmentTv : BaseFragment( } } } + } + private fun observeSelectionsAndContent(binding: FragmentResultTvBinding, storedData: ResultFragment.StoredData) { observe(viewModel.selectedSeasonIndex) { selected -> composeSelectedSeasonIndexState = selected } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 9f233ccf385..db9cb744c6c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -98,6 +98,673 @@ data class MovieRecommendationRow( val items: List ) +private fun getPlayButtonText( + context: android.content.Context, + resumeStatus: ResumeWatchingStatus?, + episodesToDisplay: List, + isMovie: Boolean +): String { + if (resumeStatus != null) { + val resumeEp = resumeStatus.result + val prefix = if (resumeStatus.progress != null) { + context.getString(R.string.resume) + } else { + context.getString(R.string.play_movie_button) + } + if (resumeStatus.isMovie) { + return prefix + } + val sShort = context.getString(R.string.season_short) + val eShort = context.getString(R.string.episode_short) + val s = resumeEp.season + val e = resumeEp.episode + val epCode = if (s != null && s > 0 && e > 0) { + "$sShort$s:$eShort$e" + } else if (e > 0) { + "$eShort$e" + } else { + "" + } + return if (epCode.isNotBlank()) "$prefix $epCode" else prefix + } + val firstEp = episodesToDisplay.firstOrNull() + if (firstEp != null && !isMovie) { + val sShort = context.getString(R.string.season_short) + val eShort = context.getString(R.string.episode_short) + val s = firstEp.season + val e = firstEp.episode + val epCode = if (s != null && s > 0 && e > 0) { + "$sShort$s:$eShort$e" + } else if (e > 0) { + "$eShort$e" + } else { + "" + } + val playStr = context.getString(R.string.play_movie_button) + return if (epCode.isNotBlank()) "$playStr $epCode" else playStr + } + return context.getString(R.string.play_movie_button) +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun HeroBannerSection( + title: String, + providerName: String?, + backdropUrl: String?, + logoUrl: String?, + heroHeight: androidx.compose.ui.unit.Dp, + playButtonText: String, + resumeProgressFraction: Float?, + isInWatchList: Boolean, + isFavorite: Boolean, + hasTrailers: Boolean, + playButtonFocusRequester: FocusRequester, + playInteractionSource: MutableInteractionSource, + inMyListInteractionSource: MutableInteractionSource, + likeInteractionSource: MutableInteractionSource, + trailerInteractionSource: MutableInteractionSource, + searchInteractionSource: MutableInteractionSource, + onPlayClick: () -> Unit, + onPlayLongClick: (() -> Unit)?, + onAddToListClick: () -> Unit, + onLikeClick: () -> Unit, + onTrailerClick: () -> Unit, + onSearchClick: (() -> Unit)? +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Box( + modifier = Modifier + .fillMaxWidth() + .height(heroHeight) + .clipToBounds() + .background(colors.background) + ) { + if (!backdropUrl.isNullOrBlank()) { + AsyncImage( + model = backdropUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clipToBounds() + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.85f) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + colors.background.copy(alpha = 0.5f), + colors.background.copy(alpha = 0.92f), + colors.background + ) + ) + ) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) + .zIndex(50f), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Box( + modifier = Modifier + .height(42.dp) + .width(110.dp) + .focusRequester(playButtonFocusRequester) + .combinedClickable( + interactionSource = playInteractionSource, + indication = null, + role = Role.Button, + onClick = onPlayClick, + onLongClick = onPlayLongClick + ) + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = inMyListInteractionSource, + indication = null, + role = Role.Button, + onClick = onAddToListClick + ) + ) + + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = likeInteractionSource, + indication = null, + role = Role.Button, + onClick = onLikeClick + ) + ) + + if (onSearchClick != null) { + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = searchInteractionSource, + indication = null, + role = Role.Button, + onClick = onSearchClick + ) + ) + } + } + + if (hasTrailers) { + Box( + modifier = Modifier + .height(40.dp) + .width(120.dp) + .clickable( + interactionSource = trailerInteractionSource, + indication = null, + role = Role.Button, + onClick = onTrailerClick + ) + ) + } + } + } + + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(dimens.spacing2Xl) + ) { + if (!providerName.isNullOrBlank()) { + Text( + text = providerName.uppercase(), + style = typography.boldTitle2.copy( + fontWeight = FontWeight.Black, + letterSpacing = (-0.8).sp + ), + color = colors.primary + ) + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { + Column { + if (!logoUrl.isNullOrBlank()) { + Box( + modifier = Modifier + .height(68.dp) + .width(240.dp) + ) { + AsyncImage( + model = logoUrl, + contentDescription = title, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + } else { + Text( + text = title, + style = typography.boldTitle1, + fontSize = 32.sp, + color = colors.textPrimary, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + HeroPlayButton( + onClick = onPlayClick, + onLongClick = onPlayLongClick, + text = playButtonText, + progress = resumeProgressFraction, + interactionSource = playInteractionSource, + enabled = false + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CircleActionButton( + icon = painterResource( + id = if (isInWatchList) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 + ), + contentDescription = if (isInWatchList) { + stringResource(id = R.string.in_my_list) + } else { + stringResource(id = R.string.add_to_my_list) + }, + onClick = onAddToListClick, + interactionSource = inMyListInteractionSource, + enabled = false + ) + + CircleActionButton( + icon = painterResource( + id = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 + ), + contentDescription = if (isFavorite) { + stringResource(id = R.string.unfavorite) + } else { + stringResource(id = R.string.favorite) + }, + onClick = onLikeClick, + interactionSource = likeInteractionSource, + enabled = false + ) + + if (onSearchClick != null) { + CircleActionButton( + icon = painterResource(id = R.drawable.search_icon), + contentDescription = stringResource(id = R.string.title_search), + onClick = onSearchClick, + interactionSource = searchInteractionSource, + enabled = false + ) + } + } + } + + if (hasTrailers) { + HeroTrailerButton( + text = stringResource(id = R.string.play_trailer), + onClick = onTrailerClick, + interactionSource = trailerInteractionSource, + enabled = false + ) + } + } + } +} + +@Composable +private fun MovieInfoSynopsisSection( + matchScore: String?, + releaseYear: String?, + seasonsCount: String?, + quality: String?, + maturityRating: String?, + advisories: String?, + top10RankText: String?, + synopsis: String, + castList: List, + genres: List, + moodTags: List +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) + ) { + Column( + modifier = Modifier + .weight(if (hasRightColumn) 0.65f else 1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() + + if (hasLine1) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (!matchScore.isNullOrBlank()) { + Text( + text = matchScore, + color = getRatingScoreColor(matchScore), + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + } + + if (!releaseYear.isNullOrBlank()) { + Text( + text = releaseYear, + color = colors.textPrimary, + style = typography.regularBody + ) + } + + if (!seasonsCount.isNullOrBlank()) { + Text( + text = seasonsCount, + color = colors.textPrimary, + style = typography.regularBody + ) + } + + if (!quality.isNullOrBlank()) { + VideoQualityBadge(quality = quality) + } + } + } + + if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (!maturityRating.isNullOrBlank()) { + MaturityRatingBadge(rating = maturityRating) + } + if (!advisories.isNullOrBlank()) { + Text( + text = advisories, + color = colors.textMuted, + style = typography.regularCaption1 + ) + } + } + } + + if (!top10RankText.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + modifier = Modifier.padding(vertical = dimens.spacingXs) + ) { + Box( + modifier = Modifier + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.primary) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(id = R.string.top_10_badge_text), + style = typography.regularCaption2, + color = colors.onPrimary, + fontWeight = FontWeight.Black + ) + } + Text( + text = top10RankText, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = colors.textPrimary + ) + } + } + + if (synopsis.isNotBlank()) { + val synopsisInteractionSource = remember { MutableInteractionSource() } + val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(if (isSynopsisFocused) colors.surfaceElevated.copy(alpha = 0.5f) else Color.Transparent) + .then( + if (isSynopsisFocused) { + Modifier.border( + BorderStroke(dimens.borderFocus, colors.primary), + MovieDetailsTokens.ShapeCardSmall + ) + } else Modifier + ) + .focusable(interactionSource = synopsisInteractionSource) + .padding(dimens.spacingS) + ) { + Text( + text = synopsis, + style = typography.regularBody, + color = if (isSynopsisFocused) colors.textPrimary else colors.textSecondary, + lineHeight = 22.sp + ) + } + } + } + + if (hasRightColumn) { + Column( + modifier = Modifier + .weight(0.35f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (castList.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = stringResource(id = R.string.cast_label), + style = typography.regularCaption2, + color = colors.textSecondary + ) + Text( + text = castList.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + } + + if (genres.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = stringResource(id = R.string.genres_label), + style = typography.regularCaption2, + color = colors.textSecondary + ) + Text( + text = genres.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary + ) + } + } + + if (moodTags.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = stringResource(id = R.string.mood_tags_label), + style = typography.regularCaption2, + color = colors.textSecondary + ) + Text( + text = moodTags.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary + ) + } + } + } + } + } +} + +@Composable +private fun EpisodesHeaderSection( + seasonOptions: List, + selectedSeasonText: String, + onSeasonSelect: ((Int) -> Unit)?, + onSeasonTextChange: (String) -> Unit, + dubOptions: List, + selectedDubText: String, + onDubSelect: ((Int) -> Unit)?, + onDubTextChange: (String) -> Unit, + rangeOptions: List, + selectedRangeText: String, + onRangeSelect: ((Int) -> Unit)?, + onRangeTextChange: (String) -> Unit +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.episodes), + style = typography.boldTitle2, + fontSize = 24.sp, + color = colors.textPrimary + ) + + if (seasonOptions.size > 1) { + SeasonDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { seasonStr -> + onSeasonTextChange(seasonStr) + val idx = seasonOptions.indexOf(seasonStr) + if (idx >= 0) onSeasonSelect?.invoke(idx) + }, + width = 180.dp + ) + } + + if (dubOptions.size > 1) { + SeasonDropdown( + options = dubOptions, + selectedOption = selectedDubText, + onOptionSelected = { dubStr -> + onDubTextChange(dubStr) + val idx = dubOptions.indexOf(dubStr) + if (idx >= 0) onDubSelect?.invoke(idx) + }, + width = 140.dp + ) + } + + if (rangeOptions.size > 1) { + SeasonDropdown( + options = rangeOptions, + selectedOption = selectedRangeText, + onOptionSelected = { rangeStr -> + onRangeTextChange(rangeStr) + val idx = rangeOptions.indexOf(rangeStr) + if (idx >= 0) onRangeSelect?.invoke(idx) + }, + width = 140.dp + ) + } + } +} + +@Composable +private fun AboutSection( + title: String, + creator: String?, + castList: List, + writers: List, + genres: List, + moodTags: List, + maturityRating: String?, + advisories: String? +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val aboutInteractionSource = remember { MutableInteractionSource() } + val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() + val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(1.dp, colors.border) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacing3Xl) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(if (isAboutFocused) colors.surfaceElevated else colors.surface) + .border(aboutBorder, MovieDetailsTokens.ShapeCardMedium) + .focusable(interactionSource = aboutInteractionSource) + .padding(dimens.spacing2Xl), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = stringResource(id = R.string.about_title_format, title), + style = typography.boldTitle2, + fontSize = 20.sp, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + if (!creator.isNullOrBlank()) { + AboutMetadataRow(label = stringResource(id = R.string.creator_label), value = creator) + } + if (castList.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.cast_label), value = castList.joinToString(", ")) + } + if (writers.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.writers_label), value = writers.joinToString(", ")) + } + if (genres.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.genres_label), value = genres.joinToString(", ")) + } + if (moodTags.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.mood_tags_label), value = moodTags.joinToString(", ")) + } + if (!maturityRating.isNullOrBlank()) { + val ratingDescription = if (!advisories.isNullOrBlank()) { + "$maturityRating ($advisories)" + } else { + maturityRating + } + AboutMetadataRow( + label = stringResource(id = R.string.maturity_rating_label), + value = ratingDescription + ) + } + } + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable fun MovieDetailsComposeScreen( @@ -156,8 +823,6 @@ fun MovieDetailsComposeScreen( val heroHeight = (screenHeight * 0.65f).coerceAtLeast(380.dp) val colors = MovieDetailsTheme.colors - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens val lazyListState = rememberLazyListState() val playInteractionSource = remember { MutableInteractionSource() } @@ -222,57 +887,7 @@ fun MovieDetailsComposeScreen( val episodesToDisplay = dynamicEpisodes ?: emptyList() val playButtonText = remember(resumeStatus, episodesToDisplay, isMovie, context) { - if (resumeStatus != null) { - val resumeEp = resumeStatus.result - val prefix = if (resumeStatus.progress != null) { - context.getString(R.string.resume) - } else { - context.getString(R.string.play_movie_button) - } - if (resumeStatus.isMovie) { - prefix - } else { - val sShort = context.getString(R.string.season_short) - val eShort = context.getString(R.string.episode_short) - val s = resumeEp.season - val e = resumeEp.episode - val epCode = if (s != null && s > 0 && e > 0) { - "$sShort$s:$eShort$e" - } else if (e > 0) { - "$eShort$e" - } else { - "" - } - if (epCode.isNotBlank()) { - "$prefix $epCode" - } else { - prefix - } - } - } else { - val firstEp = episodesToDisplay.firstOrNull() - if (firstEp != null && !isMovie) { - val sShort = context.getString(R.string.season_short) - val eShort = context.getString(R.string.episode_short) - val s = firstEp.season - val e = firstEp.episode - val epCode = if (s != null && s > 0 && e > 0) { - "$sShort$s:$eShort$e" - } else if (e > 0) { - "$eShort$e" - } else { - "" - } - val playStr = context.getString(R.string.play_movie_button) - if (epCode.isNotBlank()) { - "$playStr $epCode" - } else { - playStr - } - } else { - context.getString(R.string.play_movie_button) - } - } + getPlayButtonText(context, resumeStatus, episodesToDisplay, isMovie) } val resumeProgressFraction = remember(resumeStatus) { @@ -314,7 +929,7 @@ fun MovieDetailsComposeScreen( containerSize: Float ): Float { val parentFraction = 0.30f - val targetOffset = (parentFraction * containerSize) + val targetOffset = parentFraction * containerSize return offset - targetOffset } } @@ -328,487 +943,64 @@ fun MovieDetailsComposeScreen( .background(colors.background) ) { item(key = "hero_banner", contentType = "hero_banner") { - Box( - modifier = Modifier - .fillMaxWidth() - .height(heroHeight) - .clipToBounds() - .background(colors.background) - ) { - if (!backdropUrl.isNullOrBlank()) { - AsyncImage( - model = backdropUrl, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .clipToBounds() - ) - } - - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.85f) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - colors.background.copy(alpha = 0.5f), - colors.background.copy(alpha = 0.92f), - colors.background - ) - ) - ) - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.TopStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) - .zIndex(50f), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Box( - modifier = Modifier - .height(42.dp) - .width(110.dp) - .focusRequester(playButtonFocusRequester) - .combinedClickable( - interactionSource = playInteractionSource, - indication = null, - role = Role.Button, - onClick = onPlayClick, - onLongClick = onPlayLongClick - ) - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = inMyListInteractionSource, - indication = null, - role = Role.Button, - onClick = onAddToListClick - ) - ) - - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = likeInteractionSource, - indication = null, - role = Role.Button, - onClick = onLikeClick - ) - ) - - if (onSearchClick != null) { - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = searchInteractionSource, - indication = null, - role = Role.Button, - onClick = onSearchClick - ) - ) - } - } - - if (hasTrailers) { - Box( - modifier = Modifier - .height(40.dp) - .width(120.dp) - .clickable( - interactionSource = trailerInteractionSource, - indication = null, - role = Role.Button, - onClick = onTrailerClick - ) - ) - } - } - } - - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(dimens.spacing2Xl) - ) { - if (!providerName.isNullOrBlank()) { - Text( - text = providerName.uppercase(), - style = typography.boldTitle2.copy( - fontWeight = FontWeight.Black, - letterSpacing = (-0.8).sp - ), - color = colors.primary - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom - ) { - Column { - if (!logoUrl.isNullOrBlank()) { - Box( - modifier = Modifier - .height(68.dp) - .width(240.dp) - ) { - AsyncImage( - model = logoUrl, - contentDescription = title, - contentScale = ContentScale.Fit, - modifier = Modifier.fillMaxSize() - ) - } - } else { - Text( - text = title, - style = typography.boldTitle1, - fontSize = 32.sp, - color = colors.textPrimary, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - HeroPlayButton( - onClick = onPlayClick, - onLongClick = onPlayLongClick, - text = playButtonText, - progress = resumeProgressFraction, - interactionSource = playInteractionSource, - enabled = false - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CircleActionButton( - icon = painterResource( - id = if (isInWatchList) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 - ), - contentDescription = if (isInWatchList) { - stringResource(id = R.string.in_my_list) - } else { - stringResource(id = R.string.add_to_my_list) - }, - onClick = onAddToListClick, - interactionSource = inMyListInteractionSource, - enabled = false - ) - - CircleActionButton( - icon = painterResource( - id = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 - ), - contentDescription = if (isFavorite) { - stringResource(id = R.string.unfavorite) - } else { - stringResource(id = R.string.favorite) - }, - onClick = onLikeClick, - interactionSource = likeInteractionSource, - enabled = false - ) - - if (onSearchClick != null) { - CircleActionButton( - icon = painterResource(id = R.drawable.search_icon), - contentDescription = stringResource(id = R.string.title_search), - onClick = onSearchClick, - interactionSource = searchInteractionSource, - enabled = false - ) - } - } - } - - if (hasTrailers) { - HeroTrailerButton( - text = stringResource(id = R.string.play_trailer), - onClick = onTrailerClick, - interactionSource = trailerInteractionSource, - enabled = false - ) - } - } - } + HeroBannerSection( + title = title, + providerName = providerName, + backdropUrl = backdropUrl, + logoUrl = logoUrl, + heroHeight = heroHeight, + playButtonText = playButtonText, + resumeProgressFraction = resumeProgressFraction, + isInWatchList = isInWatchList, + isFavorite = isFavorite, + hasTrailers = hasTrailers, + playButtonFocusRequester = playButtonFocusRequester, + playInteractionSource = playInteractionSource, + inMyListInteractionSource = inMyListInteractionSource, + likeInteractionSource = likeInteractionSource, + trailerInteractionSource = trailerInteractionSource, + searchInteractionSource = searchInteractionSource, + onPlayClick = onPlayClick, + onPlayLongClick = onPlayLongClick, + onAddToListClick = onAddToListClick, + onLikeClick = onLikeClick, + onTrailerClick = onTrailerClick, + onSearchClick = onSearchClick + ) } item(key = "movie_info_synopsis", contentType = "movie_info") { - val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) - ) { - Column( - modifier = Modifier - .weight(if (hasRightColumn) 0.65f else 1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || - !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() - - if (hasLine1) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (!matchScore.isNullOrBlank()) { - Text( - text = matchScore, - color = getRatingScoreColor(matchScore), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - } - - if (!releaseYear.isNullOrBlank()) { - Text( - text = releaseYear, - color = colors.textPrimary, - style = typography.regularBody - ) - } - - if (!seasonsCount.isNullOrBlank()) { - Text( - text = seasonsCount, - color = colors.textPrimary, - style = typography.regularBody - ) - } - - if (!quality.isNullOrBlank()) { - VideoQualityBadge(quality = quality) - } - } - } - - if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - if (!maturityRating.isNullOrBlank()) { - MaturityRatingBadge(rating = maturityRating) - } - if (!advisories.isNullOrBlank()) { - Text( - text = advisories, - color = colors.textMuted, - style = typography.regularCaption1 - ) - } - } - } - - if (!top10RankText.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - modifier = Modifier.padding(vertical = dimens.spacingXs) - ) { - Box( - modifier = Modifier - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(colors.primary) - .padding(horizontal = 6.dp, vertical = 2.dp) - ) { - Text( - text = stringResource(id = R.string.top_10_badge_text), - style = typography.regularCaption2, - color = colors.onPrimary, - fontWeight = FontWeight.Black - ) - } - Text( - text = top10RankText, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = colors.textPrimary - ) - } - } - - if (synopsis.isNotBlank()) { - val synopsisInteractionSource = remember { MutableInteractionSource() } - val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() - - Box( - modifier = Modifier - .fillMaxWidth() - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(if (isSynopsisFocused) colors.surfaceElevated.copy(alpha = 0.5f) else Color.Transparent) - .then( - if (isSynopsisFocused) { - Modifier.border( - BorderStroke(dimens.borderFocus, colors.primary), - MovieDetailsTokens.ShapeCardSmall - ) - } else Modifier - ) - .focusable(interactionSource = synopsisInteractionSource) - .padding(dimens.spacingS) - ) { - Text( - text = synopsis, - style = typography.regularBody, - color = if (isSynopsisFocused) colors.textPrimary else colors.textSecondary, - lineHeight = 22.sp - ) - } - } - } - - if (hasRightColumn) { - Column( - modifier = Modifier - .weight(0.35f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (castList.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.cast_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = castList.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) - } - } - - if (genres.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.genres_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = genres.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary - ) - } - } - - if (moodTags.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.mood_tags_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = moodTags.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary - ) - } - } - } - } - } + MovieInfoSynopsisSection( + matchScore = matchScore, + releaseYear = releaseYear, + seasonsCount = seasonsCount, + quality = quality, + maturityRating = maturityRating, + advisories = advisories, + top10RankText = top10RankText, + synopsis = synopsis, + castList = castList, + genres = genres, + moodTags = moodTags + ) } if (episodesToDisplay.isNotEmpty()) { item(key = "episodes_header", contentType = "episodes_header") { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(id = R.string.episodes), - style = typography.boldTitle2, - fontSize = 24.sp, - color = colors.textPrimary - ) - - if (seasonOptions.size > 1) { - SeasonDropdown( - options = seasonOptions, - selectedOption = selectedSeasonText, - onOptionSelected = { seasonStr -> - selectedSeasonText = seasonStr - val idx = seasonOptions.indexOf(seasonStr) - if (idx >= 0) onSeasonSelect?.invoke(idx) - }, - width = 180.dp - ) - } - - if (dubOptions.size > 1) { - SeasonDropdown( - options = dubOptions, - selectedOption = selectedDubText, - onOptionSelected = { dubStr -> - selectedDubText = dubStr - val idx = dubOptions.indexOf(dubStr) - if (idx >= 0) onDubSelect?.invoke(idx) - }, - width = 140.dp - ) - } - - if (rangeOptions.size > 1) { - SeasonDropdown( - options = rangeOptions, - selectedOption = selectedRangeText, - onOptionSelected = { rangeStr -> - selectedRangeText = rangeStr - val idx = rangeOptions.indexOf(rangeStr) - if (idx >= 0) onRangeSelect?.invoke(idx) - }, - width = 140.dp - ) - } - } + EpisodesHeaderSection( + seasonOptions = seasonOptions, + selectedSeasonText = selectedSeasonText, + onSeasonSelect = onSeasonSelect, + onSeasonTextChange = { selectedSeasonText = it }, + dubOptions = dubOptions, + selectedDubText = selectedDubText, + onDubSelect = onDubSelect, + onDubTextChange = { selectedDubText = it }, + rangeOptions = rangeOptions, + selectedRangeText = selectedRangeText, + onRangeSelect = onRangeSelect, + onRangeTextChange = { selectedRangeText = it } + ) } items( @@ -833,7 +1025,7 @@ fun MovieDetailsComposeScreen( onLongClick = onEpLongClick, modifier = Modifier .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp) + .padding(horizontal = MovieDetailsTheme.dimens.spacing2Xl, vertical = 6.dp) ) } } @@ -842,13 +1034,13 @@ fun MovieDetailsComposeScreen( item(key = "recommendations_header", contentType = "recommendations_header") { Text( text = stringResource(id = R.string.more_like_this), - style = typography.boldTitle2, + style = MovieDetailsTheme.typography.boldTitle2, fontSize = 22.sp, color = colors.textPrimary, modifier = Modifier .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing3Xl, bottom = dimens.spacingL) + .padding(horizontal = MovieDetailsTheme.dimens.spacing2Xl) + .padding(top = MovieDetailsTheme.dimens.spacing3Xl, bottom = MovieDetailsTheme.dimens.spacingL) ) } @@ -877,63 +1069,16 @@ fun MovieDetailsComposeScreen( if (hasAboutContent) { item(key = "about_section", contentType = "about_section") { - val aboutInteractionSource = remember { MutableInteractionSource() } - val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() - val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(1.dp, colors.border) - - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing3Xl, bottom = dimens.spacing3Xl) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(MovieDetailsTokens.ShapeCardMedium) - .background(if (isAboutFocused) colors.surfaceElevated else colors.surface) - .border(aboutBorder, MovieDetailsTokens.ShapeCardMedium) - .focusable(interactionSource = aboutInteractionSource) - .padding(dimens.spacing2Xl), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = stringResource(id = R.string.about_title_format, title), - style = typography.boldTitle2, - fontSize = 20.sp, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - if (!creator.isNullOrBlank()) { - AboutMetadataRow(label = stringResource(id = R.string.creator_label), value = creator) - } - if (castList.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.cast_label), value = castList.joinToString(", ")) - } - if (writers.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.writers_label), value = writers.joinToString(", ")) - } - if (genres.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.genres_label), value = genres.joinToString(", ")) - } - if (moodTags.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.mood_tags_label), value = moodTags.joinToString(", ")) - } - if (!maturityRating.isNullOrBlank()) { - val ratingDescription = if (!advisories.isNullOrBlank()) { - "$maturityRating ($advisories)" - } else { - maturityRating - } - AboutMetadataRow( - label = stringResource(id = R.string.maturity_rating_label), - value = ratingDescription - ) - } - } - } + AboutSection( + title = title, + creator = creator, + castList = castList, + writers = writers, + genres = genres, + moodTags = moodTags, + maturityRating = maturityRating, + advisories = advisories + ) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt index dedfe88fc6c..b61ade9fc3f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -447,6 +448,52 @@ fun DetailsLogoView( } } +@Composable +private fun SeasonDropdownMenuItem( + option: String, + isSelected: Boolean, + onOptionSelected: (String) -> Unit +) { + val colors = MovieDetailsTheme.colors + val optionInteractionSource = remember(option) { MutableInteractionSource() } + val isOptionFocused by optionInteractionSource.collectIsFocusedAsState() + val focusAlpha = if (colors.surface.luminance() < 0.5f) 0.22f else 0.12f + val focusColor = colors.onSurface.copy(alpha = focusAlpha) + val focusBorder = colors.onSurface.copy(alpha = 0.72f) + val itemTextColor = if (isSelected) colors.primary else colors.textPrimary + + DropdownMenuItem( + text = { + Text( + text = option, + color = itemTextColor, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + fontSize = 14.sp + ) + }, + onClick = { + onOptionSelected(option) + }, + modifier = Modifier + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(if (isOptionFocused) focusColor else Color.Transparent) + .then( + if (isOptionFocused) { + Modifier.border( + BorderStroke(2.dp, focusBorder), + MovieDetailsTokens.ShapeCardSmall + ) + } else { + Modifier + } + ), + interactionSource = optionInteractionSource, + colors = MenuDefaults.itemColors( + textColor = itemTextColor + ) + ) +} + @Composable fun SeasonDropdown( options: List, @@ -535,50 +582,112 @@ fun SeasonDropdown( .heightIn(max = 280.dp) ) { options.forEach { option -> - val isSelected = option == selectedOption - val optionInteractionSource = remember(option) { MutableInteractionSource() } - val isOptionFocused by optionInteractionSource.collectIsFocusedAsState() - val focusColor = colors.onSurface.copy( - alpha = if (colors.surface.luminance() < 0.5f) 0.22f else 0.12f - ) - val focusBorder = colors.onSurface.copy(alpha = 0.72f) - - DropdownMenuItem( - text = { - Text( - text = option, - color = if (isSelected) colors.primary else colors.textPrimary, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - fontSize = 14.sp - ) - }, - onClick = { - onOptionSelected(option) + SeasonDropdownMenuItem( + option = option, + isSelected = option == selectedOption, + onOptionSelected = { + onOptionSelected(it) expanded = false - }, - modifier = Modifier - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(if (isOptionFocused) focusColor else Color.Transparent) - .then( - if (isOptionFocused) { - Modifier.border( - BorderStroke(2.dp, focusBorder), - MovieDetailsTokens.ShapeCardSmall - ) - } else { - Modifier - } - ), - interactionSource = optionInteractionSource, - colors = MenuDefaults.itemColors( - textColor = if (isSelected) colors.primary else colors.textPrimary - ) + } ) } } } } +@Composable +private fun CardImageOrPlaceholder(title: String, imageUrl: String?) { + val colors = MovieDetailsTheme.colors + if (!imageUrl.isNullOrBlank()) { + AsyncImage( + model = imageUrl, + contentDescription = title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .background(colors.surface), + contentAlignment = Alignment.Center + ) { + Text( + text = title.take(2).uppercase(), + color = colors.textSecondary, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Composable +private fun BoxScope.CardPosterBottomTitle(title: String) { + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, TransparentBlack90), + startY = 0f + ) + ) + .padding(horizontal = 8.dp, vertical = 6.dp) + ) { + Text( + text = title, + color = colors.textPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +private fun BoxScope.CardProgressBar(progress: Float) { + val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .fillMaxWidth() + .height(dimens.progressHeight) + .align(Alignment.BottomCenter) + .background(colors.border) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .background(colors.primary) + ) + } +} + +@Composable +private fun BoxScope.CardBadge(badge: MovieBadgeType) { + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .align(Alignment.TopStart) + .padding(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(colors.primary) + .padding(horizontal = 5.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(id = badge.stringRes), + color = colors.onPrimary, + fontSize = 9.sp, + fontWeight = FontWeight.Bold + ) + } +} + @Composable fun MovieDetailsMovieCard( title: String, @@ -616,7 +725,7 @@ fun MovieDetailsMovieCard( } val cardWidth = MovieCardDefaults.cardWidth(type, size) - val imageUrl = if (type == MovieCardType.POSTER) (posterUrl ?: backdropUrl) else (backdropUrl ?: posterUrl) + val imageUrl = if (type == MovieCardType.POSTER) posterUrl ?: backdropUrl else backdropUrl ?: posterUrl val border = if (isFocused) { BorderStroke(dimens.borderFocus, colors.primary) @@ -624,24 +733,24 @@ fun MovieDetailsMovieCard( BorderStroke(dimens.borderSubtle, colors.border) } + val clickableModifier = if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + } else { + Modifier.focusable(enabled = enabled, interactionSource = interactionSource) + } + Box( modifier = modifier .width(cardWidth) - .then( - if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - } else { - Modifier.focusable(enabled = enabled, interactionSource = interactionSource) - } - ) + .then(clickableModifier) .semantics { contentDescription = title }, @@ -664,86 +773,18 @@ fun MovieDetailsMovieCard( .background(colors.surface) .border(border, MovieDetailsTokens.ShapeCardSmall) ) { - if (!imageUrl.isNullOrBlank()) { - AsyncImage( - model = imageUrl, - contentDescription = title, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } else { - Box( - modifier = Modifier - .fillMaxSize() - .background(colors.surface), - contentAlignment = Alignment.Center - ) { - Text( - text = title.take(2).uppercase(), - color = colors.textSecondary, - fontSize = 18.sp, - fontWeight = FontWeight.Bold - ) - } - } + CardImageOrPlaceholder(title = title, imageUrl = imageUrl) if (type == MovieCardType.POSTER && showBottomTitle) { - Box( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf(Color.Transparent, TransparentBlack90), - startY = 0f - ) - ) - .padding(horizontal = 8.dp, vertical = 6.dp) - ) { - Text( - text = title, - color = colors.textPrimary, - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } + CardPosterBottomTitle(title = title) } if (progress != null && progress > 0f) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(dimens.progressHeight) - .align(Alignment.BottomCenter) - .background(colors.border) - ) { - Box( - modifier = Modifier - .fillMaxHeight() - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .background(colors.primary) - ) - } + CardProgressBar(progress = progress) } if (badge != null) { - Box( - modifier = Modifier - .align(Alignment.TopStart) - .padding(4.dp) - .clip(RoundedCornerShape(2.dp)) - .background(colors.primary) - .padding(horizontal = 5.dp, vertical = 2.dp) - ) { - Text( - text = stringResource(id = badge.stringRes), - color = colors.onPrimary, - fontSize = 9.sp, - fontWeight = FontWeight.Bold - ) - } + CardBadge(badge = badge) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt index aeb29c91e89..10c05c01734 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/theme/MovieDetailsTheme.kt @@ -94,7 +94,7 @@ fun getRatingScoreColor(scoreText: String?): Color { if (match != null) { val num = match.value.toDoubleOrNull() ?: return GreenAccent val isPercentage = text.contains("%") || num > 10.0 - val percentage = if (isPercentage) num else (num * 10.0) + val percentage = if (isPercentage) num else num * 10.0 return when { percentage >= 70.0 -> GreenAccent From 0d533a3f33518a7d59ac5b0d09fb9fe6c4623d05 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 13:12:50 +0700 Subject: [PATCH 27/35] fix(result): refactor detail view --- .../compose/MovieDetailsComposeScreen.kt | 1060 +---------------- .../ui/result/compose/MovieDetailsModels.kt | 69 ++ .../result/compose/components/AboutSection.kt | 130 ++ .../compose/components/EpisodeRowItem.kt | 194 +++ .../components/EpisodesHeaderSection.kt | 91 ++ .../compose/components/HeroBannerSection.kt | 309 +++++ .../components/MovieInfoSynopsisSection.kt | 235 ++++ .../components/RecommendationRowView.kt | 49 + .../compose/components/TrailerItemCard.kt | 125 ++ 9 files changed, 1221 insertions(+), 1041 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsModels.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AboutSection.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodesHeaderSection.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/HeroBannerSection.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieInfoSynopsisSection.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/RecommendationRowView.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index db9cb744c6c..29287167bf5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -1,769 +1,47 @@ package com.lagradost.cloudstream3.ui.result.compose import android.widget.Toast -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.focusable import androidx.compose.foundation.gestures.BringIntoViewSpec import androidx.compose.foundation.gestures.LocalBringIntoViewSpec import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -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.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Immutable 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.draw.clip -import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.zIndex -import coil3.compose.AsyncImage import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResumeWatchingStatus -import com.lagradost.cloudstream3.ui.result.compose.components.CircleActionButton -import com.lagradost.cloudstream3.ui.result.compose.components.HeroPlayButton -import com.lagradost.cloudstream3.ui.result.compose.components.HeroTrailerButton -import com.lagradost.cloudstream3.ui.result.compose.components.MaturityRatingBadge +import com.lagradost.cloudstream3.ui.result.compose.components.AboutSection +import com.lagradost.cloudstream3.ui.result.compose.components.EpisodeRowItem +import com.lagradost.cloudstream3.ui.result.compose.components.EpisodesHeaderSection +import com.lagradost.cloudstream3.ui.result.compose.components.HeroBannerSection import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardItem -import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardSize import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardType -import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsMovieCard -import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens -import com.lagradost.cloudstream3.ui.result.compose.components.SeasonDropdown -import com.lagradost.cloudstream3.ui.result.compose.components.VideoQualityBadge +import com.lagradost.cloudstream3.ui.result.compose.components.MovieInfoSynopsisSection +import com.lagradost.cloudstream3.ui.result.compose.components.RecommendationRowView import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme -import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 -import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor -import com.lagradost.cloudstream3.ui.result.getWatchProgress - -@Immutable -data class MovieTrailerData( - val title: String, - val runtime: String, - val rawTrailer: Any? = null -) - -@Immutable -data class MovieRecommendationRow( - val rowIndex: Int, - val items: List -) - -private fun getPlayButtonText( - context: android.content.Context, - resumeStatus: ResumeWatchingStatus?, - episodesToDisplay: List, - isMovie: Boolean -): String { - if (resumeStatus != null) { - val resumeEp = resumeStatus.result - val prefix = if (resumeStatus.progress != null) { - context.getString(R.string.resume) - } else { - context.getString(R.string.play_movie_button) - } - if (resumeStatus.isMovie) { - return prefix - } - val sShort = context.getString(R.string.season_short) - val eShort = context.getString(R.string.episode_short) - val s = resumeEp.season - val e = resumeEp.episode - val epCode = if (s != null && s > 0 && e > 0) { - "$sShort$s:$eShort$e" - } else if (e > 0) { - "$eShort$e" - } else { - "" - } - return if (epCode.isNotBlank()) "$prefix $epCode" else prefix - } - val firstEp = episodesToDisplay.firstOrNull() - if (firstEp != null && !isMovie) { - val sShort = context.getString(R.string.season_short) - val eShort = context.getString(R.string.episode_short) - val s = firstEp.season - val e = firstEp.episode - val epCode = if (s != null && s > 0 && e > 0) { - "$sShort$s:$eShort$e" - } else if (e > 0) { - "$eShort$e" - } else { - "" - } - val playStr = context.getString(R.string.play_movie_button) - return if (epCode.isNotBlank()) "$playStr $epCode" else playStr - } - return context.getString(R.string.play_movie_button) -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun HeroBannerSection( - title: String, - providerName: String?, - backdropUrl: String?, - logoUrl: String?, - heroHeight: androidx.compose.ui.unit.Dp, - playButtonText: String, - resumeProgressFraction: Float?, - isInWatchList: Boolean, - isFavorite: Boolean, - hasTrailers: Boolean, - playButtonFocusRequester: FocusRequester, - playInteractionSource: MutableInteractionSource, - inMyListInteractionSource: MutableInteractionSource, - likeInteractionSource: MutableInteractionSource, - trailerInteractionSource: MutableInteractionSource, - searchInteractionSource: MutableInteractionSource, - onPlayClick: () -> Unit, - onPlayLongClick: (() -> Unit)?, - onAddToListClick: () -> Unit, - onLikeClick: () -> Unit, - onTrailerClick: () -> Unit, - onSearchClick: (() -> Unit)? -) { - val colors = MovieDetailsTheme.colors - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - - Box( - modifier = Modifier - .fillMaxWidth() - .height(heroHeight) - .clipToBounds() - .background(colors.background) - ) { - if (!backdropUrl.isNullOrBlank()) { - AsyncImage( - model = backdropUrl, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .clipToBounds() - ) - } - - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.85f) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - colors.background.copy(alpha = 0.5f), - colors.background.copy(alpha = 0.92f), - colors.background - ) - ) - ) - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.TopStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) - .zIndex(50f), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Box( - modifier = Modifier - .height(42.dp) - .width(110.dp) - .focusRequester(playButtonFocusRequester) - .combinedClickable( - interactionSource = playInteractionSource, - indication = null, - role = Role.Button, - onClick = onPlayClick, - onLongClick = onPlayLongClick - ) - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = inMyListInteractionSource, - indication = null, - role = Role.Button, - onClick = onAddToListClick - ) - ) - - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = likeInteractionSource, - indication = null, - role = Role.Button, - onClick = onLikeClick - ) - ) - - if (onSearchClick != null) { - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = searchInteractionSource, - indication = null, - role = Role.Button, - onClick = onSearchClick - ) - ) - } - } - - if (hasTrailers) { - Box( - modifier = Modifier - .height(40.dp) - .width(120.dp) - .clickable( - interactionSource = trailerInteractionSource, - indication = null, - role = Role.Button, - onClick = onTrailerClick - ) - ) - } - } - } - - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(dimens.spacing2Xl) - ) { - if (!providerName.isNullOrBlank()) { - Text( - text = providerName.uppercase(), - style = typography.boldTitle2.copy( - fontWeight = FontWeight.Black, - letterSpacing = (-0.8).sp - ), - color = colors.primary - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom - ) { - Column { - if (!logoUrl.isNullOrBlank()) { - Box( - modifier = Modifier - .height(68.dp) - .width(240.dp) - ) { - AsyncImage( - model = logoUrl, - contentDescription = title, - contentScale = ContentScale.Fit, - modifier = Modifier.fillMaxSize() - ) - } - } else { - Text( - text = title, - style = typography.boldTitle1, - fontSize = 32.sp, - color = colors.textPrimary, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - - Spacer(modifier = Modifier.height(dimens.spacingL)) - - HeroPlayButton( - onClick = onPlayClick, - onLongClick = onPlayLongClick, - text = playButtonText, - progress = resumeProgressFraction, - interactionSource = playInteractionSource, - enabled = false - ) - - Spacer(modifier = Modifier.height(dimens.spacingM)) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CircleActionButton( - icon = painterResource( - id = if (isInWatchList) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 - ), - contentDescription = if (isInWatchList) { - stringResource(id = R.string.in_my_list) - } else { - stringResource(id = R.string.add_to_my_list) - }, - onClick = onAddToListClick, - interactionSource = inMyListInteractionSource, - enabled = false - ) - - CircleActionButton( - icon = painterResource( - id = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 - ), - contentDescription = if (isFavorite) { - stringResource(id = R.string.unfavorite) - } else { - stringResource(id = R.string.favorite) - }, - onClick = onLikeClick, - interactionSource = likeInteractionSource, - enabled = false - ) - - if (onSearchClick != null) { - CircleActionButton( - icon = painterResource(id = R.drawable.search_icon), - contentDescription = stringResource(id = R.string.title_search), - onClick = onSearchClick, - interactionSource = searchInteractionSource, - enabled = false - ) - } - } - } - - if (hasTrailers) { - HeroTrailerButton( - text = stringResource(id = R.string.play_trailer), - onClick = onTrailerClick, - interactionSource = trailerInteractionSource, - enabled = false - ) - } - } - } -} - -@Composable -private fun MovieInfoSynopsisSection( - matchScore: String?, - releaseYear: String?, - seasonsCount: String?, - quality: String?, - maturityRating: String?, - advisories: String?, - top10RankText: String?, - synopsis: String, - castList: List, - genres: List, - moodTags: List -) { - val colors = MovieDetailsTheme.colors - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) - ) { - Column( - modifier = Modifier - .weight(if (hasRightColumn) 0.65f else 1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || - !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() - - if (hasLine1) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (!matchScore.isNullOrBlank()) { - Text( - text = matchScore, - color = getRatingScoreColor(matchScore), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - } - - if (!releaseYear.isNullOrBlank()) { - Text( - text = releaseYear, - color = colors.textPrimary, - style = typography.regularBody - ) - } - - if (!seasonsCount.isNullOrBlank()) { - Text( - text = seasonsCount, - color = colors.textPrimary, - style = typography.regularBody - ) - } - - if (!quality.isNullOrBlank()) { - VideoQualityBadge(quality = quality) - } - } - } - - if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - if (!maturityRating.isNullOrBlank()) { - MaturityRatingBadge(rating = maturityRating) - } - if (!advisories.isNullOrBlank()) { - Text( - text = advisories, - color = colors.textMuted, - style = typography.regularCaption1 - ) - } - } - } - - if (!top10RankText.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - modifier = Modifier.padding(vertical = dimens.spacingXs) - ) { - Box( - modifier = Modifier - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(colors.primary) - .padding(horizontal = 6.dp, vertical = 2.dp) - ) { - Text( - text = stringResource(id = R.string.top_10_badge_text), - style = typography.regularCaption2, - color = colors.onPrimary, - fontWeight = FontWeight.Black - ) - } - Text( - text = top10RankText, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = colors.textPrimary - ) - } - } - - if (synopsis.isNotBlank()) { - val synopsisInteractionSource = remember { MutableInteractionSource() } - val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() - - Box( - modifier = Modifier - .fillMaxWidth() - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(if (isSynopsisFocused) colors.surfaceElevated.copy(alpha = 0.5f) else Color.Transparent) - .then( - if (isSynopsisFocused) { - Modifier.border( - BorderStroke(dimens.borderFocus, colors.primary), - MovieDetailsTokens.ShapeCardSmall - ) - } else Modifier - ) - .focusable(interactionSource = synopsisInteractionSource) - .padding(dimens.spacingS) - ) { - Text( - text = synopsis, - style = typography.regularBody, - color = if (isSynopsisFocused) colors.textPrimary else colors.textSecondary, - lineHeight = 22.sp - ) - } - } - } - - if (hasRightColumn) { - Column( - modifier = Modifier - .weight(0.35f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (castList.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.cast_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = castList.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) - } - } - - if (genres.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.genres_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = genres.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary - ) - } - } - - if (moodTags.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.mood_tags_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = moodTags.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary - ) - } - } - } - } - } -} - -@Composable -private fun EpisodesHeaderSection( - seasonOptions: List, - selectedSeasonText: String, - onSeasonSelect: ((Int) -> Unit)?, - onSeasonTextChange: (String) -> Unit, - dubOptions: List, - selectedDubText: String, - onDubSelect: ((Int) -> Unit)?, - onDubTextChange: (String) -> Unit, - rangeOptions: List, - selectedRangeText: String, - onRangeSelect: ((Int) -> Unit)?, - onRangeTextChange: (String) -> Unit -) { - val colors = MovieDetailsTheme.colors - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(id = R.string.episodes), - style = typography.boldTitle2, - fontSize = 24.sp, - color = colors.textPrimary - ) - - if (seasonOptions.size > 1) { - SeasonDropdown( - options = seasonOptions, - selectedOption = selectedSeasonText, - onOptionSelected = { seasonStr -> - onSeasonTextChange(seasonStr) - val idx = seasonOptions.indexOf(seasonStr) - if (idx >= 0) onSeasonSelect?.invoke(idx) - }, - width = 180.dp - ) - } - - if (dubOptions.size > 1) { - SeasonDropdown( - options = dubOptions, - selectedOption = selectedDubText, - onOptionSelected = { dubStr -> - onDubTextChange(dubStr) - val idx = dubOptions.indexOf(dubStr) - if (idx >= 0) onDubSelect?.invoke(idx) - }, - width = 140.dp - ) - } - - if (rangeOptions.size > 1) { - SeasonDropdown( - options = rangeOptions, - selectedOption = selectedRangeText, - onOptionSelected = { rangeStr -> - onRangeTextChange(rangeStr) - val idx = rangeOptions.indexOf(rangeStr) - if (idx >= 0) onRangeSelect?.invoke(idx) - }, - width = 140.dp - ) - } - } -} - -@Composable -private fun AboutSection( - title: String, - creator: String?, - castList: List, - writers: List, - genres: List, - moodTags: List, - maturityRating: String?, - advisories: String? -) { - val colors = MovieDetailsTheme.colors - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - val aboutInteractionSource = remember { MutableInteractionSource() } - val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() - val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(1.dp, colors.border) - - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl) - .padding(top = dimens.spacing3Xl, bottom = dimens.spacing3Xl) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(MovieDetailsTokens.ShapeCardMedium) - .background(if (isAboutFocused) colors.surfaceElevated else colors.surface) - .border(aboutBorder, MovieDetailsTokens.ShapeCardMedium) - .focusable(interactionSource = aboutInteractionSource) - .padding(dimens.spacing2Xl), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Text( - text = stringResource(id = R.string.about_title_format, title), - style = typography.boldTitle2, - fontSize = 20.sp, - color = colors.textPrimary - ) - - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - if (!creator.isNullOrBlank()) { - AboutMetadataRow(label = stringResource(id = R.string.creator_label), value = creator) - } - if (castList.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.cast_label), value = castList.joinToString(", ")) - } - if (writers.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.writers_label), value = writers.joinToString(", ")) - } - if (genres.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.genres_label), value = genres.joinToString(", ")) - } - if (moodTags.isNotEmpty()) { - AboutMetadataRow(label = stringResource(id = R.string.mood_tags_label), value = moodTags.joinToString(", ")) - } - if (!maturityRating.isNullOrBlank()) { - val ratingDescription = if (!advisories.isNullOrBlank()) { - "$maturityRating ($advisories)" - } else { - maturityRating - } - AboutMetadataRow( - label = stringResource(id = R.string.maturity_rating_label), - value = ratingDescription - ) - } - } - } -} @OptIn(ExperimentalFoundationApi::class) @Composable @@ -1085,304 +363,6 @@ fun MovieDetailsComposeScreen( } } -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun EpisodeRowItem( - episode: ResultEpisode, - onClick: () -> Unit, - onLongClick: (() -> Unit)? = null, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - val colors = MovieDetailsTheme.colors - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) 1.02f else 1f, - animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "episodeScale" - ) - - val background = if (isFocused) colors.surfaceElevated else colors.surface - val border = if (isFocused) BorderStroke(dimens.borderFocus, colors.primary) else null - val progress = remember(episode) { episode.getWatchProgress() } - - Row( - modifier = modifier - .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(MovieDetailsTokens.ShapeCardMedium) - .background(background) - .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardMedium) else Modifier) - .combinedClickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick, - onLongClick = onLongClick - ) - .focusable(interactionSource = interactionSource) - .padding(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) - ) { - Text( - text = episode.episode.toString(), - style = typography.boldTitle1, - fontSize = 24.sp, - color = colors.textSecondary, - modifier = Modifier.width(32.dp) - ) - - Box( - modifier = Modifier - .width(160.dp) - .height(90.dp) - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(colors.surface) - ) { - if (!episode.poster.isNullOrBlank()) { - AsyncImage( - model = episode.poster, - contentDescription = episode.name, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } - - Box( - modifier = Modifier - .size(36.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) - .align(Alignment.Center) - ) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(16.dp) - .align(Alignment.Center) - ) - } - - if (progress > 0.05f) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .align(Alignment.BottomStart) - .background(colors.border) - ) { - Box( - modifier = Modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(colors.primary) - ) - } - } - } - - Column( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "${episode.episode}. ${episode.name ?: episode.headerName}", - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = colors.textPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = if (episode.runTime != null) "${episode.runTime}m" else "", - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - - if (!episode.description.isNullOrBlank()) { - Text( - text = episode.description, - style = typography.regularCaption1, - color = colors.textMuted, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - lineHeight = 18.sp - ) - } - } - } -} - -@Composable -fun TrailerItemCard( - trailer: MovieTrailerData, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val typography = MovieDetailsTheme.typography - val dimens = MovieDetailsTheme.dimens - val colors = MovieDetailsTheme.colors - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) MovieDetailsTokens.FOCUS_SCALE_FACTOR else 1f, - animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "trailerScale" - ) - - val border = if (isFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(dimens.borderSubtle, colors.border) - - Column( - modifier = modifier - .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(MovieDetailsTokens.ShapeCardMedium) - .background(colors.surfaceElevated) - .border(border, MovieDetailsTokens.ShapeCardMedium) - .clickable( - interactionSource = interactionSource, - indication = null, - role = Role.Button, - onClick = onClick - ) - .focusable(interactionSource = interactionSource) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(130.dp) - .background(colors.surface) - ) { - Box( - modifier = Modifier - .size(42.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) - .align(Alignment.Center) - ) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(20.dp) - .align(Alignment.Center) - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(dimens.spacingM), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = trailer.title, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = colors.textPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = trailer.runtime, - style = typography.regularCaption2, - color = colors.textSecondary - ) - } - } -} - -@Composable -fun AboutMetadataRow( - label: String, - value: String, - modifier: Modifier = Modifier -) { - val typography = MovieDetailsTheme.typography - val colors = MovieDetailsTheme.colors - - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = if (label.endsWith(":")) label else "$label:", - style = typography.regularCaption1, - color = colors.textSecondary, - modifier = Modifier.width(130.dp) - ) - Text( - text = value, - style = typography.regularCaption1, - color = colors.textPrimary, - modifier = Modifier.weight(1f) - ) - } -} - -@Composable -fun RecommendationRowView( - row: MovieRecommendationRow, - onCardClick: (MovieCardItem) -> Unit, - modifier: Modifier = Modifier -) { - val dimens = MovieDetailsTheme.dimens - Row( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - row.items.forEach { cardItem -> - Box(modifier = Modifier.weight(1f)) { - MovieDetailsMovieCard( - title = cardItem.title, - type = MovieCardType.POSTER, - size = MovieCardSize.MEDIUM, - badge = cardItem.badge, - posterUrl = cardItem.posterUrl, - showLogo = false, - showBottomTitle = true, - onClick = { onCardClick(cardItem) }, - modifier = Modifier.fillMaxWidth() - ) - } - } - if (row.items.size < 6) { - repeat(6 - row.items.size) { - Spacer(modifier = Modifier.weight(1f)) - } - } - } -} - @Preview( name = "TV 1080p Landscape", device = "spec:width=1920dp,height=1080dp,dpi=320,orientation=landscape", @@ -1393,23 +373,21 @@ fun RecommendationRowView( private fun MovieDetailsComposeScreenPreview() { MovieDetailsTheme { MovieDetailsComposeScreen( - title = "HOUSE OF NINJAS", - providerName = "SUPERSTREAM", + title = "Stranger Things", + providerName = "Netflix", + backdropUrl = null, matchScore = "98% Match", releaseYear = "2024", - seasonsCount = "1 Season", - quality = "4K Ultra HD", - maturityRating = "TV-MA", - advisories = "smoking, violence, language", - top10RankText = "#2 in TV Shows Today", - synopsis = "Years after retiring from their formidable ninja lives, a dysfunctional family must return to shadowy missions to counteract a string of looming threats.", - cast = listOf("Kento Kaku", "Yosuke Eguchi", "Tae Kimura", "Kengo Kora"), - genres = listOf("TV Dramas", "Action", "Japanese", "Suspenseful"), - moodTags = listOf("Dark", "Suspenseful", "Exciting"), - creator = "Dave Boyle", - writers = listOf("Dave Boyle", "Masahiro Yamaura", "Kota Oishi"), - isInWatchList = false, - hasTrailers = true + seasonsCount = "4 Seasons", + quality = "4K ULTRA HD", + maturityRating = "16+", + advisories = "fear, language, violence", + top10RankText = "#1 in TV Shows Today", + synopsis = "When a young boy vanishes, a small town uncovers a mystery involving secret experiments, terrifying supernatural forces and one strange little girl.", + cast = listOf("Winona Ryder", "David Harbour", "Millie Bobby Brown", "Finn Wolfhard"), + genres = listOf("Sci-Fi", "Horror", "Drama"), + moodTags = listOf("Ominous", "Nostalgic", "Suspenseful"), + creator = "The Duffer Brothers" ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsModels.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsModels.kt new file mode 100644 index 00000000000..5900bf31553 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsModels.kt @@ -0,0 +1,69 @@ +package com.lagradost.cloudstream3.ui.result.compose + +import android.content.Context +import androidx.compose.runtime.Immutable +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.ResumeWatchingStatus +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardItem + +@Immutable +data class MovieTrailerData( + val title: String, + val runtime: String, + val rawTrailer: Any? = null +) + +@Immutable +data class MovieRecommendationRow( + val rowIndex: Int, + val items: List +) + +fun getPlayButtonText( + context: Context, + resumeStatus: ResumeWatchingStatus?, + episodesToDisplay: List, + isMovie: Boolean +): String { + if (resumeStatus != null) { + val resumeEp = resumeStatus.result + val prefix = if (resumeStatus.progress != null) { + context.getString(R.string.resume) + } else { + context.getString(R.string.play_movie_button) + } + if (resumeStatus.isMovie) { + return prefix + } + val sShort = context.getString(R.string.season_short) + val eShort = context.getString(R.string.episode_short) + val s = resumeEp.season + val e = resumeEp.episode + val epCode = if (s != null && s > 0 && e > 0) { + "$sShort$s:$eShort$e" + } else if (e > 0) { + "$eShort$e" + } else { + "" + } + return if (epCode.isNotBlank()) "$prefix $epCode" else prefix + } + val firstEp = episodesToDisplay.firstOrNull() + if (firstEp != null && !isMovie) { + val sShort = context.getString(R.string.season_short) + val eShort = context.getString(R.string.episode_short) + val s = firstEp.season + val e = firstEp.episode + val epCode = if (s != null && s > 0 && e > 0) { + "$sShort$s:$eShort$e" + } else if (e > 0) { + "$eShort$e" + } else { + "" + } + val playStr = context.getString(R.string.play_movie_button) + return if (epCode.isNotBlank()) "$playStr $epCode" else playStr + } + return context.getString(R.string.play_movie_button) +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AboutSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AboutSection.kt new file mode 100644 index 00000000000..2a5e58b6e3b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AboutSection.kt @@ -0,0 +1,130 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.height +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.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +fun AboutMetadataRow( + label: String, + value: String, + modifier: Modifier = Modifier +) { + val typography = MovieDetailsTheme.typography + val colors = MovieDetailsTheme.colors + + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = if (label.endsWith(":")) label else "$label:", + style = typography.regularCaption1, + color = colors.textSecondary, + modifier = Modifier.width(130.dp) + ) + Text( + text = value, + style = typography.regularCaption1, + color = colors.textPrimary, + modifier = Modifier.weight(1f) + ) + } +} + +@Composable +fun AboutSection( + title: String, + creator: String?, + castList: List, + writers: List, + genres: List, + moodTags: List, + maturityRating: String?, + advisories: String?, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val aboutInteractionSource = remember { MutableInteractionSource() } + val isAboutFocused by aboutInteractionSource.collectIsFocusedAsState() + val aboutBorder = if (isAboutFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(1.dp, colors.border) + + Box( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing3Xl, bottom = dimens.spacing3Xl) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(if (isAboutFocused) colors.surfaceElevated else colors.surface) + .border(aboutBorder, MovieDetailsTokens.ShapeCardMedium) + .focusable(interactionSource = aboutInteractionSource) + .padding(dimens.spacing2Xl), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Text( + text = stringResource(id = R.string.about_title_format, title), + style = typography.boldTitle2, + fontSize = 20.sp, + color = colors.textPrimary + ) + + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + if (!creator.isNullOrBlank()) { + AboutMetadataRow(label = stringResource(id = R.string.creator_label), value = creator) + } + if (castList.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.cast_label), value = castList.joinToString(", ")) + } + if (writers.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.writers_label), value = writers.joinToString(", ")) + } + if (genres.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.genres_label), value = genres.joinToString(", ")) + } + if (moodTags.isNotEmpty()) { + AboutMetadataRow(label = stringResource(id = R.string.mood_tags_label), value = moodTags.joinToString(", ")) + } + if (!maturityRating.isNullOrBlank()) { + val ratingDescription = if (!advisories.isNullOrBlank()) { + "$maturityRating ($advisories)" + } else { + maturityRating + } + AboutMetadataRow( + label = stringResource(id = R.string.maturity_rating_label), + value = ratingDescription + ) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt new file mode 100644 index 00000000000..4051367ad3e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt @@ -0,0 +1,194 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.animateFloatAsState +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.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.result.getWatchProgress + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun EpisodeRowItem( + episode: ResultEpisode, + onClick: () -> Unit, + modifier: Modifier = Modifier, + onLongClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.02f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "episodeScale" + ) + + val background = if (isFocused) colors.surfaceElevated else colors.surface + val border = if (isFocused) BorderStroke(dimens.borderFocus, colors.primary) else null + val progress = remember(episode) { episode.getWatchProgress() } + + Row( + modifier = modifier + .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(background) + .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardMedium) else Modifier) + .combinedClickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick, + onLongClick = onLongClick + ) + .focusable(interactionSource = interactionSource) + .padding(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) + ) { + Text( + text = episode.episode.toString(), + style = typography.boldTitle1, + fontSize = 24.sp, + color = colors.textSecondary, + modifier = Modifier.width(32.dp) + ) + + Box( + modifier = Modifier + .width(160.dp) + .height(90.dp) + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.surface) + ) { + if (!episode.poster.isNullOrBlank()) { + AsyncImage( + model = episode.poster, + contentDescription = episode.name, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier + .size(16.dp) + .align(Alignment.Center) + ) + } + + if (progress > 0.05f) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .align(Alignment.BottomStart) + .background(colors.border) + ) { + Box( + modifier = Modifier + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(colors.primary) + ) + } + } + } + + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${episode.episode}. ${episode.name ?: episode.headerName}", + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = if (episode.runTime != null) "${episode.runTime}m" else "", + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + + if (!episode.description.isNullOrBlank()) { + Text( + text = episode.description, + style = typography.regularCaption1, + color = colors.textMuted, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + lineHeight = 18.sp + ) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodesHeaderSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodesHeaderSection.kt new file mode 100644 index 00000000000..f801f7b7dfc --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodesHeaderSection.kt @@ -0,0 +1,91 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +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.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 com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +fun EpisodesHeaderSection( + seasonOptions: List, + selectedSeasonText: String, + onSeasonSelect: ((Int) -> Unit)?, + onSeasonTextChange: (String) -> Unit, + dubOptions: List, + selectedDubText: String, + onDubSelect: ((Int) -> Unit)?, + onDubTextChange: (String) -> Unit, + rangeOptions: List, + selectedRangeText: String, + onRangeSelect: ((Int) -> Unit)?, + onRangeTextChange: (String) -> Unit, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.episodes), + style = typography.boldTitle2, + fontSize = 24.sp, + color = colors.textPrimary + ) + + if (seasonOptions.size > 1) { + SeasonDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { seasonStr -> + onSeasonTextChange(seasonStr) + val idx = seasonOptions.indexOf(seasonStr) + if (idx >= 0) onSeasonSelect?.invoke(idx) + }, + width = 180.dp + ) + } + + if (dubOptions.size > 1) { + SeasonDropdown( + options = dubOptions, + selectedOption = selectedDubText, + onOptionSelected = { dubStr -> + onDubTextChange(dubStr) + val idx = dubOptions.indexOf(dubStr) + if (idx >= 0) onDubSelect?.invoke(idx) + }, + width = 140.dp + ) + } + + if (rangeOptions.size > 1) { + SeasonDropdown( + options = rangeOptions, + selectedOption = selectedRangeText, + onOptionSelected = { rangeStr -> + onRangeTextChange(rangeStr) + val idx = rangeOptions.indexOf(rangeStr) + if (idx >= 0) onRangeSelect?.invoke(idx) + }, + width = 140.dp + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/HeroBannerSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/HeroBannerSection.kt new file mode 100644 index 00000000000..e14466a4639 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/HeroBannerSection.kt @@ -0,0 +1,309 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun HeroBannerSection( + title: String, + providerName: String?, + backdropUrl: String?, + logoUrl: String?, + heroHeight: Dp, + playButtonText: String, + resumeProgressFraction: Float?, + isInWatchList: Boolean, + isFavorite: Boolean, + hasTrailers: Boolean, + playButtonFocusRequester: FocusRequester, + playInteractionSource: MutableInteractionSource, + inMyListInteractionSource: MutableInteractionSource, + likeInteractionSource: MutableInteractionSource, + trailerInteractionSource: MutableInteractionSource, + searchInteractionSource: MutableInteractionSource, + onPlayClick: () -> Unit, + onPlayLongClick: (() -> Unit)?, + onAddToListClick: () -> Unit, + onLikeClick: () -> Unit, + onTrailerClick: () -> Unit, + onSearchClick: (() -> Unit)?, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Box( + modifier = modifier + .fillMaxWidth() + .height(heroHeight) + .clipToBounds() + .background(colors.background) + ) { + if (!backdropUrl.isNullOrBlank()) { + AsyncImage( + model = backdropUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clipToBounds() + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.85f) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + colors.background.copy(alpha = 0.5f), + colors.background.copy(alpha = 0.92f), + colors.background + ) + ) + ) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) + .zIndex(50f), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Box( + modifier = Modifier + .height(42.dp) + .width(110.dp) + .focusRequester(playButtonFocusRequester) + .combinedClickable( + interactionSource = playInteractionSource, + indication = null, + role = Role.Button, + onClick = onPlayClick, + onLongClick = onPlayLongClick + ) + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = inMyListInteractionSource, + indication = null, + role = Role.Button, + onClick = onAddToListClick + ) + ) + + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = likeInteractionSource, + indication = null, + role = Role.Button, + onClick = onLikeClick + ) + ) + + if (onSearchClick != null) { + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = searchInteractionSource, + indication = null, + role = Role.Button, + onClick = onSearchClick + ) + ) + } + } + + if (hasTrailers) { + Box( + modifier = Modifier + .height(40.dp) + .width(120.dp) + .clickable( + interactionSource = trailerInteractionSource, + indication = null, + role = Role.Button, + onClick = onTrailerClick + ) + ) + } + } + } + + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(dimens.spacing2Xl) + ) { + if (!providerName.isNullOrBlank()) { + Text( + text = providerName.uppercase(), + style = typography.boldTitle2.copy( + fontWeight = FontWeight.Black, + letterSpacing = (-0.8).sp + ), + color = colors.primary + ) + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { + Column { + if (!logoUrl.isNullOrBlank()) { + Box( + modifier = Modifier + .height(68.dp) + .width(240.dp) + ) { + AsyncImage( + model = logoUrl, + contentDescription = title, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + } else { + Text( + text = title, + style = typography.boldTitle1, + fontSize = 32.sp, + color = colors.textPrimary, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + + Spacer(modifier = Modifier.height(dimens.spacingL)) + + HeroPlayButton( + onClick = onPlayClick, + onLongClick = onPlayLongClick, + text = playButtonText, + progress = resumeProgressFraction, + interactionSource = playInteractionSource, + enabled = false + ) + + Spacer(modifier = Modifier.height(dimens.spacingM)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CircleActionButton( + icon = painterResource( + id = if (isInWatchList) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 + ), + contentDescription = if (isInWatchList) { + stringResource(id = R.string.in_my_list) + } else { + stringResource(id = R.string.add_to_my_list) + }, + onClick = onAddToListClick, + interactionSource = inMyListInteractionSource, + enabled = false + ) + + CircleActionButton( + icon = painterResource( + id = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 + ), + contentDescription = if (isFavorite) { + stringResource(id = R.string.unfavorite) + } else { + stringResource(id = R.string.favorite) + }, + onClick = onLikeClick, + interactionSource = likeInteractionSource, + enabled = false + ) + + if (onSearchClick != null) { + CircleActionButton( + icon = painterResource(id = R.drawable.search_icon), + contentDescription = stringResource(id = R.string.title_search), + onClick = onSearchClick, + interactionSource = searchInteractionSource, + enabled = false + ) + } + } + } + + if (hasTrailers) { + HeroTrailerButton( + text = stringResource(id = R.string.play_trailer), + onClick = onTrailerClick, + interactionSource = trailerInteractionSource, + enabled = false + ) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieInfoSynopsisSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieInfoSynopsisSection.kt new file mode 100644 index 00000000000..28abdbab801 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieInfoSynopsisSection.kt @@ -0,0 +1,235 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor + +@Composable +fun MovieInfoSynopsisSection( + matchScore: String?, + releaseYear: String?, + seasonsCount: String?, + quality: String?, + maturityRating: String?, + advisories: String?, + top10RankText: String?, + synopsis: String, + castList: List, + genres: List, + moodTags: List, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() + + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacingL), + horizontalArrangement = Arrangement.spacedBy(dimens.spacing2Xl) + ) { + Column( + modifier = Modifier + .weight(if (hasRightColumn) 0.65f else 1f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() + + if (hasLine1) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (!matchScore.isNullOrBlank()) { + Text( + text = matchScore, + color = getRatingScoreColor(matchScore), + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + } + + if (!releaseYear.isNullOrBlank()) { + Text( + text = releaseYear, + color = colors.textPrimary, + style = typography.regularBody + ) + } + + if (!seasonsCount.isNullOrBlank()) { + Text( + text = seasonsCount, + color = colors.textPrimary, + style = typography.regularBody + ) + } + + if (!quality.isNullOrBlank()) { + VideoQualityBadge(quality = quality) + } + } + } + + if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (!maturityRating.isNullOrBlank()) { + MaturityRatingBadge(rating = maturityRating) + } + if (!advisories.isNullOrBlank()) { + Text( + text = advisories, + color = colors.textMuted, + style = typography.regularCaption1 + ) + } + } + } + + if (!top10RankText.isNullOrBlank()) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + modifier = Modifier.padding(vertical = dimens.spacingXs) + ) { + Box( + modifier = Modifier + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.primary) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(id = R.string.top_10_badge_text), + style = typography.regularCaption2, + color = colors.onPrimary, + fontWeight = FontWeight.Black + ) + } + Text( + text = top10RankText, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = colors.textPrimary + ) + } + } + + if (synopsis.isNotBlank()) { + val synopsisInteractionSource = remember { MutableInteractionSource() } + val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(if (isSynopsisFocused) colors.surfaceElevated.copy(alpha = 0.5f) else Color.Transparent) + .then( + if (isSynopsisFocused) { + Modifier.border( + BorderStroke(dimens.borderFocus, colors.primary), + MovieDetailsTokens.ShapeCardSmall + ) + } else Modifier + ) + .focusable(interactionSource = synopsisInteractionSource) + .padding(dimens.spacingS) + ) { + Text( + text = synopsis, + style = typography.regularBody, + color = if (isSynopsisFocused) colors.textPrimary else colors.textSecondary, + lineHeight = 22.sp + ) + } + } + } + + if (hasRightColumn) { + Column( + modifier = Modifier + .weight(0.35f) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (castList.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = stringResource(id = R.string.cast_label), + style = typography.regularCaption2, + color = colors.textSecondary + ) + Text( + text = castList.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + } + + if (genres.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = stringResource(id = R.string.genres_label), + style = typography.regularCaption2, + color = colors.textSecondary + ) + Text( + text = genres.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary + ) + } + } + + if (moodTags.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = stringResource(id = R.string.mood_tags_label), + style = typography.regularCaption2, + color = colors.textSecondary + ) + Text( + text = moodTags.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/RecommendationRowView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/RecommendationRowView.kt new file mode 100644 index 00000000000..edef5e651e1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/RecommendationRowView.kt @@ -0,0 +1,49 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.ui.result.compose.MovieRecommendationRow +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +fun RecommendationRowView( + row: MovieRecommendationRow, + onCardClick: (MovieCardItem) -> Unit, + modifier: Modifier = Modifier +) { + val dimens = MovieDetailsTheme.dimens + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + row.items.forEach { cardItem -> + Box(modifier = Modifier.weight(1f)) { + MovieDetailsMovieCard( + title = cardItem.title, + type = MovieCardType.POSTER, + size = MovieCardSize.MEDIUM, + badge = cardItem.badge, + posterUrl = cardItem.posterUrl, + showLogo = false, + showBottomTitle = true, + onClick = { onCardClick(cardItem) }, + modifier = Modifier.fillMaxWidth() + ) + } + } + if (row.items.size < 6) { + repeat(6 - row.items.size) { + Spacer(modifier = Modifier.weight(1f)) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt new file mode 100644 index 00000000000..f1401c05e30 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt @@ -0,0 +1,125 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.MovieTrailerData +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite +import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 + +@Composable +fun TrailerItemCard( + trailer: MovieTrailerData, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) MovieDetailsTokens.FOCUS_SCALE_FACTOR else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "trailerScale" + ) + + val border = if (isFocused) BorderStroke(dimens.borderFocus, colors.primary) else BorderStroke(dimens.borderSubtle, colors.border) + + Column( + modifier = modifier + .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(colors.surfaceElevated) + .border(border, MovieDetailsTokens.ShapeCardMedium) + .clickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = onClick + ) + .focusable(interactionSource = interactionSource) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(130.dp) + .background(colors.surface) + ) { + Box( + modifier = Modifier + .size(42.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.5.dp, PrimaryWhite), CircleShape) + .align(Alignment.Center) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier + .size(20.dp) + .align(Alignment.Center) + ) + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimens.spacingM), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = trailer.title, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = trailer.runtime, + style = typography.regularCaption2, + color = colors.textSecondary + ) + } + } +} From 6e3b8e96533316711c32b2fbded9c0da5c5534db Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 13:24:16 +0700 Subject: [PATCH 28/35] refactor(compose): restructure movie details UI to follow Atomic Design and Compose conventions --- .../ui/result/ResultFragmentTv.kt | 2 +- .../compose/MovieDetailsComposeScreen.kt | 13 +- .../compose/components/DetailsLogoView.kt | 57 ++ .../compose/components/MovieDetailsBadges.kt | 64 ++ .../compose/components/MovieDetailsButtons.kt | 258 ++++++ .../components/MovieDetailsComponents.kt | 804 ------------------ .../components/MovieDetailsMovieCard.kt | 253 ++++++ .../compose/components/MovieDetailsTokens.kt | 87 ++ .../compose/components/SeasonDropdown.kt | 193 +++++ .../compose/components/TrailerItemCard.kt | 2 +- .../compose/{ => model}/MovieDetailsModels.kt | 2 +- .../{components => sections}/AboutSection.kt | 3 +- .../EpisodesHeaderSection.kt | 3 +- .../HeroBannerSection.kt | 5 +- .../MovieInfoSynopsisSection.kt | 5 +- .../RecommendationRowView.kt | 8 +- 16 files changed, 941 insertions(+), 818 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/DetailsLogoView.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsTokens.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/SeasonDropdown.kt rename app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/{ => model}/MovieDetailsModels.kt (97%) rename app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/{components => sections}/AboutSection.kt (97%) rename app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/{components => sections}/EpisodesHeaderSection.kt (95%) rename app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/{components => sections}/HeroBannerSection.kt (97%) rename app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/{components => sections}/MovieInfoSynopsisSection.kt (96%) rename app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/{components => sections}/RecommendationRowView.kt (78%) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 460dbd22e8b..18914fdc887 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -34,7 +34,7 @@ import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent import com.lagradost.cloudstream3.ui.result.compose.MovieDetailsComposeScreen -import com.lagradost.cloudstream3.ui.result.compose.MovieTrailerData +import com.lagradost.cloudstream3.ui.result.compose.model.MovieTrailerData import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SearchClickCallback diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 29287167bf5..1675e1dffeb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -33,14 +33,17 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResumeWatchingStatus -import com.lagradost.cloudstream3.ui.result.compose.components.AboutSection import com.lagradost.cloudstream3.ui.result.compose.components.EpisodeRowItem -import com.lagradost.cloudstream3.ui.result.compose.components.EpisodesHeaderSection -import com.lagradost.cloudstream3.ui.result.compose.components.HeroBannerSection import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardItem import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardType -import com.lagradost.cloudstream3.ui.result.compose.components.MovieInfoSynopsisSection -import com.lagradost.cloudstream3.ui.result.compose.components.RecommendationRowView +import com.lagradost.cloudstream3.ui.result.compose.model.MovieRecommendationRow +import com.lagradost.cloudstream3.ui.result.compose.model.MovieTrailerData +import com.lagradost.cloudstream3.ui.result.compose.model.getPlayButtonText +import com.lagradost.cloudstream3.ui.result.compose.sections.AboutSection +import com.lagradost.cloudstream3.ui.result.compose.sections.EpisodesHeaderSection +import com.lagradost.cloudstream3.ui.result.compose.sections.HeroBannerSection +import com.lagradost.cloudstream3.ui.result.compose.sections.MovieInfoSynopsisSection +import com.lagradost.cloudstream3.ui.result.compose.sections.RecommendationRowView import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @OptIn(ExperimentalFoundationApi::class) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/DetailsLogoView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/DetailsLogoView.kt new file mode 100644 index 00000000000..7852cc31d24 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/DetailsLogoView.kt @@ -0,0 +1,57 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +enum class DetailsLogoVariant { + FULL_COLOR, + WHITE_MONO, + MINIMAL_ICON +} + +@Composable +fun DetailsLogoView( + modifier: Modifier = Modifier, + logoUrl: String? = null, + titleFallback: String = "", + variant: DetailsLogoVariant = DetailsLogoVariant.FULL_COLOR, + height: Dp = 48.dp +) { + val typography = MovieDetailsTheme.typography + val colors = MovieDetailsTheme.colors + + if (!logoUrl.isNullOrBlank()) { + AsyncImage( + model = logoUrl, + contentDescription = titleFallback, + contentScale = ContentScale.Fit, + alignment = Alignment.CenterStart, + modifier = modifier + .height(height) + .fillMaxWidth(0.6f) + ) + } else { + Text( + text = titleFallback, + style = typography.boldTitle1, + color = when (variant) { + DetailsLogoVariant.FULL_COLOR -> colors.primary + DetailsLogoVariant.WHITE_MONO -> colors.textPrimary + DetailsLogoVariant.MINIMAL_ICON -> colors.textPrimary + }, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = modifier + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt new file mode 100644 index 00000000000..79182dd31e0 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt @@ -0,0 +1,64 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +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.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +fun MaturityRatingBadge( + rating: String, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + + Box( + modifier = modifier + .clip(RoundedCornerShape(2.dp)) + .background(colors.surface) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(2.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = rating, + color = colors.textPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold + ) + } +} + +@Composable +fun VideoQualityBadge( + quality: String, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + + Box( + modifier = modifier + .clip(RoundedCornerShape(2.dp)) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(2.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = quality, + color = colors.textSecondary, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt new file mode 100644 index 00000000000..dcbb8ec2aa6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt @@ -0,0 +1,258 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +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.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.graphicsLayer +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryBlack +import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun HeroPlayButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: String = stringResource(id = R.string.play_movie_button), + progress: Float? = null, + onLongClick: (() -> Unit)? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "heroPlayBtnScale" + ) + + val background = if (isFocused) colors.primary else colors.primary.copy(alpha = 0.92f) + val contentColor = colors.onPrimary + val border = if (isFocused) BorderStroke(2.dp, colors.onBackground) else null + + Box( + modifier = modifier + .combinedClickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick, + onLongClick = onLongClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .semantics { + contentDescription = text + }, + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .height(42.dp) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(background) + .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardSmall) else Modifier) + ) { + Row( + modifier = Modifier + .fillMaxHeight() + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(22.dp) + ) + + Text( + text = text, + color = contentColor, + fontSize = 15.sp, + fontWeight = FontWeight.Bold + ) + } + + if (progress != null && progress > 0.05f) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(3.dp) + .align(Alignment.BottomStart) + .background(PrimaryBlack.copy(alpha = 0.35f)) + ) { + Box( + modifier = Modifier + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(PrimaryWhite) + ) + } + } + } + } +} + +@Composable +fun HeroTrailerButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "heroTrailerBtnScale" + ) + + val background = if (isFocused) colors.primary else colors.surfaceElevated.copy(alpha = 0.8f) + val contentColor = if (isFocused) colors.onPrimary else colors.textPrimary + val border = if (isFocused) BorderStroke(2.dp, colors.onBackground) else BorderStroke(1.dp, colors.border) + + Box( + modifier = modifier + .height(40.dp) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(background) + .border(border, MovieDetailsTokens.ShapeCardSmall) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .padding(horizontal = 14.dp), + contentAlignment = Alignment.Center + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(16.dp) + ) + + Text( + text = text, + color = contentColor, + fontSize = 13.sp, + fontWeight = FontWeight.Medium + ) + } + } +} + +@Composable +fun CircleActionButton( + icon: Painter, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors + val dimens = MovieDetailsTheme.dimens + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.15f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "circleBtnScale" + ) + + val background = if (isFocused) colors.primary else colors.surfaceElevated.copy(alpha = 0.75f) + val border = if (isFocused) BorderStroke(2.dp, colors.onBackground) else BorderStroke(1.dp, colors.border) + val iconTint = if (isFocused) colors.onPrimary else colors.textPrimary + + Box( + modifier = modifier + .size(40.dp) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(CircleShape) + .background(background) + .border(border, CircleShape) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .semantics { + this.contentDescription = contentDescription + }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = icon, + contentDescription = contentDescription, + tint = iconTint, + modifier = Modifier.size(dimens.iconM) + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt deleted file mode 100644 index b61ade9fc3f..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsComponents.kt +++ /dev/null @@ -1,804 +0,0 @@ -package com.lagradost.cloudstream3.ui.result.compose.components - -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -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.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.MenuDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -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.draw.rotate -import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.luminance -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.zIndex -import coil3.compose.AsyncImage -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme -import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryBlack -import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite -import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack90 - -object MovieDetailsTokens { - val ShapeCardSmall = RoundedCornerShape(4.dp) - val ShapeCardMedium = RoundedCornerShape(8.dp) - val ShapeCardLarge = RoundedCornerShape(12.dp) - - val FastFocusAnimationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing) - - const val FOCUS_SCALE_FACTOR = 1.05f - const val FOCUS_SCALE_FACTOR_LARGE = 1.10f - - const val ALPHA_HIGH = 0.85f - const val ALPHA_MEDIUM = 0.60f - const val ALPHA_LOW = 0.35f -} - -enum class MovieCardSize { - MEDIUM, - SMALL -} - -enum class MovieCardType { - DEFAULT, - POSTER, - MORE_LIKE_THIS, - MORE_LIKE_THIS_WITH_PLAY, - EPISODE, - TRAILER, - PLAYER_PREVIEW, - TOP10, - CONTINUE_WATCHING -} - -enum class MovieBadgeType(val stringRes: Int) { - TOP_10(R.string.badge_top_10), - RECENTLY_ADDED(R.string.badge_recently_added), - NEW_SEASON(R.string.badge_new_season), - NEW_EPISODES(R.string.badge_new_episodes), - LEAVING_SOON(R.string.badge_leaving_soon), - MUST_WATCH(R.string.badge_must_watch) -} - -@Immutable -data class MovieCardItem( - val title: String, - val type: MovieCardType = MovieCardType.DEFAULT, - val posterUrl: String? = null, - val backdropUrl: String? = null, - val progress: Float? = null, - val badge: MovieBadgeType? = null, - val matchScore: String? = null, - val maturityRating: String? = null, - val duration: String? = null, - val quality: String? = null, - val top10Rank: Int? = null, - val showLogo: Boolean = false, - val showBottomTitle: Boolean = false, - val synopsis: String? = null, - val rawItem: Any? = null -) - -object MovieCardDefaults { - val CardAspectRatioDefault = 16f / 9f - val CardAspectRatioPoster = 2f / 3f - - val ActionButtonSize = 36.dp - - fun cardWidth(type: MovieCardType, size: MovieCardSize): Dp = when (type) { - MovieCardType.POSTER -> when (size) { - MovieCardSize.MEDIUM -> 140.dp - MovieCardSize.SMALL -> 110.dp - } - else -> when (size) { - MovieCardSize.MEDIUM -> 218.dp - MovieCardSize.SMALL -> 128.dp - } - } -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun HeroPlayButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, - text: String = stringResource(id = R.string.play_movie_button), - progress: Float? = null, - onLongClick: (() -> Unit)? = null, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true -) { - val isFocused by interactionSource.collectIsFocusedAsState() - val colors = MovieDetailsTheme.colors - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) 1.05f else 1f, - animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "heroPlayBtnScale" - ) - - val background = if (isFocused) colors.primary else colors.primary.copy(alpha = 0.92f) - val contentColor = colors.onPrimary - val border = if (isFocused) BorderStroke(2.dp, colors.onBackground) else null - - Box( - modifier = modifier - .combinedClickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick, - onLongClick = onLongClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .semantics { - contentDescription = text - }, - contentAlignment = Alignment.Center - ) { - Box( - modifier = Modifier - .height(42.dp) - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(background) - .then(if (border != null) Modifier.border(border, MovieDetailsTokens.ShapeCardSmall) else Modifier) - ) { - Row( - modifier = Modifier - .fillMaxHeight() - .padding(horizontal = 24.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), - contentDescription = null, - tint = contentColor, - modifier = Modifier.size(20.dp) - ) - Text( - text = text, - color = contentColor, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) - } - - if (progress != null && progress > 0.05f) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(3.dp) - .align(Alignment.BottomStart) - .background(colors.border) - ) { - Box( - modifier = Modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(contentColor) - ) - } - } - } - } -} - -@Composable -fun HeroTrailerButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, - text: String = stringResource(id = R.string.play_trailer), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true -) { - val isFocused by interactionSource.collectIsFocusedAsState() - val colors = MovieDetailsTheme.colors - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) 1.05f else 1f, - animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "heroTrailerBtnScale" - ) - - val background = if (isFocused) colors.primary.copy(alpha = 0.9f) else colors.surface.copy(alpha = 0.6f) - val border = if (isFocused) BorderStroke(2.dp, colors.onBackground) else BorderStroke(1.dp, colors.border) - - Box( - modifier = modifier - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .semantics { - contentDescription = text - }, - contentAlignment = Alignment.Center - ) { - Row( - modifier = Modifier - .height(42.dp) - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(background) - .border(border, MovieDetailsTokens.ShapeCardSmall) - .padding(horizontal = 20.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_ondemand_video_24), - contentDescription = null, - tint = colors.textPrimary, - modifier = Modifier.size(18.dp) - ) - Text( - text = text, - color = colors.textPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.SemiBold - ) - } - } -} - -@Composable -fun CircleActionButton( - icon: Painter, - contentDescription: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isPrimary: Boolean = false, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true -) { - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = MovieDetailsTheme.dimens - val colors = MovieDetailsTheme.colors - - val bgColor = when { - isFocused -> colors.primary.copy(alpha = 0.4f) - isPrimary -> colors.primary - else -> colors.surface.copy(alpha = 0.6f) - } - - val iconTint = if (isPrimary && !isFocused) colors.onPrimary else colors.textPrimary - val border = if (isFocused) { - BorderStroke(dimens.borderFocus, colors.primary) - } else { - BorderStroke(dimens.borderDefault, colors.border) - } - - Box( - modifier = modifier - .size(MovieCardDefaults.ActionButtonSize) - .clip(CircleShape) - .background(bgColor) - .border(border, CircleShape) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource), - contentAlignment = Alignment.Center - ) { - Icon( - painter = icon, - contentDescription = contentDescription, - tint = iconTint, - modifier = Modifier.size(dimens.iconM) - ) - } -} - -@Composable -fun MaturityRatingBadge( - rating: String, - modifier: Modifier = Modifier -) { - val colors = MovieDetailsTheme.colors - - Box( - modifier = modifier - .clip(RoundedCornerShape(2.dp)) - .background(colors.surface) - .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(2.dp)) - .padding(horizontal = 6.dp, vertical = 2.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = rating, - color = colors.textPrimary, - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold - ) - } -} - -@Composable -fun VideoQualityBadge( - quality: String, - modifier: Modifier = Modifier -) { - val colors = MovieDetailsTheme.colors - - Box( - modifier = modifier - .clip(RoundedCornerShape(2.dp)) - .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(2.dp)) - .padding(horizontal = 6.dp, vertical = 2.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = quality, - color = colors.textSecondary, - fontSize = 11.sp, - fontWeight = FontWeight.Bold - ) - } -} - -enum class DetailsLogoVariant { - FULL_COLOR, - WHITE_MONO, - MINIMAL_ICON -} - -@Composable -fun DetailsLogoView( - modifier: Modifier = Modifier, - logoUrl: String? = null, - titleFallback: String = "", - variant: DetailsLogoVariant = DetailsLogoVariant.FULL_COLOR, - height: Dp = 48.dp -) { - val typography = MovieDetailsTheme.typography - val colors = MovieDetailsTheme.colors - - if (!logoUrl.isNullOrBlank()) { - AsyncImage( - model = logoUrl, - contentDescription = titleFallback, - contentScale = ContentScale.Fit, - alignment = Alignment.CenterStart, - modifier = modifier - .height(height) - .fillMaxWidth(0.6f) - ) - } else { - Text( - text = titleFallback, - style = typography.boldTitle1, - color = when (variant) { - DetailsLogoVariant.FULL_COLOR -> colors.primary - DetailsLogoVariant.WHITE_MONO -> colors.textPrimary - DetailsLogoVariant.MINIMAL_ICON -> colors.textPrimary - }, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = modifier - ) - } -} - -@Composable -private fun SeasonDropdownMenuItem( - option: String, - isSelected: Boolean, - onOptionSelected: (String) -> Unit -) { - val colors = MovieDetailsTheme.colors - val optionInteractionSource = remember(option) { MutableInteractionSource() } - val isOptionFocused by optionInteractionSource.collectIsFocusedAsState() - val focusAlpha = if (colors.surface.luminance() < 0.5f) 0.22f else 0.12f - val focusColor = colors.onSurface.copy(alpha = focusAlpha) - val focusBorder = colors.onSurface.copy(alpha = 0.72f) - val itemTextColor = if (isSelected) colors.primary else colors.textPrimary - - DropdownMenuItem( - text = { - Text( - text = option, - color = itemTextColor, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - fontSize = 14.sp - ) - }, - onClick = { - onOptionSelected(option) - }, - modifier = Modifier - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(if (isOptionFocused) focusColor else Color.Transparent) - .then( - if (isOptionFocused) { - Modifier.border( - BorderStroke(2.dp, focusBorder), - MovieDetailsTokens.ShapeCardSmall - ) - } else { - Modifier - } - ), - interactionSource = optionInteractionSource, - colors = MenuDefaults.itemColors( - textColor = itemTextColor - ) - ) -} - -@Composable -fun SeasonDropdown( - options: List, - selectedOption: String, - onOptionSelected: (String) -> Unit, - modifier: Modifier = Modifier, - width: Dp? = null, - enabled: Boolean = true -) { - var expanded by remember { mutableStateOf(false) } - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val colors = MovieDetailsTheme.colors - - val scale by animateFloatAsState( - targetValue = if (isFocused) 1.05f else 1f, - label = "DropdownScale" - ) - - val rotationAngle by animateFloatAsState( - targetValue = if (expanded) 180f else 0f, - label = "DropdownChevronRotation" - ) - - val borderStroke = when { - isFocused || expanded -> BorderStroke(2.dp, colors.primary) - else -> BorderStroke(1.dp, colors.border) - } - - val backgroundColor = when { - isFocused || expanded -> colors.surface.copy(alpha = 0.9f) - else -> colors.surface.copy(alpha = 0.6f) - } - - val baseModifier = if (width != null) modifier.width(width) else modifier - val defaultSeasonText = stringResource(id = R.string.select_season) - - Box( - modifier = baseModifier - .scale(scale) - .clip(RoundedCornerShape(4.dp)) - .background(backgroundColor) - .border(borderStroke, RoundedCornerShape(4.dp)) - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.DropdownList, - onClick = { expanded = !expanded } - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - .padding(horizontal = 14.dp, vertical = 8.dp), - contentAlignment = Alignment.CenterStart - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = selectedOption.ifEmpty { defaultSeasonText }, - color = colors.textPrimary, - fontSize = 14.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f) - ) - - Icon( - painter = painterResource(id = R.drawable.ic_baseline_keyboard_arrow_down_24), - contentDescription = null, - tint = colors.textPrimary, - modifier = Modifier - .size(18.dp) - .rotate(rotationAngle) - ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - modifier = Modifier - .background(colors.surface) - .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(4.dp)) - .heightIn(max = 280.dp) - ) { - options.forEach { option -> - SeasonDropdownMenuItem( - option = option, - isSelected = option == selectedOption, - onOptionSelected = { - onOptionSelected(it) - expanded = false - } - ) - } - } - } -} - -@Composable -private fun CardImageOrPlaceholder(title: String, imageUrl: String?) { - val colors = MovieDetailsTheme.colors - if (!imageUrl.isNullOrBlank()) { - AsyncImage( - model = imageUrl, - contentDescription = title, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } else { - Box( - modifier = Modifier - .fillMaxSize() - .background(colors.surface), - contentAlignment = Alignment.Center - ) { - Text( - text = title.take(2).uppercase(), - color = colors.textSecondary, - fontSize = 18.sp, - fontWeight = FontWeight.Bold - ) - } - } -} - -@Composable -private fun BoxScope.CardPosterBottomTitle(title: String) { - val colors = MovieDetailsTheme.colors - Box( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf(Color.Transparent, TransparentBlack90), - startY = 0f - ) - ) - .padding(horizontal = 8.dp, vertical = 6.dp) - ) { - Text( - text = title, - color = colors.textPrimary, - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} - -@Composable -private fun BoxScope.CardProgressBar(progress: Float) { - val dimens = MovieDetailsTheme.dimens - val colors = MovieDetailsTheme.colors - Box( - modifier = Modifier - .fillMaxWidth() - .height(dimens.progressHeight) - .align(Alignment.BottomCenter) - .background(colors.border) - ) { - Box( - modifier = Modifier - .fillMaxHeight() - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .background(colors.primary) - ) - } -} - -@Composable -private fun BoxScope.CardBadge(badge: MovieBadgeType) { - val colors = MovieDetailsTheme.colors - Box( - modifier = Modifier - .align(Alignment.TopStart) - .padding(4.dp) - .clip(RoundedCornerShape(2.dp)) - .background(colors.primary) - .padding(horizontal = 5.dp, vertical = 2.dp) - ) { - Text( - text = stringResource(id = badge.stringRes), - color = colors.onPrimary, - fontSize = 9.sp, - fontWeight = FontWeight.Bold - ) - } -} - -@Composable -fun MovieDetailsMovieCard( - title: String, - modifier: Modifier = Modifier, - type: MovieCardType = MovieCardType.POSTER, - size: MovieCardSize = MovieCardSize.MEDIUM, - posterUrl: String? = null, - backdropUrl: String? = null, - progress: Float? = null, - badge: MovieBadgeType? = null, - matchScore: String? = null, - maturityRating: String? = null, - duration: String? = null, - quality: String? = null, - showLogo: Boolean = false, - showBottomTitle: Boolean = false, - onClick: (() -> Unit)? = null, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true -) { - val isFocused by interactionSource.collectIsFocusedAsState() - val dimens = MovieDetailsTheme.dimens - val colors = MovieDetailsTheme.colors - - val scaleState = animateFloatAsState( - targetValue = if (isFocused) MovieDetailsTokens.FOCUS_SCALE_FACTOR else 1f, - animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, - label = "cardScale" - ) - - val aspectRatio = if (type == MovieCardType.POSTER) { - MovieCardDefaults.CardAspectRatioPoster - } else { - MovieCardDefaults.CardAspectRatioDefault - } - - val cardWidth = MovieCardDefaults.cardWidth(type, size) - val imageUrl = if (type == MovieCardType.POSTER) posterUrl ?: backdropUrl else backdropUrl ?: posterUrl - - val border = if (isFocused) { - BorderStroke(dimens.borderFocus, colors.primary) - } else { - BorderStroke(dimens.borderSubtle, colors.border) - } - - val clickableModifier = if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - } else { - Modifier.focusable(enabled = enabled, interactionSource = interactionSource) - } - - Box( - modifier = modifier - .width(cardWidth) - .then(clickableModifier) - .semantics { - contentDescription = title - }, - contentAlignment = Alignment.Center - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(aspectRatio) - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(colors.surface) - .border(border, MovieDetailsTokens.ShapeCardSmall) - ) { - CardImageOrPlaceholder(title = title, imageUrl = imageUrl) - - if (type == MovieCardType.POSTER && showBottomTitle) { - CardPosterBottomTitle(title = title) - } - - if (progress != null && progress > 0f) { - CardProgressBar(progress = progress) - } - - if (badge != null) { - CardBadge(badge = badge) - } - } - - if (type != MovieCardType.POSTER && showBottomTitle) { - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = title, - color = if (isFocused) colors.textPrimary else colors.textSecondary, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt new file mode 100644 index 00000000000..ec3b3901538 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt @@ -0,0 +1,253 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack90 + +@Composable +private fun CardImageOrPlaceholder(title: String, imageUrl: String?) { + val colors = MovieDetailsTheme.colors + if (!imageUrl.isNullOrBlank()) { + AsyncImage( + model = imageUrl, + contentDescription = title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .background(colors.surface), + contentAlignment = Alignment.Center + ) { + Text( + text = title.take(2).uppercase(), + color = colors.textSecondary, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Composable +private fun BoxScope.CardPosterBottomTitle(title: String) { + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, TransparentBlack90), + startY = 0f + ) + ) + .padding(horizontal = 8.dp, vertical = 6.dp) + ) { + Text( + text = title, + color = colors.textPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +private fun BoxScope.CardProgressBar(progress: Float) { + val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .fillMaxWidth() + .height(dimens.progressHeight) + .align(Alignment.BottomCenter) + .background(colors.border) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .background(colors.primary) + ) + } +} + +@Composable +private fun BoxScope.CardBadge(badge: MovieBadgeType) { + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .align(Alignment.TopStart) + .padding(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(colors.primary) + .padding(horizontal = 5.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(id = badge.stringRes), + color = colors.onPrimary, + fontSize = 9.sp, + fontWeight = FontWeight.Bold + ) + } +} + +@Composable +fun MovieDetailsMovieCard( + title: String, + modifier: Modifier = Modifier, + type: MovieCardType = MovieCardType.POSTER, + size: MovieCardSize = MovieCardSize.MEDIUM, + posterUrl: String? = null, + backdropUrl: String? = null, + progress: Float? = null, + badge: MovieBadgeType? = null, + matchScore: String? = null, + maturityRating: String? = null, + duration: String? = null, + quality: String? = null, + showLogo: Boolean = false, + showBottomTitle: Boolean = false, + onClick: (() -> Unit)? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + enabled: Boolean = true +) { + val isFocused by interactionSource.collectIsFocusedAsState() + val dimens = MovieDetailsTheme.dimens + val colors = MovieDetailsTheme.colors + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) MovieDetailsTokens.FOCUS_SCALE_FACTOR else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "cardScale" + ) + + val aspectRatio = if (type == MovieCardType.POSTER) { + MovieCardDefaults.CardAspectRatioPoster + } else { + MovieCardDefaults.CardAspectRatioDefault + } + + val cardWidth = MovieCardDefaults.cardWidth(type, size) + val imageUrl = if (type == MovieCardType.POSTER) posterUrl ?: backdropUrl else backdropUrl ?: posterUrl + + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, colors.primary) + } else { + BorderStroke(dimens.borderSubtle, colors.border) + } + + val clickableModifier = if (onClick != null) { + Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + } else { + Modifier.focusable(enabled = enabled, interactionSource = interactionSource) + } + + Box( + modifier = modifier + .width(cardWidth) + .then(clickableModifier) + .semantics { + contentDescription = title + }, + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(aspectRatio) + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.surface) + .border(border, MovieDetailsTokens.ShapeCardSmall) + ) { + CardImageOrPlaceholder(title = title, imageUrl = imageUrl) + + if (type == MovieCardType.POSTER && showBottomTitle) { + CardPosterBottomTitle(title = title) + } + + if (progress != null && progress > 0f) { + CardProgressBar(progress = progress) + } + + if (badge != null) { + CardBadge(badge = badge) + } + } + + if (type != MovieCardType.POSTER && showBottomTitle) { + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = title, + color = if (isFocused) colors.textPrimary else colors.textSecondary, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsTokens.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsTokens.kt new file mode 100644 index 00000000000..c818afaa34e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsTokens.kt @@ -0,0 +1,87 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Immutable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.R + +object MovieDetailsTokens { + val ShapeCardSmall = RoundedCornerShape(4.dp) + val ShapeCardMedium = RoundedCornerShape(8.dp) + val ShapeCardLarge = RoundedCornerShape(12.dp) + + val FastFocusAnimationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing) + + const val FOCUS_SCALE_FACTOR = 1.05f + const val FOCUS_SCALE_FACTOR_LARGE = 1.10f + + const val ALPHA_HIGH = 0.85f + const val ALPHA_MEDIUM = 0.60f + const val ALPHA_LOW = 0.35f +} + +enum class MovieCardSize { + MEDIUM, + SMALL +} + +enum class MovieCardType { + DEFAULT, + POSTER, + MORE_LIKE_THIS, + MORE_LIKE_THIS_WITH_PLAY, + EPISODE, + TRAILER, + PLAYER_PREVIEW, + TOP10, + CONTINUE_WATCHING +} + +enum class MovieBadgeType(val stringRes: Int) { + TOP_10(R.string.badge_top_10), + RECENTLY_ADDED(R.string.badge_recently_added), + NEW_SEASON(R.string.badge_new_season), + NEW_EPISODES(R.string.badge_new_episodes), + LEAVING_SOON(R.string.badge_leaving_soon), + MUST_WATCH(R.string.badge_must_watch) +} + +@Immutable +data class MovieCardItem( + val title: String, + val type: MovieCardType = MovieCardType.DEFAULT, + val posterUrl: String? = null, + val backdropUrl: String? = null, + val progress: Float? = null, + val badge: MovieBadgeType? = null, + val matchScore: String? = null, + val maturityRating: String? = null, + val duration: String? = null, + val quality: String? = null, + val top10Rank: Int? = null, + val showLogo: Boolean = false, + val showBottomTitle: Boolean = false, + val synopsis: String? = null, + val rawItem: Any? = null +) + +object MovieCardDefaults { + const val CardAspectRatioDefault = 16f / 9f + const val CardAspectRatioPoster = 2f / 3f + + val ActionButtonSize = 36.dp + + fun cardWidth(type: MovieCardType, size: MovieCardSize): Dp = when (type) { + MovieCardType.POSTER -> when (size) { + MovieCardSize.MEDIUM -> 140.dp + MovieCardSize.SMALL -> 110.dp + } + else -> when (size) { + MovieCardSize.MEDIUM -> 218.dp + MovieCardSize.SMALL -> 128.dp + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/SeasonDropdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/SeasonDropdown.kt new file mode 100644 index 00000000000..390b3d1bdce --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/SeasonDropdown.kt @@ -0,0 +1,193 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.heightIn +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.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MenuDefaults +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.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +private fun SeasonDropdownMenuItem( + option: String, + isSelected: Boolean, + onOptionSelected: (String) -> Unit +) { + val colors = MovieDetailsTheme.colors + val optionInteractionSource = remember(option) { MutableInteractionSource() } + val isOptionFocused by optionInteractionSource.collectIsFocusedAsState() + val focusAlpha = if (colors.surface.luminance() < 0.5f) 0.22f else 0.12f + val focusColor = colors.onSurface.copy(alpha = focusAlpha) + val focusBorder = colors.onSurface.copy(alpha = 0.72f) + val itemTextColor = if (isSelected) colors.primary else colors.textPrimary + + DropdownMenuItem( + text = { + Text( + text = option, + color = itemTextColor, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + fontSize = 14.sp + ) + }, + onClick = { + onOptionSelected(option) + }, + modifier = Modifier + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(if (isOptionFocused) focusColor else Color.Transparent) + .then( + if (isOptionFocused) { + Modifier.border( + BorderStroke(2.dp, focusBorder), + MovieDetailsTokens.ShapeCardSmall + ) + } else { + Modifier + } + ), + interactionSource = optionInteractionSource, + colors = MenuDefaults.itemColors( + textColor = itemTextColor + ) + ) +} + +@Composable +fun SeasonDropdown( + options: List, + selectedOption: String, + onOptionSelected: (String) -> Unit, + modifier: Modifier = Modifier, + width: Dp? = null, + enabled: Boolean = true +) { + var expanded by remember { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors + + val scale by animateFloatAsState( + targetValue = if (isFocused) 1.05f else 1f, + label = "DropdownScale" + ) + + val rotationAngle by animateFloatAsState( + targetValue = if (expanded) 180f else 0f, + label = "DropdownChevronRotation" + ) + + val borderStroke = when { + isFocused || expanded -> BorderStroke(2.dp, colors.primary) + else -> BorderStroke(1.dp, colors.border) + } + + val backgroundColor = when { + isFocused || expanded -> colors.surface.copy(alpha = 0.9f) + else -> colors.surface.copy(alpha = 0.6f) + } + + val baseModifier = if (width != null) modifier.width(width) else modifier + val defaultSeasonText = stringResource(id = R.string.select_season) + + Box( + modifier = baseModifier + .scale(scale) + .clip(RoundedCornerShape(4.dp)) + .background(backgroundColor) + .border(borderStroke, RoundedCornerShape(4.dp)) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.DropdownList, + onClick = { expanded = !expanded } + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + .padding(horizontal = 14.dp, vertical = 8.dp), + contentAlignment = Alignment.CenterStart + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = selectedOption.ifEmpty { defaultSeasonText }, + color = colors.textPrimary, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + + Icon( + painter = painterResource(id = R.drawable.ic_baseline_keyboard_arrow_down_24), + contentDescription = null, + tint = colors.textPrimary, + modifier = Modifier + .size(18.dp) + .rotate(rotationAngle) + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .background(colors.surface) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(4.dp)) + .heightIn(max = 280.dp) + ) { + options.forEach { option -> + SeasonDropdownMenuItem( + option = option, + isSelected = option == selectedOption, + onOptionSelected = { + onOptionSelected(it) + expanded = false + } + ) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt index f1401c05e30..9e552b1db7f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/TrailerItemCard.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.result.compose.MovieTrailerData +import com.lagradost.cloudstream3.ui.result.compose.model.MovieTrailerData import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsModels.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/MovieDetailsModels.kt similarity index 97% rename from app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsModels.kt rename to app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/MovieDetailsModels.kt index 5900bf31553..40f2c98aebd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsModels.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/MovieDetailsModels.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.ui.result.compose +package com.lagradost.cloudstream3.ui.result.compose.model import android.content.Context import androidx.compose.runtime.Immutable diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AboutSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt similarity index 97% rename from app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AboutSection.kt rename to app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt index 2a5e58b6e3b..a62bcf2b74e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AboutSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.ui.result.compose.components +package com.lagradost.cloudstream3.ui.result.compose.sections import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background @@ -25,6 +25,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @Composable diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodesHeaderSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/EpisodesHeaderSection.kt similarity index 95% rename from app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodesHeaderSection.kt rename to app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/EpisodesHeaderSection.kt index f801f7b7dfc..8c9aa2fd37d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodesHeaderSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/EpisodesHeaderSection.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.ui.result.compose.components +package com.lagradost.cloudstream3.ui.result.compose.sections import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -12,6 +12,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.SeasonDropdown import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @Composable diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/HeroBannerSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt similarity index 97% rename from app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/HeroBannerSection.kt rename to app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt index e14466a4639..f3c06c17142 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/HeroBannerSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.ui.result.compose.components +package com.lagradost.cloudstream3.ui.result.compose.sections import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -38,6 +38,9 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import coil3.compose.AsyncImage import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.CircleActionButton +import com.lagradost.cloudstream3.ui.result.compose.components.HeroPlayButton +import com.lagradost.cloudstream3.ui.result.compose.components.HeroTrailerButton import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @OptIn(ExperimentalFoundationApi::class) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieInfoSynopsisSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt similarity index 96% rename from app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieInfoSynopsisSection.kt rename to app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt index 28abdbab801..d5b9eb112ee 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieInfoSynopsisSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.ui.result.compose.components +package com.lagradost.cloudstream3.ui.result.compose.sections import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background @@ -26,6 +26,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.MaturityRatingBadge +import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens +import com.lagradost.cloudstream3.ui.result.compose.components.VideoQualityBadge import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/RecommendationRowView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/RecommendationRowView.kt similarity index 78% rename from app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/RecommendationRowView.kt rename to app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/RecommendationRowView.kt index edef5e651e1..4534c5a6392 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/RecommendationRowView.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/RecommendationRowView.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.ui.result.compose.components +package com.lagradost.cloudstream3.ui.result.compose.sections import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -9,7 +9,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream3.ui.result.compose.MovieRecommendationRow +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardItem +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardSize +import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardType +import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsMovieCard +import com.lagradost.cloudstream3.ui.result.compose.model.MovieRecommendationRow import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @Composable From 8a61c4d52563372dc0bf4289a8a100d8fb1c3296 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 14:11:25 +0700 Subject: [PATCH 29/35] fix(moview-detail): add cast search action and revert play button --- .../compose/MovieDetailsComposeScreen.kt | 7 +- .../compose/components/CastMemberCard.kt | 277 ++++++++++++++++++ .../compose/components/MovieDetailsButtons.kt | 22 +- .../result/compose/sections/AboutSection.kt | 50 +++- 4 files changed, 342 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 1675e1dffeb..02085c1a3d5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -344,7 +344,7 @@ fun MovieDetailsComposeScreen( } } - val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || + val hasAboutContent = !creator.isNullOrBlank() || !dynamicActors.isNullOrEmpty() || castList.isNotEmpty() || writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || !maturityRating.isNullOrBlank() @@ -358,7 +358,10 @@ fun MovieDetailsComposeScreen( genres = genres, moodTags = moodTags, maturityRating = maturityRating, - advisories = advisories + advisories = advisories, + actors = dynamicActors, + onActorClick = onActorClick, + onActorLongClick = onActorClick ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt new file mode 100644 index 00000000000..9107a92f79f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt @@ -0,0 +1,277 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.animation.core.animateFloatAsState +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.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +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 +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.Actor +import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.ActorRole +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +private fun CastAvatarCircle( + actor: Actor?, + size: Dp, + borderColor: Color, + borderWidth: Dp, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + Box( + modifier = modifier + .size(size) + .clip(CircleShape) + .background(colors.surface) + .border(BorderStroke(borderWidth, borderColor), CircleShape), + contentAlignment = Alignment.Center + ) { + if (!actor?.image.isNullOrBlank()) { + AsyncImage( + model = actor?.image, + contentDescription = actor?.name, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .background(colors.surfaceElevated), + contentAlignment = Alignment.Center + ) { + Text( + text = (actor?.name ?: "?").take(2).uppercase(), + color = colors.textSecondary, + fontSize = if (size > 50.dp) 18.sp else 12.sp, + fontWeight = FontWeight.Bold + ) + } + } + } +} + +@Composable +private fun CastDualAvatar( + mainActor: Actor?, + secondaryActor: Actor?, + hasVoiceActor: Boolean, + isFocused: Boolean, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val mainBorderColor = if (isFocused) colors.primary else colors.border + + Box( + modifier = modifier.size(72.dp), + contentAlignment = Alignment.Center + ) { + if (hasVoiceActor && secondaryActor?.image != null) { + CastAvatarCircle( + actor = secondaryActor, + size = 48.dp, + borderColor = colors.primary.copy(alpha = 0.6f), + borderWidth = 1.5.dp, + modifier = Modifier.offset(x = 14.dp, y = 10.dp) + ) + } + + CastAvatarCircle( + actor = mainActor, + size = 64.dp, + borderColor = mainBorderColor, + borderWidth = 2.dp + ) + } +} + +@Composable +private fun CastMemberInfo( + mainName: String, + secondaryName: String?, + roleText: String?, + isFocused: Boolean, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + Text( + text = mainName, + style = typography.mediumSmallBody, + fontWeight = FontWeight.Bold, + color = if (isFocused) colors.textPrimary else colors.textSecondary, + fontSize = 13.sp, + textAlign = TextAlign.Center, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + + if (!secondaryName.isNullOrBlank()) { + Text( + text = secondaryName, + style = typography.regularCaption2, + color = colors.textMuted, + fontSize = 11.sp, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + if (!roleText.isNullOrBlank()) { + Text( + text = roleText, + style = typography.regularCaption2, + color = colors.primary, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun CastMemberCard( + actorData: ActorData, + onActorClick: (String) -> Unit, + modifier: Modifier = Modifier, + onActorLongClick: ((String) -> Unit)? = null +) { + var isInverted by remember { mutableStateOf(false) } + val voiceActor = actorData.voiceActor + val hasVoiceActor = voiceActor != null && voiceActor.name.isNotBlank() + + val currentMain = if (isInverted && hasVoiceActor) voiceActor else actorData.actor + val currentSecondary = if (isInverted && hasVoiceActor) actorData.actor else voiceActor + + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val colors = MovieDetailsTheme.colors + val dimens = MovieDetailsTheme.dimens + + val scaleState = animateFloatAsState( + targetValue = if (isFocused) 1.08f else 1f, + animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, + label = "castCardScale" + ) + + val border = if (isFocused) { + BorderStroke(dimens.borderFocus, colors.primary) + } else { + BorderStroke(1.dp, colors.border.copy(alpha = 0.3f)) + } + + val roleText = when { + !actorData.roleString.isNullOrBlank() -> actorData.roleString + actorData.role == ActorRole.Main -> stringResource(id = R.string.actor_main) + actorData.role == ActorRole.Supporting -> stringResource(id = R.string.actor_supporting) + actorData.role == ActorRole.Background -> stringResource(id = R.string.actor_background) + else -> null + } + + Column( + modifier = modifier + .width(110.dp) + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(if (isFocused) colors.surfaceElevated else colors.surface.copy(alpha = 0.5f)) + .border(border, MovieDetailsTokens.ShapeCardMedium) + .combinedClickable( + interactionSource = interactionSource, + indication = null, + role = Role.Button, + onClick = { + if (hasVoiceActor) { + isInverted = !isInverted + } else { + onActorClick(currentMain?.name ?: "") + } + }, + onLongClick = { + val targetName = currentMain?.name ?: "" + if (targetName.isNotBlank()) { + if (onActorLongClick != null) { + onActorLongClick(targetName) + } else { + onActorClick(targetName) + } + } + } + ) + .focusable(interactionSource = interactionSource) + .padding(vertical = 10.dp, horizontal = 6.dp) + .semantics { + contentDescription = currentMain?.name ?: "" + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + CastDualAvatar( + mainActor = currentMain, + secondaryActor = currentSecondary, + hasVoiceActor = hasVoiceActor, + isFocused = isFocused + ) + + CastMemberInfo( + mainName = currentMain?.name ?: "", + secondaryName = if (hasVoiceActor) currentSecondary?.name else null, + roleText = roleText, + isFocused = isFocused + ) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt index dcbb8ec2aa6..74de238e901 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsButtons.kt @@ -119,18 +119,22 @@ fun HeroPlayButton( if (progress != null && progress > 0.05f) { Box( - modifier = Modifier - .fillMaxWidth() - .height(3.dp) - .align(Alignment.BottomStart) - .background(PrimaryBlack.copy(alpha = 0.35f)) + modifier = Modifier.matchParentSize() ) { Box( modifier = Modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(PrimaryWhite) - ) + .fillMaxWidth() + .height(5.dp) + .align(Alignment.BottomCenter) + .background(PrimaryBlack.copy(alpha = 0.45f)) + ) { + Box( + modifier = Modifier + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(PrimaryWhite) + ) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt index a62bcf2b74e..880473c0108 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt @@ -15,6 +15,8 @@ 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.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -24,7 +26,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.CastMemberCard import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @@ -66,7 +70,10 @@ fun AboutSection( moodTags: List, maturityRating: String?, advisories: String?, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + actors: List? = null, + onActorClick: ((String) -> Unit)? = null, + onActorLongClick: ((String) -> Unit)? = null ) { val colors = MovieDetailsTheme.colors val typography = MovieDetailsTheme.typography @@ -98,12 +105,49 @@ fun AboutSection( color = colors.textPrimary ) - Spacer(modifier = Modifier.height(dimens.spacingXs)) + if (!actors.isNullOrEmpty()) { + Spacer(modifier = Modifier.height(dimens.spacingXs)) + + Column(verticalArrangement = Arrangement.spacedBy(dimens.spacingS)) { + Text( + text = stringResource(id = R.string.cast_label).trimEnd(':'), + style = typography.boldTitle2, + fontSize = 15.sp, + color = colors.textPrimary + ) + + LazyRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + items( + items = actors, + key = { it.actor.name } + ) { actorData -> + CastMemberCard( + actorData = actorData, + onActorClick = { name -> + onActorClick?.invoke(name) + }, + onActorLongClick = { name -> + if (onActorLongClick != null) { + onActorLongClick(name) + } else { + onActorClick?.invoke(name) + } + } + ) + } + } + } + + Spacer(modifier = Modifier.height(dimens.spacingS)) + } if (!creator.isNullOrBlank()) { AboutMetadataRow(label = stringResource(id = R.string.creator_label), value = creator) } - if (castList.isNotEmpty()) { + if (actors.isNullOrEmpty() && castList.isNotEmpty()) { AboutMetadataRow(label = stringResource(id = R.string.cast_label), value = castList.joinToString(", ")) } if (writers.isNotEmpty()) { From 8840bbc8c4df1fa31cbc19ec15d318eacc7f7872 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 14:47:33 +0700 Subject: [PATCH 30/35] fix(movie-detail): adjust cast ui --- .../ui/result/ResultFragmentTv.kt | 16 ++- .../compose/MovieDetailsComposeScreen.kt | 18 ++- .../compose/components/CastMemberCard.kt | 110 ++++++++++-------- .../result/compose/sections/AboutSection.kt | 50 +------- .../compose/sections/CastAndCrewSection.kt | 77 ++++++++++++ 5 files changed, 171 insertions(+), 100 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/CastAndCrewSection.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 18914fdc887..40ae7efa63e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -251,7 +251,20 @@ class ResultFragmentTv : BaseFragment( val rating = d.ratingText?.asStringNull(context) val contentRating = d.contentRatingText?.asStringNull(context) val genres = d.tags - val actors = composeActorsState.ifEmpty { d.actors ?: emptyList() } + val castNames = d.actorsText?.asStringNull(context) + ?.replace(Regex("""^Cast:\s*""", RegexOption.IGNORE_CASE), "") + ?.split(",") + ?.map { it.trim() } + ?.filter { it.isNotBlank() } + ?: emptyList() + val actors = composeActorsState.ifEmpty { + d.actors ?: castNames.map { ActorData(com.lagradost.cloudstream3.Actor(it)) } + } + val nextAiring = d.nextAiringEpisode?.asStringNull(context)?.let { ep -> + d.nextAiringDate?.asStringNull(context)?.let { date -> "$ep: $date" } ?: ep + } ?: d.nextAiringDate?.asStringNull(context) + val ongoingInfo = d.onGoingText?.asStringNull(context) + val advisoriesText = listOfNotNull(ongoingInfo, nextAiring).joinToString(" • ").ifBlank { null } MovieDetailsComposeScreen( title = title, @@ -263,6 +276,7 @@ class ResultFragmentTv : BaseFragment( releaseYear = year, seasonsCount = duration, maturityRating = contentRating, + advisories = advisoriesText, synopsis = plot, genres = genres, dynamicActors = actors, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 02085c1a3d5..df961cba7a4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -40,6 +40,7 @@ import com.lagradost.cloudstream3.ui.result.compose.model.MovieRecommendationRow import com.lagradost.cloudstream3.ui.result.compose.model.MovieTrailerData import com.lagradost.cloudstream3.ui.result.compose.model.getPlayButtonText import com.lagradost.cloudstream3.ui.result.compose.sections.AboutSection +import com.lagradost.cloudstream3.ui.result.compose.sections.CastAndCrewSection import com.lagradost.cloudstream3.ui.result.compose.sections.EpisodesHeaderSection import com.lagradost.cloudstream3.ui.result.compose.sections.HeroBannerSection import com.lagradost.cloudstream3.ui.result.compose.sections.MovieInfoSynopsisSection @@ -344,7 +345,17 @@ fun MovieDetailsComposeScreen( } } - val hasAboutContent = !creator.isNullOrBlank() || !dynamicActors.isNullOrEmpty() || castList.isNotEmpty() || + if (!dynamicActors.isNullOrEmpty()) { + item(key = "cast_and_crew_section", contentType = "cast_section") { + CastAndCrewSection( + actors = dynamicActors, + onActorClick = onActorClick, + onActorLongClick = onActorClick + ) + } + } + + val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || !maturityRating.isNullOrBlank() @@ -358,10 +369,7 @@ fun MovieDetailsComposeScreen( genres = genres, moodTags = moodTags, maturityRating = maturityRating, - advisories = advisories, - actors = dynamicActors, - onActorClick = onActorClick, - onActorLongClick = onActorClick + advisories = advisories ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt index 9107a92f79f..f812cfd6908 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width 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.getValue @@ -62,7 +63,7 @@ private fun CastAvatarCircle( modifier = modifier .size(size) .clip(CircleShape) - .background(colors.surface) + .background(colors.surfaceElevated) .border(BorderStroke(borderWidth, borderColor), CircleShape), contentAlignment = Alignment.Center ) { @@ -83,7 +84,7 @@ private fun CastAvatarCircle( Text( text = (actor?.name ?: "?").take(2).uppercase(), color = colors.textSecondary, - fontSize = if (size > 50.dp) 18.sp else 12.sp, + fontSize = if (size > 60.dp) 20.sp else 14.sp, fontWeight = FontWeight.Bold ) } @@ -103,24 +104,24 @@ private fun CastDualAvatar( val mainBorderColor = if (isFocused) colors.primary else colors.border Box( - modifier = modifier.size(72.dp), + modifier = modifier.size(92.dp), contentAlignment = Alignment.Center ) { if (hasVoiceActor && secondaryActor?.image != null) { CastAvatarCircle( actor = secondaryActor, - size = 48.dp, - borderColor = colors.primary.copy(alpha = 0.6f), + size = 56.dp, + borderColor = colors.primary.copy(alpha = 0.7f), borderWidth = 1.5.dp, - modifier = Modifier.offset(x = 14.dp, y = 10.dp) + modifier = Modifier.offset(x = 18.dp, y = 14.dp) ) } CastAvatarCircle( actor = mainActor, - size = 64.dp, + size = 80.dp, borderColor = mainBorderColor, - borderWidth = 2.dp + borderWidth = if (isFocused) 2.5.dp else 1.5.dp ) } } @@ -139,7 +140,7 @@ private fun CastMemberInfo( Column( modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(2.dp) + verticalArrangement = Arrangement.spacedBy(3.dp) ) { Text( text = mainName, @@ -148,7 +149,7 @@ private fun CastMemberInfo( color = if (isFocused) colors.textPrimary else colors.textSecondary, fontSize = 13.sp, textAlign = TextAlign.Center, - maxLines = 2, + maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -165,16 +166,23 @@ private fun CastMemberInfo( } if (!roleText.isNullOrBlank()) { - Text( - text = roleText, - style = typography.regularCaption2, - color = colors.primary, - fontSize = 11.sp, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + Box( + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(colors.primary.copy(alpha = 0.15f)) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = roleText, + style = typography.regularCaption2, + color = colors.primary, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } } } } @@ -200,7 +208,7 @@ fun CastMemberCard( val dimens = MovieDetailsTheme.dimens val scaleState = animateFloatAsState( - targetValue = if (isFocused) 1.08f else 1f, + targetValue = if (isFocused) 1.06f else 1f, animationSpec = MovieDetailsTokens.FastFocusAnimationSpec, label = "castCardScale" ) @@ -208,7 +216,7 @@ fun CastMemberCard( val border = if (isFocused) { BorderStroke(dimens.borderFocus, colors.primary) } else { - BorderStroke(1.dp, colors.border.copy(alpha = 0.3f)) + BorderStroke(1.dp, colors.border.copy(alpha = 0.35f)) } val roleText = when { @@ -219,17 +227,11 @@ fun CastMemberCard( else -> null } - Column( + val cardWidth = 130.dp + + Box( modifier = modifier - .width(110.dp) - .zIndex(if (isFocused) 10f else 0f) - .graphicsLayer { - scaleX = scaleState.value - scaleY = scaleState.value - } - .clip(MovieDetailsTokens.ShapeCardMedium) - .background(if (isFocused) colors.surfaceElevated else colors.surface.copy(alpha = 0.5f)) - .border(border, MovieDetailsTokens.ShapeCardMedium) + .width(cardWidth) .combinedClickable( interactionSource = interactionSource, indication = null, @@ -253,25 +255,39 @@ fun CastMemberCard( } ) .focusable(interactionSource = interactionSource) - .padding(vertical = 10.dp, horizontal = 6.dp) .semantics { contentDescription = currentMain?.name ?: "" }, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(6.dp) + contentAlignment = Alignment.Center ) { - CastDualAvatar( - mainActor = currentMain, - secondaryActor = currentSecondary, - hasVoiceActor = hasVoiceActor, - isFocused = isFocused - ) + Column( + modifier = Modifier + .fillMaxWidth() + .zIndex(if (isFocused) 10f else 0f) + .graphicsLayer { + scaleX = scaleState.value + scaleY = scaleState.value + } + .clip(MovieDetailsTokens.ShapeCardMedium) + .background(if (isFocused) colors.surfaceElevated else colors.surface) + .border(border, MovieDetailsTokens.ShapeCardMedium) + .padding(vertical = 12.dp, horizontal = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + CastDualAvatar( + mainActor = currentMain, + secondaryActor = currentSecondary, + hasVoiceActor = hasVoiceActor, + isFocused = isFocused + ) - CastMemberInfo( - mainName = currentMain?.name ?: "", - secondaryName = if (hasVoiceActor) currentSecondary?.name else null, - roleText = roleText, - isFocused = isFocused - ) + CastMemberInfo( + mainName = currentMain?.name ?: "", + secondaryName = if (hasVoiceActor) currentSecondary?.name else null, + roleText = roleText, + isFocused = isFocused + ) + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt index 880473c0108..a62bcf2b74e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/AboutSection.kt @@ -15,8 +15,6 @@ 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.LazyRow -import androidx.compose.foundation.lazy.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -26,9 +24,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.result.compose.components.CastMemberCard import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @@ -70,10 +66,7 @@ fun AboutSection( moodTags: List, maturityRating: String?, advisories: String?, - modifier: Modifier = Modifier, - actors: List? = null, - onActorClick: ((String) -> Unit)? = null, - onActorLongClick: ((String) -> Unit)? = null + modifier: Modifier = Modifier ) { val colors = MovieDetailsTheme.colors val typography = MovieDetailsTheme.typography @@ -105,49 +98,12 @@ fun AboutSection( color = colors.textPrimary ) - if (!actors.isNullOrEmpty()) { - Spacer(modifier = Modifier.height(dimens.spacingXs)) - - Column(verticalArrangement = Arrangement.spacedBy(dimens.spacingS)) { - Text( - text = stringResource(id = R.string.cast_label).trimEnd(':'), - style = typography.boldTitle2, - fontSize = 15.sp, - color = colors.textPrimary - ) - - LazyRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - items( - items = actors, - key = { it.actor.name } - ) { actorData -> - CastMemberCard( - actorData = actorData, - onActorClick = { name -> - onActorClick?.invoke(name) - }, - onActorLongClick = { name -> - if (onActorLongClick != null) { - onActorLongClick(name) - } else { - onActorClick?.invoke(name) - } - } - ) - } - } - } - - Spacer(modifier = Modifier.height(dimens.spacingS)) - } + Spacer(modifier = Modifier.height(dimens.spacingXs)) if (!creator.isNullOrBlank()) { AboutMetadataRow(label = stringResource(id = R.string.creator_label), value = creator) } - if (actors.isNullOrEmpty() && castList.isNotEmpty()) { + if (castList.isNotEmpty()) { AboutMetadataRow(label = stringResource(id = R.string.cast_label), value = castList.joinToString(", ")) } if (writers.isNotEmpty()) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/CastAndCrewSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/CastAndCrewSection.kt new file mode 100644 index 00000000000..16e08e60d92 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/CastAndCrewSection.kt @@ -0,0 +1,77 @@ +package com.lagradost.cloudstream3.ui.result.compose.sections + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.CastMemberCard +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +fun CastAndCrewSection( + actors: List, + onActorClick: ((String) -> Unit)?, + modifier: Modifier = Modifier, + onActorLongClick: ((String) -> Unit)? = null +) { + if (actors.isEmpty()) return + + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Column( + modifier = modifier + .fillMaxWidth() + .padding(top = dimens.spacing3Xl) + ) { + Text( + text = stringResource(id = R.string.cast_label).trimEnd(':'), + style = typography.boldTitle2, + fontSize = 22.sp, + color = colors.textPrimary, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimens.spacing2Xl) + .padding(bottom = dimens.spacingL) + ) + + LazyRow( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + contentPadding = PaddingValues(horizontal = dimens.spacing2Xl, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(14.dp) + ) { + items( + items = actors, + key = { it.actor.name } + ) { actorData -> + CastMemberCard( + actorData = actorData, + onActorClick = { name -> + onActorClick?.invoke(name) + }, + onActorLongClick = { name -> + if (onActorLongClick != null) { + onActorLongClick(name) + } else { + onActorClick?.invoke(name) + } + } + ) + } + } + } +} From 0548b7d023039edf24a5e09b2b7281ffa68a6db1 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 18:56:36 +0700 Subject: [PATCH 31/35] feat(movie-detail): add episode release date --- .../ui/result/ResultFragmentTv.kt | 10 +- .../ui/result/ResultViewModel2.kt | 4 + .../compose/MovieDetailsComposeScreen.kt | 32 +++- .../compose/components/AiringCountdown.kt | 42 +++++ .../components/AiringScheduleBanner.kt | 83 +++++++++ .../compose/components/EpisodeRowItem.kt | 164 ++++++++++++++++-- .../compose/components/MovieDetailsBadges.kt | 90 ++++++++++ .../compose/model/AiringScheduleUiState.kt | 125 +++++++++++++ .../compose/sections/EpisodesHeaderSection.kt | 101 ++++++----- .../sections/MovieInfoSynopsisSection.kt | 20 ++- 10 files changed, 603 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringScheduleBanner.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 40ae7efa63e..6e8fcf811b4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -260,11 +260,7 @@ class ResultFragmentTv : BaseFragment( val actors = composeActorsState.ifEmpty { d.actors ?: castNames.map { ActorData(com.lagradost.cloudstream3.Actor(it)) } } - val nextAiring = d.nextAiringEpisode?.asStringNull(context)?.let { ep -> - d.nextAiringDate?.asStringNull(context)?.let { date -> "$ep: $date" } ?: ep - } ?: d.nextAiringDate?.asStringNull(context) val ongoingInfo = d.onGoingText?.asStringNull(context) - val advisoriesText = listOfNotNull(ongoingInfo, nextAiring).joinToString(" • ").ifBlank { null } MovieDetailsComposeScreen( title = title, @@ -276,7 +272,11 @@ class ResultFragmentTv : BaseFragment( releaseYear = year, seasonsCount = duration, maturityRating = contentRating, - advisories = advisoriesText, + advisories = null, + statusText = ongoingInfo, + nextAiringUnixTime = d.nextAiringUnixTime, + nextAiringEpisode = d.nextAiringEpisode?.asStringNull(context), + nextAiringDate = d.nextAiringDate?.asStringNull(context), synopsis = plot, genres = genres, dynamicActors = actors, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index c519e0de230..9038785f244 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -192,6 +192,7 @@ data class ResultData( val yearText: UiText?, val nextAiringDate: UiText?, val nextAiringEpisode: UiText?, + val nextAiringUnixTime: Long? = null, val plotHeaderText: UiText, val posterHeaders: Map? = null, ) @@ -236,10 +237,12 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { var nextAiringEpisode: UiText? = null var nextAiringDate: UiText? = null + var nextAiringUnixTime: Long? = null if (this is EpisodeResponse) { val airing = this.nextAiring if (airing != null && airing.unixTime > unixTime) { + nextAiringUnixTime = airing.unixTime val seconds = airing.unixTime - unixTime val days = TimeUnit.SECONDS.toDays(seconds) val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24 @@ -287,6 +290,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { ), nextAiringDate = nextAiringDate, nextAiringEpisode = nextAiringEpisode, + nextAiringUnixTime = nextAiringUnixTime, posterImage = posterUrl ?: backgroundPosterUrl, posterHeaders = posterHeaders, posterBackgroundImage = backgroundPosterUrl ?: posterUrl, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index df961cba7a4..a4ad756c6e2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -39,6 +39,7 @@ import com.lagradost.cloudstream3.ui.result.compose.components.MovieCardType import com.lagradost.cloudstream3.ui.result.compose.model.MovieRecommendationRow import com.lagradost.cloudstream3.ui.result.compose.model.MovieTrailerData import com.lagradost.cloudstream3.ui.result.compose.model.getPlayButtonText +import com.lagradost.cloudstream3.ui.result.compose.model.resolveAiringSchedule import com.lagradost.cloudstream3.ui.result.compose.sections.AboutSection import com.lagradost.cloudstream3.ui.result.compose.sections.CastAndCrewSection import com.lagradost.cloudstream3.ui.result.compose.sections.EpisodesHeaderSection @@ -85,6 +86,10 @@ fun MovieDetailsComposeScreen( onRangeSelect: ((Int) -> Unit)? = null, onPlayLongClick: (() -> Unit)? = null, onEpisodeLongClick: ((ResultEpisode) -> Unit)? = null, + statusText: String? = null, + nextAiringUnixTime: Long? = null, + nextAiringEpisode: String? = null, + nextAiringDate: String? = null, isInWatchList: Boolean = false, isFavorite: Boolean = false, hasTrailers: Boolean = false, @@ -168,6 +173,24 @@ fun MovieDetailsComposeScreen( val episodesToDisplay = dynamicEpisodes ?: emptyList() + val airingSchedule = remember( + statusText, + nextAiringUnixTime, + nextAiringEpisode, + nextAiringDate, + episodesToDisplay, + context + ) { + resolveAiringSchedule( + context = context, + statusText = statusText, + nextAiringUnixTime = nextAiringUnixTime, + nextAiringEpisode = nextAiringEpisode, + nextAiringDate = nextAiringDate, + episodes = episodesToDisplay + ) + } + val playButtonText = remember(resumeStatus, episodesToDisplay, isMovie, context) { getPlayButtonText(context, resumeStatus, episodesToDisplay, isMovie) } @@ -263,7 +286,8 @@ fun MovieDetailsComposeScreen( synopsis = synopsis, castList = castList, genres = genres, - moodTags = moodTags + moodTags = moodTags, + airingSchedule = airingSchedule ) } @@ -281,7 +305,8 @@ fun MovieDetailsComposeScreen( rangeOptions = rangeOptions, selectedRangeText = selectedRangeText, onRangeSelect = onRangeSelect, - onRangeTextChange = { selectedRangeText = it } + onRangeTextChange = { selectedRangeText = it }, + airingSchedule = airingSchedule ) } @@ -397,6 +422,9 @@ private fun MovieDetailsComposeScreenPreview() { maturityRating = "16+", advisories = "fear, language, violence", top10RankText = "#1 in TV Shows Today", + statusText = "Ongoing", + nextAiringEpisode = "Episode 5", + nextAiringDate = "2d 14h", synopsis = "When a young boy vanishes, a small town uncovers a mystery involving secret experiments, terrifying supernatural forces and one strange little girl.", cast = listOf("Winona Ryder", "David Harbour", "Millie Bobby Brown", "Finn Wolfhard"), genres = listOf("Sci-Fi", "Horror", "Drama"), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt new file mode 100644 index 00000000000..e3045eecd9b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt @@ -0,0 +1,42 @@ +package com.lagradost.cloudstream3.ui.result.compose.components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.produceState +import kotlinx.coroutines.delay + +fun formatRemainingTime(targetUnixTimeSeconds: Long): String { + val currentSeconds = System.currentTimeMillis() / 1000 + val diff = targetUnixTimeSeconds - currentSeconds + if (diff <= 0) return "Releasing soon" + val days = diff / 86400 + val hours = (diff % 86400) / 3600 + val minutes = (diff % 3600) / 60 + val seconds = diff % 60 + return when { + days > 0 -> "${days}d ${hours}h ${minutes}m" + hours > 0 -> "${hours}h ${minutes}m ${seconds}s" + minutes > 0 -> "${minutes}m ${seconds}s" + else -> "${seconds}s" + } +} + +@Composable +fun rememberAiringCountdown(targetUnixTimeSeconds: Long?): String? { + if (targetUnixTimeSeconds == null || targetUnixTimeSeconds <= 0) return null + return produceState( + initialValue = formatRemainingTime(targetUnixTimeSeconds), + key1 = targetUnixTimeSeconds + ) { + while (true) { + val currentSeconds = System.currentTimeMillis() / 1000 + val diff = targetUnixTimeSeconds - currentSeconds + value = formatRemainingTime(targetUnixTimeSeconds) + if (diff <= 0) break + if (diff > 86400) { + delay(60_000L) + } else { + delay(1_000L) + } + } + }.value +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringScheduleBanner.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringScheduleBanner.kt new file mode 100644 index 00000000000..f5049387313 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringScheduleBanner.kt @@ -0,0 +1,83 @@ +package com.lagradost.cloudstream3.ui.result.compose.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.Box +import androidx.compose.foundation.layout.Column +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.CircleShape +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.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.model.AiringScheduleUiState +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme + +@Composable +fun AiringScheduleBanner( + airingSchedule: AiringScheduleUiState, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val countdown = rememberAiringCountdown(airingSchedule.targetUnixTimeSeconds) + ?: airingSchedule.fallbackDateText + ?: return + + val releaseInfo = airingSchedule.releaseDateFormatted + + Box( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(colors.surfaceElevated) + .border(BorderStroke(1.dp, colors.orangeAccent.copy(alpha = 0.35f)), RoundedCornerShape(8.dp)) + .padding(horizontal = 14.dp, vertical = 10.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Box( + modifier = Modifier + .size(34.dp) + .clip(CircleShape) + .background(colors.orangeAccent.copy(alpha = 0.2f)), + contentAlignment = Alignment.Center + ) { + Text(text = "📅", fontSize = 16.sp) + } + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + Text( + text = airingSchedule.displayEpisodeLong ?: stringResource(id = R.string.next_episode), + style = typography.mediumSmallBody, + color = colors.textPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold + ) + Text( + text = if (releaseInfo != null) "$releaseInfo ($countdown)" else countdown, + style = typography.regularCaption1, + color = colors.orangeAccent, + fontSize = 12.sp, + fontWeight = FontWeight.Medium + ) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt index 4051367ad3e..2a42240b6fe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt @@ -21,9 +21,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -31,7 +33,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -39,12 +43,94 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.result.compose.theme.PrimaryWhite import com.lagradost.cloudstream3.ui.result.compose.theme.TransparentBlack60 +import com.lagradost.cloudstream3.ui.result.getDisplayPosition import com.lagradost.cloudstream3.ui.result.getWatchProgress +import com.lagradost.cloudstream3.utils.AppContextUtils.html +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Immutable +data class EpisodeRowUiState( + val episodeNumberText: String, + val title: String, + val description: String?, + val isFiller: Boolean, + val isWatched: Boolean, + val isUpcoming: Boolean, + val watchProgress: Float, + val ratingText: String?, + val runtimeText: String?, + val airDateText: String? +) { + val hasMeta: Boolean + get() = ratingText != null || !runtimeText.isNullOrBlank() || !airDateText.isNullOrBlank() +} + +@Composable +fun rememberEpisodeRowUiState(episode: ResultEpisode): EpisodeRowUiState { + val context = LocalContext.current + return remember(episode, context) { + val nowMs = APIHolder.unixTimeMS + val isWatched = episode.videoWatchState == VideoWatchState.Watched || + (episode.getDisplayPosition() >= episode.duration && episode.getDisplayPosition() > 0L) + val isUpcoming = episode.airDate != null && nowMs < episode.airDate + val progress = episode.getWatchProgress() + + val rating10p = episode.score?.toFloat(10) + val ratingText = if (rating10p != null && rating10p > 0.1f) { + context.getString(R.string.rated_format).format(rating10p) + } else { + null + } + + val runtimeText = episode.runTime?.times(60L)?.toInt()?.let { secondsToReadable(it, "") } + + val airDateText = if (episode.airDate != null) { + if (isUpcoming) { + val diffSec = (episode.airDate - nowMs) / 1000 + context.getString( + R.string.episode_upcoming_format, + secondsToReadable(diffSec.toInt(), "") + ) + } else { + SimpleDateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()).format(Date(episode.airDate)) + } + } else { + null + } + + val title = if (episode.name == null) { + "${context.getString(R.string.episode)} ${episode.episode}" + } else { + "${episode.episode}. ${episode.name}" + } + + val description = episode.description?.html()?.toString()?.takeIf { it.isNotBlank() } + + EpisodeRowUiState( + episodeNumberText = episode.episode.toString(), + title = title, + description = description, + isFiller = episode.isFiller == true, + isWatched = isWatched, + isUpcoming = isUpcoming, + watchProgress = progress, + ratingText = ratingText, + runtimeText = runtimeText, + airDateText = airDateText + ) + } +} @OptIn(ExperimentalFoundationApi::class) @Composable @@ -54,6 +140,7 @@ fun EpisodeRowItem( modifier: Modifier = Modifier, onLongClick: (() -> Unit)? = null ) { + val state = rememberEpisodeRowUiState(episode) val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val typography = MovieDetailsTheme.typography @@ -68,7 +155,6 @@ fun EpisodeRowItem( val background = if (isFocused) colors.surfaceElevated else colors.surface val border = if (isFocused) BorderStroke(dimens.borderFocus, colors.primary) else null - val progress = remember(episode) { episode.getWatchProgress() } Row( modifier = modifier @@ -94,7 +180,7 @@ fun EpisodeRowItem( horizontalArrangement = Arrangement.spacedBy(dimens.spacingL) ) { Text( - text = episode.episode.toString(), + text = state.episodeNumberText, style = typography.boldTitle1, fontSize = 24.sp, color = colors.textSecondary, @@ -125,8 +211,13 @@ fun EpisodeRowItem( .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) .align(Alignment.Center) ) { + val iconRes = when { + state.isUpcoming -> R.drawable.hourglass_24 + state.isWatched -> R.drawable.ic_baseline_check_24 + else -> R.drawable.ic_baseline_play_arrow_24 + } Icon( - painter = painterResource(id = R.drawable.ic_baseline_play_arrow_24), + painter = painterResource(id = iconRes), contentDescription = null, tint = PrimaryWhite, modifier = Modifier @@ -135,7 +226,7 @@ fun EpisodeRowItem( ) } - if (progress > 0.05f) { + if (!state.isUpcoming && !state.isWatched && state.watchProgress > 0.05f) { Box( modifier = Modifier .fillMaxWidth() @@ -145,7 +236,7 @@ fun EpisodeRowItem( ) { Box( modifier = Modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxWidth(state.watchProgress.coerceIn(0f, 1f)) .fillMaxHeight() .background(colors.primary) ) @@ -161,27 +252,70 @@ fun EpisodeRowItem( ) { Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically ) { + if (state.isFiller) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(colors.surfaceElevated) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(4.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(R.string.filler), + style = typography.regularCaption2, + color = colors.textPrimary, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold + ) + } + } Text( - text = "${episode.episode}. ${episode.name ?: episode.headerName}", + text = state.title, style = typography.mediumBody, fontWeight = FontWeight.Bold, color = colors.textPrimary, maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = if (episode.runTime != null) "${episode.runTime}m" else "", - style = typography.regularCaption1, - color = colors.textSecondary + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) ) } - if (!episode.description.isNullOrBlank()) { + if (state.hasMeta) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (state.ratingText != null) { + Text( + text = state.ratingText, + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + if (!state.runtimeText.isNullOrBlank()) { + Text( + text = state.runtimeText, + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + if (!state.airDateText.isNullOrBlank()) { + Text( + text = state.airDateText, + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + } + } + + if (!state.description.isNullOrBlank()) { Text( - text = episode.description, + text = state.description, style = typography.regularCaption1, color = colors.textMuted, maxLines = 2, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt index 79182dd31e0..c6b2f76a8fa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt @@ -3,8 +3,12 @@ package com.lagradost.cloudstream3.ui.result.compose.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.Box +import androidx.compose.foundation.layout.Row 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 @@ -14,6 +18,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.ui.result.compose.model.AiringScheduleUiState import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @Composable @@ -62,3 +67,88 @@ fun VideoQualityBadge( ) } } + +@Composable +fun OngoingStatusBadge( + modifier: Modifier = Modifier, + statusText: String? = null, + isOngoing: Boolean = true +) { + val colors = MovieDetailsTheme.colors + val isOngoingState = isOngoing || statusText?.contains("ongoing", ignoreCase = true) == true + val badgeColor = if (isOngoingState) colors.greenAccent else colors.textSecondary + val displayText = (statusText ?: if (isOngoingState) "Ongoing" else "Completed").uppercase() + + Box( + modifier = modifier + .clip(RoundedCornerShape(3.dp)) + .background(badgeColor.copy(alpha = 0.15f)) + .border(BorderStroke(1.dp, badgeColor.copy(alpha = 0.5f)), RoundedCornerShape(3.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp), + contentAlignment = Alignment.Center + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Box( + modifier = Modifier + .size(6.dp) + .clip(CircleShape) + .background(badgeColor) + ) + Text( + text = displayText, + color = badgeColor, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Composable +fun AiringCountdownBadge( + airingSchedule: AiringScheduleUiState, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val countdown = rememberAiringCountdown(airingSchedule.targetUnixTimeSeconds) + ?: airingSchedule.fallbackDateText + ?: return + + val badgeText = buildString { + if (!airingSchedule.dayOfWeek.isNullOrBlank()) { + append("${airingSchedule.dayOfWeek} • ") + } + if (!airingSchedule.displayEpisodeShort.isNullOrBlank()) { + append("${airingSchedule.displayEpisodeShort} • ") + } + append(countdown) + } + + Box( + modifier = modifier + .clip(RoundedCornerShape(3.dp)) + .background(colors.orangeAccent.copy(alpha = 0.15f)) + .border(BorderStroke(1.dp, colors.orangeAccent.copy(alpha = 0.5f)), RoundedCornerShape(3.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp), + contentAlignment = Alignment.Center + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = "⏱️", + fontSize = 10.sp + ) + Text( + text = badgeText, + color = colors.orangeAccent, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt new file mode 100644 index 00000000000..4215bdba384 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt @@ -0,0 +1,125 @@ +package com.lagradost.cloudstream3.ui.result.compose.model + +import android.content.Context +import androidx.compose.runtime.Immutable +import com.lagradost.cloudstream3.APIHolder +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Immutable +data class AiringScheduleUiState( + val statusText: String? = null, + val isOngoing: Boolean = false, + val targetUnixTimeSeconds: Long? = null, + val displayEpisodeShort: String? = null, + val displayEpisodeLong: String? = null, + val releaseDateFormatted: String? = null, + val dayOfWeek: String? = null, + val fallbackDateText: String? = null +) { + val hasAiringInfo: Boolean + get() = (targetUnixTimeSeconds != null) || !fallbackDateText.isNullOrBlank() +} + +fun resolveAiringSchedule( + context: Context, + statusText: String? = null, + nextAiringUnixTime: Long? = null, + nextAiringEpisode: String? = null, + nextAiringDate: String? = null, + episodes: List? = null +): AiringScheduleUiState? { + val isOngoing = statusText?.contains("ongoing", ignoreCase = true) == true + + val nowMs = APIHolder.unixTimeMS + val nowSec = APIHolder.unixTime + val upcomingEpisode = episodes?.filter { (it.airDate ?: 0L) > nowMs }?.minByOrNull { it.airDate ?: Long.MAX_VALUE } + + val resolvedUnixTime = if (nextAiringUnixTime != null && nextAiringUnixTime > nowSec) { + nextAiringUnixTime + } else { + upcomingEpisode?.airDate?.div(1000L) + } + + val resolvedEpisode = if (!nextAiringEpisode.isNullOrBlank()) { + nextAiringEpisode + } else if (upcomingEpisode != null) { + if (upcomingEpisode.season != null && upcomingEpisode.season > 1) { + context.getString(R.string.next_season_episode_format, upcomingEpisode.season, upcomingEpisode.episode) + } else { + context.getString(R.string.next_episode_format, upcomingEpisode.episode) + } + } else { + null + } + + val resolvedDate = if (!nextAiringDate.isNullOrBlank()) { + nextAiringDate + } else if (upcomingEpisode?.airDate != null) { + val diffSec = (upcomingEpisode.airDate - nowMs) / 1000 + if (diffSec > 0) { + context.getString( + R.string.episode_upcoming_format, + secondsToReadable(diffSec.toInt(), "") + ) + } else { + null + } + } else { + null + } + + if (statusText.isNullOrBlank() && resolvedUnixTime == null && resolvedDate.isNullOrBlank()) { + return null + } + + var releaseDateFormatted: String? = null + var dayOfWeek: String? = null + if (resolvedUnixTime != null && resolvedUnixTime > 0) { + try { + val date = Date(resolvedUnixTime * 1000) + dayOfWeek = SimpleDateFormat("EEE", Locale.getDefault()).format(date) + releaseDateFormatted = SimpleDateFormat("EEEE, dd MMM • HH:mm", Locale.getDefault()).format(date) + } catch (_: Exception) {} + } + + var displayEpisodeShort: String? = null + var displayEpisodeLong: String? = null + if (resolvedEpisode != null) { + val match = Regex("""(?:Season\s*(\d+)\s*)?(?:Episode|Ep\.?)\s*(\d+)""", RegexOption.IGNORE_CASE).find(resolvedEpisode) + if (match != null) { + val season = match.groupValues.getOrNull(1)?.takeIf { it.isNotBlank() } + val ep = match.groupValues.getOrNull(2)?.takeIf { it.isNotBlank() } + if (season != null && ep != null) { + displayEpisodeShort = "S$season:E$ep" + displayEpisodeLong = "Season $season Episode $ep" + } else if (ep != null) { + displayEpisodeShort = "Ep $ep" + displayEpisodeLong = "Episode $ep" + } else { + displayEpisodeLong = resolvedEpisode + } + } else { + val digitMatch = Regex("""\b(\d+)\b""").find(resolvedEpisode) + if (digitMatch != null) { + displayEpisodeShort = "Ep ${digitMatch.value}" + } + displayEpisodeLong = resolvedEpisode + } + } + + return AiringScheduleUiState( + statusText = statusText, + isOngoing = isOngoing, + targetUnixTimeSeconds = resolvedUnixTime, + displayEpisodeShort = displayEpisodeShort, + displayEpisodeLong = displayEpisodeLong, + releaseDateFormatted = releaseDateFormatted, + dayOfWeek = dayOfWeek, + fallbackDateText = resolvedDate + ) +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/EpisodesHeaderSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/EpisodesHeaderSection.kt index 8c9aa2fd37d..ea3b7a4da72 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/EpisodesHeaderSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/EpisodesHeaderSection.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.result.compose.sections 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.foundation.layout.padding @@ -12,11 +13,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.AiringScheduleBanner import com.lagradost.cloudstream3.ui.result.compose.components.SeasonDropdown +import com.lagradost.cloudstream3.ui.result.compose.model.AiringScheduleUiState import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @Composable fun EpisodesHeaderSection( + modifier: Modifier = Modifier, seasonOptions: List, selectedSeasonText: String, onSeasonSelect: ((Int) -> Unit)?, @@ -29,64 +33,73 @@ fun EpisodesHeaderSection( selectedRangeText: String, onRangeSelect: ((Int) -> Unit)?, onRangeTextChange: (String) -> Unit, - modifier: Modifier = Modifier + airingSchedule: AiringScheduleUiState? = null ) { val colors = MovieDetailsTheme.colors val typography = MovieDetailsTheme.typography val dimens = MovieDetailsTheme.dimens - Row( + Column( modifier = modifier .fillMaxWidth() .padding(horizontal = dimens.spacing2Xl) .padding(top = dimens.spacing2Xl, bottom = dimens.spacingL), - horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), - verticalAlignment = Alignment.CenterVertically + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - Text( - text = stringResource(id = R.string.episodes), - style = typography.boldTitle2, - fontSize = 24.sp, - color = colors.textPrimary - ) - - if (seasonOptions.size > 1) { - SeasonDropdown( - options = seasonOptions, - selectedOption = selectedSeasonText, - onOptionSelected = { seasonStr -> - onSeasonTextChange(seasonStr) - val idx = seasonOptions.indexOf(seasonStr) - if (idx >= 0) onSeasonSelect?.invoke(idx) - }, - width = 180.dp + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimens.spacingL), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.episodes), + style = typography.boldTitle2, + fontSize = 24.sp, + color = colors.textPrimary ) - } - if (dubOptions.size > 1) { - SeasonDropdown( - options = dubOptions, - selectedOption = selectedDubText, - onOptionSelected = { dubStr -> - onDubTextChange(dubStr) - val idx = dubOptions.indexOf(dubStr) - if (idx >= 0) onDubSelect?.invoke(idx) - }, - width = 140.dp - ) + if (seasonOptions.size > 1) { + SeasonDropdown( + options = seasonOptions, + selectedOption = selectedSeasonText, + onOptionSelected = { seasonStr -> + onSeasonTextChange(seasonStr) + val idx = seasonOptions.indexOf(seasonStr) + if (idx >= 0) onSeasonSelect?.invoke(idx) + }, + width = 180.dp + ) + } + + if (dubOptions.size > 1) { + SeasonDropdown( + options = dubOptions, + selectedOption = selectedDubText, + onOptionSelected = { dubStr -> + onDubTextChange(dubStr) + val idx = dubOptions.indexOf(dubStr) + if (idx >= 0) onDubSelect?.invoke(idx) + }, + width = 140.dp + ) + } + + if (rangeOptions.size > 1) { + SeasonDropdown( + options = rangeOptions, + selectedOption = selectedRangeText, + onOptionSelected = { rangeStr -> + onRangeTextChange(rangeStr) + val idx = rangeOptions.indexOf(rangeStr) + if (idx >= 0) onRangeSelect?.invoke(idx) + }, + width = 140.dp + ) + } } - if (rangeOptions.size > 1) { - SeasonDropdown( - options = rangeOptions, - selectedOption = selectedRangeText, - onOptionSelected = { rangeStr -> - onRangeTextChange(rangeStr) - val idx = rangeOptions.indexOf(rangeStr) - if (idx >= 0) onRangeSelect?.invoke(idx) - }, - width = 140.dp - ) + if (airingSchedule?.hasAiringInfo == true) { + AiringScheduleBanner(airingSchedule = airingSchedule) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt index d5b9eb112ee..ff43db9d5c6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt @@ -26,14 +26,18 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.compose.components.AiringCountdownBadge import com.lagradost.cloudstream3.ui.result.compose.components.MaturityRatingBadge import com.lagradost.cloudstream3.ui.result.compose.components.MovieDetailsTokens +import com.lagradost.cloudstream3.ui.result.compose.components.OngoingStatusBadge import com.lagradost.cloudstream3.ui.result.compose.components.VideoQualityBadge +import com.lagradost.cloudstream3.ui.result.compose.model.AiringScheduleUiState import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor @Composable fun MovieInfoSynopsisSection( + modifier: Modifier = Modifier, matchScore: String?, releaseYear: String?, seasonsCount: String?, @@ -45,7 +49,7 @@ fun MovieInfoSynopsisSection( castList: List, genres: List, moodTags: List, - modifier: Modifier = Modifier + airingSchedule: AiringScheduleUiState? = null ) { val colors = MovieDetailsTheme.colors val typography = MovieDetailsTheme.typography @@ -65,7 +69,8 @@ fun MovieInfoSynopsisSection( verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || - !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() || + airingSchedule != null if (hasLine1) { Row( @@ -100,6 +105,17 @@ fun MovieInfoSynopsisSection( if (!quality.isNullOrBlank()) { VideoQualityBadge(quality = quality) } + + if (!airingSchedule?.statusText.isNullOrBlank()) { + OngoingStatusBadge( + statusText = airingSchedule.statusText, + isOngoing = airingSchedule.isOngoing + ) + } + + if (airingSchedule?.hasAiringInfo == true) { + AiringCountdownBadge(airingSchedule = airingSchedule) + } } } From 6c219b68738352ab2c2458163634f7be1d6021e8 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 19:18:18 +0700 Subject: [PATCH 32/35] fix(movie-detail): check ongoing properly --- .../lagradost/cloudstream3/ui/result/ResultFragmentTv.kt | 2 ++ .../lagradost/cloudstream3/ui/result/ResultViewModel2.kt | 4 ++++ .../ui/result/compose/MovieDetailsComposeScreen.kt | 4 ++++ .../ui/result/compose/components/MovieDetailsBadges.kt | 8 +++++--- .../ui/result/compose/model/AiringScheduleUiState.kt | 3 +-- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 6e8fcf811b4..e0cf042be2d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -261,6 +261,7 @@ class ResultFragmentTv : BaseFragment( d.actors ?: castNames.map { ActorData(com.lagradost.cloudstream3.Actor(it)) } } val ongoingInfo = d.onGoingText?.asStringNull(context) + val isOngoing = d.isOngoing ?: (d.showStatus?.let { it == com.lagradost.cloudstream3.ShowStatus.Ongoing } == true) MovieDetailsComposeScreen( title = title, @@ -274,6 +275,7 @@ class ResultFragmentTv : BaseFragment( maturityRating = contentRating, advisories = null, statusText = ongoingInfo, + isOngoing = isOngoing, nextAiringUnixTime = d.nextAiringUnixTime, nextAiringEpisode = d.nextAiringEpisode?.asStringNull(context), nextAiringDate = d.nextAiringDate?.asStringNull(context), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 9038785f244..9b77e7546b9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -186,6 +186,8 @@ data class ResultData( val metaText: UiText?, val durationText: UiText?, val onGoingText: UiText?, + val showStatus: ShowStatus? = null, + val isOngoing: Boolean? = null, val noEpisodesFoundText: UiText?, val titleText: UiText, val typeText: UiText, @@ -357,6 +359,8 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { } ) } else null, + showStatus = if (this is EpisodeResponse) this.showStatus else null, + isOngoing = if (this is EpisodeResponse) this.showStatus == ShowStatus.Ongoing else null, noEpisodesFoundText = if ((this is TvSeriesLoadResponse && this.episodes.isEmpty()) || (this is AnimeLoadResponse && !this.episodes.any { it.value.isNotEmpty() })) txt( R.string.no_episodes_found diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index a4ad756c6e2..64417ebb9be 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -87,6 +87,7 @@ fun MovieDetailsComposeScreen( onPlayLongClick: (() -> Unit)? = null, onEpisodeLongClick: ((ResultEpisode) -> Unit)? = null, statusText: String? = null, + isOngoing: Boolean = false, nextAiringUnixTime: Long? = null, nextAiringEpisode: String? = null, nextAiringDate: String? = null, @@ -175,6 +176,7 @@ fun MovieDetailsComposeScreen( val airingSchedule = remember( statusText, + isOngoing, nextAiringUnixTime, nextAiringEpisode, nextAiringDate, @@ -184,6 +186,7 @@ fun MovieDetailsComposeScreen( resolveAiringSchedule( context = context, statusText = statusText, + isOngoing = isOngoing, nextAiringUnixTime = nextAiringUnixTime, nextAiringEpisode = nextAiringEpisode, nextAiringDate = nextAiringDate, @@ -423,6 +426,7 @@ private fun MovieDetailsComposeScreenPreview() { advisories = "fear, language, violence", top10RankText = "#1 in TV Shows Today", statusText = "Ongoing", + isOngoing = true, nextAiringEpisode = "Episode 5", nextAiringDate = "2d 14h", synopsis = "When a young boy vanishes, a small town uncovers a mystery involving secret experiments, terrifying supernatural forces and one strange little girl.", diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt index c6b2f76a8fa..cd85f3de279 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsBadges.kt @@ -15,9 +15,11 @@ 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.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.result.compose.model.AiringScheduleUiState import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @@ -75,9 +77,9 @@ fun OngoingStatusBadge( isOngoing: Boolean = true ) { val colors = MovieDetailsTheme.colors - val isOngoingState = isOngoing || statusText?.contains("ongoing", ignoreCase = true) == true - val badgeColor = if (isOngoingState) colors.greenAccent else colors.textSecondary - val displayText = (statusText ?: if (isOngoingState) "Ongoing" else "Completed").uppercase() + val badgeColor = if (isOngoing) colors.greenAccent else colors.textSecondary + val defaultText = stringResource(if (isOngoing) R.string.status_ongoing else R.string.status_completed) + val displayText = (statusText ?: defaultText).uppercase() Box( modifier = modifier diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt index 4215bdba384..08e352d056f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt @@ -28,13 +28,12 @@ data class AiringScheduleUiState( fun resolveAiringSchedule( context: Context, statusText: String? = null, + isOngoing: Boolean = false, nextAiringUnixTime: Long? = null, nextAiringEpisode: String? = null, nextAiringDate: String? = null, episodes: List? = null ): AiringScheduleUiState? { - val isOngoing = statusText?.contains("ongoing", ignoreCase = true) == true - val nowMs = APIHolder.unixTimeMS val nowSec = APIHolder.unixTime val upcomingEpisode = episodes?.filter { (it.airDate ?: 0L) > nowMs }?.minByOrNull { it.airDate ?: Long.MAX_VALUE } From 9a11c35c078bfd6a3e416d8b3dab65260bec5a54 Mon Sep 17 00:00:00 2001 From: Spring Date: Tue, 25 Aug 2026 19:48:34 +0700 Subject: [PATCH 33/35] fix(lint): breakdown complex param and function --- .../compose/MovieDetailsComposeScreen.kt | 297 ++++++++---- .../compose/components/AiringCountdown.kt | 4 +- .../compose/components/CastMemberCard.kt | 59 ++- .../compose/components/EpisodeRowItem.kt | 314 ++++++------ .../components/MovieDetailsMovieCard.kt | 108 +++-- .../compose/model/AiringScheduleUiState.kt | 140 +++--- .../compose/model/MovieDetailsModels.kt | 84 ++-- .../compose/sections/HeroBannerSection.kt | 455 +++++++++++------- .../sections/MovieInfoSynopsisSection.kt | 398 ++++++++------- 9 files changed, 1094 insertions(+), 765 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index 64417ebb9be..ba1029b212f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Text @@ -46,8 +47,160 @@ import com.lagradost.cloudstream3.ui.result.compose.sections.EpisodesHeaderSecti import com.lagradost.cloudstream3.ui.result.compose.sections.HeroBannerSection import com.lagradost.cloudstream3.ui.result.compose.sections.MovieInfoSynopsisSection import com.lagradost.cloudstream3.ui.result.compose.sections.RecommendationRowView +import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsColors import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme +private fun LazyListScope.episodesSection( + episodesToDisplay: List, + seasonOptions: List, + selectedSeasonText: String, + onSeasonSelect: ((Int) -> Unit)?, + onSeasonTextChange: (String) -> Unit, + dubOptions: List, + selectedDubText: String, + onDubSelect: ((Int) -> Unit)?, + onDubTextChange: (String) -> Unit, + rangeOptions: List, + selectedRangeText: String, + onRangeSelect: ((Int) -> Unit)?, + onRangeTextChange: (String) -> Unit, + airingSchedule: com.lagradost.cloudstream3.ui.result.compose.model.AiringScheduleUiState?, + onEpisodeClick: ((ResultEpisode) -> Unit)?, + onEpisodeLongClick: ((ResultEpisode) -> Unit)?, + showToast: (String) -> Unit +) { + if (episodesToDisplay.isEmpty()) return + + item(key = "episodes_header", contentType = "episodes_header") { + EpisodesHeaderSection( + seasonOptions = seasonOptions, + selectedSeasonText = selectedSeasonText, + onSeasonSelect = onSeasonSelect, + onSeasonTextChange = onSeasonTextChange, + dubOptions = dubOptions, + selectedDubText = selectedDubText, + onDubSelect = onDubSelect, + onDubTextChange = onDubTextChange, + rangeOptions = rangeOptions, + selectedRangeText = selectedRangeText, + onRangeSelect = onRangeSelect, + onRangeTextChange = onRangeTextChange, + airingSchedule = airingSchedule + ) + } + + items( + items = episodesToDisplay, + key = { ep -> "episode_${ep.id}" }, + contentType = { "episode_row" } + ) { ep -> + val onEpClick = remember(ep) { + { + if (onEpisodeClick != null) onEpisodeClick(ep) + else showToast("Playing ${ep.headerName}: ${ep.name}") + } + } + val onEpLongClick = remember(ep) { + if (onEpisodeLongClick != null) { + { onEpisodeLongClick(ep) } + } else null + } + EpisodeRowItem( + episode = ep, + onClick = onEpClick, + onLongClick = onEpLongClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = MovieDetailsTheme.dimens.spacing2Xl, vertical = 6.dp) + ) + } +} + +private fun LazyListScope.recommendationsSection( + chunkedRecommendations: List, + dynamicRecommendations: List?, + onRecommendationClick: ((SearchResponse) -> Unit)?, + showToast: (String) -> Unit, + colors: MovieDetailsColors +) { + if (chunkedRecommendations.isEmpty()) return + + item(key = "recommendations_header", contentType = "recommendations_header") { + Text( + text = stringResource(id = R.string.more_like_this), + style = MovieDetailsTheme.typography.boldTitle2, + fontSize = 22.sp, + color = colors.textPrimary, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = MovieDetailsTheme.dimens.spacing2Xl) + .padding(top = MovieDetailsTheme.dimens.spacing3Xl, bottom = MovieDetailsTheme.dimens.spacingL) + ) + } + + items( + items = chunkedRecommendations, + key = { row -> "rec_row_${row.rowIndex}" }, + contentType = { "recommendation_row" } + ) { row -> + RecommendationRowView( + row = row, + onCardClick = { cardItem -> + val rec = dynamicRecommendations?.find { it.name == cardItem.title } + if (rec != null && onRecommendationClick != null) { + onRecommendationClick(rec) + } else { + showToast("Opened recommendation: ${cardItem.title}") + } + } + ) + } +} + +private fun LazyListScope.castSection( + dynamicActors: List?, + onActorClick: ((String) -> Unit)? +) { + if (dynamicActors.isNullOrEmpty()) return + + item(key = "cast_and_crew_section", contentType = "cast_section") { + CastAndCrewSection( + actors = dynamicActors, + onActorClick = onActorClick, + onActorLongClick = onActorClick + ) + } +} + +private fun LazyListScope.aboutSection( + title: String, + creator: String?, + castList: List, + writers: List, + genres: List, + moodTags: List, + maturityRating: String?, + advisories: String? +) { + val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || + writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || + !maturityRating.isNullOrBlank() + if (!hasAboutContent) return + + item(key = "about_section", contentType = "about_section") { + AboutSection( + title = title, + creator = creator, + castList = castList, + writers = writers, + genres = genres, + moodTags = moodTags, + maturityRating = maturityRating, + advisories = advisories + ) + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable fun MovieDetailsComposeScreen( @@ -294,113 +447,49 @@ fun MovieDetailsComposeScreen( ) } - if (episodesToDisplay.isNotEmpty()) { - item(key = "episodes_header", contentType = "episodes_header") { - EpisodesHeaderSection( - seasonOptions = seasonOptions, - selectedSeasonText = selectedSeasonText, - onSeasonSelect = onSeasonSelect, - onSeasonTextChange = { selectedSeasonText = it }, - dubOptions = dubOptions, - selectedDubText = selectedDubText, - onDubSelect = onDubSelect, - onDubTextChange = { selectedDubText = it }, - rangeOptions = rangeOptions, - selectedRangeText = selectedRangeText, - onRangeSelect = onRangeSelect, - onRangeTextChange = { selectedRangeText = it }, - airingSchedule = airingSchedule - ) - } - - items( - items = episodesToDisplay, - key = { ep -> "episode_${ep.id}" }, - contentType = { "episode_row" } - ) { ep -> - val onEpClick = remember(ep) { - { - if (onEpisodeClick != null) onEpisodeClick(ep) - else showToast("Playing ${ep.headerName}: ${ep.name}") - } - } - val onEpLongClick = remember(ep) { - if (onEpisodeLongClick != null) { - { onEpisodeLongClick(ep) } - } else null - } - EpisodeRowItem( - episode = ep, - onClick = onEpClick, - onLongClick = onEpLongClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = MovieDetailsTheme.dimens.spacing2Xl, vertical = 6.dp) - ) - } - } - - if (chunkedRecommendations.isNotEmpty()) { - item(key = "recommendations_header", contentType = "recommendations_header") { - Text( - text = stringResource(id = R.string.more_like_this), - style = MovieDetailsTheme.typography.boldTitle2, - fontSize = 22.sp, - color = colors.textPrimary, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = MovieDetailsTheme.dimens.spacing2Xl) - .padding(top = MovieDetailsTheme.dimens.spacing3Xl, bottom = MovieDetailsTheme.dimens.spacingL) - ) - } + episodesSection( + episodesToDisplay = episodesToDisplay, + seasonOptions = seasonOptions, + selectedSeasonText = selectedSeasonText, + onSeasonSelect = onSeasonSelect, + onSeasonTextChange = { selectedSeasonText = it }, + dubOptions = dubOptions, + selectedDubText = selectedDubText, + onDubSelect = onDubSelect, + onDubTextChange = { selectedDubText = it }, + rangeOptions = rangeOptions, + selectedRangeText = selectedRangeText, + onRangeSelect = onRangeSelect, + onRangeTextChange = { selectedRangeText = it }, + airingSchedule = airingSchedule, + onEpisodeClick = onEpisodeClick, + onEpisodeLongClick = onEpisodeLongClick, + showToast = ::showToast + ) - items( - items = chunkedRecommendations, - key = { row -> "rec_row_${row.rowIndex}" }, - contentType = { "recommendation_row" } - ) { row -> - RecommendationRowView( - row = row, - onCardClick = { cardItem -> - val rec = dynamicRecommendations?.find { it.name == cardItem.title } - if (rec != null && onRecommendationClick != null) { - onRecommendationClick(rec) - } else { - showToast("Opened recommendation: ${cardItem.title}") - } - } - ) - } - } + recommendationsSection( + chunkedRecommendations = chunkedRecommendations, + dynamicRecommendations = dynamicRecommendations, + onRecommendationClick = onRecommendationClick, + showToast = ::showToast, + colors = colors + ) - if (!dynamicActors.isNullOrEmpty()) { - item(key = "cast_and_crew_section", contentType = "cast_section") { - CastAndCrewSection( - actors = dynamicActors, - onActorClick = onActorClick, - onActorLongClick = onActorClick - ) - } - } + castSection( + dynamicActors = dynamicActors, + onActorClick = onActorClick + ) - val hasAboutContent = !creator.isNullOrBlank() || castList.isNotEmpty() || - writers.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() || - !maturityRating.isNullOrBlank() - - if (hasAboutContent) { - item(key = "about_section", contentType = "about_section") { - AboutSection( - title = title, - creator = creator, - castList = castList, - writers = writers, - genres = genres, - moodTags = moodTags, - maturityRating = maturityRating, - advisories = advisories - ) - } - } + aboutSection( + title = title, + creator = creator, + castList = castList, + writers = writers, + genres = genres, + moodTags = moodTags, + maturityRating = maturityRating, + advisories = advisories + ) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt index e3045eecd9b..2f0a4e0b7b3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/AiringCountdown.kt @@ -9,8 +9,8 @@ fun formatRemainingTime(targetUnixTimeSeconds: Long): String { val diff = targetUnixTimeSeconds - currentSeconds if (diff <= 0) return "Releasing soon" val days = diff / 86400 - val hours = (diff % 86400) / 3600 - val minutes = (diff % 3600) / 60 + val hours = diff % 86400 / 3600 + val minutes = diff % 3600 / 60 val seconds = diff % 60 return when { days > 0 -> "${days}d ${hours}h ${minutes}m" diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt index f812cfd6908..be824a61394 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/CastMemberCard.kt @@ -187,6 +187,44 @@ private fun CastMemberInfo( } } +@Composable +private fun resolveRoleText(actorData: ActorData): String? { + if (!actorData.roleString.isNullOrBlank()) { + return actorData.roleString + } + return when (actorData.role) { + ActorRole.Main -> stringResource(id = R.string.actor_main) + ActorRole.Supporting -> stringResource(id = R.string.actor_supporting) + ActorRole.Background -> stringResource(id = R.string.actor_background) + else -> null + } +} + +private fun resolveActiveActors( + actorData: ActorData, + isInverted: Boolean, + hasVoiceActor: Boolean +): Pair { + return if (isInverted && hasVoiceActor) { + actorData.voiceActor to actorData.actor + } else { + actorData.actor to actorData.voiceActor + } +} + +private fun handleCastLongClick( + targetName: String, + onActorClick: (String) -> Unit, + onActorLongClick: ((String) -> Unit)? +) { + if (targetName.isBlank()) return + if (onActorLongClick != null) { + onActorLongClick(targetName) + } else { + onActorClick(targetName) + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable fun CastMemberCard( @@ -199,8 +237,7 @@ fun CastMemberCard( val voiceActor = actorData.voiceActor val hasVoiceActor = voiceActor != null && voiceActor.name.isNotBlank() - val currentMain = if (isInverted && hasVoiceActor) voiceActor else actorData.actor - val currentSecondary = if (isInverted && hasVoiceActor) actorData.actor else voiceActor + val (currentMain, currentSecondary) = resolveActiveActors(actorData, isInverted, hasVoiceActor) val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() @@ -219,14 +256,7 @@ fun CastMemberCard( BorderStroke(1.dp, colors.border.copy(alpha = 0.35f)) } - val roleText = when { - !actorData.roleString.isNullOrBlank() -> actorData.roleString - actorData.role == ActorRole.Main -> stringResource(id = R.string.actor_main) - actorData.role == ActorRole.Supporting -> stringResource(id = R.string.actor_supporting) - actorData.role == ActorRole.Background -> stringResource(id = R.string.actor_background) - else -> null - } - + val roleText = resolveRoleText(actorData) val cardWidth = 130.dp Box( @@ -244,14 +274,7 @@ fun CastMemberCard( } }, onLongClick = { - val targetName = currentMain?.name ?: "" - if (targetName.isNotBlank()) { - if (onActorLongClick != null) { - onActorLongClick(targetName) - } else { - onActorClick(targetName) - } - } + handleCastLongClick(currentMain?.name ?: "", onActorClick, onActorLongClick) } ) .focusable(interactionSource = interactionSource) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt index 2a42240b6fe..f601fe1cf3e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/EpisodeRowItem.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight @@ -132,6 +133,178 @@ fun rememberEpisodeRowUiState(episode: ResultEpisode): EpisodeRowUiState { } } +@Composable +private fun EpisodePlayStatusBadge(state: EpisodeRowUiState, modifier: Modifier = Modifier) { + val iconRes = when { + state.isUpcoming -> R.drawable.hourglass_24 + state.isWatched -> R.drawable.ic_baseline_check_24 + else -> R.drawable.ic_baseline_play_arrow_24 + } + Box( + modifier = modifier + .size(36.dp) + .clip(CircleShape) + .background(TransparentBlack60) + .border(BorderStroke(1.dp, PrimaryWhite), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = null, + tint = PrimaryWhite, + modifier = Modifier.size(16.dp) + ) + } +} + +@Composable +private fun BoxScope.EpisodeProgressBar(watchProgress: Float) { + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .align(Alignment.BottomStart) + .background(colors.border) + ) { + Box( + modifier = Modifier + .fillMaxWidth(watchProgress.coerceIn(0f, 1f)) + .fillMaxHeight() + .background(colors.primary) + ) + } +} + +@Composable +private fun EpisodeThumbnail(episode: ResultEpisode, state: EpisodeRowUiState) { + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .width(160.dp) + .height(90.dp) + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.surface) + ) { + if (!episode.poster.isNullOrBlank()) { + AsyncImage( + model = episode.poster, + contentDescription = episode.name, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + + EpisodePlayStatusBadge( + state = state, + modifier = Modifier.align(Alignment.Center) + ) + + if (!state.isUpcoming && !state.isWatched && state.watchProgress > 0.05f) { + EpisodeProgressBar(watchProgress = state.watchProgress) + } + } +} + +@Composable +private fun EpisodeTitleRow(state: EpisodeRowUiState) { + val typography = MovieDetailsTheme.typography + val colors = MovieDetailsTheme.colors + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (state.isFiller) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(colors.surfaceElevated) + .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(4.dp)) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(R.string.filler), + style = typography.regularCaption2, + color = colors.textPrimary, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold + ) + } + } + Text( + text = state.title, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) + ) + } +} + +@Composable +private fun EpisodeMetaRow(state: EpisodeRowUiState) { + if (!state.hasMeta) return + val typography = MovieDetailsTheme.typography + val colors = MovieDetailsTheme.colors + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (state.ratingText != null) { + Text( + text = state.ratingText, + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + if (!state.runtimeText.isNullOrBlank()) { + Text( + text = state.runtimeText, + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + if (!state.airDateText.isNullOrBlank()) { + Text( + text = state.airDateText, + style = typography.regularCaption1, + color = colors.textSecondary + ) + } + } +} + +@Composable +private fun EpisodeInfoColumn(state: EpisodeRowUiState, modifier: Modifier = Modifier) { + val typography = MovieDetailsTheme.typography + val colors = MovieDetailsTheme.colors + + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + EpisodeTitleRow(state = state) + EpisodeMetaRow(state = state) + + if (!state.description.isNullOrBlank()) { + Text( + text = state.description, + style = typography.regularCaption1, + color = colors.textMuted, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + lineHeight = 18.sp + ) + } + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable fun EpisodeRowItem( @@ -187,142 +360,11 @@ fun EpisodeRowItem( modifier = Modifier.width(32.dp) ) - Box( - modifier = Modifier - .width(160.dp) - .height(90.dp) - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(colors.surface) - ) { - if (!episode.poster.isNullOrBlank()) { - AsyncImage( - model = episode.poster, - contentDescription = episode.name, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } + EpisodeThumbnail(episode = episode, state = state) - Box( - modifier = Modifier - .size(36.dp) - .clip(CircleShape) - .background(TransparentBlack60) - .border(BorderStroke(1.dp, PrimaryWhite), CircleShape) - .align(Alignment.Center) - ) { - val iconRes = when { - state.isUpcoming -> R.drawable.hourglass_24 - state.isWatched -> R.drawable.ic_baseline_check_24 - else -> R.drawable.ic_baseline_play_arrow_24 - } - Icon( - painter = painterResource(id = iconRes), - contentDescription = null, - tint = PrimaryWhite, - modifier = Modifier - .size(16.dp) - .align(Alignment.Center) - ) - } - - if (!state.isUpcoming && !state.isWatched && state.watchProgress > 0.05f) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .align(Alignment.BottomStart) - .background(colors.border) - ) { - Box( - modifier = Modifier - .fillMaxWidth(state.watchProgress.coerceIn(0f, 1f)) - .fillMaxHeight() - .background(colors.primary) - ) - } - } - } - - Column( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - if (state.isFiller) { - Box( - modifier = Modifier - .clip(RoundedCornerShape(4.dp)) - .background(colors.surfaceElevated) - .border(BorderStroke(1.dp, colors.border), RoundedCornerShape(4.dp)) - .padding(horizontal = 6.dp, vertical = 2.dp) - ) { - Text( - text = stringResource(R.string.filler), - style = typography.regularCaption2, - color = colors.textPrimary, - fontSize = 11.sp, - fontWeight = FontWeight.SemiBold - ) - } - } - Text( - text = state.title, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = colors.textPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f, fill = false) - ) - } - - if (state.hasMeta) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - if (state.ratingText != null) { - Text( - text = state.ratingText, - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - if (!state.runtimeText.isNullOrBlank()) { - Text( - text = state.runtimeText, - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - if (!state.airDateText.isNullOrBlank()) { - Text( - text = state.airDateText, - style = typography.regularCaption1, - color = colors.textSecondary - ) - } - } - } - - if (!state.description.isNullOrBlank()) { - Text( - text = state.description, - style = typography.regularCaption1, - color = colors.textMuted, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - lineHeight = 18.sp - ) - } - } + EpisodeInfoColumn( + state = state, + modifier = Modifier.weight(1f) + ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt index ec3b3901538..cf52f10f15d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/components/MovieDetailsMovieCard.kt @@ -137,6 +137,67 @@ private fun BoxScope.CardBadge(badge: MovieBadgeType) { } } +private fun resolveImageUrl(type: MovieCardType, posterUrl: String?, backdropUrl: String?): String? { + return if (type == MovieCardType.POSTER) { + posterUrl ?: backdropUrl + } else { + backdropUrl ?: posterUrl + } +} + +private fun Modifier.applyCardClickable( + enabled: Boolean, + interactionSource: MutableInteractionSource, + onClick: (() -> Unit)? +): Modifier { + return if (onClick != null) { + this + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + .focusable(enabled = enabled, interactionSource = interactionSource) + } else { + this.focusable(enabled = enabled, interactionSource = interactionSource) + } +} + +@Composable +private fun BoxScope.CardOverlays( + type: MovieCardType, + showBottomTitle: Boolean, + title: String, + progress: Float?, + badge: MovieBadgeType? +) { + if (type == MovieCardType.POSTER && showBottomTitle) { + CardPosterBottomTitle(title = title) + } + if (progress != null && progress > 0f) { + CardProgressBar(progress = progress) + } + if (badge != null) { + CardBadge(badge = badge) + } +} + +@Composable +private fun CardExternalTitle(title: String, isFocused: Boolean) { + val colors = MovieDetailsTheme.colors + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = title, + color = if (isFocused) colors.textPrimary else colors.textSecondary, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) +} + @Composable fun MovieDetailsMovieCard( title: String, @@ -174,7 +235,7 @@ fun MovieDetailsMovieCard( } val cardWidth = MovieCardDefaults.cardWidth(type, size) - val imageUrl = if (type == MovieCardType.POSTER) posterUrl ?: backdropUrl else backdropUrl ?: posterUrl + val imageUrl = resolveImageUrl(type, posterUrl, backdropUrl) val border = if (isFocused) { BorderStroke(dimens.borderFocus, colors.primary) @@ -182,24 +243,10 @@ fun MovieDetailsMovieCard( BorderStroke(dimens.borderSubtle, colors.border) } - val clickableModifier = if (onClick != null) { - Modifier - .clickable( - interactionSource = interactionSource, - indication = null, - enabled = enabled, - role = Role.Button, - onClick = onClick - ) - .focusable(enabled = enabled, interactionSource = interactionSource) - } else { - Modifier.focusable(enabled = enabled, interactionSource = interactionSource) - } - Box( modifier = modifier .width(cardWidth) - .then(clickableModifier) + .applyCardClickable(enabled, interactionSource, onClick) .semantics { contentDescription = title }, @@ -223,30 +270,17 @@ fun MovieDetailsMovieCard( .border(border, MovieDetailsTokens.ShapeCardSmall) ) { CardImageOrPlaceholder(title = title, imageUrl = imageUrl) - - if (type == MovieCardType.POSTER && showBottomTitle) { - CardPosterBottomTitle(title = title) - } - - if (progress != null && progress > 0f) { - CardProgressBar(progress = progress) - } - - if (badge != null) { - CardBadge(badge = badge) - } + CardOverlays( + type = type, + showBottomTitle = showBottomTitle, + title = title, + progress = progress, + badge = badge + ) } if (type != MovieCardType.POSTER && showBottomTitle) { - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = title, - color = if (isFocused) colors.textPrimary else colors.textSecondary, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + CardExternalTitle(title = title, isFocused = isFocused) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt index 08e352d056f..acbc1859225 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/AiringScheduleUiState.kt @@ -22,7 +22,78 @@ data class AiringScheduleUiState( val fallbackDateText: String? = null ) { val hasAiringInfo: Boolean - get() = (targetUnixTimeSeconds != null) || !fallbackDateText.isNullOrBlank() + get() = targetUnixTimeSeconds != null || !fallbackDateText.isNullOrBlank() +} + +private fun findUpcomingEpisode(episodes: List?, nowMs: Long): ResultEpisode? { + return episodes?.filter { (it.airDate ?: 0L) > nowMs }?.minByOrNull { it.airDate ?: Long.MAX_VALUE } +} + +private fun resolveUpcomingEpisode( + context: Context, + nextAiringEpisode: String?, + upcomingEpisode: ResultEpisode? +): String? { + if (!nextAiringEpisode.isNullOrBlank()) { + return nextAiringEpisode + } + if (upcomingEpisode == null) { + return null + } + return if (upcomingEpisode.season != null && upcomingEpisode.season > 1) { + context.getString(R.string.next_season_episode_format, upcomingEpisode.season, upcomingEpisode.episode) + } else { + context.getString(R.string.next_episode_format, upcomingEpisode.episode) + } +} + +private fun resolveUpcomingDate( + context: Context, + nextAiringDate: String?, + upcomingEpisode: ResultEpisode?, + nowMs: Long +): String? { + if (!nextAiringDate.isNullOrBlank()) { + return nextAiringDate + } + val airDate = upcomingEpisode?.airDate ?: return null + val diffSec = (airDate - nowMs) / 1000 + return if (diffSec > 0) { + context.getString(R.string.episode_upcoming_format, secondsToReadable(diffSec.toInt(), "")) + } else { + null + } +} + +private fun formatAiringDateTime(resolvedUnixTime: Long?): Pair { + if (resolvedUnixTime == null || resolvedUnixTime <= 0) return null to null + return try { + val date = Date(resolvedUnixTime * 1000) + val dayOfWeek = SimpleDateFormat("EEE", Locale.getDefault()).format(date) + val releaseDate = SimpleDateFormat("EEEE, dd MMM • HH:mm", Locale.getDefault()).format(date) + releaseDate to dayOfWeek + } catch (_: Exception) { + null to null + } +} + +private fun parseEpisodeDisplayLabels(resolvedEpisode: String?): Pair { + if (resolvedEpisode == null) return null to null + + val match = Regex("""(?:Season\s*(\d+)\s*)?(?:Episode|Ep\.?)\s*(\d+)""", RegexOption.IGNORE_CASE).find(resolvedEpisode) + if (match != null) { + val season = match.groupValues.getOrNull(1)?.takeIf { it.isNotBlank() } + val ep = match.groupValues.getOrNull(2)?.takeIf { it.isNotBlank() } + return when { + season != null && ep != null -> "S$season:E$ep" to "Season $season Episode $ep" + ep != null -> "Ep $ep" to "Episode $ep" + else -> null to resolvedEpisode + } + } + + val digitMatch = Regex("""\b(\d+)\b""").find(resolvedEpisode) + val shortLabel = digitMatch?.let { "Ep ${it.value}" } + return shortLabel to resolvedEpisode } fun resolveAiringSchedule( @@ -36,7 +107,7 @@ fun resolveAiringSchedule( ): AiringScheduleUiState? { val nowMs = APIHolder.unixTimeMS val nowSec = APIHolder.unixTime - val upcomingEpisode = episodes?.filter { (it.airDate ?: 0L) > nowMs }?.minByOrNull { it.airDate ?: Long.MAX_VALUE } + val upcomingEpisode = findUpcomingEpisode(episodes, nowMs) val resolvedUnixTime = if (nextAiringUnixTime != null && nextAiringUnixTime > nowSec) { nextAiringUnixTime @@ -44,72 +115,15 @@ fun resolveAiringSchedule( upcomingEpisode?.airDate?.div(1000L) } - val resolvedEpisode = if (!nextAiringEpisode.isNullOrBlank()) { - nextAiringEpisode - } else if (upcomingEpisode != null) { - if (upcomingEpisode.season != null && upcomingEpisode.season > 1) { - context.getString(R.string.next_season_episode_format, upcomingEpisode.season, upcomingEpisode.episode) - } else { - context.getString(R.string.next_episode_format, upcomingEpisode.episode) - } - } else { - null - } - - val resolvedDate = if (!nextAiringDate.isNullOrBlank()) { - nextAiringDate - } else if (upcomingEpisode?.airDate != null) { - val diffSec = (upcomingEpisode.airDate - nowMs) / 1000 - if (diffSec > 0) { - context.getString( - R.string.episode_upcoming_format, - secondsToReadable(diffSec.toInt(), "") - ) - } else { - null - } - } else { - null - } + val resolvedEpisode = resolveUpcomingEpisode(context, nextAiringEpisode, upcomingEpisode) + val resolvedDate = resolveUpcomingDate(context, nextAiringDate, upcomingEpisode, nowMs) if (statusText.isNullOrBlank() && resolvedUnixTime == null && resolvedDate.isNullOrBlank()) { return null } - var releaseDateFormatted: String? = null - var dayOfWeek: String? = null - if (resolvedUnixTime != null && resolvedUnixTime > 0) { - try { - val date = Date(resolvedUnixTime * 1000) - dayOfWeek = SimpleDateFormat("EEE", Locale.getDefault()).format(date) - releaseDateFormatted = SimpleDateFormat("EEEE, dd MMM • HH:mm", Locale.getDefault()).format(date) - } catch (_: Exception) {} - } - - var displayEpisodeShort: String? = null - var displayEpisodeLong: String? = null - if (resolvedEpisode != null) { - val match = Regex("""(?:Season\s*(\d+)\s*)?(?:Episode|Ep\.?)\s*(\d+)""", RegexOption.IGNORE_CASE).find(resolvedEpisode) - if (match != null) { - val season = match.groupValues.getOrNull(1)?.takeIf { it.isNotBlank() } - val ep = match.groupValues.getOrNull(2)?.takeIf { it.isNotBlank() } - if (season != null && ep != null) { - displayEpisodeShort = "S$season:E$ep" - displayEpisodeLong = "Season $season Episode $ep" - } else if (ep != null) { - displayEpisodeShort = "Ep $ep" - displayEpisodeLong = "Episode $ep" - } else { - displayEpisodeLong = resolvedEpisode - } - } else { - val digitMatch = Regex("""\b(\d+)\b""").find(resolvedEpisode) - if (digitMatch != null) { - displayEpisodeShort = "Ep ${digitMatch.value}" - } - displayEpisodeLong = resolvedEpisode - } - } + val (releaseDateFormatted, dayOfWeek) = formatAiringDateTime(resolvedUnixTime) + val (displayEpisodeShort, displayEpisodeLong) = parseEpisodeDisplayLabels(resolvedEpisode) return AiringScheduleUiState( statusText = statusText, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/MovieDetailsModels.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/MovieDetailsModels.kt index 40f2c98aebd..5528a01fb1a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/MovieDetailsModels.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/model/MovieDetailsModels.kt @@ -20,6 +20,50 @@ data class MovieRecommendationRow( val items: List ) +private fun formatEpisodeCode(context: Context, season: Int?, episode: Int?): String { + val s = season ?: 0 + val e = episode ?: 0 + val sShort = context.getString(R.string.season_short) + val eShort = context.getString(R.string.episode_short) + return when { + s > 0 && e > 0 -> "$sShort$s:$eShort$e" + e > 0 -> "$eShort$e" + else -> "" + } +} + +private fun formatWithEpisodeCode(prefix: String, epCode: String): String { + return if (epCode.isNotBlank()) "$prefix $epCode" else prefix +} + +private fun resolveResumeButtonText(context: Context, resumeStatus: ResumeWatchingStatus): String { + val prefix = if (resumeStatus.progress != null) { + context.getString(R.string.resume) + } else { + context.getString(R.string.play_movie_button) + } + if (resumeStatus.isMovie) { + return prefix + } + val resumeEp = resumeStatus.result + val epCode = formatEpisodeCode(context, resumeEp.season, resumeEp.episode) + return formatWithEpisodeCode(prefix, epCode) +} + +private fun resolveFirstEpisodeButtonText( + context: Context, + episodesToDisplay: List, + isMovie: Boolean +): String { + val playStr = context.getString(R.string.play_movie_button) + val firstEp = episodesToDisplay.firstOrNull() + if (firstEp != null && !isMovie) { + val epCode = formatEpisodeCode(context, firstEp.season, firstEp.episode) + return formatWithEpisodeCode(playStr, epCode) + } + return playStr +} + fun getPlayButtonText( context: Context, resumeStatus: ResumeWatchingStatus?, @@ -27,43 +71,7 @@ fun getPlayButtonText( isMovie: Boolean ): String { if (resumeStatus != null) { - val resumeEp = resumeStatus.result - val prefix = if (resumeStatus.progress != null) { - context.getString(R.string.resume) - } else { - context.getString(R.string.play_movie_button) - } - if (resumeStatus.isMovie) { - return prefix - } - val sShort = context.getString(R.string.season_short) - val eShort = context.getString(R.string.episode_short) - val s = resumeEp.season - val e = resumeEp.episode - val epCode = if (s != null && s > 0 && e > 0) { - "$sShort$s:$eShort$e" - } else if (e > 0) { - "$eShort$e" - } else { - "" - } - return if (epCode.isNotBlank()) "$prefix $epCode" else prefix - } - val firstEp = episodesToDisplay.firstOrNull() - if (firstEp != null && !isMovie) { - val sShort = context.getString(R.string.season_short) - val eShort = context.getString(R.string.episode_short) - val s = firstEp.season - val e = firstEp.episode - val epCode = if (s != null && s > 0 && e > 0) { - "$sShort$s:$eShort$e" - } else if (e > 0) { - "$eShort$e" - } else { - "" - } - val playStr = context.getString(R.string.play_movie_button) - return if (epCode.isNotBlank()) "$playStr $epCode" else playStr + return resolveResumeButtonText(context, resumeStatus) } - return context.getString(R.string.play_movie_button) + return resolveFirstEpisodeButtonText(context, episodesToDisplay, isMovie) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt index f3c06c17142..731fac88d51 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -43,19 +44,152 @@ import com.lagradost.cloudstream3.ui.result.compose.components.HeroPlayButton import com.lagradost.cloudstream3.ui.result.compose.components.HeroTrailerButton import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme -@OptIn(ExperimentalFoundationApi::class) @Composable -fun HeroBannerSection( - title: String, - providerName: String?, - backdropUrl: String?, - logoUrl: String?, - heroHeight: Dp, - playButtonText: String, - resumeProgressFraction: Float?, +private fun HeroBackdrop(backdropUrl: String?) { + if (backdropUrl.isNullOrBlank()) return + AsyncImage( + model = backdropUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clipToBounds() + ) +} + +@Composable +private fun BoxScope.HeroGradientOverlay() { + val colors = MovieDetailsTheme.colors + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.85f) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + colors.background.copy(alpha = 0.5f), + colors.background.copy(alpha = 0.92f), + colors.background + ) + ) + ) + ) +} + +@Composable +private fun BoxScope.HeroProviderBadge(providerName: String?) { + if (providerName.isNullOrBlank()) return + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(dimens.spacing2Xl) + ) { + Text( + text = providerName.uppercase(), + style = typography.boldTitle2.copy( + fontWeight = FontWeight.Black, + letterSpacing = (-0.8).sp + ), + color = colors.primary + ) + } +} + +@Composable +private fun HeroTitleOrLogo(title: String, logoUrl: String?) { + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + + if (!logoUrl.isNullOrBlank()) { + Box( + modifier = Modifier + .height(68.dp) + .width(240.dp) + ) { + AsyncImage( + model = logoUrl, + contentDescription = title, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + } else { + Text( + text = title, + style = typography.boldTitle1, + fontSize = 32.sp, + color = colors.textPrimary, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +private fun HeroActionButtons( isInWatchList: Boolean, isFavorite: Boolean, - hasTrailers: Boolean, + onAddToListClick: () -> Unit, + onLikeClick: () -> Unit, + onSearchClick: (() -> Unit)?, + inMyListInteractionSource: MutableInteractionSource, + likeInteractionSource: MutableInteractionSource, + searchInteractionSource: MutableInteractionSource +) { + val dimens = MovieDetailsTheme.dimens + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + CircleActionButton( + icon = painterResource( + id = if (isInWatchList) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 + ), + contentDescription = if (isInWatchList) { + stringResource(id = R.string.in_my_list) + } else { + stringResource(id = R.string.add_to_my_list) + }, + onClick = onAddToListClick, + interactionSource = inMyListInteractionSource, + enabled = false + ) + + CircleActionButton( + icon = painterResource( + id = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 + ), + contentDescription = if (isFavorite) { + stringResource(id = R.string.unfavorite) + } else { + stringResource(id = R.string.favorite) + }, + onClick = onLikeClick, + interactionSource = likeInteractionSource, + enabled = false + ) + + if (onSearchClick != null) { + CircleActionButton( + icon = painterResource(id = R.drawable.search_icon), + contentDescription = stringResource(id = R.string.title_search), + onClick = onSearchClick, + interactionSource = searchInteractionSource, + enabled = false + ) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun BoxScope.HeroFocusOverlay( playButtonFocusRequester: FocusRequester, playInteractionSource: MutableInteractionSource, inMyListInteractionSource: MutableInteractionSource, @@ -68,146 +202,147 @@ fun HeroBannerSection( onLikeClick: () -> Unit, onTrailerClick: () -> Unit, onSearchClick: (() -> Unit)?, - modifier: Modifier = Modifier + hasTrailers: Boolean ) { - val colors = MovieDetailsTheme.colors - val typography = MovieDetailsTheme.typography val dimens = MovieDetailsTheme.dimens - - Box( - modifier = modifier + Column( + modifier = Modifier .fillMaxWidth() - .height(heroHeight) - .clipToBounds() - .background(colors.background) + .align(Alignment.TopStart) + .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) + .zIndex(50f), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - if (!backdropUrl.isNullOrBlank()) { - AsyncImage( - model = backdropUrl, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .clipToBounds() - ) - } - Box( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.85f) - .align(Alignment.BottomCenter) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - colors.background.copy(alpha = 0.5f), - colors.background.copy(alpha = 0.92f), - colors.background - ) - ) + .height(42.dp) + .width(110.dp) + .focusRequester(playButtonFocusRequester) + .combinedClickable( + interactionSource = playInteractionSource, + indication = null, + role = Role.Button, + onClick = onPlayClick, + onLongClick = onPlayLongClick ) ) - Column( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.TopStart) - .padding(horizontal = dimens.spacing2Xl, vertical = dimens.spacing2Xl) - .zIndex(50f), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - Box( - modifier = Modifier - .height(42.dp) - .width(110.dp) - .focusRequester(playButtonFocusRequester) - .combinedClickable( - interactionSource = playInteractionSource, - indication = null, - role = Role.Button, - onClick = onPlayClick, - onLongClick = onPlayLongClick - ) - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = inMyListInteractionSource, - indication = null, - role = Role.Button, - onClick = onAddToListClick - ) - ) - - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = likeInteractionSource, - indication = null, - role = Role.Button, - onClick = onLikeClick - ) - ) + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = inMyListInteractionSource, + indication = null, + role = Role.Button, + onClick = onAddToListClick + ) + ) - if (onSearchClick != null) { - Box( - modifier = Modifier - .size(40.dp) - .clickable( - interactionSource = searchInteractionSource, - indication = null, - role = Role.Button, - onClick = onSearchClick - ) + Box( + modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = likeInteractionSource, + indication = null, + role = Role.Button, + onClick = onLikeClick ) - } - } + ) - if (hasTrailers) { + if (onSearchClick != null) { Box( modifier = Modifier - .height(40.dp) - .width(120.dp) + .size(40.dp) .clickable( - interactionSource = trailerInteractionSource, + interactionSource = searchInteractionSource, indication = null, role = Role.Button, - onClick = onTrailerClick + onClick = onSearchClick ) ) } } - } - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(dimens.spacing2Xl) - ) { - if (!providerName.isNullOrBlank()) { - Text( - text = providerName.uppercase(), - style = typography.boldTitle2.copy( - fontWeight = FontWeight.Black, - letterSpacing = (-0.8).sp - ), - color = colors.primary + if (hasTrailers) { + Box( + modifier = Modifier + .height(40.dp) + .width(120.dp) + .clickable( + interactionSource = trailerInteractionSource, + indication = null, + role = Role.Button, + onClick = onTrailerClick + ) ) } } + } +} + +@Composable +fun HeroBannerSection( + title: String, + providerName: String?, + backdropUrl: String?, + logoUrl: String?, + heroHeight: Dp, + playButtonText: String, + resumeProgressFraction: Float?, + isInWatchList: Boolean, + isFavorite: Boolean, + hasTrailers: Boolean, + playButtonFocusRequester: FocusRequester, + playInteractionSource: MutableInteractionSource, + inMyListInteractionSource: MutableInteractionSource, + likeInteractionSource: MutableInteractionSource, + trailerInteractionSource: MutableInteractionSource, + searchInteractionSource: MutableInteractionSource, + onPlayClick: () -> Unit, + onPlayLongClick: (() -> Unit)?, + onAddToListClick: () -> Unit, + onLikeClick: () -> Unit, + onTrailerClick: () -> Unit, + onSearchClick: (() -> Unit)?, + modifier: Modifier = Modifier +) { + val colors = MovieDetailsTheme.colors + val dimens = MovieDetailsTheme.dimens + + Box( + modifier = modifier + .fillMaxWidth() + .height(heroHeight) + .clipToBounds() + .background(colors.background) + ) { + HeroBackdrop(backdropUrl = backdropUrl) + HeroGradientOverlay() + HeroFocusOverlay( + playButtonFocusRequester = playButtonFocusRequester, + playInteractionSource = playInteractionSource, + inMyListInteractionSource = inMyListInteractionSource, + likeInteractionSource = likeInteractionSource, + trailerInteractionSource = trailerInteractionSource, + searchInteractionSource = searchInteractionSource, + onPlayClick = onPlayClick, + onPlayLongClick = onPlayLongClick, + onAddToListClick = onAddToListClick, + onLikeClick = onLikeClick, + onTrailerClick = onTrailerClick, + onSearchClick = onSearchClick, + hasTrailers = hasTrailers + ) + HeroProviderBadge(providerName = providerName) Row( modifier = Modifier @@ -218,29 +353,7 @@ fun HeroBannerSection( verticalAlignment = Alignment.Bottom ) { Column { - if (!logoUrl.isNullOrBlank()) { - Box( - modifier = Modifier - .height(68.dp) - .width(240.dp) - ) { - AsyncImage( - model = logoUrl, - contentDescription = title, - contentScale = ContentScale.Fit, - modifier = Modifier.fillMaxSize() - ) - } - } else { - Text( - text = title, - style = typography.boldTitle1, - fontSize = 32.sp, - color = colors.textPrimary, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } + HeroTitleOrLogo(title = title, logoUrl = logoUrl) Spacer(modifier = Modifier.height(dimens.spacingL)) @@ -255,48 +368,16 @@ fun HeroBannerSection( Spacer(modifier = Modifier.height(dimens.spacingM)) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - CircleActionButton( - icon = painterResource( - id = if (isInWatchList) R.drawable.ic_baseline_check_24 else R.drawable.ic_baseline_add_24 - ), - contentDescription = if (isInWatchList) { - stringResource(id = R.string.in_my_list) - } else { - stringResource(id = R.string.add_to_my_list) - }, - onClick = onAddToListClick, - interactionSource = inMyListInteractionSource, - enabled = false - ) - - CircleActionButton( - icon = painterResource( - id = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 - ), - contentDescription = if (isFavorite) { - stringResource(id = R.string.unfavorite) - } else { - stringResource(id = R.string.favorite) - }, - onClick = onLikeClick, - interactionSource = likeInteractionSource, - enabled = false - ) - - if (onSearchClick != null) { - CircleActionButton( - icon = painterResource(id = R.drawable.search_icon), - contentDescription = stringResource(id = R.string.title_search), - onClick = onSearchClick, - interactionSource = searchInteractionSource, - enabled = false - ) - } - } + HeroActionButtons( + isInWatchList = isInWatchList, + isFavorite = isFavorite, + onAddToListClick = onAddToListClick, + onLikeClick = onLikeClick, + onSearchClick = onSearchClick, + inMyListInteractionSource = inMyListInteractionSource, + likeInteractionSource = likeInteractionSource, + searchInteractionSource = searchInteractionSource + ) } if (hasTrailers) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt index ff43db9d5c6..9cc1ad9c958 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/MovieInfoSynopsisSection.kt @@ -35,6 +35,205 @@ import com.lagradost.cloudstream3.ui.result.compose.model.AiringScheduleUiState import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme import com.lagradost.cloudstream3.ui.result.compose.theme.getRatingScoreColor +@Composable +private fun PrimaryMetadataRow( + matchScore: String?, + releaseYear: String?, + seasonsCount: String?, + quality: String?, + airingSchedule: AiringScheduleUiState? +) { + val hasContent = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || + !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() || + airingSchedule != null + if (!hasContent) return + + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + if (!matchScore.isNullOrBlank()) { + Text( + text = matchScore, + color = getRatingScoreColor(matchScore), + style = typography.mediumBody, + fontWeight = FontWeight.Bold + ) + } + + if (!releaseYear.isNullOrBlank()) { + Text( + text = releaseYear, + color = colors.textPrimary, + style = typography.regularBody + ) + } + + if (!seasonsCount.isNullOrBlank()) { + Text( + text = seasonsCount, + color = colors.textPrimary, + style = typography.regularBody + ) + } + + if (!quality.isNullOrBlank()) { + VideoQualityBadge(quality = quality) + } + + if (!airingSchedule?.statusText.isNullOrBlank()) { + OngoingStatusBadge( + statusText = airingSchedule.statusText, + isOngoing = airingSchedule.isOngoing + ) + } + + if (airingSchedule?.hasAiringInfo == true) { + AiringCountdownBadge(airingSchedule = airingSchedule) + } + } +} + +@Composable +private fun MaturityAdvisoriesRow(maturityRating: String?, advisories: String?) { + if (maturityRating.isNullOrBlank() && advisories.isNullOrBlank()) return + + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) + ) { + if (!maturityRating.isNullOrBlank()) { + MaturityRatingBadge(rating = maturityRating) + } + if (!advisories.isNullOrBlank()) { + Text( + text = advisories, + color = colors.textMuted, + style = typography.regularCaption1 + ) + } + } +} + +@Composable +private fun Top10RankBadge(top10RankText: String?) { + if (top10RankText.isNullOrBlank()) return + + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), + modifier = Modifier.padding(vertical = dimens.spacingXs) + ) { + Box( + modifier = Modifier + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(colors.primary) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Text( + text = stringResource(id = R.string.top_10_badge_text), + style = typography.regularCaption2, + color = colors.onPrimary, + fontWeight = FontWeight.Black + ) + } + Text( + text = top10RankText, + style = typography.mediumBody, + fontWeight = FontWeight.Bold, + color = colors.textPrimary + ) + } +} + +@Composable +private fun InteractiveSynopsis(synopsis: String) { + if (synopsis.isBlank()) return + + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + val dimens = MovieDetailsTheme.dimens + val synopsisInteractionSource = remember { MutableInteractionSource() } + val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() + + val background = if (isSynopsisFocused) colors.surfaceElevated.copy(alpha = 0.5f) else Color.Transparent + val borderModifier = if (isSynopsisFocused) { + Modifier.border(BorderStroke(dimens.borderFocus, colors.primary), MovieDetailsTokens.ShapeCardSmall) + } else { + Modifier + } + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(MovieDetailsTokens.ShapeCardSmall) + .background(background) + .then(borderModifier) + .focusable(interactionSource = synopsisInteractionSource) + .padding(dimens.spacingS) + ) { + Text( + text = synopsis, + style = typography.regularBody, + color = if (isSynopsisFocused) colors.textPrimary else colors.textSecondary, + lineHeight = 22.sp + ) + } +} + +@Composable +private fun MovieSideInfoItem(labelRes: Int, items: List, maxLines: Int = Int.MAX_VALUE) { + if (items.isEmpty()) return + + val colors = MovieDetailsTheme.colors + val typography = MovieDetailsTheme.typography + + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = stringResource(id = labelRes), + style = typography.regularCaption2, + color = colors.textSecondary + ) + Text( + text = items.joinToString(", "), + style = typography.regularCaption1, + color = colors.textPrimary, + maxLines = maxLines, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +private fun MovieSideInfoColumn( + castList: List, + genres: List, + moodTags: List, + modifier: Modifier = Modifier +) { + val dimens = MovieDetailsTheme.dimens + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimens.spacingM) + ) { + MovieSideInfoItem(labelRes = R.string.cast_label, items = castList, maxLines = 3) + MovieSideInfoItem(labelRes = R.string.genres_label, items = genres) + MovieSideInfoItem(labelRes = R.string.mood_tags_label, items = moodTags) + } +} + @Composable fun MovieInfoSynopsisSection( modifier: Modifier = Modifier, @@ -51,8 +250,6 @@ fun MovieInfoSynopsisSection( moodTags: List, airingSchedule: AiringScheduleUiState? = null ) { - val colors = MovieDetailsTheme.colors - val typography = MovieDetailsTheme.typography val dimens = MovieDetailsTheme.dimens val hasRightColumn = castList.isNotEmpty() || genres.isNotEmpty() || moodTags.isNotEmpty() @@ -68,187 +265,28 @@ fun MovieInfoSynopsisSection( .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(dimens.spacingM) ) { - val hasLine1 = !matchScore.isNullOrBlank() || !releaseYear.isNullOrBlank() || - !seasonsCount.isNullOrBlank() || !quality.isNullOrBlank() || - airingSchedule != null - - if (hasLine1) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (!matchScore.isNullOrBlank()) { - Text( - text = matchScore, - color = getRatingScoreColor(matchScore), - style = typography.mediumBody, - fontWeight = FontWeight.Bold - ) - } - - if (!releaseYear.isNullOrBlank()) { - Text( - text = releaseYear, - color = colors.textPrimary, - style = typography.regularBody - ) - } - - if (!seasonsCount.isNullOrBlank()) { - Text( - text = seasonsCount, - color = colors.textPrimary, - style = typography.regularBody - ) - } - - if (!quality.isNullOrBlank()) { - VideoQualityBadge(quality = quality) - } - - if (!airingSchedule?.statusText.isNullOrBlank()) { - OngoingStatusBadge( - statusText = airingSchedule.statusText, - isOngoing = airingSchedule.isOngoing - ) - } - - if (airingSchedule?.hasAiringInfo == true) { - AiringCountdownBadge(airingSchedule = airingSchedule) - } - } - } - - if (!maturityRating.isNullOrBlank() || !advisories.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS) - ) { - if (!maturityRating.isNullOrBlank()) { - MaturityRatingBadge(rating = maturityRating) - } - if (!advisories.isNullOrBlank()) { - Text( - text = advisories, - color = colors.textMuted, - style = typography.regularCaption1 - ) - } - } - } - - if (!top10RankText.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimens.spacingS), - modifier = Modifier.padding(vertical = dimens.spacingXs) - ) { - Box( - modifier = Modifier - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(colors.primary) - .padding(horizontal = 6.dp, vertical = 2.dp) - ) { - Text( - text = stringResource(id = R.string.top_10_badge_text), - style = typography.regularCaption2, - color = colors.onPrimary, - fontWeight = FontWeight.Black - ) - } - Text( - text = top10RankText, - style = typography.mediumBody, - fontWeight = FontWeight.Bold, - color = colors.textPrimary - ) - } - } - - if (synopsis.isNotBlank()) { - val synopsisInteractionSource = remember { MutableInteractionSource() } - val isSynopsisFocused by synopsisInteractionSource.collectIsFocusedAsState() - - Box( - modifier = Modifier - .fillMaxWidth() - .clip(MovieDetailsTokens.ShapeCardSmall) - .background(if (isSynopsisFocused) colors.surfaceElevated.copy(alpha = 0.5f) else Color.Transparent) - .then( - if (isSynopsisFocused) { - Modifier.border( - BorderStroke(dimens.borderFocus, colors.primary), - MovieDetailsTokens.ShapeCardSmall - ) - } else Modifier - ) - .focusable(interactionSource = synopsisInteractionSource) - .padding(dimens.spacingS) - ) { - Text( - text = synopsis, - style = typography.regularBody, - color = if (isSynopsisFocused) colors.textPrimary else colors.textSecondary, - lineHeight = 22.sp - ) - } - } + PrimaryMetadataRow( + matchScore = matchScore, + releaseYear = releaseYear, + seasonsCount = seasonsCount, + quality = quality, + airingSchedule = airingSchedule + ) + MaturityAdvisoriesRow( + maturityRating = maturityRating, + advisories = advisories + ) + Top10RankBadge(top10RankText = top10RankText) + InteractiveSynopsis(synopsis = synopsis) } if (hasRightColumn) { - Column( - modifier = Modifier - .weight(0.35f) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(dimens.spacingM) - ) { - if (castList.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.cast_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = castList.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) - } - } - - if (genres.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.genres_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = genres.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary - ) - } - } - - if (moodTags.isNotEmpty()) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text( - text = stringResource(id = R.string.mood_tags_label), - style = typography.regularCaption2, - color = colors.textSecondary - ) - Text( - text = moodTags.joinToString(", "), - style = typography.regularCaption1, - color = colors.textPrimary - ) - } - } - } + MovieSideInfoColumn( + castList = castList, + genres = genres, + moodTags = moodTags, + modifier = Modifier.weight(0.35f) + ) } } } From 35cc8b3c9293f67d8b0c3b2877b4cc5650265aaf Mon Sep 17 00:00:00 2001 From: Spring Date: Wed, 26 Aug 2026 19:44:49 +0700 Subject: [PATCH 34/35] feat(hero-banner): optimize banner animation and performance --- .../compose/sections/HeroBannerSection.kt | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt index 731fac88d51..64f54504f8e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/sections/HeroBannerSection.kt @@ -1,5 +1,9 @@ package com.lagradost.cloudstream3.ui.result.compose.sections +import android.graphics.Bitmap +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -20,6 +24,8 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds @@ -27,7 +33,9 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -38,6 +46,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.bitmapConfig +import coil3.request.crossfade import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.result.compose.components.CircleActionButton import com.lagradost.cloudstream3.ui.result.compose.components.HeroPlayButton @@ -45,15 +56,42 @@ import com.lagradost.cloudstream3.ui.result.compose.components.HeroTrailerButton import com.lagradost.cloudstream3.ui.result.compose.theme.MovieDetailsTheme @Composable -private fun HeroBackdrop(backdropUrl: String?) { +private fun HeroBackdrop( + backdropUrl: String?, +) { if (backdropUrl.isNullOrBlank()) return + val alpha = remember(backdropUrl) { Animatable(0f) } + + LaunchedEffect(backdropUrl) { + alpha.animateTo( + targetValue = 1f, + animationSpec = tween( + durationMillis = 350, + easing = FastOutSlowInEasing, + ), + ) + } + + val context = LocalContext.current + val backdropRequest = remember(backdropUrl) { + ImageRequest.Builder(context) + .data(backdropUrl) + .size(1280, 720) + .bitmapConfig(Bitmap.Config.RGB_565) + .crossfade(true) + .build() + } + AsyncImage( - model = backdropUrl, + model = backdropRequest, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier .fillMaxSize() .clipToBounds() + .graphicsLayer { + this.alpha = alpha.value + }, ) } @@ -63,7 +101,7 @@ private fun BoxScope.HeroGradientOverlay() { Box( modifier = Modifier .fillMaxWidth() - .fillMaxHeight(0.85f) + .fillMaxHeight(0.7f) .align(Alignment.BottomCenter) .background( Brush.verticalGradient( @@ -107,13 +145,21 @@ private fun HeroTitleOrLogo(title: String, logoUrl: String?) { val typography = MovieDetailsTheme.typography if (!logoUrl.isNullOrBlank()) { + val context = LocalContext.current + val logoRequest = remember(logoUrl) { + ImageRequest.Builder(context) + .data(logoUrl) + .size(480, 240) + .crossfade(true) + .build() + } Box( modifier = Modifier .height(68.dp) .width(240.dp) ) { AsyncImage( - model = logoUrl, + model = logoRequest, contentDescription = title, contentScale = ContentScale.Fit, modifier = Modifier.fillMaxSize() @@ -315,7 +361,6 @@ fun HeroBannerSection( onSearchClick: (() -> Unit)?, modifier: Modifier = Modifier ) { - val colors = MovieDetailsTheme.colors val dimens = MovieDetailsTheme.dimens Box( @@ -323,9 +368,10 @@ fun HeroBannerSection( .fillMaxWidth() .height(heroHeight) .clipToBounds() - .background(colors.background) ) { - HeroBackdrop(backdropUrl = backdropUrl) + HeroBackdrop( + backdropUrl = backdropUrl, + ) HeroGradientOverlay() HeroFocusOverlay( playButtonFocusRequester = playButtonFocusRequester, From 2a0a10da54af86ebbfa9b7b70050d7721dfd4b0f Mon Sep 17 00:00:00 2001 From: Spring Date: Wed, 26 Aug 2026 19:52:44 +0700 Subject: [PATCH 35/35] feat(detail): optimize performance on detail page --- .../compose/MovieDetailsComposeScreen.kt | 58 +++++++------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt index ba1029b212f..f5002448200 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/compose/MovieDetailsComposeScreen.kt @@ -261,7 +261,7 @@ fun MovieDetailsComposeScreen( val context = LocalContext.current val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp - val heroHeight = (screenHeight * 0.65f).coerceAtLeast(380.dp) + val heroHeight = remember(screenHeight) { (screenHeight * 0.65f).coerceAtLeast(380.dp) } val colors = MovieDetailsTheme.colors val lazyListState = rememberLazyListState() @@ -287,12 +287,6 @@ fun MovieDetailsComposeScreen( mutableStateOf(seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.firstOrNull() ?: "" }) } - LaunchedEffect(selectedSeasonIndex, seasonOptions) { - if (seasonOptions.isNotEmpty()) { - selectedSeasonText = seasonOptions.getOrElse(selectedSeasonIndex) { seasonOptions.first() } - } - } - val dubOptions = remember(dynamicDubs) { dynamicDubs ?: emptyList() } @@ -301,12 +295,6 @@ fun MovieDetailsComposeScreen( mutableStateOf(dubOptions.getOrElse(selectedDubIndex) { dubOptions.firstOrNull() ?: "" }) } - LaunchedEffect(selectedDubIndex, dubOptions) { - if (dubOptions.isNotEmpty()) { - selectedDubText = dubOptions.getOrElse(selectedDubIndex) { dubOptions.first() } - } - } - val rangeOptions = remember(dynamicRanges) { dynamicRanges ?: emptyList() } @@ -315,16 +303,12 @@ fun MovieDetailsComposeScreen( mutableStateOf(rangeOptions.getOrElse(selectedRangeIndex) { rangeOptions.firstOrNull() ?: "" }) } - LaunchedEffect(selectedRangeIndex, rangeOptions) { - if (rangeOptions.isNotEmpty()) { - selectedRangeText = rangeOptions.getOrElse(selectedRangeIndex) { rangeOptions.first() } + val showToast: (String) -> Unit = remember(context) { + { message -> + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } } - fun showToast(message: String) { - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } - val episodesToDisplay = dynamicEpisodes ?: emptyList() val airingSchedule = remember( @@ -364,21 +348,21 @@ fun MovieDetailsComposeScreen( dynamicActors?.map { it.actor.name }?.filter { it.isNotBlank() }?.ifEmpty { cast } ?: cast } - val recommendationCards = remember(dynamicRecommendations) { - dynamicRecommendations?.map { rec -> - MovieCardItem( - title = rec.name, - type = MovieCardType.POSTER, - posterUrl = rec.posterUrl, - showLogo = false, - showBottomTitle = true - ) - } ?: emptyList() - } - - val chunkedRecommendations = remember(recommendationCards) { - recommendationCards.chunked(6).mapIndexed { idx, list -> - MovieRecommendationRow(idx, list) + val chunkedRecommendations = remember(dynamicRecommendations) { + if (dynamicRecommendations.isNullOrEmpty()) { + emptyList() + } else { + dynamicRecommendations.map { rec -> + MovieCardItem( + title = rec.name, + type = MovieCardType.POSTER, + posterUrl = rec.posterUrl, + showLogo = false, + showBottomTitle = true + ) + }.chunked(6).mapIndexed { idx, list -> + MovieRecommendationRow(idx, list) + } } } @@ -464,14 +448,14 @@ fun MovieDetailsComposeScreen( airingSchedule = airingSchedule, onEpisodeClick = onEpisodeClick, onEpisodeLongClick = onEpisodeLongClick, - showToast = ::showToast + showToast = showToast ) recommendationsSection( chunkedRecommendations = chunkedRecommendations, dynamicRecommendations = dynamicRecommendations, onRecommendationClick = onRecommendationClick, - showToast = ::showToast, + showToast = showToast, colors = colors )