├── .editorconfig ├── .gitignore ├── ComposeFadingEdges ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── gigamole │ └── composefadingedges │ ├── FadingEdges.kt │ ├── FadingEdgesDefaults.kt │ ├── FadingEdgesGravity.kt │ ├── FadingEdgesOrientation.kt │ ├── content │ ├── FadingEdgesContentType.kt │ ├── FadingEdgesContentTypeDefaults.kt │ └── scrollconfig │ │ ├── FadingEdgesScrollConfig.kt │ │ └── FadingEdgesScrollConfigDefaults.kt │ └── fill │ ├── FadingEdgesFillType.kt │ └── FadingEdgesFillTypeDefaults.kt ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── kotlin │ └── com │ │ └── gigamole │ │ └── composefadingedges │ │ └── sample │ │ ├── MainActivity.kt │ │ ├── MainApplication.kt │ │ └── MainTheme.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── font │ ├── opensans_regular.ttf │ └── space_grotesk_bold.ttf │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ └── values │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── media ├── credits.png ├── demo.gif ├── footer.png ├── header-new.png ├── header.png ├── sample-1.gif ├── sample-2.gif └── sample-3.gif ├── plugins ├── .gitignore ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── java │ ├── CommonExtension.kt │ ├── ProjectConfig.kt │ ├── composefadingedges.application.gradle.kts │ └── composefadingedges.library.gradle.kts └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = crlf 4 | indent_size = 4 5 | indent_style = space 6 | insert_final_newline = false 7 | max_line_length = 170 8 | tab_width = 4 9 | ij_continuation_indent_size = 8 10 | ij_formatter_off_tag = @formatter:off 11 | ij_formatter_on_tag = @formatter:on 12 | ij_formatter_tags_enabled = false 13 | ij_smart_tabs = false 14 | ij_visual_guides = none 15 | ij_wrap_on_typing = false 16 | 17 | # noinspection LongLine 18 | [*.java] 19 | ij_java_align_consecutive_assignments = false 20 | ij_java_align_consecutive_variable_declarations = false 21 | ij_java_align_group_field_declarations = false 22 | ij_java_align_multiline_annotation_parameters = false 23 | ij_java_align_multiline_array_initializer_expression = false 24 | ij_java_align_multiline_assignment = false 25 | ij_java_align_multiline_binary_operation = false 26 | ij_java_align_multiline_chained_methods = false 27 | ij_java_align_multiline_extends_list = false 28 | ij_java_align_multiline_for = true 29 | ij_java_align_multiline_method_parentheses = false 30 | ij_java_align_multiline_parameters = true 31 | ij_java_align_multiline_parameters_in_calls = false 32 | ij_java_align_multiline_parenthesized_expression = false 33 | ij_java_align_multiline_records = true 34 | ij_java_align_multiline_resources = true 35 | ij_java_align_multiline_ternary_operation = false 36 | ij_java_align_multiline_text_blocks = false 37 | ij_java_align_multiline_throws_list = false 38 | ij_java_align_subsequent_simple_methods = false 39 | ij_java_align_throws_keyword = false 40 | ij_java_align_types_in_multi_catch = true 41 | ij_java_annotation_parameter_wrap = off 42 | ij_java_array_initializer_new_line_after_left_brace = false 43 | ij_java_array_initializer_right_brace_on_new_line = false 44 | ij_java_array_initializer_wrap = off 45 | ij_java_assert_statement_colon_on_next_line = false 46 | ij_java_assert_statement_wrap = off 47 | ij_java_assignment_wrap = off 48 | ij_java_binary_operation_sign_on_next_line = false 49 | ij_java_binary_operation_wrap = off 50 | ij_java_blank_lines_after_anonymous_class_header = 0 51 | ij_java_blank_lines_after_class_header = 0 52 | ij_java_blank_lines_after_imports = 1 53 | ij_java_blank_lines_after_package = 1 54 | ij_java_blank_lines_around_class = 1 55 | ij_java_blank_lines_around_field = 0 56 | ij_java_blank_lines_around_field_in_interface = 0 57 | ij_java_blank_lines_around_initializer = 1 58 | ij_java_blank_lines_around_method = 1 59 | ij_java_blank_lines_around_method_in_interface = 1 60 | ij_java_blank_lines_before_class_end = 0 61 | ij_java_blank_lines_before_imports = 1 62 | ij_java_blank_lines_before_method_body = 0 63 | ij_java_blank_lines_before_package = 0 64 | ij_java_block_brace_style = end_of_line 65 | ij_java_block_comment_add_space = false 66 | ij_java_block_comment_at_first_column = true 67 | ij_java_builder_methods = none 68 | ij_java_call_parameters_new_line_after_left_paren = false 69 | ij_java_call_parameters_right_paren_on_new_line = false 70 | ij_java_call_parameters_wrap = off 71 | ij_java_case_statement_on_separate_line = true 72 | ij_java_catch_on_new_line = false 73 | ij_java_class_annotation_wrap = split_into_lines 74 | ij_java_class_brace_style = end_of_line 75 | ij_java_class_count_to_use_import_on_demand = 99 76 | ij_java_class_names_in_javadoc = 1 77 | ij_java_do_not_indent_top_level_class_members = false 78 | ij_java_do_not_wrap_after_single_annotation = false 79 | ij_java_do_not_wrap_after_single_annotation_in_parameter = false 80 | ij_java_do_while_brace_force = never 81 | ij_java_doc_add_blank_line_after_description = true 82 | ij_java_doc_add_blank_line_after_param_comments = false 83 | ij_java_doc_add_blank_line_after_return = false 84 | ij_java_doc_add_p_tag_on_empty_lines = true 85 | ij_java_doc_align_exception_comments = true 86 | ij_java_doc_align_param_comments = true 87 | ij_java_doc_do_not_wrap_if_one_line = false 88 | ij_java_doc_enable_formatting = true 89 | ij_java_doc_enable_leading_asterisks = true 90 | ij_java_doc_indent_on_continuation = false 91 | ij_java_doc_keep_empty_lines = true 92 | ij_java_doc_keep_empty_parameter_tag = true 93 | ij_java_doc_keep_empty_return_tag = true 94 | ij_java_doc_keep_empty_throws_tag = true 95 | ij_java_doc_keep_invalid_tags = true 96 | ij_java_doc_param_description_on_new_line = false 97 | ij_java_doc_preserve_line_breaks = false 98 | ij_java_doc_use_throws_not_exception_tag = true 99 | ij_java_else_on_new_line = false 100 | ij_java_enum_constants_wrap = off 101 | ij_java_extends_keyword_wrap = off 102 | ij_java_extends_list_wrap = off 103 | ij_java_field_annotation_wrap = split_into_lines 104 | ij_java_finally_on_new_line = false 105 | ij_java_for_brace_force = never 106 | ij_java_for_statement_new_line_after_left_paren = false 107 | ij_java_for_statement_right_paren_on_new_line = false 108 | ij_java_for_statement_wrap = off 109 | ij_java_generate_final_locals = false 110 | ij_java_generate_final_parameters = false 111 | ij_java_if_brace_force = never 112 | ij_java_imports_layout = $android.**, $androidx.**, $com.**, $junit.**, $net.**, $org.**, $java.**, $javax.**, $*, |, android.**, |, androidx.**, |, com.**, |, junit.**, |, net.**, |, org.**, |, java.**, |, javax.**, |, *, | 113 | ij_java_indent_case_from_switch = true 114 | ij_java_insert_inner_class_imports = false 115 | ij_java_insert_override_annotation = true 116 | ij_java_keep_blank_lines_before_right_brace = 2 117 | ij_java_keep_blank_lines_between_package_declaration_and_header = 2 118 | ij_java_keep_blank_lines_in_code = 2 119 | ij_java_keep_blank_lines_in_declarations = 2 120 | ij_java_keep_builder_methods_indents = false 121 | ij_java_keep_control_statement_in_one_line = true 122 | ij_java_keep_first_column_comment = true 123 | ij_java_keep_indents_on_empty_lines = false 124 | ij_java_keep_line_breaks = true 125 | ij_java_keep_multiple_expressions_in_one_line = false 126 | ij_java_keep_simple_blocks_in_one_line = false 127 | ij_java_keep_simple_classes_in_one_line = false 128 | ij_java_keep_simple_lambdas_in_one_line = false 129 | ij_java_keep_simple_methods_in_one_line = false 130 | ij_java_label_indent_absolute = false 131 | ij_java_label_indent_size = 0 132 | ij_java_lambda_brace_style = end_of_line 133 | ij_java_layout_static_imports_separately = true 134 | ij_java_line_comment_add_space = false 135 | ij_java_line_comment_add_space_on_reformat = false 136 | ij_java_line_comment_at_first_column = true 137 | ij_java_method_annotation_wrap = split_into_lines 138 | ij_java_method_brace_style = end_of_line 139 | ij_java_method_call_chain_wrap = off 140 | ij_java_method_parameters_new_line_after_left_paren = false 141 | ij_java_method_parameters_right_paren_on_new_line = false 142 | ij_java_method_parameters_wrap = off 143 | ij_java_modifier_list_wrap = false 144 | ij_java_multi_catch_types_wrap = normal 145 | ij_java_names_count_to_use_import_on_demand = 99 146 | ij_java_new_line_after_lparen_in_annotation = false 147 | ij_java_new_line_after_lparen_in_record_header = false 148 | ij_java_parameter_annotation_wrap = off 149 | ij_java_parentheses_expression_new_line_after_left_paren = false 150 | ij_java_parentheses_expression_right_paren_on_new_line = false 151 | ij_java_place_assignment_sign_on_next_line = false 152 | ij_java_prefer_longer_names = true 153 | ij_java_prefer_parameters_wrap = false 154 | ij_java_record_components_wrap = normal 155 | ij_java_repeat_synchronized = true 156 | ij_java_replace_instanceof_and_cast = false 157 | ij_java_replace_null_check = true 158 | ij_java_replace_sum_lambda_with_method_ref = true 159 | ij_java_resource_list_new_line_after_left_paren = false 160 | ij_java_resource_list_right_paren_on_new_line = false 161 | ij_java_resource_list_wrap = off 162 | ij_java_rparen_on_new_line_in_annotation = false 163 | ij_java_rparen_on_new_line_in_record_header = false 164 | ij_java_space_after_closing_angle_bracket_in_type_argument = false 165 | ij_java_space_after_colon = true 166 | ij_java_space_after_comma = true 167 | ij_java_space_after_comma_in_type_arguments = true 168 | ij_java_space_after_for_semicolon = true 169 | ij_java_space_after_quest = true 170 | ij_java_space_after_type_cast = true 171 | ij_java_space_before_annotation_array_initializer_left_brace = false 172 | ij_java_space_before_annotation_parameter_list = false 173 | ij_java_space_before_array_initializer_left_brace = false 174 | ij_java_space_before_catch_keyword = true 175 | ij_java_space_before_catch_left_brace = true 176 | ij_java_space_before_catch_parentheses = true 177 | ij_java_space_before_class_left_brace = true 178 | ij_java_space_before_colon = true 179 | ij_java_space_before_colon_in_foreach = true 180 | ij_java_space_before_comma = false 181 | ij_java_space_before_do_left_brace = true 182 | ij_java_space_before_else_keyword = true 183 | ij_java_space_before_else_left_brace = true 184 | ij_java_space_before_finally_keyword = true 185 | ij_java_space_before_finally_left_brace = true 186 | ij_java_space_before_for_left_brace = true 187 | ij_java_space_before_for_parentheses = true 188 | ij_java_space_before_for_semicolon = false 189 | ij_java_space_before_if_left_brace = true 190 | ij_java_space_before_if_parentheses = true 191 | ij_java_space_before_method_call_parentheses = false 192 | ij_java_space_before_method_left_brace = true 193 | ij_java_space_before_method_parentheses = false 194 | ij_java_space_before_opening_angle_bracket_in_type_parameter = false 195 | ij_java_space_before_quest = true 196 | ij_java_space_before_switch_left_brace = true 197 | ij_java_space_before_switch_parentheses = true 198 | ij_java_space_before_synchronized_left_brace = true 199 | ij_java_space_before_synchronized_parentheses = true 200 | ij_java_space_before_try_left_brace = true 201 | ij_java_space_before_try_parentheses = true 202 | ij_java_space_before_type_parameter_list = false 203 | ij_java_space_before_while_keyword = true 204 | ij_java_space_before_while_left_brace = true 205 | ij_java_space_before_while_parentheses = true 206 | ij_java_space_inside_one_line_enum_braces = false 207 | ij_java_space_within_empty_array_initializer_braces = false 208 | ij_java_space_within_empty_method_call_parentheses = false 209 | ij_java_space_within_empty_method_parentheses = false 210 | ij_java_spaces_around_additive_operators = true 211 | ij_java_spaces_around_annotation_eq = true 212 | ij_java_spaces_around_assignment_operators = true 213 | ij_java_spaces_around_bitwise_operators = true 214 | ij_java_spaces_around_equality_operators = true 215 | ij_java_spaces_around_lambda_arrow = true 216 | ij_java_spaces_around_logical_operators = true 217 | ij_java_spaces_around_method_ref_dbl_colon = false 218 | ij_java_spaces_around_multiplicative_operators = true 219 | ij_java_spaces_around_relational_operators = true 220 | ij_java_spaces_around_shift_operators = true 221 | ij_java_spaces_around_type_bounds_in_type_parameters = true 222 | ij_java_spaces_around_unary_operator = false 223 | ij_java_spaces_within_angle_brackets = false 224 | ij_java_spaces_within_annotation_parentheses = false 225 | ij_java_spaces_within_array_initializer_braces = false 226 | ij_java_spaces_within_braces = false 227 | ij_java_spaces_within_brackets = false 228 | ij_java_spaces_within_cast_parentheses = false 229 | ij_java_spaces_within_catch_parentheses = false 230 | ij_java_spaces_within_for_parentheses = false 231 | ij_java_spaces_within_if_parentheses = false 232 | ij_java_spaces_within_method_call_parentheses = false 233 | ij_java_spaces_within_method_parentheses = false 234 | ij_java_spaces_within_parentheses = false 235 | ij_java_spaces_within_record_header = false 236 | ij_java_spaces_within_switch_parentheses = false 237 | ij_java_spaces_within_synchronized_parentheses = false 238 | ij_java_spaces_within_try_parentheses = false 239 | ij_java_spaces_within_while_parentheses = false 240 | ij_java_special_else_if_treatment = true 241 | ij_java_subclass_name_suffix = Impl 242 | ij_java_ternary_operation_signs_on_next_line = false 243 | ij_java_ternary_operation_wrap = off 244 | ij_java_test_name_suffix = Test 245 | ij_java_throws_keyword_wrap = off 246 | ij_java_throws_list_wrap = off 247 | ij_java_use_external_annotations = false 248 | ij_java_use_fq_class_names = false 249 | ij_java_use_relative_indents = false 250 | ij_java_use_single_class_imports = true 251 | ij_java_variable_annotation_wrap = off 252 | ij_java_visibility = public 253 | ij_java_while_brace_force = never 254 | ij_java_while_on_new_line = false 255 | ij_java_wrap_comments = false 256 | ij_java_wrap_first_method_in_call_chain = false 257 | ij_java_wrap_long_lines = false 258 | 259 | [*.properties] 260 | ij_properties_align_group_field_declarations = false 261 | ij_properties_keep_blank_lines = false 262 | ij_properties_key_value_delimiter = equals 263 | ij_properties_spaces_around_key_value_delimiter = false 264 | 265 | [.editorconfig] 266 | ij_editorconfig_align_group_field_declarations = false 267 | ij_editorconfig_space_after_colon = false 268 | ij_editorconfig_space_after_comma = true 269 | ij_editorconfig_space_before_colon = false 270 | ij_editorconfig_space_before_comma = false 271 | ij_editorconfig_spaces_around_assignment_operators = true 272 | 273 | [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] 274 | ij_continuation_indent_size = 4 275 | ij_xml_align_attributes = false 276 | ij_xml_align_text = false 277 | ij_xml_attribute_wrap = normal 278 | ij_xml_block_comment_add_space = false 279 | ij_xml_block_comment_at_first_column = true 280 | ij_xml_keep_blank_lines = 2 281 | ij_xml_keep_indents_on_empty_lines = false 282 | ij_xml_keep_line_breaks = false 283 | ij_xml_keep_line_breaks_in_text = true 284 | ij_xml_keep_whitespaces = false 285 | ij_xml_keep_whitespaces_around_cdata = preserve 286 | ij_xml_keep_whitespaces_inside_cdata = false 287 | ij_xml_line_comment_at_first_column = true 288 | ij_xml_space_after_tag_name = false 289 | ij_xml_space_around_equals_in_attribute = false 290 | ij_xml_space_inside_empty_tag = true 291 | ij_xml_text_wrap = normal 292 | ij_xml_use_custom_settings = true 293 | 294 | [{*.kt,*.kts}] 295 | ij_kotlin_align_in_columns_case_branch = false 296 | ij_kotlin_align_multiline_binary_operation = false 297 | ij_kotlin_align_multiline_extends_list = false 298 | ij_kotlin_align_multiline_method_parentheses = false 299 | ij_kotlin_align_multiline_parameters = true 300 | ij_kotlin_align_multiline_parameters_in_calls = false 301 | ij_kotlin_allow_trailing_comma = true 302 | ij_kotlin_allow_trailing_comma_on_call_site = false 303 | ij_kotlin_assignment_wrap = normal 304 | ij_kotlin_blank_lines_after_class_header = 0 305 | ij_kotlin_blank_lines_around_block_when_branches = 0 306 | ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 307 | ij_kotlin_block_comment_add_space = false 308 | ij_kotlin_block_comment_at_first_column = true 309 | ij_kotlin_call_parameters_new_line_after_left_paren = true 310 | ij_kotlin_call_parameters_right_paren_on_new_line = true 311 | ij_kotlin_call_parameters_wrap = on_every_item 312 | ij_kotlin_catch_on_new_line = false 313 | ij_kotlin_class_annotation_wrap = split_into_lines 314 | ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL 315 | ij_kotlin_continuation_indent_for_chained_calls = false 316 | ij_kotlin_continuation_indent_for_expression_bodies = false 317 | ij_kotlin_continuation_indent_in_argument_lists = false 318 | ij_kotlin_continuation_indent_in_elvis = false 319 | ij_kotlin_continuation_indent_in_if_conditions = false 320 | ij_kotlin_continuation_indent_in_parameter_lists = false 321 | ij_kotlin_continuation_indent_in_supertype_lists = false 322 | ij_kotlin_else_on_new_line = false 323 | ij_kotlin_enum_constants_wrap = off 324 | ij_kotlin_extends_list_wrap = normal 325 | ij_kotlin_field_annotation_wrap = split_into_lines 326 | ij_kotlin_finally_on_new_line = false 327 | ij_kotlin_if_rparen_on_new_line = true 328 | ij_kotlin_import_nested_classes = false 329 | ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^ 330 | ij_kotlin_insert_whitespaces_in_simple_one_line_method = true 331 | ij_kotlin_keep_blank_lines_before_right_brace = 2 332 | ij_kotlin_keep_blank_lines_in_code = 2 333 | ij_kotlin_keep_blank_lines_in_declarations = 2 334 | ij_kotlin_keep_first_column_comment = true 335 | ij_kotlin_keep_indents_on_empty_lines = false 336 | ij_kotlin_keep_line_breaks = true 337 | ij_kotlin_lbrace_on_next_line = false 338 | ij_kotlin_line_comment_add_space = false 339 | ij_kotlin_line_comment_add_space_on_reformat = false 340 | ij_kotlin_line_comment_at_first_column = true 341 | ij_kotlin_method_annotation_wrap = split_into_lines 342 | ij_kotlin_method_call_chain_wrap = normal 343 | ij_kotlin_method_parameters_new_line_after_left_paren = true 344 | ij_kotlin_method_parameters_right_paren_on_new_line = true 345 | ij_kotlin_method_parameters_wrap = on_every_item 346 | ij_kotlin_name_count_to_use_star_import = 2147483647 347 | ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 348 | ij_kotlin_parameter_annotation_wrap = off 349 | ij_kotlin_space_after_comma = true 350 | ij_kotlin_space_after_extend_colon = true 351 | ij_kotlin_space_after_type_colon = true 352 | ij_kotlin_space_before_catch_parentheses = true 353 | ij_kotlin_space_before_comma = false 354 | ij_kotlin_space_before_extend_colon = true 355 | ij_kotlin_space_before_for_parentheses = true 356 | ij_kotlin_space_before_if_parentheses = true 357 | ij_kotlin_space_before_lambda_arrow = true 358 | ij_kotlin_space_before_type_colon = false 359 | ij_kotlin_space_before_when_parentheses = true 360 | ij_kotlin_space_before_while_parentheses = true 361 | ij_kotlin_spaces_around_additive_operators = true 362 | ij_kotlin_spaces_around_assignment_operators = true 363 | ij_kotlin_spaces_around_equality_operators = true 364 | ij_kotlin_spaces_around_function_type_arrow = true 365 | ij_kotlin_spaces_around_logical_operators = true 366 | ij_kotlin_spaces_around_multiplicative_operators = true 367 | ij_kotlin_spaces_around_range = false 368 | ij_kotlin_spaces_around_relational_operators = true 369 | ij_kotlin_line_break_after_multiline_when_entry = false 370 | ij_kotlin_spaces_around_unary_operator = false 371 | ij_kotlin_spaces_around_when_arrow = true 372 | ij_kotlin_use_custom_formatting_for_modifiers = true 373 | ij_kotlin_variable_annotation_wrap = off 374 | ij_kotlin_while_on_new_line = false 375 | ij_kotlin_wrap_elvis_expressions = 1 376 | ij_kotlin_wrap_expression_body_functions = 1 377 | ij_kotlin_wrap_first_method_in_call_chain = false 378 | 379 | [{*.har,*.json}] 380 | indent_size = 2 381 | ij_json_keep_blank_lines_in_code = 0 382 | ij_json_keep_indents_on_empty_lines = false 383 | ij_json_keep_line_breaks = true 384 | ij_json_space_after_colon = true 385 | ij_json_space_after_comma = true 386 | ij_json_space_before_colon = true 387 | ij_json_space_before_comma = false 388 | ij_json_spaces_within_braces = false 389 | ij_json_spaces_within_brackets = false 390 | ij_json_wrap_long_lines = false 391 | 392 | [*.md] 393 | max_line_length = 1024 394 | 395 | [{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] 396 | ij_toml_keep_indents_on_empty_lines = false 397 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | # Package files 20 | *.jar 21 | 22 | # Maven 23 | target/ 24 | 25 | # JetBrains IDE 26 | .idea/ 27 | 28 | # Unit test reports 29 | TEST*.xml 30 | 31 | # Generated by MacOS 32 | .DS_Store 33 | 34 | # Generated by Windows 35 | Thumbs.db 36 | 37 | # Applications 38 | *.app 39 | *.exe 40 | *.war 41 | 42 | # Large media files 43 | *.mp4 44 | *.tiff 45 | *.avi 46 | *.flv 47 | *.mov 48 | *.wmv 49 | 50 | 51 | *.iml 52 | .gradle 53 | /local.properties 54 | /.idea/caches 55 | /.idea/libraries 56 | /.idea/modules.xml 57 | /.idea/workspace.xml 58 | /.idea/navEditor.xml 59 | /.idea/assetWizardSettings.xml 60 | build 61 | /build 62 | /captures 63 | .externalNativeBuild 64 | .cxx 65 | local.properties 66 | buildSrc/build/classes/kotlin/main/gradle/kotlin/dsl/ 67 | buildSrc/build/ 68 | presentation/envTest/release/ 69 | presentation/prod/release/ 70 | presentation/envTest/ 71 | plugins/build/ 72 | gradle/libs.versions.updates.toml -------------------------------------------------------------------------------- /ComposeFadingEdges/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ComposeFadingEdges/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | @Suppress( 4 | "DSL_SCOPE_VIOLATION", 5 | "MISSING_DEPENDENCY_CLASS", 6 | "UNRESOLVED_REFERENCE_WRONG_RECEIVER", 7 | "FUNCTION_CALL_EXPECTED" 8 | ) 9 | 10 | plugins { 11 | id("composefadingedges.library") 12 | `maven-publish` 13 | } 14 | 15 | group = ProjectConfig.group 16 | version = ProjectConfig.versionName 17 | 18 | publishing { 19 | publications { 20 | register(ProjectConfig.publication) { 21 | groupId = ProjectConfig.group 22 | artifactId = ProjectConfig.artifact 23 | version = ProjectConfig.versionName 24 | 25 | afterEvaluate { 26 | from(components[ProjectConfig.publication]) 27 | } 28 | } 29 | } 30 | } 31 | 32 | android { 33 | namespace = ProjectConfig.namespace 34 | 35 | composeOptions { 36 | kotlinCompilerExtensionVersion = libs.versions.compose.compiler.version.get() 37 | } 38 | 39 | publishing { 40 | singleVariant(ProjectConfig.publication) { 41 | withSourcesJar() 42 | withJavadocJar() 43 | } 44 | } 45 | } 46 | 47 | dependencies { 48 | implementation(libs.androidx.ktx) 49 | 50 | implementation(platform(libs.compose.bom)) 51 | implementation(libs.bundles.compose) 52 | 53 | debugImplementation(libs.bundles.debug.compose) 54 | } -------------------------------------------------------------------------------- /ComposeFadingEdges/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/FadingEdges.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package com.gigamole.composefadingedges 4 | 5 | import androidx.compose.animation.core.animateFloatAsState 6 | import androidx.compose.foundation.basicMarquee 7 | import androidx.compose.foundation.gestures.Orientation 8 | import androidx.compose.foundation.horizontalScroll 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.lazy.LazyColumn 11 | import androidx.compose.foundation.lazy.LazyListLayoutInfo 12 | import androidx.compose.foundation.lazy.LazyRow 13 | import androidx.compose.foundation.lazy.grid.LazyGridItemInfo 14 | import androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo 15 | import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid 16 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 17 | import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid 18 | import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemInfo 19 | import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLayoutInfo 20 | import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid 21 | import androidx.compose.foundation.verticalScroll 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableFloatStateOf 24 | import androidx.compose.runtime.mutableStateOf 25 | import androidx.compose.runtime.remember 26 | import androidx.compose.runtime.setValue 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.composed 29 | import androidx.compose.ui.draw.drawWithContent 30 | import androidx.compose.ui.geometry.Offset 31 | import androidx.compose.ui.geometry.Size 32 | import androidx.compose.ui.graphics.BlendMode 33 | import androidx.compose.ui.graphics.Brush 34 | import androidx.compose.ui.graphics.Color 35 | import androidx.compose.ui.graphics.CompositingStrategy 36 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope 37 | import androidx.compose.ui.graphics.drawscope.DrawScope 38 | import androidx.compose.ui.graphics.graphicsLayer 39 | import androidx.compose.ui.layout.layout 40 | import androidx.compose.ui.platform.LocalDensity 41 | import androidx.compose.ui.unit.Constraints 42 | import androidx.compose.ui.unit.Dp 43 | import androidx.compose.ui.unit.constrainWidth 44 | import androidx.compose.ui.unit.dp 45 | import com.gigamole.composefadingedges.content.FadingEdgesContentType 46 | import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfig 47 | import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfigDefaults 48 | import com.gigamole.composefadingedges.fill.FadingEdgesFillType 49 | import kotlin.math.abs 50 | import kotlin.math.ceil 51 | import kotlin.math.min 52 | 53 | /** 54 | * The utility [Modifier] to add fading edges to the text marquee (custom or default [basicMarquee]). 55 | * 56 | * The ComposeFadingEdges advises to use the default [basicMarquee], because custom implementations can differentiate drastically. In case the custom marquee is not 57 | * working well with [marqueeHorizontalFadingEdges], please, report an issue. 58 | * 59 | * When [isMarqueeAutoLayout] is enabled, the origin width is extended based on the provided [length] and [gravity], while maintaining the same offset. This ensures that 60 | * when fading edges [length] paddings are applied to the marquee text, the text remains positioned at its origin location. It is important to leave sufficient free space 61 | * around the marquee to ensure the effect is drawn correctly. On the other hand, when [isMarqueeAutoLayout] is disabled, the fading edges length paddings are applied to 62 | * the marquee text, and the origin position is offset by the [length]. In this case, you need to manually handle this padding offset. In the [isMarqueeAutoLayout] the 63 | * [isMarqueeAutoPadding] is automatically true. 64 | * 65 | * When the text content is unfit, the marquee and [horizontalFadingEdges] will not be applied. 66 | * 67 | * @param gravity The [FadingEdgesGravity]. 68 | * @param length The fading edges length. 69 | * @param fillType The [FadingEdgesFillType]. 70 | * @param isMarqueeAutoLayout Determines whether the [horizontalFadingEdges] and text marquee should be automatically aligned during the layout process to accommodate 71 | * additional text paddings required for proper fading edges drawing. 72 | * @param isMarqueeAutoPadding Determines if padding values according to the [gravity] and [length] should be applied. 73 | * @param marqueeProvider The custom or default [basicMarquee] provider. 74 | * @return The [Modifier] with extended width in case of [isMarqueeAutoLayout] is enabled, [horizontalFadingEdges], provided marquee with [marqueeProvider], and 75 | * additional horizontal paddings. 76 | * @author GIGAMOLE 77 | */ 78 | fun Modifier.marqueeHorizontalFadingEdges( 79 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 80 | length: Dp = FadingEdgesDefaults.Length, 81 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 82 | isMarqueeAutoLayout: Boolean = FadingEdgesDefaults.IsMarqueeAutoLayout, 83 | isMarqueeAutoPadding: Boolean = FadingEdgesDefaults.IsMarqueeAutoPadding, 84 | marqueeProvider: () -> Modifier 85 | ): Modifier = composed { 86 | var isMarqueeAndFadingEdgesVisible by remember { mutableStateOf(false) } 87 | var isMarqueeAutoLayoutApplied by remember { mutableStateOf(false) } 88 | 89 | Modifier 90 | .layout { measurable, constraints -> 91 | val lengthPx = length.roundToPx() 92 | val widthLength = when (gravity) { 93 | FadingEdgesGravity.All -> { 94 | lengthPx * 2 95 | } 96 | FadingEdgesGravity.Start, 97 | FadingEdgesGravity.End -> { 98 | lengthPx 99 | } 100 | } 101 | 102 | val contentConstraints = constraints.copy(maxWidth = Constraints.Infinity) 103 | val contentPlaceable = measurable.measure(contentConstraints) 104 | val containerWidth = constraints.constrainWidth(contentPlaceable.width) 105 | val contentWidth = contentPlaceable.width - 106 | if (isMarqueeAutoLayout && isMarqueeAutoLayoutApplied) { 107 | widthLength 108 | } else { 109 | 0 110 | } 111 | 112 | isMarqueeAndFadingEdgesVisible = contentWidth > containerWidth 113 | 114 | if (isMarqueeAutoLayout && isMarqueeAndFadingEdgesVisible) { 115 | isMarqueeAutoLayoutApplied = true 116 | 117 | // Extend the content width for fading edges and an additional text padding later. 118 | val placeable = measurable.measure( 119 | constraints = constraints.copy( 120 | maxWidth = constraints.maxWidth + widthLength 121 | ) 122 | ) 123 | 124 | layout( 125 | width = placeable.width, 126 | height = placeable.height 127 | ) { 128 | placeable.placeWithLayer( 129 | x = 0, 130 | y = 0 131 | ) 132 | } 133 | } else { 134 | isMarqueeAutoLayoutApplied = false 135 | 136 | val placeable = measurable.measure(constraints) 137 | 138 | layout( 139 | width = placeable.width, 140 | height = placeable.height 141 | ) { 142 | placeable.placeWithLayer( 143 | x = 0, 144 | y = 0 145 | ) 146 | } 147 | } 148 | } 149 | .then( 150 | if (isMarqueeAndFadingEdgesVisible) { 151 | Modifier 152 | .horizontalFadingEdges( 153 | gravity = gravity, 154 | length = length, 155 | fillType = fillType 156 | ) 157 | .then(marqueeProvider()) 158 | .then( 159 | if (isMarqueeAutoLayout || isMarqueeAutoPadding) { 160 | // Applying an additional padding to text, so fading edges dont cover it, at its origin position. 161 | when (gravity) { 162 | FadingEdgesGravity.All -> { 163 | Modifier.padding(horizontal = length) 164 | } 165 | FadingEdgesGravity.Start -> { 166 | Modifier.padding(start = length) 167 | } 168 | FadingEdgesGravity.End -> { 169 | Modifier.padding(end = length) 170 | } 171 | } 172 | } else { 173 | Modifier 174 | } 175 | ) 176 | } else { 177 | Modifier 178 | } 179 | ) 180 | } 181 | 182 | /** 183 | * The [Modifier] to add horizontal [fadingEdges] with a [FadingEdgesContentType.Static] content type. The fading edges are always fully shown. 184 | * 185 | * In case, the content is scrollable ([horizontalScroll] or [verticalScroll]) this [Modifier] should be added first. 186 | * 187 | * @param gravity The [FadingEdgesGravity]. 188 | * @param length The fading edges length. 189 | * @param fillType The [FadingEdgesFillType]. 190 | * @return The [Modifier] with always fully shown horizontal [fadingEdges]. 191 | * @author GIGAMOLE 192 | */ 193 | fun Modifier.horizontalFadingEdges( 194 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 195 | length: Dp = FadingEdgesDefaults.Length, 196 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 197 | ): Modifier = horizontalFadingEdges( 198 | gravity = gravity, 199 | length = length, 200 | contentType = FadingEdgesContentType.Static, 201 | fillType = fillType 202 | ) 203 | 204 | /** 205 | * The [Modifier] to add horizontal [fadingEdges] to a content with [horizontalScroll]. 206 | * 207 | * This [Modifier] should be added before the [horizontalScroll]. 208 | * 209 | * @param contentType The [FadingEdgesContentType.Dynamic.Scroll] content type for a [horizontalScroll] container. 210 | * @param gravity The [FadingEdgesGravity]. 211 | * @param length The fading edges length. 212 | * @param fillType The [FadingEdgesFillType]. 213 | * @return The [Modifier] with horizontal [fadingEdges]. 214 | * @author GIGAMOLE 215 | */ 216 | fun Modifier.horizontalFadingEdges( 217 | contentType: FadingEdgesContentType.Dynamic.Scroll, 218 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 219 | length: Dp = FadingEdgesDefaults.Length, 220 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 221 | ): Modifier = horizontalFadingEdges( 222 | gravity = gravity, 223 | length = length, 224 | contentType = contentType as FadingEdgesContentType, 225 | fillType = fillType 226 | ) 227 | 228 | /** 229 | * The [Modifier] to add horizontal [fadingEdges] to a [LazyRow]. 230 | * 231 | * @param contentType The [FadingEdgesContentType.Dynamic.Lazy.List] content type. 232 | * @param gravity The [FadingEdgesGravity]. 233 | * @param length The fading edges length. 234 | * @param fillType The [FadingEdgesFillType]. 235 | * @return The [Modifier] with horizontal [fadingEdges]. 236 | * @author GIGAMOLE 237 | */ 238 | fun Modifier.horizontalFadingEdges( 239 | contentType: FadingEdgesContentType.Dynamic.Lazy.List, 240 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 241 | length: Dp = FadingEdgesDefaults.Length, 242 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 243 | ): Modifier = horizontalFadingEdges( 244 | gravity = gravity, 245 | length = length, 246 | contentType = contentType as FadingEdgesContentType, 247 | fillType = fillType 248 | ) 249 | 250 | /** 251 | * The [Modifier] to add horizontal [fadingEdges] to a [LazyHorizontalGrid]. 252 | * 253 | * @param contentType The [FadingEdgesContentType.Dynamic.Lazy.Grid] content type. 254 | * @param gravity The [FadingEdgesGravity]. 255 | * @param length The fading edges length. 256 | * @param fillType The [FadingEdgesFillType]. 257 | * @return The [Modifier] with horizontal [fadingEdges]. 258 | * @author GIGAMOLE 259 | */ 260 | fun Modifier.horizontalFadingEdges( 261 | contentType: FadingEdgesContentType.Dynamic.Lazy.Grid, 262 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 263 | length: Dp = FadingEdgesDefaults.Length, 264 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 265 | ): Modifier = horizontalFadingEdges( 266 | gravity = gravity, 267 | length = length, 268 | contentType = contentType as FadingEdgesContentType, 269 | fillType = fillType 270 | ) 271 | 272 | /** 273 | * The [Modifier] to add horizontal [fadingEdges] to a [LazyHorizontalStaggeredGrid]. 274 | * 275 | * @param contentType The [FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid] content type. 276 | * @param gravity The [FadingEdgesGravity]. 277 | * @param length The fading edges length. 278 | * @param fillType The [FadingEdgesFillType]. 279 | * @return The [Modifier] with horizontal [fadingEdges]. 280 | * @author GIGAMOLE 281 | */ 282 | fun Modifier.horizontalFadingEdges( 283 | contentType: FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid, 284 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 285 | length: Dp = FadingEdgesDefaults.Length, 286 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 287 | ): Modifier = horizontalFadingEdges( 288 | gravity = gravity, 289 | length = length, 290 | contentType = contentType as FadingEdgesContentType, 291 | fillType = fillType 292 | ) 293 | 294 | /** 295 | * The [Modifier] to add horizontal [fadingEdges]. 296 | * 297 | * In case, the content is scrollable ([horizontalScroll] or [verticalScroll]) this [Modifier] should be added first. 298 | * 299 | * @param contentType The [FadingEdgesContentType]. 300 | * @param gravity The [FadingEdgesGravity]. 301 | * @param length The fading edges length. 302 | * @param fillType The [FadingEdgesFillType]. 303 | * @return The [Modifier] with horizontal [fadingEdges]. 304 | * @author GIGAMOLE 305 | */ 306 | fun Modifier.horizontalFadingEdges( 307 | contentType: FadingEdgesContentType, 308 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 309 | length: Dp = FadingEdgesDefaults.Length, 310 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 311 | ): Modifier = fadingEdges( 312 | orientation = FadingEdgesOrientation.Horizontal, 313 | gravity = gravity, 314 | length = length, 315 | contentType = contentType, 316 | fillType = fillType 317 | ) 318 | 319 | /** 320 | * The [Modifier] to add vertical [fadingEdges] with a [FadingEdgesContentType.Static] content type. The fading edges are always fully shown. 321 | * 322 | * @param gravity The [FadingEdgesGravity]. 323 | * @param length The fading edges length. 324 | * @param fillType The [FadingEdgesFillType]. 325 | * @return The [Modifier] with always fully shown vertical [fadingEdges]. 326 | * @author GIGAMOLE 327 | */ 328 | fun Modifier.verticalFadingEdges( 329 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 330 | length: Dp = FadingEdgesDefaults.Length, 331 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 332 | ): Modifier = verticalFadingEdges( 333 | gravity = gravity, 334 | length = length, 335 | contentType = FadingEdgesContentType.Static, 336 | fillType = fillType 337 | ) 338 | 339 | /** 340 | * The [Modifier] to add vertical [fadingEdges] to a content with [verticalScroll]. 341 | * 342 | * This [Modifier] should be added before the [verticalScroll]. 343 | * 344 | * @param contentType The [FadingEdgesContentType.Dynamic.Scroll] content type for a [verticalScroll] container. 345 | * @param gravity The [FadingEdgesGravity]. 346 | * @param length The fading edges length. 347 | * @param fillType The [FadingEdgesFillType]. 348 | * @return The [Modifier] with vertical [fadingEdges]. 349 | * @author GIGAMOLE 350 | */ 351 | fun Modifier.verticalFadingEdges( 352 | contentType: FadingEdgesContentType.Dynamic.Scroll, 353 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 354 | length: Dp = FadingEdgesDefaults.Length, 355 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 356 | ): Modifier = verticalFadingEdges( 357 | gravity = gravity, 358 | length = length, 359 | contentType = contentType as FadingEdgesContentType, 360 | fillType = fillType 361 | ) 362 | 363 | /** 364 | * The [Modifier] to add vertical [fadingEdges] to a [LazyColumn]. 365 | * 366 | * @param contentType The [FadingEdgesContentType.Dynamic.Lazy.List] content type. 367 | * @param gravity The [FadingEdgesGravity]. 368 | * @param length The fading edges length. 369 | * @param fillType The [FadingEdgesFillType]. 370 | * @return The [Modifier] with vertical [fadingEdges]. 371 | * @author GIGAMOLE 372 | */ 373 | fun Modifier.verticalFadingEdges( 374 | contentType: FadingEdgesContentType.Dynamic.Lazy.List, 375 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 376 | length: Dp = FadingEdgesDefaults.Length, 377 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 378 | ): Modifier = verticalFadingEdges( 379 | gravity = gravity, 380 | length = length, 381 | contentType = contentType as FadingEdgesContentType, 382 | fillType = fillType 383 | ) 384 | 385 | /** 386 | * The [Modifier] to add vertical [fadingEdges] to a [LazyVerticalGrid]. 387 | * 388 | * @param contentType The [FadingEdgesContentType.Dynamic.Lazy.Grid] content type. 389 | * @param gravity The [FadingEdgesGravity]. 390 | * @param length The fading edges length. 391 | * @param fillType The [FadingEdgesFillType]. 392 | * @return The [Modifier] with vertical [fadingEdges]. 393 | * @author GIGAMOLE 394 | */ 395 | fun Modifier.verticalFadingEdges( 396 | contentType: FadingEdgesContentType.Dynamic.Lazy.Grid, 397 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 398 | length: Dp = FadingEdgesDefaults.Length, 399 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 400 | ): Modifier = verticalFadingEdges( 401 | gravity = gravity, 402 | length = length, 403 | contentType = contentType as FadingEdgesContentType, 404 | fillType = fillType 405 | ) 406 | 407 | /** 408 | * The [Modifier] to add vertical [fadingEdges] to a [LazyVerticalStaggeredGrid]. 409 | * 410 | * @param contentType The [FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid] content type. 411 | * @param gravity The [FadingEdgesGravity]. 412 | * @param length The fading edges length. 413 | * @param fillType The [FadingEdgesFillType]. 414 | * @return The [Modifier] with vertical [fadingEdges]. 415 | * @author GIGAMOLE 416 | */ 417 | fun Modifier.verticalFadingEdges( 418 | contentType: FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid, 419 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 420 | length: Dp = FadingEdgesDefaults.Length, 421 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 422 | ): Modifier = verticalFadingEdges( 423 | gravity = gravity, 424 | length = length, 425 | contentType = contentType as FadingEdgesContentType, 426 | fillType = fillType 427 | ) 428 | 429 | /** 430 | * The [Modifier] to add vertical [fadingEdges]. 431 | * 432 | * In case, the content is scrollable ([horizontalScroll] or [verticalScroll]) this [Modifier] should be added first. 433 | * 434 | * @param contentType The [FadingEdgesContentType]. 435 | * @param gravity The [FadingEdgesGravity]. 436 | * @param length The fading edges length. 437 | * @param fillType The [FadingEdgesFillType]. 438 | * @return The [Modifier] with vertical [fadingEdges]. 439 | * @author GIGAMOLE 440 | */ 441 | fun Modifier.verticalFadingEdges( 442 | contentType: FadingEdgesContentType, 443 | gravity: FadingEdgesGravity = FadingEdgesDefaults.Gravity, 444 | length: Dp = FadingEdgesDefaults.Length, 445 | fillType: FadingEdgesFillType = FadingEdgesDefaults.FillType, 446 | ): Modifier = fadingEdges( 447 | orientation = FadingEdgesOrientation.Vertical, 448 | gravity = gravity, 449 | length = length, 450 | contentType = contentType, 451 | fillType = fillType 452 | ) 453 | 454 | /** 455 | * The core fading edges [Modifier]. 456 | * 457 | * @param orientation The [FadingEdgesOrientation]. 458 | * @param gravity The [FadingEdgesGravity]. 459 | * @param length The fading edges length. 460 | * @param contentType The [FadingEdgesContentType]. 461 | * @param fillType The [FadingEdgesFillType]. 462 | * @return The [Modifier] with [fadingEdges]. 463 | * @author GIGAMOLE 464 | */ 465 | internal fun Modifier.fadingEdges( 466 | orientation: FadingEdgesOrientation, 467 | gravity: FadingEdgesGravity, 468 | length: Dp, 469 | contentType: FadingEdgesContentType, 470 | fillType: FadingEdgesFillType 471 | ): Modifier = composed { 472 | // No need to add fading edges when they are empty. 473 | if (length <= 0.dp) { 474 | return@composed Modifier 475 | } 476 | 477 | val isFadeClip: Boolean 478 | val solidColor: Color 479 | val startLength: Float 480 | val endLength: Float 481 | 482 | when (fillType) { 483 | is FadingEdgesFillType.FadeClip -> { 484 | isFadeClip = true 485 | solidColor = Color.Black 486 | } 487 | is FadingEdgesFillType.FadeColor -> { 488 | isFadeClip = false 489 | solidColor = fillType.color.copy(alpha = 1.0F) 490 | } 491 | } 492 | 493 | val midColor = solidColor.copy(alpha = fillType.secondStopAlpha) 494 | val transparentColor = solidColor.copy(alpha = 0.0F) 495 | val startColors = arrayOf( 496 | fillType.fillStops.first to solidColor, 497 | fillType.fillStops.second to midColor, 498 | fillType.fillStops.third to transparentColor 499 | ) 500 | val endColors = arrayOf( 501 | 1.0F - fillType.fillStops.third to transparentColor, 502 | 1.0F - fillType.fillStops.second to midColor, 503 | 1.0F - fillType.fillStops.first to solidColor 504 | ) 505 | val lengthPx = with(LocalDensity.current) { length.toPx() } 506 | 507 | when (contentType) { 508 | is FadingEdgesContentType.Dynamic -> { 509 | var scrollStartLength by remember { mutableFloatStateOf(0.0F) } 510 | var scrollEndLength by remember { mutableFloatStateOf(0.0F) } 511 | 512 | when (contentType) { 513 | is FadingEdgesContentType.Dynamic.Scroll -> { 514 | with(contentType) { 515 | val rawStartScroll = state.value.toFloat() 516 | val rawEndScroll = (state.maxValue - state.value).toFloat() 517 | 518 | when (scrollConfig) { 519 | is FadingEdgesScrollConfig.Dynamic -> { 520 | val maxScroll = state.maxValue.toFloat() 521 | val dynamicStartScroll: Float 522 | val dynamicEndScroll: Float 523 | 524 | if (scrollConfig.isLerpByDifferenceForPartialContent && maxScroll > 0 && maxScroll <= lengthPx) { 525 | dynamicStartScroll = lengthPx * (rawStartScroll / maxScroll).coerceIn(0.0F, 1.0F) 526 | dynamicEndScroll = lengthPx * (rawEndScroll / maxScroll).coerceIn(0.0F, 1.0F) 527 | } else { 528 | dynamicStartScroll = rawStartScroll 529 | dynamicEndScroll = rawEndScroll 530 | } 531 | 532 | scrollStartLength = lengthPx * ((dynamicStartScroll / lengthPx) * scrollConfig.scrollFactor).coerceIn(0.0F, 1.0F) 533 | scrollEndLength = lengthPx * ((dynamicEndScroll / lengthPx) * scrollConfig.scrollFactor).coerceIn(0.0F, 1.0F) 534 | } 535 | is FadingEdgesScrollConfig.Full -> { 536 | scrollStartLength = if (state.canScrollBackward) { 537 | lengthPx 538 | } else { 539 | 0.0F 540 | } 541 | scrollEndLength = if (state.canScrollForward) { 542 | lengthPx 543 | } else { 544 | 0.0F 545 | } 546 | } 547 | is FadingEdgesScrollConfig.Static -> { 548 | if (isScrollPossible) { 549 | scrollStartLength = lengthPx 550 | scrollEndLength = lengthPx 551 | } else { 552 | scrollStartLength = 0.0F 553 | scrollEndLength = 0.0F 554 | } 555 | } 556 | } 557 | } 558 | } 559 | is FadingEdgesContentType.Dynamic.Lazy.List -> { 560 | with(contentType) { 561 | val layoutInfo = state.layoutInfo 562 | val lazyListScrollStartLength = run { 563 | val fraction = if (layoutInfo.visibleItemsInfo.isEmpty()) { 564 | 0.0F 565 | } else { 566 | when (scrollConfig) { 567 | is FadingEdgesScrollConfig.Dynamic -> { 568 | val firstItem = layoutInfo.visibleItemsInfo.first() 569 | 570 | if (firstItem.index > 0) { 571 | 1.0F 572 | } else { 573 | val firstItemSize = firstItem.size 574 | val minLength = if (firstItemSize == 0) { 575 | lengthPx 576 | } else { 577 | min(lengthPx, firstItemSize.toFloat()) 578 | } 579 | 580 | var lerpLength = minLength 581 | 582 | if (scrollConfig.isLerpByDifferenceForPartialContent && 583 | firstItem.index == 0 && 584 | state.canScrollBackward && 585 | layoutInfo.totalItemsCount == layoutInfo.visibleItemsInfo.size 586 | ) { 587 | lerpLength = lerpLazyListPartialContentByDifference( 588 | layoutInfo = layoutInfo, 589 | lerpLength = lerpLength, 590 | minLength = minLength 591 | ) 592 | } 593 | 594 | abs(firstItem.offset).toFloat() / lerpLength * scrollConfig.scrollFactor 595 | } 596 | } 597 | is FadingEdgesScrollConfig.Full -> { 598 | if (state.canScrollBackward) { 599 | 1.0F 600 | } else { 601 | 0.0F 602 | } 603 | } 604 | is FadingEdgesScrollConfig.Static -> { 605 | if (isScrollPossible) { 606 | 1.0F 607 | } else { 608 | 0.0F 609 | } 610 | } 611 | } 612 | }.coerceIn(0.0F, 1.0F) 613 | 614 | fraction * lengthPx 615 | } 616 | val lazyListScrollEndLength = run { 617 | val fraction = if (layoutInfo.visibleItemsInfo.isEmpty()) { 618 | 0.0F 619 | } else { 620 | when (scrollConfig) { 621 | is FadingEdgesScrollConfig.Dynamic -> { 622 | val lastItem = layoutInfo.visibleItemsInfo.last() 623 | 624 | if (lastItem.index < layoutInfo.totalItemsCount - 1) { 625 | 1.0F 626 | } else { 627 | val lastItemSize = lastItem.size 628 | val minLength = if (lastItemSize == 0) { 629 | lengthPx 630 | } else { 631 | min(lengthPx, lastItemSize.toFloat()) 632 | } 633 | 634 | var lerpLength = minLength 635 | 636 | if (scrollConfig.isLerpByDifferenceForPartialContent && 637 | lastItem.index == layoutInfo.totalItemsCount - 1 && 638 | state.canScrollForward && 639 | layoutInfo.totalItemsCount == layoutInfo.visibleItemsInfo.size 640 | ) { 641 | lerpLength = lerpLazyListPartialContentByDifference( 642 | layoutInfo = layoutInfo, 643 | lerpLength = lerpLength, 644 | minLength = minLength 645 | ) 646 | } 647 | 648 | val lastItemOffset = lastItem.size + lastItem.offset + layoutInfo.beforeContentPadding - layoutInfo.viewportEndOffset 649 | 650 | lastItemOffset / lerpLength * scrollConfig.scrollFactor 651 | } 652 | } 653 | is FadingEdgesScrollConfig.Full -> { 654 | if (state.canScrollForward) { 655 | 1.0F 656 | } else { 657 | 0.0F 658 | } 659 | } 660 | is FadingEdgesScrollConfig.Static -> { 661 | if (isScrollPossible) { 662 | 1.0F 663 | } else { 664 | 0.0F 665 | } 666 | } 667 | } 668 | }.coerceIn(0.0F, 1.0F) 669 | 670 | fraction * lengthPx 671 | } 672 | 673 | scrollStartLength = lazyListScrollStartLength 674 | scrollEndLength = lazyListScrollEndLength 675 | } 676 | } 677 | is FadingEdgesContentType.Dynamic.Lazy.Grid -> { 678 | with(contentType) { 679 | val layoutInfo = state.layoutInfo 680 | val entriesCount = ceil(layoutInfo.totalItemsCount.toFloat() / spanCount.toFloat()).toInt() 681 | 682 | fun LazyGridItemInfo.entryIndex(): Int { 683 | return when (layoutInfo.orientation) { 684 | Orientation.Vertical -> { 685 | row 686 | } 687 | Orientation.Horizontal -> { 688 | column 689 | } 690 | } 691 | } 692 | 693 | fun LazyGridItemInfo.entryOffset(): Int { 694 | return when (layoutInfo.orientation) { 695 | Orientation.Vertical -> { 696 | offset.y 697 | } 698 | Orientation.Horizontal -> { 699 | offset.x 700 | } 701 | } 702 | } 703 | 704 | fun List.entrySize(): Int { 705 | return maxOf { 706 | when (layoutInfo.orientation) { 707 | Orientation.Vertical -> { 708 | it.size.height 709 | } 710 | Orientation.Horizontal -> { 711 | it.size.width 712 | } 713 | } 714 | } 715 | } 716 | 717 | val lazyGridScrollStartLength = run { 718 | val fraction = if (layoutInfo.visibleItemsInfo.isEmpty()) { 719 | 0.0F 720 | } else { 721 | when (scrollConfig) { 722 | is FadingEdgesScrollConfig.Dynamic -> { 723 | val firstItem = layoutInfo.visibleItemsInfo.first() 724 | val firstEntryIndex = firstItem.entryIndex() 725 | 726 | if (firstEntryIndex > 0) { 727 | 1.0F 728 | } else { 729 | val firstEntrySize = layoutInfo.visibleItemsInfo.take(spanCount).entrySize() 730 | val minLength = if (firstEntrySize == 0) { 731 | lengthPx 732 | } else { 733 | min(lengthPx, firstEntrySize.toFloat()) 734 | } 735 | 736 | var lerpLength = minLength 737 | 738 | if (scrollConfig.isLerpByDifferenceForPartialContent && 739 | firstEntryIndex == 0 && 740 | state.canScrollBackward && 741 | layoutInfo.totalItemsCount == layoutInfo.visibleItemsInfo.size 742 | ) { 743 | lerpLength = lerpLazyGridPartialContentByDifference( 744 | layoutInfo = layoutInfo, 745 | spanCount = spanCount, 746 | lerpLength = lerpLength, 747 | minLength = minLength 748 | ) 749 | } 750 | 751 | abs(firstItem.entryOffset()).toFloat() / lerpLength * scrollConfig.scrollFactor 752 | } 753 | } 754 | is FadingEdgesScrollConfig.Full -> { 755 | if (state.canScrollBackward) { 756 | 1.0F 757 | } else { 758 | 0.0F 759 | } 760 | } 761 | is FadingEdgesScrollConfig.Static -> { 762 | if (isScrollPossible) { 763 | 1.0F 764 | } else { 765 | 0.0F 766 | } 767 | } 768 | } 769 | }.coerceIn(0.0F, 1.0F) 770 | 771 | fraction * lengthPx 772 | } 773 | val lazyGridScrollEndLength = run { 774 | val fraction = if (layoutInfo.visibleItemsInfo.isEmpty()) { 775 | 0.0F 776 | } else { 777 | when (scrollConfig) { 778 | is FadingEdgesScrollConfig.Dynamic -> { 779 | val lastItem = layoutInfo.visibleItemsInfo.last() 780 | val lastEntryIndex = lastItem.entryIndex() 781 | 782 | if (lastEntryIndex < entriesCount - 1) { 783 | 1.0F 784 | } else { 785 | val lastEntrySize = layoutInfo.visibleItemsInfo.takeLastWhile { 786 | when (layoutInfo.orientation) { 787 | Orientation.Vertical -> { 788 | it.row == lastEntryIndex 789 | } 790 | Orientation.Horizontal -> { 791 | it.column == lastEntryIndex 792 | } 793 | } 794 | }.entrySize() 795 | val minLength = if (lastEntrySize == 0) { 796 | lengthPx 797 | } else { 798 | min(lengthPx, lastEntrySize.toFloat()) 799 | } 800 | 801 | var lerpLength = minLength 802 | 803 | if (scrollConfig.isLerpByDifferenceForPartialContent && 804 | lastEntryIndex == entriesCount - 1 && 805 | state.canScrollForward && 806 | layoutInfo.totalItemsCount == layoutInfo.visibleItemsInfo.size 807 | ) { 808 | lerpLength = lerpLazyGridPartialContentByDifference( 809 | layoutInfo = layoutInfo, 810 | spanCount = spanCount, 811 | lerpLength = lerpLength, 812 | minLength = minLength 813 | ) 814 | } 815 | 816 | val lastItemOffset = lastEntrySize + lastItem.entryOffset() + layoutInfo.beforeContentPadding - layoutInfo.viewportEndOffset 817 | 818 | lastItemOffset / lerpLength * scrollConfig.scrollFactor 819 | } 820 | } 821 | is FadingEdgesScrollConfig.Full -> { 822 | if (state.canScrollForward) { 823 | 1.0F 824 | } else { 825 | 0.0F 826 | } 827 | } 828 | is FadingEdgesScrollConfig.Static -> { 829 | if (isScrollPossible) { 830 | 1.0F 831 | } else { 832 | 0.0F 833 | } 834 | } 835 | } 836 | }.coerceIn(0.0F, 1.0F) 837 | 838 | fraction * lengthPx 839 | } 840 | 841 | scrollStartLength = lazyGridScrollStartLength 842 | scrollEndLength = lazyGridScrollEndLength 843 | } 844 | } 845 | is FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid -> { 846 | with(contentType) { 847 | val layoutInfo = state.layoutInfo 848 | 849 | fun LazyStaggeredGridItemInfo.itemSize(): Int { 850 | return when (layoutInfo.orientation) { 851 | Orientation.Vertical -> { 852 | size.height 853 | } 854 | Orientation.Horizontal -> { 855 | size.width 856 | } 857 | } 858 | } 859 | 860 | fun LazyStaggeredGridItemInfo.itemOffset(): Int { 861 | return when (layoutInfo.orientation) { 862 | Orientation.Vertical -> { 863 | offset.y 864 | } 865 | Orientation.Horizontal -> { 866 | offset.x 867 | } 868 | } 869 | } 870 | 871 | val lazyStaggeredGridScrollStartLength = run { 872 | val fraction = if (layoutInfo.visibleItemsInfo.isEmpty()) { 873 | 0.0F 874 | } else { 875 | when (scrollConfig) { 876 | is FadingEdgesScrollConfig.Dynamic -> { 877 | val firstItem = layoutInfo.visibleItemsInfo.first() 878 | 879 | if (firstItem.index > 0) { 880 | 1.0F 881 | } else { 882 | val firstItemSize = firstItem.itemSize() 883 | val minLength = if (firstItemSize == 0) { 884 | lengthPx 885 | } else { 886 | min(lengthPx, firstItemSize.toFloat()) 887 | } 888 | 889 | var lerpLength = minLength 890 | 891 | if (scrollConfig.isLerpByDifferenceForPartialContent && 892 | firstItem.index == 0 && 893 | state.canScrollBackward && 894 | layoutInfo.totalItemsCount == layoutInfo.visibleItemsInfo.size 895 | ) { 896 | lerpLength = lerpLazyStaggeredGridPartialContentByDifference( 897 | layoutInfo = layoutInfo, 898 | lerpLength = lerpLength, 899 | minLength = minLength 900 | ) 901 | } 902 | 903 | abs(firstItem.itemOffset()).toFloat() / lerpLength * scrollConfig.scrollFactor 904 | } 905 | } 906 | is FadingEdgesScrollConfig.Full -> { 907 | if (state.canScrollBackward) { 908 | 1.0F 909 | } else { 910 | 0.0F 911 | } 912 | } 913 | is FadingEdgesScrollConfig.Static -> { 914 | if (isScrollPossible) { 915 | 1.0F 916 | } else { 917 | 0.0F 918 | } 919 | } 920 | } 921 | }.coerceIn(0.0F, 1.0F) 922 | 923 | fraction * lengthPx 924 | } 925 | val lazyStaggeredGridScrollEndLength = run { 926 | val fraction = if (layoutInfo.visibleItemsInfo.isEmpty()) { 927 | 0.0F 928 | } else { 929 | when (scrollConfig) { 930 | is FadingEdgesScrollConfig.Dynamic -> { 931 | val rawLastItem = layoutInfo.visibleItemsInfo.last() 932 | val isRealLastItemVisible = rawLastItem.index == layoutInfo.totalItemsCount - 1 933 | val lastItem = if (isRealLastItemVisible) { 934 | layoutInfo.visibleItemsInfo.findLast { 935 | (it.itemOffset() + it.itemSize()) > (rawLastItem.itemOffset() + rawLastItem.itemSize()) 936 | } ?: rawLastItem 937 | } else { 938 | rawLastItem 939 | } 940 | 941 | if (isRealLastItemVisible.not()) { 942 | 1.0F 943 | } else { 944 | val lastItemSize = lastItem.itemSize() 945 | val minLength = if (lastItemSize == 0) { 946 | lengthPx 947 | } else { 948 | min(lengthPx, lastItemSize.toFloat()) 949 | } 950 | 951 | var lerpLength = minLength 952 | 953 | if (scrollConfig.isLerpByDifferenceForPartialContent && 954 | state.canScrollForward && 955 | layoutInfo.totalItemsCount == layoutInfo.visibleItemsInfo.size 956 | ) { 957 | lerpLength = lerpLazyStaggeredGridPartialContentByDifference( 958 | layoutInfo = layoutInfo, 959 | lerpLength = lerpLength, 960 | minLength = minLength 961 | ) 962 | } 963 | 964 | val lastItemOffset = 965 | lastItem.itemSize() + lastItem.itemOffset() + layoutInfo.beforeContentPadding - layoutInfo.viewportEndOffset 966 | 967 | lastItemOffset / lerpLength * scrollConfig.scrollFactor 968 | } 969 | } 970 | is FadingEdgesScrollConfig.Full -> { 971 | if (state.canScrollForward) { 972 | 1.0F 973 | } else { 974 | 0.0F 975 | } 976 | } 977 | is FadingEdgesScrollConfig.Static -> { 978 | if (isScrollPossible) { 979 | 1.0F 980 | } else { 981 | 0.0F 982 | } 983 | } 984 | } 985 | }.coerceIn(0.0F, 1.0F) 986 | 987 | fraction * lengthPx 988 | } 989 | 990 | scrollStartLength = lazyStaggeredGridScrollStartLength 991 | scrollEndLength = lazyStaggeredGridScrollEndLength 992 | } 993 | } 994 | } 995 | 996 | val animationSpec = contentType.scrollConfig.animationSpec 997 | if (animationSpec == null) { 998 | startLength = scrollStartLength 999 | endLength = scrollEndLength 1000 | } else { 1001 | startLength = animateFloatAsState( 1002 | targetValue = scrollStartLength, 1003 | animationSpec = animationSpec, 1004 | label = "StartLength" 1005 | ).value 1006 | endLength = animateFloatAsState( 1007 | targetValue = scrollEndLength, 1008 | animationSpec = animationSpec, 1009 | label = "EndLength" 1010 | ).value 1011 | } 1012 | } 1013 | is FadingEdgesContentType.Static -> { 1014 | startLength = lengthPx 1015 | endLength = lengthPx 1016 | } 1017 | } 1018 | 1019 | this 1020 | .then( 1021 | if (isFadeClip) { 1022 | Modifier.graphicsLayer { 1023 | // Required to apply clip mask to a drawn content. 1024 | compositingStrategy = CompositingStrategy.Offscreen 1025 | } 1026 | } else { 1027 | Modifier 1028 | } 1029 | ) 1030 | .drawWithContent { 1031 | drawContent() 1032 | 1033 | if (gravity.isStart()) { 1034 | drawFadingEdge( 1035 | orientation = orientation, 1036 | isStartGravity = true, 1037 | colorStops = startColors, 1038 | isClip = isFadeClip, 1039 | length = startLength 1040 | ) 1041 | } 1042 | 1043 | if (gravity.isEnd()) { 1044 | drawFadingEdge( 1045 | orientation = orientation, 1046 | isStartGravity = false, 1047 | colorStops = endColors, 1048 | isClip = isFadeClip, 1049 | length = endLength 1050 | ) 1051 | } 1052 | } 1053 | } 1054 | 1055 | /** 1056 | * Interpolates the fading edges length difference in partial content state. 1057 | * 1058 | * @param layoutInfo The [LazyListLayoutInfo]. 1059 | * @param lerpLength The default partial content fading edges length difference. 1060 | * @param minLength The minimum fading edges length. 1061 | * @return The interpolated fading edges length difference. 1062 | * @see FadingEdgesScrollConfig.Dynamic 1063 | * @see FadingEdgesContentType.Dynamic.Lazy.List 1064 | * @see FadingEdgesScrollConfigDefaults.Dynamic.IsLerpByDifferenceForPartialContent 1065 | * @author GIGAMOLE 1066 | */ 1067 | internal fun lerpLazyListPartialContentByDifference( 1068 | layoutInfo: LazyListLayoutInfo, 1069 | lerpLength: Float, 1070 | minLength: Float 1071 | ): Float = with(layoutInfo) { 1072 | val visibleSize = visibleItemsInfo.sumOf { it.size } + 1073 | beforeContentPadding + 1074 | afterContentPadding + 1075 | mainAxisItemSpacing * (visibleItemsInfo.size - 1) 1076 | 1077 | val side = when (orientation) { 1078 | Orientation.Vertical -> { 1079 | viewportSize.height 1080 | } 1081 | Orientation.Horizontal -> { 1082 | viewportSize.width 1083 | } 1084 | } 1085 | 1086 | if (visibleSize > side && visibleSize <= side + minLength) { 1087 | val finalLength = (visibleSize - side).toFloat() 1088 | 1089 | if (finalLength > 0) { 1090 | return@with finalLength 1091 | } 1092 | } 1093 | 1094 | lerpLength 1095 | } 1096 | 1097 | /** 1098 | * Interpolates the fading edges length difference in partial content state. 1099 | * 1100 | * @param layoutInfo The [LazyGridItemInfo]. 1101 | * @param spanCount The grid span count. 1102 | * @param lerpLength The default partial content fading edges length difference. 1103 | * @param minLength The minimum fading edges length. 1104 | * @return The interpolated fading edges length difference. 1105 | * @see FadingEdgesScrollConfig.Dynamic 1106 | * @see FadingEdgesContentType.Dynamic.Lazy.Grid 1107 | * @see FadingEdgesScrollConfigDefaults.Dynamic.IsLerpByDifferenceForPartialContent 1108 | * @author GIGAMOLE 1109 | */ 1110 | internal fun lerpLazyGridPartialContentByDifference( 1111 | layoutInfo: LazyGridLayoutInfo, 1112 | spanCount: Int, 1113 | lerpLength: Float, 1114 | minLength: Float 1115 | ): Float = with(layoutInfo) { 1116 | val entriesCount = ceil(totalItemsCount.toFloat() / spanCount.toFloat()).toInt() 1117 | val visibleSize = visibleItemsInfo.chunked(size = spanCount) { chunk -> 1118 | chunk.maxOf { 1119 | when (orientation) { 1120 | Orientation.Vertical -> { 1121 | it.size.height 1122 | } 1123 | Orientation.Horizontal -> { 1124 | it.size.width 1125 | } 1126 | } 1127 | } 1128 | }.sum() + 1129 | beforeContentPadding + 1130 | afterContentPadding + 1131 | mainAxisItemSpacing * (entriesCount - 1) 1132 | 1133 | val side = when (orientation) { 1134 | Orientation.Vertical -> { 1135 | viewportSize.height 1136 | } 1137 | Orientation.Horizontal -> { 1138 | viewportSize.width 1139 | } 1140 | } 1141 | 1142 | if (visibleSize > side && visibleSize <= side + minLength) { 1143 | val finalLength = (visibleSize - side).toFloat() 1144 | 1145 | if (finalLength > 0) { 1146 | return@with finalLength 1147 | } 1148 | } 1149 | 1150 | lerpLength 1151 | } 1152 | 1153 | /** 1154 | * Interpolates the fading edges length difference in partial content state. 1155 | * 1156 | * @param layoutInfo The [LazyStaggeredGridLayoutInfo]. 1157 | * @param lerpLength The default partial content fading edges length difference. 1158 | * @param minLength The minimum fading edges length. 1159 | * @return The interpolated fading edges length difference. 1160 | * @see FadingEdgesScrollConfig.Dynamic 1161 | * @see FadingEdgesContentType.Dynamic.Lazy.Grid 1162 | * @see FadingEdgesScrollConfigDefaults.Dynamic.IsLerpByDifferenceForPartialContent 1163 | * @author GIGAMOLE 1164 | */ 1165 | internal fun lerpLazyStaggeredGridPartialContentByDifference( 1166 | layoutInfo: LazyStaggeredGridLayoutInfo, 1167 | lerpLength: Float, 1168 | minLength: Float 1169 | ): Float = with(layoutInfo) { 1170 | val maxVisibleLaneInfo = visibleItemsInfo 1171 | .groupBy { it.lane } 1172 | .map { entry -> 1173 | entry.value.size to entry.value.sumOf { 1174 | when (orientation) { 1175 | Orientation.Vertical -> { 1176 | it.size.height 1177 | } 1178 | Orientation.Horizontal -> { 1179 | it.size.width 1180 | } 1181 | } 1182 | } 1183 | }.maxBy { it.second } 1184 | val visibleSize = maxVisibleLaneInfo.second + 1185 | beforeContentPadding + 1186 | afterContentPadding + 1187 | mainAxisItemSpacing * (maxVisibleLaneInfo.first - 1) 1188 | 1189 | val side = when (orientation) { 1190 | Orientation.Vertical -> { 1191 | viewportSize.height 1192 | } 1193 | Orientation.Horizontal -> { 1194 | viewportSize.width 1195 | } 1196 | } 1197 | 1198 | if (visibleSize > side && visibleSize <= side + minLength) { 1199 | val finalLength = (visibleSize - side).toFloat() 1200 | 1201 | if (finalLength > 0) { 1202 | return@with finalLength 1203 | } 1204 | } 1205 | 1206 | lerpLength 1207 | } 1208 | 1209 | /** 1210 | * Draws a single fading edge fill gradient for [fadingEdges]. 1211 | * 1212 | * @param orientation The [FadingEdgesOrientation]. 1213 | * @param isStartGravity Determines whether the fading edge is aligned with the start gravity. 1214 | * @param colorStops An array of color stops specifying the fading edge fill gradient. 1215 | * @param isClip Determines whether the fading edge fill gradient using a clipping mask. 1216 | * @param length The fading edge length. 1217 | * @author GIGAMOLE 1218 | */ 1219 | private fun ContentDrawScope.drawFadingEdge( 1220 | orientation: FadingEdgesOrientation, 1221 | isStartGravity: Boolean, 1222 | colorStops: Array>, 1223 | isClip: Boolean, 1224 | length: Float 1225 | ) { 1226 | val blendMode = if (isClip) { 1227 | BlendMode.DstOut 1228 | } else { 1229 | DrawScope.DefaultBlendMode 1230 | } 1231 | 1232 | when (orientation) { 1233 | FadingEdgesOrientation.Vertical -> { 1234 | val topLeft = if (isStartGravity) { 1235 | Offset.Zero 1236 | } else { 1237 | Offset( 1238 | x = 0.0F, 1239 | y = size.height - length 1240 | ) 1241 | } 1242 | 1243 | drawRect( 1244 | brush = Brush.verticalGradient( 1245 | colorStops = colorStops, 1246 | startY = if (isStartGravity) { 1247 | 0.0F 1248 | } else { 1249 | size.height - length 1250 | }, 1251 | endY = if (isStartGravity) { 1252 | length 1253 | } else { 1254 | size.height 1255 | }, 1256 | ), 1257 | topLeft = topLeft, 1258 | size = if (isStartGravity) { 1259 | Size( 1260 | width = size.width, 1261 | height = length 1262 | ) 1263 | } else { 1264 | Size( 1265 | width = size.width - topLeft.x, 1266 | height = size.height - topLeft.y 1267 | ) 1268 | }, 1269 | blendMode = blendMode 1270 | ) 1271 | } 1272 | FadingEdgesOrientation.Horizontal -> { 1273 | val topLeft = if (isStartGravity) { 1274 | Offset.Zero 1275 | } else { 1276 | Offset( 1277 | x = size.width - length, 1278 | y = 0.0F 1279 | ) 1280 | } 1281 | 1282 | drawRect( 1283 | brush = Brush.horizontalGradient( 1284 | colorStops = colorStops, 1285 | startX = if (isStartGravity) { 1286 | 0.0F 1287 | } else { 1288 | size.width - length 1289 | }, 1290 | endX = if (isStartGravity) { 1291 | length 1292 | } else { 1293 | size.width 1294 | }, 1295 | ), 1296 | topLeft = topLeft, 1297 | size = if (isStartGravity) { 1298 | Size( 1299 | width = length, 1300 | height = size.height 1301 | ) 1302 | } else { 1303 | Size( 1304 | width = size.width - topLeft.x, 1305 | height = size.height - topLeft.y 1306 | ) 1307 | }, 1308 | blendMode = blendMode 1309 | ) 1310 | } 1311 | } 1312 | } 1313 | -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/FadingEdgesDefaults.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges 2 | 3 | import androidx.compose.ui.unit.Dp 4 | import androidx.compose.ui.unit.dp 5 | import com.gigamole.composefadingedges.fill.FadingEdgesFillType 6 | 7 | /** 8 | * The default values for [fadingEdges]. 9 | * 10 | * @see FadingEdgesGravity 11 | * @see FadingEdgesFillType 12 | * @author GIGAMOLE 13 | */ 14 | object FadingEdgesDefaults { 15 | 16 | /** The default fading edges [FadingEdgesGravity]. */ 17 | val Gravity: FadingEdgesGravity = FadingEdgesGravity.All 18 | 19 | /** The default fading edges length. */ 20 | val Length: Dp = 16.dp 21 | 22 | /** The default fading edges [FadingEdgesFillType]. */ 23 | val FillType: FadingEdgesFillType = FadingEdgesFillType.FadeClip() 24 | 25 | /** 26 | * Determines whether the [horizontalFadingEdges] and horizontal text marquee should be automatically aligned in the layout process (due to the additional text 27 | * paddings for a correct fading edges drawing). 28 | * 29 | * @see marqueeHorizontalFadingEdges 30 | */ 31 | const val IsMarqueeAutoLayout: Boolean = true 32 | 33 | /** 34 | * Determines whether the [horizontalFadingEdges] and horizontal text marquee should apply paddings paddings according to the gravity and length. 35 | * 36 | * @see marqueeHorizontalFadingEdges 37 | */ 38 | const val IsMarqueeAutoPadding: Boolean = true 39 | 40 | } -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/FadingEdgesGravity.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges 2 | 3 | import com.gigamole.composefadingedges.content.FadingEdgesContentType 4 | import com.gigamole.composefadingedges.fill.FadingEdgesFillType 5 | 6 | /** 7 | * The fading edges gravity for [fadingEdges]. 8 | * 9 | * @see FadingEdgesContentType 10 | * @see FadingEdgesFillType 11 | * @see FadingEdgesGravity 12 | * @author GIGAMOLE 13 | */ 14 | enum class FadingEdgesGravity { 15 | 16 | /** The fading edges at the [Start] and [End] gravity. */ 17 | All, 18 | 19 | /** The fading edge at the [Start] gravity. */ 20 | Start, 21 | 22 | /** The fading edge at the [End] gravity. */ 23 | End; 24 | 25 | /** 26 | * Util function to check whether the fading edge at the [Start] gravity. 27 | * 28 | * @return The indication whether the fading edge at the [Start] or not. 29 | */ 30 | fun isStart(): Boolean = this == All || this == Start 31 | 32 | /** 33 | * Util function to check whether the fading edge at the [End] gravity. 34 | * 35 | * @return The indication whether the fading edge at the [End] or not. 36 | */ 37 | fun isEnd(): Boolean = this == All || this == End 38 | } -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/FadingEdgesOrientation.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges 2 | 3 | import com.gigamole.composefadingedges.content.FadingEdgesContentType 4 | import com.gigamole.composefadingedges.fill.FadingEdgesFillType 5 | 6 | /** 7 | * The fading edges orientation for [fadingEdges]. 8 | * 9 | * @see FadingEdgesContentType 10 | * @see FadingEdgesFillType 11 | * @see FadingEdgesGravity 12 | * @author GIGAMOLE 13 | */ 14 | enum class FadingEdgesOrientation { 15 | 16 | /** The fading edges for a vertical content orientation. */ 17 | Vertical, 18 | 19 | /** The fading edges for a horizontal content orientation. */ 20 | Horizontal 21 | } -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/content/FadingEdgesContentType.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.content 2 | 3 | import androidx.compose.foundation.ScrollState 4 | import androidx.compose.foundation.lazy.LazyListState 5 | import androidx.compose.foundation.lazy.grid.LazyGridState 6 | import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState 7 | import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfig 8 | import com.gigamole.composefadingedges.fadingEdges 9 | import com.gigamole.composefadingedges.fill.FadingEdgesFillType 10 | 11 | /** 12 | * The fading edges content type for [fadingEdges]. 13 | * 14 | * @see FadingEdgesScrollConfig 15 | * @see FadingEdgesFillType 16 | * @author GIGAMOLE 17 | */ 18 | sealed interface FadingEdgesContentType { 19 | 20 | /** The fading edges for a static content. The fading edges are always fully shown. */ 21 | data object Static : FadingEdgesContentType 22 | 23 | /** The fading edges for a dynamic content. The fading edges are dynamic by the scroll amount. */ 24 | sealed interface Dynamic : FadingEdgesContentType { 25 | 26 | /** The fading edges scroll configuration. */ 27 | val scrollConfig: FadingEdgesScrollConfig 28 | 29 | /** Indicates whether the scroll is possible. */ 30 | val isScrollPossible: Boolean 31 | 32 | /** 33 | * The fading edges for a [ScrollState] scrollable content. 34 | * 35 | * @property scrollConfig The fading edges scroll configuration. 36 | * @property state The scrollable container state. 37 | */ 38 | data class Scroll( 39 | override val scrollConfig: FadingEdgesScrollConfig = FadingEdgesContentTypeDefaults.ScrollConfig, 40 | val state: ScrollState 41 | ) : Dynamic { 42 | 43 | /** Indicates whether the scroll is possible. */ 44 | override val isScrollPossible: Boolean 45 | get() = state.canScrollForward || state.canScrollBackward 46 | } 47 | 48 | /** The fading edges for a dynamic lazy content. */ 49 | sealed interface Lazy : Dynamic { 50 | 51 | /** 52 | * The fading edges for a [LazyListState] scrollable content. 53 | * 54 | * @property scrollConfig The fading edges scroll configuration. 55 | * @property state The lazy list state. 56 | */ 57 | data class List( 58 | override val scrollConfig: FadingEdgesScrollConfig = FadingEdgesContentTypeDefaults.ScrollConfig, 59 | val state: LazyListState 60 | ) : Lazy { 61 | 62 | /** Indicates whether the scroll is possible. */ 63 | override val isScrollPossible: Boolean 64 | get() = state.canScrollForward || state.canScrollBackward 65 | } 66 | 67 | /** 68 | * The fading edges for a [LazyGridState] scrollable content. 69 | * 70 | * @property scrollConfig The fading edges scroll configuration. 71 | * @property state The lazy list state. 72 | * @property spanCount The grid span count. 73 | */ 74 | data class Grid( 75 | override val scrollConfig: FadingEdgesScrollConfig = FadingEdgesContentTypeDefaults.ScrollConfig, 76 | val state: LazyGridState, 77 | val spanCount: Int 78 | ) : Lazy { 79 | 80 | /** Indicates whether the scroll is possible. */ 81 | override val isScrollPossible: Boolean 82 | get() = state.canScrollForward || state.canScrollBackward 83 | } 84 | 85 | /** 86 | * The fading edges for a [LazyStaggeredGridState] scrollable content. 87 | * 88 | * The ComposeFadingEdges library highly recommends to use a [scrollConfig] with a provided [FadingEdgesScrollConfig.animationSpec], because staggered layout 89 | * sometimes can be unpredicted and some items can be placed in the way that the interpolation between these items is too short or almost zero, which causes 90 | * some fading edges jumps. Also, [LazyStaggeredGridState.layoutInfo] item spacing, before and after content paddings, causes some extra calculations, so if 91 | * it is possible, add these padding to the items/cards instead, to improve performance. 92 | * 93 | * @property scrollConfig The fading edges scroll configuration. 94 | * @property state The lazy list state. 95 | * @property spanCount The staggered grid span count. 96 | */ 97 | data class StaggeredGrid( 98 | override val scrollConfig: FadingEdgesScrollConfig = FadingEdgesContentTypeDefaults.ScrollConfig, 99 | val state: LazyStaggeredGridState, 100 | val spanCount: Int 101 | ) : Lazy { 102 | 103 | /** Indicates whether the scroll is possible. */ 104 | override val isScrollPossible: Boolean 105 | get() = state.canScrollForward || state.canScrollBackward 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/content/FadingEdgesContentTypeDefaults.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.content 2 | 3 | import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfig 4 | 5 | /** 6 | * The default values for [FadingEdgesContentType]. 7 | * 8 | * @author GIGAMOLE 9 | */ 10 | object FadingEdgesContentTypeDefaults { 11 | 12 | /** The default [FadingEdgesScrollConfig]. */ 13 | val ScrollConfig: FadingEdgesScrollConfig = FadingEdgesScrollConfig.Static() 14 | } -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/content/scrollconfig/FadingEdgesScrollConfig.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.content.scrollconfig 2 | 3 | import androidx.compose.animation.core.AnimationSpec 4 | import com.gigamole.composefadingedges.content.FadingEdgesContentType 5 | import com.gigamole.composefadingedges.fadingEdges 6 | import com.gigamole.composefadingedges.lerpLazyListPartialContentByDifference 7 | 8 | /** 9 | * The fading edges scroll config for [FadingEdgesContentType.Dynamic] types. 10 | * 11 | * The ComposeFadingEdges advises using an animation (custom or default [FadingEdgesScrollConfigDefaults.AnimationSpec]) to provide an interactive fading edges size 12 | * transitions (due to the dynamic items size or partial content). 13 | * 14 | * @see FadingEdgesContentType 15 | * @see fadingEdges 16 | * @author GIGAMOLE 17 | */ 18 | sealed interface FadingEdgesScrollConfig { 19 | 20 | val animationSpec: AnimationSpec? 21 | 22 | /** 23 | * The dynamic fading edges scroll configuration interpolates their lengths on the scroll offset. The fading edges fade in when the content is away from the edges and 24 | * fade out as it gets closer. The fading edges are fully hidden when the content size is not scrollable. 25 | * 26 | * The [isLerpByDifferenceForPartialContent] functionality is useful when the [FadingEdgesContentType.Dynamic] combined content size (items size and offset) is 27 | * slightly larger than the viewport size but smaller than the fading edges length. By enabling this feature, the fading edges can smoothly interpolate their scroll 28 | * offset length by considering the partial content difference, rather than fading edges length. 29 | * 30 | * @property animationSpec The fading edges scroll animation specification. 31 | * @property isLerpByDifferenceForPartialContent Determines whether the fading edges should interpolate their scroll offset length for partial content. 32 | * @property scrollFactor The fading edges scroll factor. 33 | * @see FadingEdgesScrollConfigDefaults.Dynamic.IsLerpByDifferenceForPartialContent 34 | * @see fadingEdges 35 | * @see lerpLazyListPartialContentByDifference 36 | */ 37 | data class Dynamic( 38 | override val animationSpec: AnimationSpec? = FadingEdgesScrollConfigDefaults.AnimationSpec, 39 | val isLerpByDifferenceForPartialContent: Boolean = FadingEdgesScrollConfigDefaults.Dynamic.IsLerpByDifferenceForPartialContent, 40 | val scrollFactor: Float = FadingEdgesScrollConfigDefaults.Dynamic.ScrollFactor, 41 | ) : FadingEdgesScrollConfig 42 | 43 | /** 44 | * The full show and hide length fading edges scroll configuration. The fading edges are fully shown when the content is out from the edges and fully hidden at the 45 | * edges. The fading edges are fully hidden when the content size is not scrollable. 46 | * 47 | * @property animationSpec The fading edges scroll animation specification. 48 | */ 49 | data class Full( 50 | override val animationSpec: AnimationSpec? = FadingEdgesScrollConfigDefaults.AnimationSpec 51 | ) : FadingEdgesScrollConfig 52 | 53 | /** 54 | * The static length fading edges scroll configuration. The fading edges are always fully shown, unless the content size is not scrollable. 55 | * 56 | * @property animationSpec The fading edges scroll animation specification. 57 | */ 58 | data class Static( 59 | override val animationSpec: AnimationSpec? = FadingEdgesScrollConfigDefaults.AnimationSpec 60 | ) : FadingEdgesScrollConfig 61 | } 62 | -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/content/scrollconfig/FadingEdgesScrollConfigDefaults.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.content.scrollconfig 2 | 3 | import androidx.compose.animation.core.AnimationSpec 4 | import androidx.compose.animation.core.spring 5 | import com.gigamole.composefadingedges.FadingEdgesDefaults 6 | import com.gigamole.composefadingedges.content.FadingEdgesContentType 7 | import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfigDefaults.AnimationSpec 8 | import com.gigamole.composefadingedges.fadingEdges 9 | import com.gigamole.composefadingedges.lerpLazyListPartialContentByDifference 10 | 11 | /** 12 | * The default values for [FadingEdgesScrollConfig]. 13 | * 14 | * @author GIGAMOLE 15 | */ 16 | object FadingEdgesScrollConfigDefaults { 17 | 18 | /** 19 | * The default fading edges scroll animation specification. 20 | * 21 | * The ComposeFadingEdges advises using an animation (custom or default [AnimationSpec]) to provide an interactive fading edges size transitions (due to the dynamic 22 | * items size or partial content). Especially useful when [Dynamic.IsLerpByDifferenceForPartialContent] is enabled. 23 | */ 24 | val AnimationSpec: AnimationSpec 25 | get() = spring() 26 | 27 | /** The default values for [FadingEdgesScrollConfig.Dynamic] type. */ 28 | object Dynamic { 29 | 30 | /** 31 | * Determines whether the fading edges should interpolate their scroll offset length for partial content. 32 | * 33 | * This functionality is useful when the [FadingEdgesContentType.Dynamic] combined content size (items size and offset) is slightly larger than the viewport size 34 | * but smaller than the fading edges length. By enabling this feature, the fading edges can smoothly interpolate their scroll offset length by considering the 35 | * partial content difference, rather than fading edges length. 36 | * 37 | * @see FadingEdgesDefaults 38 | * @see fadingEdges 39 | * @see lerpLazyListPartialContentByDifference 40 | */ 41 | const val IsLerpByDifferenceForPartialContent: Boolean = true 42 | 43 | /** The default fading edges scroll factor. */ 44 | const val ScrollFactor: Float = 1.25F 45 | } 46 | } -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/fill/FadingEdgesFillType.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.fill 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import com.gigamole.composefadingedges.content.FadingEdgesContentType 5 | import com.gigamole.composefadingedges.fadingEdges 6 | 7 | /** 8 | * The fading edges fill gradient type for [fadingEdges]. 9 | * 10 | * @see FadingEdgesContentType 11 | * @author GIGAMOLE 12 | */ 13 | sealed interface FadingEdgesFillType { 14 | 15 | val fillStops: Triple 16 | val secondStopAlpha: Float 17 | 18 | /** 19 | * The fill gradient using the clipping mechanism on a drawn content to create fading edges effect. 20 | * 21 | * The output clipping fill gradient is the following: 22 | * - [fillStops] first stop <-> Clip mask color with 1.0F. 23 | * - [fillStops] second stop <-> Clip mask color with [secondStopAlpha]. 24 | * - [fillStops] third stop <-> Clip mask color with 0.0F. 25 | * 26 | * And reversed for an opposite fading edge. 27 | * 28 | * @property fillStops The fading edges fill gradient stops. Range from 0.0F to 1.0F. 29 | * @property secondStopAlpha The fading edges second stop alpha. Range from 0.0F to 1.0F. 30 | * @see FadingEdgesFillType 31 | */ 32 | data class FadeClip( 33 | override val fillStops: Triple = FadingEdgesFillTypeDefaults.FillStops, 34 | override val secondStopAlpha: Float = FadingEdgesFillTypeDefaults.SecondStopAlpha, 35 | ) : FadingEdgesFillType 36 | 37 | /** 38 | * The fill gradient using drawing mechanism to draw fading edges [color] gradient over the content. 39 | * 40 | * The output drawing fill gradient is the following: 41 | * - [fillStops] first stop <-> [color] with 1.0F. 42 | * - [fillStops] second stop <-> [color] with [secondStopAlpha]. 43 | * - [fillStops] third stop <-> [color] with 0.0F. 44 | * 45 | * And reversed for an opposite fading edge. 46 | * 47 | * @property fillStops The fading edges fill gradient stops. Range from 0.0F to 1.0F. 48 | * @property secondStopAlpha The fading edges second stop alpha. Range from 0.0F to 1.0F. 49 | * @property color The fading edges fill gradient color. 50 | * @see FadingEdgesFillType 51 | */ 52 | data class FadeColor( 53 | override val fillStops: Triple = FadingEdgesFillTypeDefaults.FillStops, 54 | override val secondStopAlpha: Float = FadingEdgesFillTypeDefaults.SecondStopAlpha, 55 | val color: Color = FadingEdgesFillTypeDefaults.FadeColor.FadeColor 56 | ) : FadingEdgesFillType 57 | } 58 | -------------------------------------------------------------------------------- /ComposeFadingEdges/src/main/kotlin/com/gigamole/composefadingedges/fill/FadingEdgesFillTypeDefaults.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.fill 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | /** 6 | * The default values for [FadingEdgesFillType]. 7 | * 8 | * @author GIGAMOLE 9 | */ 10 | object FadingEdgesFillTypeDefaults { 11 | 12 | /** 13 | * The default fading edges fill gradient stops. Range from 0.0F to 1.0F. 14 | * 15 | * @see SecondStopAlpha 16 | */ 17 | val FillStops: Triple = Triple( 18 | first = 0.0F, 19 | second = 0.5F, 20 | third = 1.0F 21 | ) 22 | 23 | /** 24 | * The default fading edges second stop alpha. Range from 0.0F to 1.0F. 25 | * 26 | * @see SecondStopAlpha 27 | */ 28 | const val SecondStopAlpha: Float = 0.5F 29 | 30 | /** The default values for [FadingEdgesFillType.FadeColor]. */ 31 | object FadeColor { 32 | 33 | /** The default fading edges fill gradient color. */ 34 | val FadeColor: Color = Color.White 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 GIGAMOLE gigamole53@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](/media/header-new.png) 2 | 3 | ![](https://jitpack.io/v/GIGAMOLE/ComposeFadingEdges.svg?style=flat-square) | [Setup Guide](#setup) 4 | | [Report new issue](https://github.com/GIGAMOLE/ComposeFadingEdges/issues/new) 5 | 6 | # ComposeFadingEdges 7 | 8 | The `ComposeFadingEdges` is a powerful Android Compose library that seamlessly incorporates customisable fading edges with horizontal or vertical orientations, static or scrollable content, clip or color draw. 9 | 10 | ![](/media/demo.gif) 11 | 12 | Features: 13 | 14 | - **Multiple Modifiers:** Add fading edges with custom orientation, gravity, length, content and fill type. 15 | - **Scrolls States:** Ready for `ScrollState`, `LazyListState`, `LazyGridState`, `LazyStaggeredGridState`. 16 | - **Scrolling Settings:** Configure fading edges scroll behavior with dynamic, full, or static settings. 17 | - **Text Marquee:** Easily add fading edges to text marquee with automatic layout alignment. 18 | - **Adjustable Fade:** Choose between fade clip or fade color fill types for fading edges. 19 | - **Sample App:** Explore and experiment with [sample app](#sample-app). 20 | 21 | ## Sample App 22 | 23 | | Sample 1 | Sample 2 | Sample 3 | 24 | |-|-|-| 25 | | | | | 26 | 27 | Download or clone this repository to discover the sample app. 28 | 29 | ## Setup 30 | 31 | Add to the root `settings.gradle.kts`: 32 | 33 | ``` groovy 34 | dependencyResolutionManagement { 35 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 36 | repositories { 37 | mavenCentral() 38 | maven { url 'https://jitpack.io' } 39 | } 40 | } 41 | ``` 42 | 43 | Add to the package `build.gradle.kts`: 44 | 45 | ``` groovy 46 | dependencies { 47 | implementation("com.github.GIGAMOLE:ComposeFadingEdges:{latest-version}") 48 | } 49 | ``` 50 | 51 | Latest version: ![](https://jitpack.io/v/GIGAMOLE/ComposeFadingEdges.svg?style=flat-square). 52 | 53 | Also, it's possible to download the latest artifact from the [releases page](https://github.com/GIGAMOLE/ComposeFadingEdges/releases). 54 | 55 | ## Guide 56 | 57 | The `ComposeFadingEdges` comes with multiple `Modifiers`: [`Modifier.horizontalFadingEdges(...)`](#fading-edges-modifiers) 58 | , [`Modifier.verticalFadingEdges(...)`](#fading-edges-modifiers), etc. 59 | 60 | For more technical and detailed documentation, read the library `KDoc`. 61 | 62 | ### Fading Edges Modifiers 63 | 64 | The fading edges `Modifiers` are available for different content orientation and type. 65 | 66 | |Param|Description| 67 | |-|-| 68 | |`orientation`|The fading edges orientation: `Horizontal` or `Vertical`.| 69 | |`gravity`|The fading edges gravity: `All`, `Start`, or `End`.| 70 | |`length`|The fading edges length.| 71 | |`contentType`|The [`FadingEdgesContentType`](#fadingedgescontentType).| 72 | |`fillType`|The [`FadingEdgesFillType`](#fadingedgesfilltype).| 73 | 74 | In case, `contentType` is not provided, the `FadingEdgesContentType` equals to `Static`. 75 | 76 | The `ComposeFadingEdges` also provides fading edges for [text marquee](#modifiermarqueehorizontalfadingedges). 77 | 78 | ### FadingEdgesContentType 79 | 80 | The `FadingEdgesContentType` can be `Static` or `Dynamic`. 81 | 82 | The `Dynamic` content sub-type can be the following: 83 | 84 | - `Scroll`: The dynamic fading edges for a `ScrollState` content. 85 | - `Lazy.List`: The dynamic fading edges for a `LazyListState` content. 86 | - `Lazy.Grid`: The dynamic fading edges for a `LazyGridState` content. 87 | - `Lazy.StaggeredGrid`: The dynamic fading edges for a `LazyStaggeredGridState` content. 88 | 89 | The `Dynamic` content type requires [`FadingEdgesScrollConfig`](#fadingedgesscrollconfig). 90 | 91 | #### FadingEdgesScrollConfig 92 | 93 | The `FadingEdgesScrollConfig` can be `Dynamic`, `Full` or `Static`. 94 | 95 | All of them can configure fading edges scroll based offset `animationSpec`. 96 | 97 | The `Dynamic` configuration comes with additional params: 98 | 99 | |Param|Description| 100 | |-|-| 101 | |`isLerpByDifferenceForPartialContent`|Determines whether the fading edges should interpolate their scroll offset length for partial content.| 102 | |`scrollFactor`|The fading edges scroll factor.| 103 | 104 | ### FadingEdgesFillType 105 | 106 | The `FadingEdgesFillType` can be `FadeClip` or `FadeColor`. 107 | 108 | The fading edges fill gradient is automatically faded from max alpha to min alpha. 109 | 110 | All of them can configure fading edges fill gradient `fillStops` and `secondStopAlpha`. 111 | 112 | The `FadeClip` type clips the content with the fading edges fill gradient. 113 | 114 | The `FadeColor` type requires a `color` param and draws the fading edges fill gradient over the content. 115 | 116 | ### Modifier.marqueeHorizontalFadingEdges 117 | 118 | The utility `Modifier` to add fading edges to the text marquee (custom or default `basicMarquee`). 119 | 120 | This `Modifier` comes with common [fading edges params](#fading-edges-modifiers) and a few additional params: 121 | 122 | |Param|Description| 123 | |-|-| 124 | |`isMarqueeAutoLayout`|Determines whether the `horizontalFadingEdges(...)` and text marquee should be automatically aligned during the layout process to accommodate additional text paddings required for proper fading edges drawing.| 125 | |`isMarqueeAutoPadding`|Determines if padding values according to the `gravity` and `length` should be applied.| 126 | |`marqueeProvider`|The custom or default `basicMarquee` provider.| 127 | 128 | ## License 129 | 130 | MIT License. See the [LICENSE](https://github.com/GIGAMOLE/ComposeFadingEdges/blob/master/LICENSE) file for more details. 131 | 132 | ## Credits 133 | 134 | Special thanks to the [GoDaddy](https://github.com/godaddy) for the amazing [color picker library](https://github.com/godaddy/compose-color-picker). 135 | 136 | ## Author: 137 | 138 | [Basil Miller](https://www.linkedin.com/in/gigamole/) 139 | [gigamole53@gmail.com](mailto:gigamole53@gmail.com) 140 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | @Suppress( 4 | "DSL_SCOPE_VIOLATION", 5 | "MISSING_DEPENDENCY_CLASS", 6 | "UNRESOLVED_REFERENCE_WRONG_RECEIVER", 7 | "FUNCTION_CALL_EXPECTED" 8 | ) 9 | 10 | plugins { 11 | id("composefadingedges.application") 12 | } 13 | 14 | android { 15 | val appId = "${ProjectConfig.namespace}.sample" 16 | 17 | namespace = appId 18 | 19 | defaultConfig { 20 | applicationId = appId 21 | } 22 | 23 | composeOptions { 24 | kotlinCompilerExtensionVersion = libs.versions.compose.compiler.version.get() 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation(projects.composeFadingEdges) 30 | 31 | implementation(libs.compose.color.picker) 32 | 33 | implementation(libs.androidx.ktx) 34 | 35 | implementation(platform(libs.compose.bom)) 36 | implementation(libs.bundles.compose) 37 | implementation(libs.compose.ui.util) 38 | 39 | debugImplementation(libs.bundles.debug.compose) 40 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep,includedescriptorclasses class net.sqlcipher.** { *; } 24 | -keep,includedescriptorclasses interface net.sqlcipher.** { *; } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/kotlin/com/gigamole/composefadingedges/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, ExperimentalFoundationApi::class) 2 | 3 | package com.gigamole.composefadingedges.sample 4 | 5 | import android.os.Bundle 6 | import androidx.activity.ComponentActivity 7 | import androidx.activity.compose.setContent 8 | import androidx.compose.animation.Crossfade 9 | import androidx.compose.animation.core.AnimationSpec 10 | import androidx.compose.animation.core.spring 11 | import androidx.compose.animation.core.tween 12 | import androidx.compose.foundation.ExperimentalFoundationApi 13 | import androidx.compose.foundation.MarqueeSpacing 14 | import androidx.compose.foundation.background 15 | import androidx.compose.foundation.basicMarquee 16 | import androidx.compose.foundation.border 17 | import androidx.compose.foundation.clickable 18 | import androidx.compose.foundation.horizontalScroll 19 | import androidx.compose.foundation.layout.Arrangement 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.IntrinsicSize 23 | import androidx.compose.foundation.layout.PaddingValues 24 | import androidx.compose.foundation.layout.Row 25 | import androidx.compose.foundation.layout.Spacer 26 | import androidx.compose.foundation.layout.aspectRatio 27 | import androidx.compose.foundation.layout.fillMaxHeight 28 | import androidx.compose.foundation.layout.fillMaxSize 29 | import androidx.compose.foundation.layout.fillMaxWidth 30 | import androidx.compose.foundation.layout.height 31 | import androidx.compose.foundation.layout.padding 32 | import androidx.compose.foundation.layout.requiredSizeIn 33 | import androidx.compose.foundation.layout.width 34 | import androidx.compose.foundation.lazy.LazyColumn 35 | import androidx.compose.foundation.lazy.LazyRow 36 | import androidx.compose.foundation.lazy.grid.GridCells 37 | import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid 38 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 39 | import androidx.compose.foundation.lazy.grid.rememberLazyGridState 40 | import androidx.compose.foundation.lazy.rememberLazyListState 41 | import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid 42 | import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid 43 | import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells 44 | import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState 45 | import androidx.compose.foundation.rememberScrollState 46 | import androidx.compose.foundation.shape.RoundedCornerShape 47 | import androidx.compose.foundation.verticalScroll 48 | import androidx.compose.material3.Button 49 | import androidx.compose.material3.Divider 50 | import androidx.compose.material3.ExperimentalMaterial3Api 51 | import androidx.compose.material3.MaterialTheme 52 | import androidx.compose.material3.ModalBottomSheet 53 | import androidx.compose.material3.Slider 54 | import androidx.compose.material3.Switch 55 | import androidx.compose.material3.Tab 56 | import androidx.compose.material3.TabRow 57 | import androidx.compose.material3.Text 58 | import androidx.compose.material3.rememberModalBottomSheetState 59 | import androidx.compose.runtime.Composable 60 | import androidx.compose.runtime.LaunchedEffect 61 | import androidx.compose.runtime.derivedStateOf 62 | import androidx.compose.runtime.getValue 63 | import androidx.compose.runtime.mutableFloatStateOf 64 | import androidx.compose.runtime.mutableIntStateOf 65 | import androidx.compose.runtime.mutableStateOf 66 | import androidx.compose.runtime.remember 67 | import androidx.compose.runtime.rememberCoroutineScope 68 | import androidx.compose.runtime.setValue 69 | import androidx.compose.ui.Alignment 70 | import androidx.compose.ui.Modifier 71 | import androidx.compose.ui.draw.clipToBounds 72 | import androidx.compose.ui.graphics.Color 73 | import androidx.compose.ui.text.font.FontWeight 74 | import androidx.compose.ui.unit.dp 75 | import com.gigamole.composefadingedges.FadingEdgesDefaults 76 | import com.gigamole.composefadingedges.FadingEdgesGravity 77 | import com.gigamole.composefadingedges.FadingEdgesOrientation 78 | import com.gigamole.composefadingedges.content.FadingEdgesContentType 79 | import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfig 80 | import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfigDefaults 81 | import com.gigamole.composefadingedges.fill.FadingEdgesFillType 82 | import com.gigamole.composefadingedges.fill.FadingEdgesFillTypeDefaults 83 | import com.gigamole.composefadingedges.horizontalFadingEdges 84 | import com.gigamole.composefadingedges.marqueeHorizontalFadingEdges 85 | import com.gigamole.composefadingedges.verticalFadingEdges 86 | import com.godaddy.android.colorpicker.ClassicColorPicker 87 | import com.godaddy.android.colorpicker.HsvColor 88 | import kotlinx.coroutines.delay 89 | import kotlinx.coroutines.launch 90 | import kotlin.random.Random 91 | 92 | private val ALPHANUMERIC = ('A'..'Z') + ('a'..'z') + ('0'..'9') 93 | 94 | class MainActivity : ComponentActivity() { 95 | 96 | override fun onCreate(savedInstanceState: Bundle?) { 97 | super.onCreate(savedInstanceState) 98 | 99 | setContent { 100 | MainScreen() 101 | } 102 | } 103 | } 104 | 105 | @Composable 106 | private fun MainScreen() { 107 | MainTheme { 108 | MainScreenContent() 109 | // MainScreenDemoContent() 110 | } 111 | } 112 | 113 | @Suppress("unused") 114 | @Composable 115 | fun MainScreenDemoContent() { 116 | val initialDelay = 1000L 117 | val scrollDuration = 900L 118 | val fadingEdgesDuration = 450L 119 | val fadingEdgeInitialScrollLength = 50 120 | val fadingEdgeScrollLength = 90 121 | val coroutineScope = rememberCoroutineScope() 122 | val scrollState = rememberScrollState(initial = fadingEdgeInitialScrollLength) 123 | val scrollToAnimationSpec = tween(durationMillis = scrollDuration.toInt()) 124 | val fadingEdgesAnimationSpec = tween(durationMillis = fadingEdgesDuration.toInt()) 125 | var areStaticFadingEdges by remember { mutableStateOf(false) } 126 | val fadingEdgesScrollConfig by remember(areStaticFadingEdges) { 127 | derivedStateOf { 128 | if (areStaticFadingEdges) { 129 | FadingEdgesScrollConfig.Static(animationSpec = fadingEdgesAnimationSpec) 130 | } else { 131 | FadingEdgesScrollConfig.Dynamic(animationSpec = fadingEdgesAnimationSpec) 132 | } 133 | } 134 | } 135 | 136 | LaunchedEffect(Unit) { 137 | coroutineScope.launch { 138 | areStaticFadingEdges = false 139 | delay(initialDelay) 140 | kotlinx.coroutines.coroutineScope { 141 | scrollState.animateScrollTo( 142 | value = fadingEdgeScrollLength, 143 | animationSpec = scrollToAnimationSpec 144 | ) 145 | } 146 | delay(scrollDuration) 147 | kotlinx.coroutines.coroutineScope { 148 | scrollState.animateScrollTo( 149 | value = 0, 150 | animationSpec = scrollToAnimationSpec 151 | ) 152 | } 153 | delay(scrollDuration) 154 | areStaticFadingEdges = true 155 | kotlinx.coroutines.coroutineScope { 156 | scrollState.animateScrollTo( 157 | value = fadingEdgeScrollLength, 158 | animationSpec = scrollToAnimationSpec 159 | ) 160 | } 161 | delay(scrollDuration) 162 | kotlinx.coroutines.coroutineScope { 163 | scrollState.animateScrollTo( 164 | value = fadingEdgeInitialScrollLength, 165 | animationSpec = scrollToAnimationSpec 166 | ) 167 | } 168 | delay(scrollDuration) 169 | areStaticFadingEdges = false 170 | } 171 | } 172 | 173 | Box( 174 | modifier = Modifier 175 | .fillMaxSize() 176 | .background(color = Color.White), 177 | contentAlignment = Alignment.Center 178 | ) { 179 | Column( 180 | verticalArrangement = Arrangement.spacedBy( 181 | space = 16.dp, 182 | alignment = Alignment.CenterVertically 183 | ) 184 | ) { 185 | Box( 186 | modifier = Modifier 187 | .height(height = 170.dp) 188 | .verticalFadingEdges( 189 | contentType = FadingEdgesContentType.Dynamic.Scroll( 190 | state = scrollState, 191 | scrollConfig = fadingEdgesScrollConfig 192 | ) 193 | ) 194 | .verticalScroll(state = scrollState) 195 | ) { 196 | Text( 197 | modifier = Modifier 198 | .width(width = 250.dp) 199 | .background(color = Color(0xFF9FDAFF)) 200 | .padding( 201 | horizontal = 32.dp, 202 | vertical = 32.dp 203 | ), 204 | text = "Compose\nFading\nEdges", 205 | color = Color(0xFF007AC9), 206 | style = MaterialTheme.typography.displaySmall, 207 | fontWeight = FontWeight.Bold, 208 | fontFamily = FontFamilySpaceGrotesk 209 | ) 210 | } 211 | 212 | Box { 213 | Text( 214 | modifier = Modifier 215 | .width(width = 250.dp) 216 | .marqueeHorizontalFadingEdges(isMarqueeAutoLayout = false) { 217 | Modifier 218 | .background(color = Color(0xFFFEECF4)) 219 | .basicMarquee( 220 | delayMillis = initialDelay.toInt(), 221 | spacing = MarqueeSpacing.fractionOfContainer(fraction = 0.05F), 222 | velocity = 60.dp 223 | ) 224 | .padding( 225 | horizontal = 16.dp, 226 | vertical = 8.dp 227 | ) 228 | }, 229 | text = "Enrich Android Compose UI with fading edges", 230 | color = Color(0xFFE21776), 231 | style = MaterialTheme.typography.labelLarge, 232 | fontWeight = FontWeight.Normal, 233 | fontFamily = FontFamilyOpenSans 234 | ) 235 | } 236 | } 237 | } 238 | } 239 | 240 | val SampleBgColors = listOf( 241 | Color(0xFF00A6FB), 242 | Color(0xFF0582CA), 243 | Color(0xFF006494), 244 | Color(0xFF003554), 245 | Color(0xFF051923), 246 | ) 247 | 248 | @Composable 249 | fun MainScreenContent() { 250 | val rawStaticScrollState = rememberScrollState() 251 | val rawScrollState = rememberScrollState() 252 | val rawLazyListState = rememberLazyListState() 253 | val rawLazyGridState = rememberLazyGridState() 254 | val rawLazyStaggeredGridState = rememberLazyStaggeredGridState() 255 | val configScrollState = rememberScrollState() 256 | val coroutineScope = rememberCoroutineScope() 257 | 258 | var sampleContentType by remember { mutableStateOf(SampleContentType.Static) } 259 | var itemsCount by remember { mutableIntStateOf(10) } 260 | var gridItemsCount by remember { mutableIntStateOf(20) } 261 | var itemAdditionalSize by remember { mutableStateOf(0.dp) } 262 | var itemsSpacing by remember { mutableStateOf(0.dp) } 263 | var itemsRandomHeight by remember { mutableStateOf(false) } 264 | var isColorPickerVisible by remember { mutableStateOf(false) } 265 | var randomSeed by remember { mutableIntStateOf(0) } 266 | var fadingEdgesSampleGridSpanCount by remember { mutableStateOf(SampleGridSpanCount.Span_2) } 267 | 268 | var fadingEdgesOrientation by remember { mutableStateOf(FadingEdgesOrientation.Vertical) } 269 | var fadingEdgesGravity by remember { mutableStateOf(FadingEdgesDefaults.Gravity) } 270 | var fadingEdgesLength by remember { mutableStateOf(FadingEdgesDefaults.Length) } 271 | var fadingEdgesSampleScrollConfig by remember { mutableStateOf(SampleScrollConfig.Static) } 272 | var fadingEdgesSampleAnimationType by remember { mutableStateOf(SampleAnimationType.Spring) } 273 | var fadingEdgesDynamicScrollFactor by remember { mutableFloatStateOf(FadingEdgesScrollConfigDefaults.Dynamic.ScrollFactor) } 274 | var fadingEdgesDynamicIsLerpByDifferenceForPartialContent by remember { mutableStateOf(FadingEdgesScrollConfigDefaults.Dynamic.IsLerpByDifferenceForPartialContent) } 275 | var fadingEdgesSampleFillType by remember { mutableStateOf(SampleFillType.Clip) } 276 | var fadingEdgesFillStopFirst by remember { mutableFloatStateOf(FadingEdgesFillTypeDefaults.FillStops.first) } 277 | var fadingEdgesFillStopSecond by remember { mutableFloatStateOf(FadingEdgesFillTypeDefaults.FillStops.second) } 278 | var fadingEdgesFillStopThird by remember { mutableFloatStateOf(FadingEdgesFillTypeDefaults.FillStops.third) } 279 | var fadingEdgesFillSecondStopAlpha by remember { mutableFloatStateOf(FadingEdgesFillTypeDefaults.SecondStopAlpha) } 280 | var fadingEdgesColor by remember { mutableStateOf(FadingEdgesFillTypeDefaults.FadeColor.FadeColor) } 281 | var fadingEdgesMarqueeTextLength by remember { mutableIntStateOf(50) } 282 | var fadingEdgesIsMarqueeAutoLayout by remember { mutableStateOf(FadingEdgesDefaults.IsMarqueeAutoLayout) } 283 | var fadingEdgesIsMarqueeAutoPadding by remember { mutableStateOf(FadingEdgesDefaults.IsMarqueeAutoPadding) } 284 | 285 | val fadingEdgesGridSpanCount by remember(fadingEdgesSampleGridSpanCount) { 286 | derivedStateOf { 287 | fadingEdgesSampleGridSpanCount.spanCount 288 | } 289 | } 290 | val fadingEdgesScrollAnimationSpec: AnimationSpec? by remember(fadingEdgesSampleAnimationType) { 291 | derivedStateOf { 292 | when (fadingEdgesSampleAnimationType) { 293 | SampleAnimationType.None -> { 294 | null 295 | } 296 | SampleAnimationType.Spring -> { 297 | spring() 298 | } 299 | SampleAnimationType.Tween_700 -> { 300 | tween(durationMillis = 700) 301 | } 302 | } 303 | } 304 | } 305 | val fadingEdgesMarqueeText by remember( 306 | fadingEdgesMarqueeTextLength 307 | ) { 308 | derivedStateOf { 309 | buildString { 310 | repeat(fadingEdgesMarqueeTextLength) { 311 | append(ALPHANUMERIC.random()) 312 | } 313 | } 314 | } 315 | } 316 | val fadingEdgesScrollConfig by remember( 317 | fadingEdgesSampleScrollConfig, 318 | fadingEdgesScrollAnimationSpec, 319 | fadingEdgesDynamicScrollFactor, 320 | fadingEdgesDynamicIsLerpByDifferenceForPartialContent 321 | ) { 322 | derivedStateOf { 323 | when (fadingEdgesSampleScrollConfig) { 324 | SampleScrollConfig.Static -> { 325 | FadingEdgesScrollConfig.Static(animationSpec = fadingEdgesScrollAnimationSpec) 326 | } 327 | SampleScrollConfig.Dynamic -> { 328 | FadingEdgesScrollConfig.Dynamic( 329 | animationSpec = fadingEdgesScrollAnimationSpec, 330 | isLerpByDifferenceForPartialContent = fadingEdgesDynamicIsLerpByDifferenceForPartialContent, 331 | scrollFactor = fadingEdgesDynamicScrollFactor 332 | ) 333 | } 334 | SampleScrollConfig.Full -> { 335 | FadingEdgesScrollConfig.Full(animationSpec = fadingEdgesScrollAnimationSpec) 336 | } 337 | } 338 | } 339 | } 340 | 341 | val fadingEdgesContentType by remember( 342 | sampleContentType, 343 | fadingEdgesScrollConfig, 344 | fadingEdgesGridSpanCount 345 | ) { 346 | derivedStateOf { 347 | when (sampleContentType) { 348 | SampleContentType.Static -> { 349 | FadingEdgesContentType.Static 350 | } 351 | SampleContentType.Scroll -> { 352 | FadingEdgesContentType.Dynamic.Scroll( 353 | state = rawScrollState, 354 | scrollConfig = fadingEdgesScrollConfig 355 | ) 356 | } 357 | SampleContentType.LazyList -> { 358 | FadingEdgesContentType.Dynamic.Lazy.List( 359 | state = rawLazyListState, 360 | scrollConfig = fadingEdgesScrollConfig 361 | ) 362 | } 363 | SampleContentType.LazyGrid -> { 364 | FadingEdgesContentType.Dynamic.Lazy.Grid( 365 | state = rawLazyGridState, 366 | scrollConfig = fadingEdgesScrollConfig, 367 | spanCount = fadingEdgesGridSpanCount 368 | ) 369 | } 370 | SampleContentType.LazyStgrGrid -> { 371 | FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid( 372 | state = rawLazyStaggeredGridState, 373 | scrollConfig = fadingEdgesScrollConfig, 374 | spanCount = fadingEdgesGridSpanCount 375 | ) 376 | } 377 | } 378 | } 379 | } 380 | 381 | val fadingEdgesFillThresholds by remember( 382 | fadingEdgesFillStopFirst, 383 | fadingEdgesFillStopSecond, 384 | fadingEdgesFillStopThird 385 | ) { 386 | derivedStateOf { 387 | Triple( 388 | first = fadingEdgesFillStopFirst, 389 | second = fadingEdgesFillStopSecond, 390 | third = fadingEdgesFillStopThird, 391 | ) 392 | } 393 | } 394 | val fadingEdgesFillType by remember( 395 | fadingEdgesSampleFillType, 396 | fadingEdgesFillThresholds, 397 | fadingEdgesFillSecondStopAlpha, 398 | fadingEdgesColor 399 | ) { 400 | derivedStateOf { 401 | when (fadingEdgesSampleFillType) { 402 | SampleFillType.Clip -> { 403 | FadingEdgesFillType.FadeClip( 404 | fillStops = fadingEdgesFillThresholds, 405 | secondStopAlpha = fadingEdgesFillSecondStopAlpha 406 | ) 407 | } 408 | SampleFillType.Color -> { 409 | FadingEdgesFillType.FadeColor( 410 | fillStops = fadingEdgesFillThresholds, 411 | secondStopAlpha = fadingEdgesFillSecondStopAlpha, 412 | color = fadingEdgesColor 413 | ) 414 | } 415 | } 416 | } 417 | } 418 | 419 | fun resetScrolls() { 420 | coroutineScope.launch { 421 | rawStaticScrollState.scrollTo(0) 422 | } 423 | coroutineScope.launch { 424 | rawScrollState.scrollTo(0) 425 | } 426 | coroutineScope.launch { 427 | rawLazyListState.scrollToItem(0) 428 | } 429 | coroutineScope.launch { 430 | rawLazyGridState.scrollToItem(0) 431 | } 432 | coroutineScope.launch { 433 | rawLazyStaggeredGridState.scrollToItem(0) 434 | } 435 | coroutineScope.launch { 436 | configScrollState.scrollTo(0) 437 | } 438 | } 439 | 440 | fun reset() { 441 | sampleContentType = SampleContentType.Static 442 | 443 | itemsCount = 10 444 | gridItemsCount = 20 445 | itemAdditionalSize = 0.dp 446 | itemsSpacing = 0.dp 447 | itemsRandomHeight = false 448 | isColorPickerVisible = false 449 | fadingEdgesSampleGridSpanCount = SampleGridSpanCount.Span_2 450 | 451 | fadingEdgesOrientation = FadingEdgesOrientation.Vertical 452 | fadingEdgesGravity = FadingEdgesDefaults.Gravity 453 | fadingEdgesLength = FadingEdgesDefaults.Length 454 | fadingEdgesSampleScrollConfig = SampleScrollConfig.Static 455 | fadingEdgesSampleAnimationType = SampleAnimationType.Spring 456 | fadingEdgesDynamicScrollFactor = FadingEdgesScrollConfigDefaults.Dynamic.ScrollFactor 457 | fadingEdgesDynamicIsLerpByDifferenceForPartialContent = FadingEdgesScrollConfigDefaults.Dynamic.IsLerpByDifferenceForPartialContent 458 | fadingEdgesSampleFillType = SampleFillType.Clip 459 | fadingEdgesFillStopFirst = FadingEdgesFillTypeDefaults.FillStops.first 460 | fadingEdgesFillStopSecond = FadingEdgesFillTypeDefaults.FillStops.second 461 | fadingEdgesFillStopThird = FadingEdgesFillTypeDefaults.FillStops.third 462 | fadingEdgesFillSecondStopAlpha = FadingEdgesFillTypeDefaults.SecondStopAlpha 463 | fadingEdgesColor = FadingEdgesFillTypeDefaults.FadeColor.FadeColor 464 | fadingEdgesMarqueeTextLength = 50 465 | fadingEdgesIsMarqueeAutoLayout = FadingEdgesDefaults.IsMarqueeAutoLayout 466 | fadingEdgesIsMarqueeAutoPadding = FadingEdgesDefaults.IsMarqueeAutoPadding 467 | 468 | coroutineScope.launch { 469 | resetScrolls() 470 | } 471 | } 472 | 473 | LaunchedEffect(sampleContentType) { 474 | resetScrolls() 475 | } 476 | 477 | @Composable 478 | fun SampleItem( 479 | index: Int, 480 | orientation: FadingEdgesOrientation 481 | ) { 482 | val rawItemSize = if (itemsRandomHeight) { 483 | val random = Random(index + randomSeed) 484 | 485 | if (random.nextFloat() >= 0.1F) { 486 | 65.dp + 200.dp * random.nextFloat() 487 | } else { 488 | 400.dp 489 | } 490 | } else { 491 | 65.dp 492 | } 493 | val itemSize = rawItemSize + itemAdditionalSize 494 | 495 | Box( 496 | modifier = Modifier 497 | .background(color = SampleBgColors[index % SampleBgColors.size]) 498 | .then( 499 | when (orientation) { 500 | FadingEdgesOrientation.Vertical -> { 501 | Modifier 502 | .fillMaxWidth() 503 | .requiredSizeIn(minHeight = 10.dp) 504 | .height(height = itemSize) 505 | .padding(horizontal = 20.dp) 506 | } 507 | FadingEdgesOrientation.Horizontal -> { 508 | Modifier 509 | .fillMaxHeight() 510 | .requiredSizeIn(minWidth = 10.dp) 511 | .width(width = itemSize) 512 | .padding(vertical = 20.dp) 513 | } 514 | } 515 | ), 516 | contentAlignment = when (orientation) { 517 | FadingEdgesOrientation.Vertical -> { 518 | Alignment.CenterStart 519 | } 520 | FadingEdgesOrientation.Horizontal -> { 521 | Alignment.Center 522 | } 523 | } 524 | 525 | ) { 526 | val text = (index + 1).toString() 527 | 528 | Text( 529 | text = text, 530 | style = MaterialTheme.typography.bodyLarge, 531 | color = Color.White, 532 | fontWeight = FontWeight.Bold 533 | ) 534 | } 535 | } 536 | 537 | Column(modifier = Modifier.fillMaxSize()) { 538 | Crossfade( 539 | modifier = Modifier 540 | .fillMaxWidth() 541 | .aspectRatio(ratio = 0.95F) 542 | .padding(all = 20.dp) 543 | .clipToBounds(), 544 | targetState = sampleContentType to fadingEdgesOrientation, 545 | label = "ContentTypeCrossafe" 546 | ) { (sampleScrollTypeState, fadingEdgesOrientationState) -> 547 | when (sampleScrollTypeState) { 548 | SampleContentType.Scroll -> { 549 | when (fadingEdgesOrientationState) { 550 | FadingEdgesOrientation.Vertical -> { 551 | Column( 552 | modifier = Modifier 553 | .fillMaxSize() 554 | .verticalFadingEdges( 555 | contentType = fadingEdgesContentType, 556 | gravity = fadingEdgesGravity, 557 | length = fadingEdgesLength, 558 | fillType = fadingEdgesFillType 559 | ) 560 | .verticalScroll(state = rawScrollState) 561 | .padding(vertical = itemsSpacing), 562 | verticalArrangement = Arrangement.spacedBy(space = itemsSpacing) 563 | ) { 564 | repeat(itemsCount) { 565 | SampleItem( 566 | index = it, 567 | orientation = fadingEdgesOrientationState 568 | ) 569 | } 570 | } 571 | } 572 | FadingEdgesOrientation.Horizontal -> { 573 | Row( 574 | modifier = Modifier 575 | .fillMaxSize() 576 | .horizontalFadingEdges( 577 | contentType = fadingEdgesContentType, 578 | gravity = fadingEdgesGravity, 579 | length = fadingEdgesLength, 580 | fillType = fadingEdgesFillType 581 | ) 582 | .horizontalScroll(state = rawScrollState) 583 | .padding(horizontal = itemsSpacing), 584 | horizontalArrangement = Arrangement.spacedBy(space = itemsSpacing) 585 | ) { 586 | repeat(itemsCount) { 587 | SampleItem( 588 | index = it, 589 | orientation = fadingEdgesOrientationState 590 | ) 591 | } 592 | } 593 | } 594 | } 595 | } 596 | SampleContentType.LazyList -> { 597 | when (fadingEdgesOrientationState) { 598 | FadingEdgesOrientation.Vertical -> { 599 | LazyColumn( 600 | modifier = Modifier 601 | .fillMaxSize() 602 | .verticalFadingEdges( 603 | contentType = fadingEdgesContentType, 604 | gravity = fadingEdgesGravity, 605 | length = fadingEdgesLength, 606 | fillType = fadingEdgesFillType 607 | ), 608 | contentPadding = PaddingValues(vertical = itemsSpacing), 609 | state = rawLazyListState, 610 | verticalArrangement = Arrangement.spacedBy(space = itemsSpacing) 611 | ) { 612 | items(itemsCount) { 613 | SampleItem( 614 | index = it, 615 | orientation = fadingEdgesOrientationState 616 | ) 617 | } 618 | } 619 | } 620 | FadingEdgesOrientation.Horizontal -> { 621 | LazyRow( 622 | modifier = Modifier 623 | .fillMaxSize() 624 | .horizontalFadingEdges( 625 | contentType = fadingEdgesContentType, 626 | gravity = fadingEdgesGravity, 627 | length = fadingEdgesLength, 628 | fillType = fadingEdgesFillType 629 | ), 630 | contentPadding = PaddingValues(horizontal = itemsSpacing), 631 | state = rawLazyListState, 632 | horizontalArrangement = Arrangement.spacedBy(space = itemsSpacing) 633 | ) { 634 | items(itemsCount) { 635 | SampleItem( 636 | index = it, 637 | orientation = fadingEdgesOrientationState 638 | ) 639 | } 640 | } 641 | } 642 | } 643 | } 644 | SampleContentType.LazyGrid -> { 645 | when (fadingEdgesOrientationState) { 646 | FadingEdgesOrientation.Vertical -> { 647 | LazyVerticalGrid( 648 | modifier = Modifier 649 | .fillMaxSize() 650 | .verticalFadingEdges( 651 | contentType = fadingEdgesContentType, 652 | gravity = fadingEdgesGravity, 653 | length = fadingEdgesLength, 654 | fillType = fadingEdgesFillType 655 | ), 656 | contentPadding = PaddingValues(vertical = itemsSpacing), 657 | state = rawLazyGridState, 658 | verticalArrangement = Arrangement.spacedBy(space = itemsSpacing), 659 | columns = GridCells.Fixed(count = fadingEdgesGridSpanCount) 660 | ) { 661 | items(gridItemsCount) { 662 | SampleItem( 663 | index = it, 664 | orientation = fadingEdgesOrientationState 665 | ) 666 | } 667 | } 668 | } 669 | FadingEdgesOrientation.Horizontal -> { 670 | LazyHorizontalGrid( 671 | modifier = Modifier 672 | .fillMaxSize() 673 | .horizontalFadingEdges( 674 | contentType = fadingEdgesContentType, 675 | gravity = fadingEdgesGravity, 676 | length = fadingEdgesLength, 677 | fillType = fadingEdgesFillType 678 | ), 679 | contentPadding = PaddingValues(horizontal = itemsSpacing), 680 | state = rawLazyGridState, 681 | horizontalArrangement = Arrangement.spacedBy(space = itemsSpacing), 682 | rows = GridCells.Fixed(count = fadingEdgesGridSpanCount) 683 | ) { 684 | items(gridItemsCount) { 685 | SampleItem( 686 | index = it, 687 | orientation = fadingEdgesOrientationState 688 | ) 689 | } 690 | } 691 | } 692 | } 693 | } 694 | SampleContentType.LazyStgrGrid -> { 695 | when (fadingEdgesOrientationState) { 696 | FadingEdgesOrientation.Vertical -> { 697 | LazyVerticalStaggeredGrid( 698 | modifier = Modifier 699 | .fillMaxSize() 700 | .verticalFadingEdges( 701 | contentType = fadingEdgesContentType, 702 | gravity = fadingEdgesGravity, 703 | length = fadingEdgesLength, 704 | fillType = fadingEdgesFillType 705 | ), 706 | contentPadding = PaddingValues(vertical = itemsSpacing), 707 | state = rawLazyStaggeredGridState, 708 | verticalItemSpacing = itemsSpacing, 709 | columns = StaggeredGridCells.Fixed(count = fadingEdgesGridSpanCount) 710 | ) { 711 | items(gridItemsCount) { 712 | SampleItem( 713 | index = it, 714 | orientation = fadingEdgesOrientationState 715 | ) 716 | } 717 | } 718 | } 719 | FadingEdgesOrientation.Horizontal -> { 720 | LazyHorizontalStaggeredGrid( 721 | modifier = Modifier 722 | .fillMaxSize() 723 | .horizontalFadingEdges( 724 | contentType = fadingEdgesContentType, 725 | gravity = fadingEdgesGravity, 726 | length = fadingEdgesLength, 727 | fillType = fadingEdgesFillType 728 | ), 729 | contentPadding = PaddingValues(horizontal = itemsSpacing), 730 | state = rawLazyStaggeredGridState, 731 | horizontalItemSpacing = itemsSpacing, 732 | rows = StaggeredGridCells.Fixed(count = fadingEdgesGridSpanCount) 733 | ) { 734 | items(gridItemsCount) { 735 | SampleItem( 736 | index = it, 737 | orientation = fadingEdgesOrientationState 738 | ) 739 | } 740 | } 741 | } 742 | } 743 | } 744 | SampleContentType.Static -> { 745 | when (fadingEdgesOrientationState) { 746 | FadingEdgesOrientation.Vertical -> { 747 | Column( 748 | modifier = Modifier 749 | .fillMaxSize() 750 | .verticalFadingEdges( 751 | gravity = fadingEdgesGravity, 752 | length = fadingEdgesLength, 753 | fillType = fadingEdgesFillType 754 | ) 755 | .verticalScroll(state = rawStaticScrollState) 756 | .padding(vertical = itemsSpacing), 757 | verticalArrangement = Arrangement.spacedBy(space = itemsSpacing) 758 | ) { 759 | repeat(itemsCount) { 760 | SampleItem( 761 | index = it, 762 | orientation = fadingEdgesOrientationState 763 | ) 764 | } 765 | } 766 | } 767 | FadingEdgesOrientation.Horizontal -> { 768 | Row( 769 | modifier = Modifier 770 | .fillMaxSize() 771 | .horizontalFadingEdges( 772 | gravity = fadingEdgesGravity, 773 | length = fadingEdgesLength, 774 | fillType = fadingEdgesFillType 775 | ) 776 | .horizontalScroll(state = rawStaticScrollState) 777 | .padding(horizontal = itemsSpacing), 778 | horizontalArrangement = Arrangement.spacedBy(space = itemsSpacing) 779 | ) { 780 | repeat(itemsCount) { 781 | SampleItem( 782 | index = it, 783 | orientation = fadingEdgesOrientationState 784 | ) 785 | } 786 | } 787 | } 788 | } 789 | } 790 | } 791 | } 792 | Divider() 793 | TabRow( 794 | selectedTabIndex = sampleContentType.ordinal, 795 | modifier = Modifier.fillMaxWidth(), 796 | tabs = { 797 | SampleContentType.values().forEach { enumValue -> 798 | Tab( 799 | selected = enumValue == sampleContentType, 800 | text = { 801 | Text( 802 | modifier = Modifier.basicMarquee(), 803 | text = enumValue.name.uppercase(), 804 | maxLines = 1 805 | ) 806 | }, 807 | onClick = { 808 | sampleContentType = enumValue 809 | }, 810 | ) 811 | } 812 | } 813 | ) 814 | Column( 815 | modifier = Modifier 816 | .fillMaxWidth() 817 | .verticalScroll(state = configScrollState) 818 | .padding(all = 20.dp), 819 | verticalArrangement = Arrangement.spacedBy(space = 4.dp) 820 | ) { 821 | if (sampleContentType.isLazyGridOrStgrGrid) { 822 | Text( 823 | text = "SPAN COUNT:", 824 | style = MaterialTheme.typography.labelLarge 825 | ) 826 | TabRow( 827 | selectedTabIndex = fadingEdgesSampleGridSpanCount.ordinal, 828 | modifier = Modifier.fillMaxWidth(), 829 | tabs = { 830 | SampleGridSpanCount.values().forEach { enumValue -> 831 | Tab( 832 | selected = enumValue == fadingEdgesSampleGridSpanCount, 833 | text = { 834 | Text( 835 | modifier = Modifier.basicMarquee(), 836 | text = enumValue.name.uppercase(), 837 | maxLines = 1 838 | ) 839 | }, 840 | onClick = { 841 | fadingEdgesSampleGridSpanCount = enumValue 842 | }, 843 | ) 844 | } 845 | } 846 | ) 847 | } 848 | 849 | val endValueRange = if (sampleContentType.isLazyGridOrStgrGrid) { 850 | 40 851 | } else { 852 | 20 853 | } 854 | 855 | Text( 856 | modifier = Modifier.padding( 857 | top = if (sampleContentType.isLazyGridOrStgrGrid) { 858 | 20.dp 859 | } else { 860 | 0.dp 861 | } 862 | ), 863 | text = "ITEMS COUNT:", 864 | style = MaterialTheme.typography.labelLarge 865 | ) 866 | Slider( 867 | modifier = Modifier.fillMaxWidth(), 868 | value = if (sampleContentType.isLazyGridOrStgrGrid) { 869 | gridItemsCount.toFloat() 870 | } else { 871 | itemsCount.toFloat() 872 | }, 873 | onValueChange = { 874 | if (sampleContentType.isLazyGridOrStgrGrid) { 875 | gridItemsCount = it.toInt() 876 | } else { 877 | itemsCount = it.toInt() 878 | } 879 | }, 880 | valueRange = 0.0F..endValueRange.toFloat(), 881 | steps = endValueRange 882 | ) 883 | 884 | Text( 885 | text = "ITEM ADDITIONAL SIZE:", 886 | style = MaterialTheme.typography.labelLarge 887 | ) 888 | Slider( 889 | modifier = Modifier.fillMaxWidth(), 890 | value = itemAdditionalSize.value, 891 | onValueChange = { 892 | itemAdditionalSize = it.dp 893 | }, 894 | valueRange = -40.0F..40.0F, 895 | steps = 50 896 | ) 897 | 898 | Text( 899 | text = "ITEMS SPACING:", 900 | style = MaterialTheme.typography.labelLarge 901 | ) 902 | Slider( 903 | modifier = Modifier.fillMaxWidth(), 904 | value = itemsSpacing.value, 905 | onValueChange = { 906 | itemsSpacing = it.dp 907 | }, 908 | valueRange = 0F..25.0F, 909 | steps = 25 910 | ) 911 | 912 | Row( 913 | modifier = Modifier.fillMaxWidth(), 914 | horizontalArrangement = Arrangement.spacedBy(space = 20.dp), 915 | verticalAlignment = Alignment.CenterVertically 916 | ) { 917 | Text( 918 | modifier = Modifier 919 | .weight(weight = 1.0F) 920 | .basicMarquee(), 921 | text = "ITEMS RANDOM HEIGHT:", 922 | style = MaterialTheme.typography.labelLarge, 923 | maxLines = 1 924 | ) 925 | Switch( 926 | checked = itemsRandomHeight, 927 | onCheckedChange = { 928 | itemsRandomHeight = it 929 | if (itemsRandomHeight) { 930 | randomSeed = Random.nextInt() 931 | } 932 | } 933 | ) 934 | } 935 | 936 | Text( 937 | text = "ORIENTATION:", 938 | style = MaterialTheme.typography.labelLarge 939 | ) 940 | TabRow( 941 | selectedTabIndex = fadingEdgesOrientation.ordinal, 942 | modifier = Modifier.fillMaxWidth(), 943 | tabs = { 944 | FadingEdgesOrientation.values().forEach { enumValue -> 945 | Tab( 946 | selected = enumValue == fadingEdgesOrientation, 947 | text = { 948 | Text( 949 | modifier = Modifier.basicMarquee(), 950 | text = enumValue.name.uppercase(), 951 | maxLines = 1 952 | ) 953 | }, 954 | onClick = { 955 | fadingEdgesOrientation = enumValue 956 | }, 957 | ) 958 | } 959 | } 960 | ) 961 | 962 | Text( 963 | modifier = Modifier.padding(top = 20.dp), 964 | text = "GRAVITY:", 965 | style = MaterialTheme.typography.labelLarge 966 | ) 967 | TabRow( 968 | selectedTabIndex = fadingEdgesGravity.ordinal, 969 | modifier = Modifier.fillMaxWidth(), 970 | tabs = { 971 | FadingEdgesGravity.values().forEach { enumValue -> 972 | Tab( 973 | selected = enumValue == fadingEdgesGravity, 974 | text = { 975 | Text( 976 | modifier = Modifier.basicMarquee(), 977 | text = enumValue.name.uppercase(), 978 | maxLines = 1 979 | ) 980 | }, 981 | onClick = { 982 | fadingEdgesGravity = enumValue 983 | }, 984 | ) 985 | } 986 | } 987 | ) 988 | 989 | Text( 990 | modifier = Modifier.padding(top = 20.dp), 991 | text = "LENGTH:", 992 | style = MaterialTheme.typography.labelLarge 993 | ) 994 | Slider( 995 | modifier = Modifier.fillMaxWidth(), 996 | value = fadingEdgesLength.value, 997 | onValueChange = { 998 | fadingEdgesLength = it.dp 999 | }, 1000 | valueRange = 0F..65.0F, 1001 | steps = 40 1002 | ) 1003 | 1004 | if (sampleContentType != SampleContentType.Static) { 1005 | Text( 1006 | modifier = Modifier.padding(top = 20.dp), 1007 | text = "SCROLL CONFIG:", 1008 | style = MaterialTheme.typography.labelLarge 1009 | ) 1010 | TabRow( 1011 | selectedTabIndex = fadingEdgesSampleScrollConfig.ordinal, 1012 | modifier = Modifier.fillMaxWidth(), 1013 | tabs = { 1014 | SampleScrollConfig.values().forEach { enumValue -> 1015 | Tab( 1016 | selected = enumValue == fadingEdgesSampleScrollConfig, 1017 | text = { 1018 | Text( 1019 | modifier = Modifier.basicMarquee(), 1020 | text = enumValue.name.uppercase(), 1021 | maxLines = 1 1022 | ) 1023 | }, 1024 | onClick = { 1025 | fadingEdgesSampleScrollConfig = enumValue 1026 | }, 1027 | ) 1028 | } 1029 | } 1030 | ) 1031 | 1032 | Text( 1033 | modifier = Modifier.padding(top = 20.dp), 1034 | text = "ANIMATION TYPE:", 1035 | style = MaterialTheme.typography.labelLarge 1036 | ) 1037 | TabRow( 1038 | selectedTabIndex = fadingEdgesSampleAnimationType.ordinal, 1039 | modifier = Modifier.fillMaxWidth(), 1040 | tabs = { 1041 | SampleAnimationType.values().forEach { enumValue -> 1042 | Tab( 1043 | selected = enumValue == fadingEdgesSampleAnimationType, 1044 | text = { 1045 | Text( 1046 | modifier = Modifier.basicMarquee(), 1047 | text = enumValue.name.uppercase(), 1048 | maxLines = 1 1049 | ) 1050 | }, 1051 | onClick = { 1052 | fadingEdgesSampleAnimationType = enumValue 1053 | }, 1054 | ) 1055 | } 1056 | } 1057 | ) 1058 | 1059 | if (fadingEdgesSampleScrollConfig == SampleScrollConfig.Dynamic) { 1060 | Row( 1061 | modifier = Modifier 1062 | .fillMaxWidth() 1063 | .padding(top = 20.dp), 1064 | horizontalArrangement = Arrangement.spacedBy(space = 20.dp), 1065 | verticalAlignment = Alignment.CenterVertically 1066 | ) { 1067 | Text( 1068 | modifier = Modifier 1069 | .weight(weight = 1.0F) 1070 | .marqueeHorizontalFadingEdges(length = 8.dp) { 1071 | Modifier.basicMarquee() 1072 | }, 1073 | text = "IS LERP BY DIFFERENCE FOR PARTIAL CONTENT:", 1074 | style = MaterialTheme.typography.labelLarge, 1075 | maxLines = 1 1076 | ) 1077 | 1078 | Switch( 1079 | modifier = Modifier.padding(start = 8.dp), 1080 | checked = fadingEdgesDynamicIsLerpByDifferenceForPartialContent, 1081 | onCheckedChange = { 1082 | fadingEdgesDynamicIsLerpByDifferenceForPartialContent = it 1083 | } 1084 | ) 1085 | } 1086 | 1087 | Text( 1088 | text = "SCROLL FACTOR:", 1089 | style = MaterialTheme.typography.labelLarge 1090 | ) 1091 | Slider( 1092 | modifier = Modifier.fillMaxWidth(), 1093 | value = fadingEdgesDynamicScrollFactor, 1094 | onValueChange = { 1095 | fadingEdgesDynamicScrollFactor = it 1096 | }, 1097 | valueRange = 0.2F..3.0F, 1098 | steps = 20 1099 | ) 1100 | } else { 1101 | Spacer(modifier = Modifier.height(height = 16.dp)) 1102 | } 1103 | } 1104 | 1105 | Text( 1106 | text = "FILL TYPE:", 1107 | style = MaterialTheme.typography.labelLarge 1108 | ) 1109 | TabRow( 1110 | selectedTabIndex = fadingEdgesSampleFillType.ordinal, 1111 | modifier = Modifier.fillMaxWidth(), 1112 | tabs = { 1113 | SampleFillType.values().forEach { enumValue -> 1114 | Tab( 1115 | selected = enumValue == fadingEdgesSampleFillType, 1116 | text = { 1117 | Text( 1118 | modifier = Modifier.basicMarquee(), 1119 | text = enumValue.name.uppercase(), 1120 | maxLines = 1 1121 | ) 1122 | }, 1123 | onClick = { 1124 | fadingEdgesSampleFillType = enumValue 1125 | }, 1126 | ) 1127 | } 1128 | } 1129 | ) 1130 | 1131 | Text( 1132 | modifier = Modifier.padding(top = 20.dp), 1133 | text = "FILL STOP FIRST:", 1134 | style = MaterialTheme.typography.labelLarge 1135 | ) 1136 | Slider( 1137 | modifier = Modifier.fillMaxWidth(), 1138 | value = fadingEdgesFillStopFirst, 1139 | onValueChange = { 1140 | fadingEdgesFillStopFirst = it 1141 | }, 1142 | valueRange = 0.0F..fadingEdgesFillStopSecond, 1143 | steps = (fadingEdgesFillStopSecond * 9.0F).toInt() 1144 | ) 1145 | 1146 | Text( 1147 | text = "FILL STOP SECOND:", 1148 | style = MaterialTheme.typography.labelLarge 1149 | ) 1150 | Slider( 1151 | modifier = Modifier.fillMaxWidth(), 1152 | value = fadingEdgesFillStopSecond, 1153 | onValueChange = { 1154 | fadingEdgesFillStopSecond = it 1155 | }, 1156 | valueRange = fadingEdgesFillStopFirst..fadingEdgesFillStopThird, 1157 | steps = ((fadingEdgesFillStopThird - fadingEdgesFillStopFirst) * 9.0F).toInt() 1158 | ) 1159 | 1160 | Text( 1161 | text = "FILL STOP THIRD:", 1162 | style = MaterialTheme.typography.labelLarge 1163 | ) 1164 | Slider( 1165 | modifier = Modifier.fillMaxWidth(), 1166 | value = fadingEdgesFillStopThird, 1167 | onValueChange = { 1168 | fadingEdgesFillStopThird = it 1169 | }, 1170 | valueRange = fadingEdgesFillStopSecond..1.0F, 1171 | steps = ((1.0F - fadingEdgesFillStopSecond) * 9.0F).toInt() 1172 | ) 1173 | 1174 | Text( 1175 | text = "FILL SECOND STOP ALPHA:", 1176 | style = MaterialTheme.typography.labelLarge 1177 | ) 1178 | Slider( 1179 | modifier = Modifier.fillMaxWidth(), 1180 | value = fadingEdgesFillSecondStopAlpha, 1181 | onValueChange = { 1182 | fadingEdgesFillSecondStopAlpha = it 1183 | }, 1184 | valueRange = 0.2F..0.8F, 1185 | steps = 7 1186 | ) 1187 | 1188 | if (fadingEdgesSampleFillType == SampleFillType.Color) { 1189 | Text( 1190 | text = "COLOR:", 1191 | style = MaterialTheme.typography.labelLarge 1192 | ) 1193 | Row( 1194 | modifier = Modifier 1195 | .fillMaxWidth() 1196 | .height(intrinsicSize = IntrinsicSize.Min), 1197 | horizontalArrangement = Arrangement.spacedBy(space = 20.dp) 1198 | ) { 1199 | Button( 1200 | modifier = Modifier.weight(weight = 1.0F), 1201 | onClick = { 1202 | isColorPickerVisible = true 1203 | } 1204 | ) { 1205 | Text(text = "PICK COLOR") 1206 | } 1207 | Box( 1208 | modifier = Modifier 1209 | .fillMaxHeight() 1210 | .aspectRatio(ratio = 1.0F) 1211 | .background( 1212 | color = fadingEdgesColor, 1213 | shape = RoundedCornerShape(size = 4.dp) 1214 | ) 1215 | .border( 1216 | width = 1.dp, 1217 | color = Color.Black, 1218 | shape = RoundedCornerShape(size = 4.dp) 1219 | ) 1220 | .clickable { 1221 | isColorPickerVisible = true 1222 | } 1223 | ) 1224 | } 1225 | Spacer(modifier = Modifier.height(height = 4.dp)) 1226 | } 1227 | 1228 | if (fadingEdgesContentType == FadingEdgesContentType.Static) { 1229 | Text( 1230 | text = "MARQUEE TEXT LENGTH:", 1231 | style = MaterialTheme.typography.labelLarge 1232 | ) 1233 | Slider( 1234 | modifier = Modifier.fillMaxWidth(), 1235 | value = fadingEdgesMarqueeTextLength.toFloat(), 1236 | onValueChange = { 1237 | fadingEdgesMarqueeTextLength = it.toInt() 1238 | }, 1239 | valueRange = 0.0F..100.0F, 1240 | steps = 11 1241 | ) 1242 | Row( 1243 | modifier = Modifier.fillMaxWidth(), 1244 | horizontalArrangement = Arrangement.spacedBy(space = 20.dp), 1245 | verticalAlignment = Alignment.CenterVertically 1246 | ) { 1247 | Text( 1248 | modifier = Modifier.weight(weight = 1.0F), 1249 | text = "IS MARQUEE AUTO LAYOUT:", 1250 | style = MaterialTheme.typography.labelLarge, 1251 | maxLines = 1 1252 | ) 1253 | Switch( 1254 | modifier = Modifier.padding(start = 8.dp), 1255 | checked = fadingEdgesIsMarqueeAutoLayout, 1256 | onCheckedChange = { 1257 | fadingEdgesIsMarqueeAutoLayout = it 1258 | } 1259 | ) 1260 | } 1261 | Row( 1262 | modifier = Modifier.fillMaxWidth(), 1263 | horizontalArrangement = Arrangement.spacedBy(space = 20.dp), 1264 | verticalAlignment = Alignment.CenterVertically 1265 | ) { 1266 | Text( 1267 | modifier = Modifier.weight(weight = 1.0F), 1268 | text = "IS MARQUEE AUTO PADDING:", 1269 | style = MaterialTheme.typography.labelLarge, 1270 | maxLines = 1 1271 | ) 1272 | Switch( 1273 | modifier = Modifier.padding(start = 8.dp), 1274 | checked = fadingEdgesIsMarqueeAutoPadding, 1275 | onCheckedChange = { 1276 | fadingEdgesIsMarqueeAutoPadding = it 1277 | } 1278 | ) 1279 | } 1280 | Box( 1281 | modifier = Modifier 1282 | .fillMaxWidth() 1283 | .padding(vertical = 20.dp) 1284 | .border( 1285 | color = Color.Red.copy(alpha = 0.25F), 1286 | width = 1.dp 1287 | ) 1288 | ) { 1289 | Text( 1290 | modifier = Modifier 1291 | .fillMaxWidth() 1292 | .marqueeHorizontalFadingEdges( 1293 | gravity = FadingEdgesGravity.All, 1294 | length = fadingEdgesLength, 1295 | fillType = fadingEdgesFillType, 1296 | isMarqueeAutoLayout = fadingEdgesIsMarqueeAutoLayout, 1297 | isMarqueeAutoPadding = fadingEdgesIsMarqueeAutoPadding 1298 | ) { 1299 | Modifier.basicMarquee(iterations = Int.MAX_VALUE) 1300 | }, 1301 | text = fadingEdgesMarqueeText, 1302 | style = MaterialTheme.typography.labelLarge, 1303 | maxLines = 1 1304 | ) 1305 | } 1306 | } 1307 | 1308 | Button( 1309 | modifier = Modifier.fillMaxWidth(), 1310 | onClick = { 1311 | reset() 1312 | } 1313 | ) { 1314 | Text(text = "RESET") 1315 | } 1316 | } 1317 | } 1318 | 1319 | if (isColorPickerVisible) { 1320 | ModalBottomSheet( 1321 | sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), 1322 | onDismissRequest = { 1323 | isColorPickerVisible = false 1324 | } 1325 | ) { 1326 | ClassicColorPicker( 1327 | modifier = Modifier 1328 | .fillMaxWidth() 1329 | .height(height = 300.dp) 1330 | .padding(horizontal = 20.dp) 1331 | .padding(bottom = 20.dp), 1332 | color = HsvColor.from(fadingEdgesColor) 1333 | ) { 1334 | fadingEdgesColor = it.toColor() 1335 | } 1336 | } 1337 | } 1338 | } 1339 | 1340 | enum class SampleContentType { 1341 | Static, 1342 | Scroll, 1343 | LazyList, 1344 | LazyGrid, 1345 | LazyStgrGrid; 1346 | 1347 | val isLazyGridOrStgrGrid: Boolean 1348 | get() = this == LazyGrid || this == LazyStgrGrid 1349 | } 1350 | 1351 | enum class SampleScrollConfig { 1352 | Static, 1353 | Dynamic, 1354 | Full 1355 | } 1356 | 1357 | @Suppress("EnumEntryName") 1358 | enum class SampleAnimationType { 1359 | None, 1360 | Spring, 1361 | Tween_700 1362 | } 1363 | 1364 | enum class SampleFillType { 1365 | Clip, 1366 | Color 1367 | } 1368 | 1369 | @Suppress("EnumEntryName") 1370 | enum class SampleGridSpanCount(val spanCount: Int) { 1371 | Span_1(1), 1372 | Span_2(2), 1373 | Span_3(3) 1374 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/gigamole/composefadingedges/sample/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.sample 2 | 3 | import android.app.Application 4 | 5 | class MainApplication : Application() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/gigamole/composefadingedges/sample/MainTheme.kt: -------------------------------------------------------------------------------- 1 | package com.gigamole.composefadingedges.sample 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.material3.lightColorScheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.text.font.Font 8 | import androidx.compose.ui.text.font.FontFamily 9 | import androidx.compose.ui.text.font.FontStyle 10 | import androidx.compose.ui.text.font.FontWeight 11 | 12 | val FontFamilySpaceGrotesk = FontFamily( 13 | Font( 14 | resId = R.font.space_grotesk_bold, 15 | weight = FontWeight.Bold, 16 | style = FontStyle.Normal 17 | ) 18 | ) 19 | val FontFamilyOpenSans = FontFamily( 20 | Font( 21 | resId = R.font.opensans_regular, 22 | weight = FontWeight.Normal, 23 | style = FontStyle.Normal 24 | ) 25 | ) 26 | 27 | @Composable 28 | fun MainTheme(content: @Composable () -> Unit) { 29 | MaterialTheme( 30 | colorScheme = lightColorScheme( 31 | primary = Color.Black, 32 | primaryContainer = Color.White, 33 | onPrimary = Color.White, 34 | secondary = Color.Black, 35 | secondaryContainer = Color.White, 36 | onSecondary = Color.White, 37 | tertiary = Color.Black, 38 | tertiaryContainer = Color.White, 39 | onTertiary = Color.White, 40 | surface = Color.White, 41 | onSurface = Color.Black, 42 | surfaceVariant = Color.LightGray, 43 | onSurfaceVariant = Color.DarkGray, 44 | outline = Color.DarkGray, 45 | background = Color.White, 46 | onBackground = Color.Black 47 | ), 48 | content = content 49 | ) 50 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/font/opensans_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/font/opensans_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/space_grotesk_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/font/space_grotesk_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ComposeFadingEdges 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("LongLine") 2 | 3 | // As a suggestions from here: https://youtrack.jetbrains.com/issue/KTIJ-19369/False-positive-cant-be-called-in-this-context-by-implicit-receiver-with-plugins-in-Gradle-version-catalogs-as-a-TOML-file#focus=Comments-27-5860112.0-0 4 | @Suppress( 5 | "DSL_SCOPE_VIOLATION", 6 | "MISSING_DEPENDENCY_CLASS", 7 | "UNRESOLVED_REFERENCE_WRONG_RECEIVER", 8 | "FUNCTION_CALL_EXPECTED" 9 | ) 10 | 11 | plugins { 12 | alias(libs.plugins.ksp.gradle.plugin) 13 | } 14 | 15 | buildscript { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | gradlePluginPortal() 20 | } 21 | dependencies { 22 | classpath(libs.android.gradle.plugin) 23 | classpath(libs.kotlin.gradle.plugin) 24 | } 25 | } 26 | 27 | allprojects { 28 | repositories { 29 | google() 30 | mavenCentral() 31 | maven("https://jitpack.io") 32 | } 33 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | org.gradle.parallel=true 11 | org.gradle.unsafe.configuration-cache=true 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | # AndroidX package structure to make it clearer which packages are bundled with the 17 | # Android operating system, and which are packaged with your app's APK 18 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 19 | android.useAndroidX=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # Enables namespacing of each library's R class so that its R class includes only the 23 | # resources declared in the library itself and none from the library's dependencies, 24 | # thereby reducing the size of the R class for that library 25 | android.nonTransitiveRClass=true 26 | android.defaults.buildfeatures.buildconfig=true 27 | android.nonFinalResIds=false 28 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | # Android/Kotlin 4 | android_gradle_plugin_version = "8.1.2" 5 | androidx_ktx_version = "1.12.0" 6 | kotlin_version = "1.9.10" 7 | 8 | # Plugins 9 | ksp_version = "1.9.10-1.0.13" 10 | 11 | # Compose 12 | compose_compiler_version = "1.5.3" 13 | compose_bom_version = "2023.10.01" 14 | compose_activity_version = "1.8.0" 15 | compose_color_picker_version = "0.7.0" 16 | 17 | [plugins] 18 | 19 | ksp_gradle_plugin = { id = "com.google.devtools.ksp", version.ref = "ksp_version" } 20 | 21 | [libraries] 22 | 23 | # Android/Kotlin 24 | android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "android_gradle_plugin_version" } 25 | androidx_core = { module = "androidx.core:core", version.ref = "androidx_ktx_version" } 26 | androidx_ktx = { module = "androidx.core:core-ktx", version.ref = "androidx_ktx_version" } 27 | kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" } 28 | 29 | # Compose 30 | compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom_version" } 31 | compose_material3 = { module = "androidx.compose.material3:material3" } 32 | compose_preview = { module = "androidx.compose.ui:ui-tooling-preview" } 33 | compose_runtime = { module = "androidx.compose.runtime:runtime" } 34 | compose_ui_util = { module = "androidx.compose.ui:ui-util" } 35 | compose_activity = { module = "androidx.activity:activity-compose", version.ref = "compose_activity_version" } 36 | compose_color_picker = { module = "com.godaddy.android.colorpicker:compose-color-picker", version.ref = "compose_color_picker_version" } 37 | debug_compose_tooling = { module = "androidx.compose.ui:ui-tooling" } 38 | 39 | [bundles] 40 | 41 | compose = [ 42 | "compose.material3", 43 | "compose.preview", 44 | "compose.runtime", 45 | "compose.activity" 46 | ] 47 | debug_compose = [ 48 | "debug.compose.tooling" 49 | ] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Feb 16 10:06:11 CET 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 -------------------------------------------------------------------------------- /media/credits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/credits.png -------------------------------------------------------------------------------- /media/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/demo.gif -------------------------------------------------------------------------------- /media/footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/footer.png -------------------------------------------------------------------------------- /media/header-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/header-new.png -------------------------------------------------------------------------------- /media/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/header.png -------------------------------------------------------------------------------- /media/sample-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/sample-1.gif -------------------------------------------------------------------------------- /media/sample-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/sample-2.gif -------------------------------------------------------------------------------- /media/sample-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GIGAMOLE/ComposeFadingEdges/e4f8fe6c171ecdff9827e52276ae0f03fd1acf4b/media/sample-3.gif -------------------------------------------------------------------------------- /plugins/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .gradle 3 | -------------------------------------------------------------------------------- /plugins/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | `kotlin-dsl-precompiled-script-plugins` 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | google() 9 | } 10 | 11 | dependencies { 12 | implementation(libs.android.gradle.plugin) 13 | implementation(libs.kotlin.gradle.plugin) 14 | } 15 | -------------------------------------------------------------------------------- /plugins/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | dependencyResolutionManagement { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | versionCatalogs { 9 | create("libs") { 10 | from(files("../gradle/libs.versions.toml")) 11 | } 12 | } 13 | } 14 | 15 | rootProject.name = "plugins" 16 | -------------------------------------------------------------------------------- /plugins/src/main/java/CommonExtension.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage", "PackageDirectoryMismatch", "unused") 2 | 3 | import com.android.build.gradle.BaseExtension 4 | import com.android.build.gradle.LibraryExtension 5 | import com.android.build.gradle.internal.dsl.BaseAppModuleExtension 6 | 7 | private fun BaseExtension.baseSetup() { 8 | defaultConfig { 9 | minSdk = ProjectConfig.minSdk 10 | targetSdk = ProjectConfig.targetSdk 11 | 12 | versionCode = ProjectConfig.versionCode 13 | versionName = ProjectConfig.versionName 14 | } 15 | 16 | compileOptions { 17 | sourceCompatibility = ProjectConfig.javaCompileVersion 18 | targetCompatibility = ProjectConfig.javaCompileVersion 19 | } 20 | } 21 | 22 | fun LibraryExtension.setup() { 23 | baseSetup() 24 | 25 | defaultConfig { 26 | compileSdk = ProjectConfig.compileSdk 27 | } 28 | 29 | buildFeatures { 30 | compose = true 31 | } 32 | } 33 | 34 | fun BaseAppModuleExtension.setup() { 35 | baseSetup() 36 | 37 | defaultConfig { 38 | compileSdk = ProjectConfig.compileSdk 39 | } 40 | 41 | buildFeatures { 42 | compose = true 43 | } 44 | } -------------------------------------------------------------------------------- /plugins/src/main/java/ProjectConfig.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | import org.gradle.api.JavaVersion 4 | import org.gradle.jvm.toolchain.JavaLanguageVersion 5 | 6 | object ProjectConfig { 7 | const val versionCode = 1 8 | const val versionName = "1.0.4" 9 | 10 | const val namespace = "com.gigamole.composefadingedges" 11 | const val group = "com.github.GIGAMOLE" 12 | const val artifact = "ComposeFadingEdges" 13 | const val publication = "release" 14 | 15 | const val compileSdk = 34 16 | const val targetSdk = 34 17 | const val minSdk = 21 18 | 19 | val javaCompileVersion = JavaVersion.VERSION_17 20 | private val javaCompileVersionText = javaCompileVersion.toString() 21 | val javaLanguageVersion: JavaLanguageVersion = JavaLanguageVersion.of(javaCompileVersionText) 22 | val kotlinJvmTarget = javaCompileVersionText 23 | } 24 | -------------------------------------------------------------------------------- /plugins/src/main/java/composefadingedges.application.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | } 5 | 6 | android { 7 | setup() 8 | 9 | kotlinOptions { 10 | jvmTarget = ProjectConfig.kotlinJvmTarget 11 | } 12 | } -------------------------------------------------------------------------------- /plugins/src/main/java/composefadingedges.library.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("android") 4 | } 5 | 6 | android { 7 | setup() 8 | 9 | kotlinOptions { 10 | jvmTarget = ProjectConfig.kotlinJvmTarget 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | include(":app") 4 | include(":ComposeFadingEdges") 5 | 6 | pluginManagement { 7 | repositories { 8 | gradlePluginPortal() 9 | mavenCentral() 10 | google() 11 | includeBuild("plugins") 12 | } 13 | } 14 | 15 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 16 | 17 | rootProject.name = "ComposeFadingEdgesProject" 18 | --------------------------------------------------------------------------------